分析:
第一步,在调用 WinMain 之前先调用 FillHiliteInfo。这个函数读出 wordfile.txt 的内容然后解析该内容。
FillHiliteInfo proc uses edi LOCAL buffer[1024]:BYTE LOCAL pTemp:DWORD LOCAL BlockSize:DWORD invoke RtlZeroMemory,addr ASMSyntaxArray,sizeof ASMSyntaxArray
初始化 ASMSyntaxArray 为 0 。
invoke GetModuleFileName,hInstance,addr buffer,sizeof buffer invoke lstrlen,addr buffer mov ecx,eax dec ecx lea edi,buffer add edi,ecx std mov al,"\" repne scasb cld inc edi mov byte ptr [edi],0 invoke lstrcat,addr buffer,addr WordFileName
构建 of wordfile.txt 的完全路径: 我假定该文件总是跟程序文件在同一个文件夹。
invoke GetFileAttributes,addr buffer .if eax!=-1
我使用这个方法来快速检验一个文件是否存在。
mov BlockSize,1024*10 invoke HeapAlloc,hMainHeap,0,BlockSize mov pTemp,eax
分配内存块来保存高亮词。缺省是10K。是从缺省堆中分配的。
@@: invoke GetPrivateProfileString,addr ASMSection,addr C1Key,addr ZeroString,pTemp,BlockSize,addr buffer .if eax!=0
我使用 GetPrivateProfileString 来检索 wordfile.txt 中每一个关键字的内容。关键字从 C1 到 C10。
inc eax .if eax==BlockSize ; 缓冲区太小 add BlockSize,1024*10 invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSize mov pTemp,eax jmp @B .endif
检查内存块是否足够大。如果不够的话,我们就增大10K,直到足够大为止。
mov edx,offset ASMColorArray invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray
传递需要高亮显示的词的缓冲区, 内存块句柄,从WordFile.txt中读出的数据的大小,用来高亮显示词的颜色数组的地址,和ASMSyntaxArray的地址。
现在让我们来看看 ParseBuffer 做了什么。基本上,这个函数接收包含需要高亮显示词的缓冲区,将它们解析为单个的词并将它们每个都保存在一个 WORDINFO 结构数组中,这样可以从 ASMSyntaxArray 中快速访问它们。
ParseBuffer proc uses edi esi hHeap:DWORD,pBuffer:DWORD, nSize:DWORD, ArrayOffset:DWORD,pArray:DWORD LOCAL buffer[128]:BYTE LOCAL InProgress:DWORD mov InProgress,FALSE
InProgress 是一个标志,我用来指示扫描处理是否已经开始。如果它的值是FALSE,表示我们还没遇到一个非空格字符。
lea esi,buffer mov edi,pBuffer invoke CharLower,edi
ESI 指向一个局部缓冲区,用来保存从词表中分离出来的单个词。EDI 指向词表字符串。为了方便以后的搜索,我们将所有的词都转换为小写形式。
mov ecx,nSize SearchLoop: or ecx,ecx jz Finished cmp byte ptr [edi]," " je EndOfWord cmp byte ptr [edi],9 ; tab je EndOfWord
扫描缓冲区里的整个词表,搜索空格(White Space)字符。如果找到的话,我们必须判断它表示一个词的结束还是开始。
mov InProgress,TRUE mov al,byte ptr [edi] mov byte ptr [esi],al inc esi SkipIt: inc edi dec ecx jmp SearchLoop
如果该字节经过详细审查证实不是空格,我们就将它复制到一个缓冲区来组成一个词,并继续扫描。
EndOfWord: cmp InProgress,TRUE je WordFound jmp SkipIt
如果找到了一个空格字符,我们就检查 InProgress 的值。如果值为 TRUE ,我们可以假定空格标识一个词的结束,我们可以继续将解析出来的在局部缓冲区(由ESI指向)中的词保存到一个 WORDINFO 结构中。如果值为 FALSE ,我们继续扫描直到发现一个非空白(non-white space)的空格字符。
WordFound: mov byte ptr [esi],0 push ecx invoke HeapAlloc,hHeap,HEAP_ZERO_MEMORY,sizeof WORDINFO
到达词的结尾后,我们就在词缓冲区尾加一个0,标识该词是一个 ASCIIZ 字符串。然后我们从堆中为该词分配一块 WORDINFO 内存。
push esi mov esi,eax assume esi:ptr WORDINFO invoke lstrlen,addr buffer mov [esi].WordLen,eax
我们取得局部变量中该词的长度,保存在 WORDINFO 结构的 WordLen 成员中,以后用来快速比较。
push ArrayOffset pop [esi].pColor
将包含用来高亮显示该词的颜色的双字的地址保存到 pColor 成员中。
inc eax invoke HeapAlloc,hHeap,HEAP_ZERO_MEMORY,eax mov [esi].pszWord,eax mov edx,eax invoke lstrcpy,edx,addr buffer
从堆中分配一块内存用来保存词本身。现在 WORDINFO 结构已经都填充好了,可以插入到适当的链表中去。
mov eax,pArray movzx edx,byte ptr [buffer] shl edx,2 ; 乘以 4 add eax,edx
pArray包含的是 ASMSyntaxArray 的地址。我们要移动到跟词中第一个字符具有相同索引的DWORD上。因此我们将词中的第一个字符放入EDX,然后将EDX乘以4(因为 ASMSyntaxArray 中的每个元素都是 4 字节大小),再将偏移量加到 ASMSyntaxArray 的地址上,我们就在EAX中得到相应DWORD的地址。
.if dword ptr [eax]==0 mov dword ptr [eax],esi .else push dword ptr [eax] pop [esi].NextLink mov dword ptr [eax],esi .endif
检查那个DWORD的值,如果为 0 , 意味着当前链表中没有词是以这个字符开头的。因此我们将当前 WORDINFO 结构的地址保存到那个DWORD中。
如果值为非0, 意味着数组中至少有一个词是以这个字符开头的,因此我们将这个WORDINFO 结构插入到该链表头并更新其 NextLink 成员来指向下一个WORDINFO 结构。
pop esi pop ecx lea esi,buffer mov InProgress,FALSE jmp SkipIt
完成这个操作后,我们就开始下一轮扫描循环直到到达缓冲区的尾。
invoke SendMessage,hwndRichEdit,EM_SETTYPOGRAPHYOPTIONS,TO_SIMPLELINEBREAK,TO_SIMPLELINEBREAK invoke SendMessage,hwndRichEdit,EM_GETTYPOGRAPHYOPTIONS,1,1 .if eax==0 ; 说明该消息没被处理 mov RichEditVersion,2 .else mov RichEditVersion,3 invoke SendMessage,hwndRichEdit,EM_SETEDITSTYLE,SES_EMULATESYSEDIT,SES_EMULATESYSEDIT .endif
创建RichEdit控件之后,我们需要确定其版本。这一步很必要,因为EM_POSFROMCHAR 在 RichEdit 2.0 和 3.0 中是不同的,而 EM_POSFROMCHAR 对于我们的语法高亮显示程序是至关重要的。我从来没见到有文档介绍到检查RichEdit控件版本的方法,因此我必须使用一个解决办法(workaround)。在这种情况下,我设置一个选项指定 3.0 版本并立即检索它的值。如果我能检索到该值的话我就假设该控件是3.0版本的。
如果你使用 RichEdit 3.0 ,你会注意到更改一个大文件的正文颜色会花费很长的时间。这个问题好象是3.0版本特有的。我找到一个解决办法:通过发送EM_SETEDITSTYLE 消息,使控件模拟系统Edit控件行为。
取得版本信息后,我们继续下一步,子类化RichEdit控件。现在我们检查RichEdit的新的窗口过程。
NewRichEditProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD ........ ....... .if uMsg==WM_PAINT push edi push esi invoke HideCaret,hWnd invoke CallWindowProc,OldWndProc,hWnd,uMsg,wParam,lParam push eax
我们处理 WM_PAINT 消息。首先,我们隐藏光标以便避免一些高亮后的难看的gfx (ugly gfx)。我们发送WM_PAINT消息给 RichEdit的原来的处理过程,让它更新窗口。CallWindowProc 返回后,正文已经被使用原来的颜色/背景色更新过了。现在是我们做语法高亮显示的机会了!
mov edi,offset ASMSyntaxArray invoke GetDC,hWnd mov hdc,eax invoke SetBkMode,hdc,TRANSPARENT
保存 ASMSyntaxArray 的地址到 EDI。然后我们取得DC的句柄,设置正文背景模式为 Transparent ,以便我们写入的正文使用缺省的背景色。
invoke SendMessage,hWnd,EM_GETRECT,0,addr rect invoke SendMessage,hWnd,EM_CHARFROMPOS,0,addr rect invoke SendMessage,hWnd,EM_LINEFROMCHAR,eax,0 invoke SendMessage,hWnd,EM_LINEINDEX,eax,0
我们要取得可见的正文,因此我们首先必须发送 EM_GETRECT 消息给RichEdit控件来取得格式化矩形(formatting rectangle)。现在我们有了范围矩形,我们通过EM_CHARFROMPOS消息,取得矩形左上角的最接近的字符索引 。一旦我们有了字符索引(控件中第一个可见字符), 我们就可以从该位置开始做语法高亮显示了。但是效果可能没有从该字符所在行的第一个字符开始高亮显示的效果好。这就是为什么我需要通过 EM_LINEFROMCHAR 消息去取得第一个可见字符所在行的行号的原因。为了取得该行的第一个字符,我需要发送 EM_LINEINDEX 消息。
mov txtrange.chrg.cpMin,eax mov FirstChar,eax invoke SendMessage,hWnd,EM_CHARFROMPOS,0,addr rect.right mov txtrange.chrg.cpMax,eax
取得第一个字符的索引后,将之保存到变量 FirstChar 中以便以后引用。跟着我们来取得最后一个可见字符的索引,通过发送 EM_CHARFROMPOS 消息,在 lParam 中传递矩形框的右下角的位置。
push rect.left pop RealRect.left push rect.top pop RealRect.top push rect.right pop RealRect.right push rect.bottom pop RealRect.bottom invoke CreateRectRgn,RealRect.left,RealRect.top,RealRect.right,RealRect.bottom mov hRgn,eax invoke SelectObject,hdc,hRgn mov hOldRgn,eax
在进行语法高亮显示时,我注意到这个方法的一个不好看的边缘效果:如果RichEdit控件有页边空白的话(你可以通过发送 EM_SETMARGINS 消息给RichEdit控件来指定页边空白), DrawText 写出了页边空白。 因而我需要创建一个裁剪区域,大小为矩形框的大小,这可以通过调用 CreateRectRgn 来实现。这样 GDI 函数的输出就会被裁剪在“可写”区域内。
跟着,我们需要先高亮显示注释,并将它们排除在目标之外。我使用的方法是搜索 ";" 并用注释颜色来高亮显示那些正文,直到遇到回车符。我在这里不分析这个过程了:它相当的长和复杂。现在可以说,当所有的注释高亮显示后,我们就在缓冲区中用 0 来替换掉它们,以便注释中的词在以后不会再被重复高亮处理。
mov ecx,BufferSize
lea esi,buffer
.while ecx>0
mov al,byte ptr [esi]
.if al==" " || al==0Dh || al=="/" || al=="," || al=="|" || al=="+" || al=="-" || al=="*" || al=="&" || al=="<" || al==">" || al=="=" || al=="(" || al==")" || al=="{" || al=="}" || al=="[" || al=="]" || al=="^" || al==":" || al==9
mov byte ptr [esi],0
.endif
dec ecx
inc esi
.endw将注释内容排除掉后,我们通过使用 0 替换掉分隔符,将缓冲区中的词表分开为一个个单个的词。用这个方法,我们在处理缓冲区中的词时就再不需要考虑分隔符了:只有一个分隔符,就是 NULL 。
lea esi,buffer mov ecx,BufferSize .while ecx>0 mov al,byte ptr [esi] .if al!=0
搜索缓冲区找到第一个非NULL字符,也就是某个词的第一个字符。
push ecx invoke lstrlen,esi push eax mov edx,eax
取得词的长度并保存到 EDX
movzx eax,byte ptr [esi] .if al>="A" && al<="Z" sub al,"A" add al,"a" .endif
将字符转换为小写的 (如果是大写字符的话)
shl eax,2 add eax,edi ; edi 为 WORDINFO 指针数组的指针 .if dword ptr [eax]!=0
之后,我们跳到 ASMSyntaxArray 中的相应的DWORD,并检查该DWORD的值是否为 0 ,是的话我们就跳到下一个词。
mov eax,dword ptr [eax] assume eax:ptr WORDINFO .while eax!=0 .if edx==[eax].WordLen
如果DWORD的值不为 0 ,它就指向一个 WORDINFO 结构的链表。我们跟着遍历该链表,将局部缓冲区中词的长度跟 WORDINFO 结构中词的长度进行比较。这是在我们比较词之前进行的快速测试,可以节约一些时钟周期。
pushad invoke lstrcmpi,[eax].pszWord,esi .if eax==0
如果两个词的长度相等,我们就继续使用 lstrcmpi 比较两个词。
popad mov ecx,esi lea edx,buffer sub ecx,edx add ecx,FirstChar
我们用缓冲区中的匹配词的第一个字符的地址来构造字符索引。首先取得它跟缓冲区开始地址的相对偏移量,然后加上第一个可见字符的字符索引。
pushad .if RichEditVersion==3 invoke SendMessage,hWnd,EM_POSFROMCHAR,addr rect,ecx .else invoke SendMessage,hWnd,EM_POSFROMCHAR,ecx,0 mov ecx,eax and ecx,0FFFFh mov rect.left,ecx shr eax,16 mov rect.top,eax .endif popad
知道需要高亮显示的词的第一个字符的字符索引后,我们跟着通过发送 EM_POSFROMCHAR 消息取得它的位置。然而这个消息在RichEdit 2.0 和 3.0 中的解释是不同的。对RichEdit 2.0 来说,wParam 包含字符索引,lParam 没有使用。它在 EAX 返回 坐标。对 RichEdit 3.0 来说,wParam 是一个指向 POINT 结构的指针,该调用将会将结果坐标填入这里,而 lParam 则包含字符索引。
正如你看到的,如果传递错误的参数给 EM_POSFROMCHAR ,它可以对你的系统造成重大破坏。这个就是为什么我必须区分 RichEdit 控件的版本的缘故。
mov edx,[eax].pColor invoke SetTextColor,hdc,dword ptr [edx] invoke DrawText,hdc,esi,-1,addr rect,0
一旦我们取得了开始坐标,我们就使用 WORDINFO 结构中指定的颜色来设置正文颜色。跟着使用新颜色值重写该词。
As the final words, 这个方法可以从几个方面提高。例如,我取得从第一个可见行到最后一个可见行的所有正文。如果那些行很长的话,处理那些不可见的词会降低性能。你可以通过一行一行地取得真正的可见行来进行优化。同样,搜索算法也可以使用更多的不同方法来提高速度。不要受我的错误影响(Don't take me wrong):这个例子用来进行语法高亮显示的方法是快,但是它可以更快!:)
![]() |
谢亚龙逼女足姑娘作检讨(图)
“安静”为啥成裁判口头语?
姚明私下发给刘翔的短信
|
![]() |
曝光:姚明小时候与可爱女生合影(图) 组图:隋菲菲私家相册 率性美感领衔女篮 |
![]() |
![]() |
![]() |


档案
日志
相册
视频








评论
想第一时间抢沙发么?