Zurück zu Teil 1
Analyse:
Die erste Aktion nach dem Aufruf von WinMain ist der Aufruf von FillHiliteInfo. Diese Funktion liest den Inhalt der wordfile.txt ein und zerlegt den Inhalt.
FillHiliteInfo proc uses edi LOCAL buffer[1024]:BYTE LOCAL pTemp:DWORD LOCAL BlockSize:DWORD invoke RtlZeroMemory,addr ASMSyntaxArray,sizeof ASMSyntaxArrayInitialisiere ASMSyntaxArray mit null.
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 WordFileNameKonstruiere den vollständigen Pfad-Namen der wordfile.txt. Ich gehe davon aus, dass sie immer im selben Verzeichnis wie das Programm liegt.
invoke GetFileAttributes,addr buffer .if eax!=-1Ich benutze diese Methode als schnelle überprüfung, ob eine Datei existiert.
mov BlockSize,1024*10 invoke HeapAlloc,hMainHeap,0,BlockSize mov pTemp,eaxAlloziiere den Speicherblock um die Wörter abzuspeichern. Standard ist 10K. Der Speicher wir vom Standard-Heap alloziiert.
@@: invoke GetPrivateProfileString,addr ASMSection,addr C1Key,addr ZeroString,pTemp,BlockSize,addr buffer .if eax!=0Ich benutze GetPrivateProfileString um den Inhalt jedes Keys aus wordfile.txt zu erhalten. Die Keys starten bei C1 bis C10.
inc eax .if eax==BlockSize ; der Buffer ist zu klein add BlockSize,1024*10 invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSize mov pTemp,eax jmp @B .endifüberprüfung, ob der Speicherblock groß genug ist. Wenn nicht, inkrementieren wir die Größe um 10K bis der Block groß genug ist.
mov edx,offset ASMColorArray invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArrayübergebe die Wörter, das Speicherblock-Handle, die Größe der Daten die aus wordfile.txt gelesen wurden, die Adresse das Farb-DWords und die Adresse von ASMSyntaxArray.
Lassen Sie uns nun analysieren, was ParseBuffer macht. Kurz gesagt, akzeptiert diese Funktion den Buffer, der die zu hilightende Wörter enthält, teilt sie in individuelle Wörter und speichert jedes von ihnen in ein WORDINFO Struktur Array, auf das schnell mittels ASMSyntaxArray zugegriffen werden kann.
ParseBuffer proc uses edi esi hHeap:DWORD,pBuffer:DWORD, nSize:DWORD, ArrayOffset:DWORD,pArray:DWORD LOCAL buffer[128]:BYTE LOCAL InProgress:DWORD mov InProgress,FALSEInProgress ist das Flag, das ich benutze, um zu indizieren, ob das Scan-Prozess schon begonnen hat. Wenn der Wert FALSE ist, haben wir noch kein nicht-space-Zeichen erreicht.
lea esi,buffer mov edi,pBuffer invoke CharLower,ediESI zeigt auf unseren lokalen Buffer, der die Wörter enthält, die wir in die Wörterliste zerlegt haben. EDI zeigt auf den Wörter-Listen-String. Um die Suche später zu vereinfachen, konvertieren wir alle Buchstaben in Kleinbuchstaben.
mov ecx,nSize SearchLoop: or ecx,ecx jz Finished cmp byte ptr [edi]," " je EndOfWord cmp byte ptr [edi],9 ; tab je EndOfWordScanne die gesamte Wörter-Liste in dem Buffer, nach White-Spaces suchend. Wenn ein White-Space gefunden wird, müssen wir bestimmen, ob es den Anfang oder das Ende eines Wortes markiert.
mov InProgress,TRUE mov al,byte ptr [edi] mov byte ptr [esi],al inc esi SkipIt: inc edi dec ecx jmp SearchLoopWenn das untersuchte Byte kein White-Space ist, kopieren wir es in den Buffer um das ein Wort zu konstruieren und fahren mit dem Scan fort.
EndOfWord: cmp InProgress,TRUE je WordFound jmp SkipItWenn ein White-Space gefunden wurde, überprüfen wir den Wert in InProgress. Wenn der Wert gleich TRUE ist, können wir davon ausgehen, dass das White-Space das Ende eines Wortes markiert und wir das Wort in den lokalen Buffer schreiben können (ESI zeigt darauf), in eine WORDINFO Struktur. Wenn der FALSE ist, fahren wir mit dem Scan fort, bis wir ein nicht-White-Space Zeichen finden.
WordFound: mov byte ptr [esi],0 push ecx invoke HeapAlloc,hHeap,HEAP_ZERO_MEMORY,sizeof WORDINFOWenn das Ende eines Wortes gefunden wurde, hängen wir eine 0 an den Buffer, um aus dem Wort ein ASCIIZ String zu machen. Wir alloziieren dann einen Speicherblock vom Heap in der Größe von WORDINFO für diese Wort.
push esi mov esi,eax assume esi:ptr WORDINFO invoke lstrlen,addr buffer mov [esi].WordLen,eaxWir ermitteln die Länge des Wortes in dem lokalen Buffer und speicher sie im WordLen Element der WORDINFO Struktur, um sie als schnellen Vergleich zu nutzen.
push ArrayOffset pop [esi].pColorSpeichern Sie die Adresse des DWord, dass die Farbe enthält, die zum hilighten benutzt werden soll, im pColor Element.
inc eax invoke HeapAlloc,hHeap,HEAP_ZERO_MEMORY,eax mov [esi].pszWord,eax mov edx,eax invoke lstrcpy,edx,addr bufferAlloziieren Speicher vom Heap um das Wort selbst zu speichern. Genau jetzt, ist die WORDINFO Struktur bereit, um sie in die entsprechende verkettete Liste einzufügen.
mov eax,pArray movzx edx,byte ptr [buffer] shl edx,2 ; multipliziere mit 4 add eax,edxpArray enthält die Adresse von ASMSyntaxArray. Wir wollen zu dem DWord gehen, das den selben Index wie der Wert des ersten Zeichens im Wort hat. Deshalb speichern wir das erste Zeichen des Wortes in EDX, multiplizieren EDX mit 4 (da jedes Element in ASMSyntaxArray 4 Bytes groß ist) und addieren dann den Offset zu der Adresse von ASMSyntaxArray. Wir haben die Adresse des korrespondierenden DWords in EAX.
.if dword ptr [eax]==0 mov dword ptr [eax],esi .else push dword ptr [eax] pop [esi].NextLink mov dword ptr [eax],esi .endifüberprüfe den Wert des DWord. Wenn er 0 ist, bedeutet das, dass zur Zeit kein Wort vorliegt, dass mit dem Zeichen aus der Liste beginnt. Deshalb speichern wir die Adresse der aktuellen WORDINFO Struktur in diesem DWord.
Wenn der Wert in dem DWord nicht 0 ist, bedeutet das, dass mindest ein Wort mit diesem Zeichen in dem Array beginnt. Deshalb fügen wir diese WORDINFO Struktur am Kopf der verketteten Liste ein und aktuallisieren das NextLink-Element, damit es auf die nächste WORDINFO Struktur zeigt.
pop esi pop ecx lea esi,buffer mov InProgress,FALSE jmp SkipItNachdem diese Operation beendet ist, beginnen wir mit dem nächsten Scan-Zyklus, bis das Ende des Buffers erreicht wurde.
invoke SendMessage,hwndRichEdit,EM_SETTYPOGRAPHYOPTIONS,TO_SIMPLELINEBREAK,TO_SIMPLELINEBREAK invoke SendMessage,hwndRichEdit,EM_GETTYPOGRAPHYOPTIONS,1,1 .if eax==0 ; bedeutet, dass die Nachricht nicht verarbeitet wurde mov RichEditVersion,2 .else mov RichEditVersion,3 invoke SendMessage,hwndRichEdit,EM_SETEDITSTYLE,SES_EMULATESYSEDIT,SES_EMULATESYSEDIT .endifNachdem das RichEdit Steuerelement erzeugt wurde, müsse wir seine Version bestimmen. Dieser Schritt ist notwendig, da EM_POSFROMCHAR sich für RichEdit 2.0 und 3.0 unterschiedlich verhält und EM_POSFROMCHAR ist entscheidend für unsere Syntax Hilighting Routine. Ich habe niemals einen dokumentierten Weg gesehen, wie man die Version des RichEdit Steuerelementes überprüft, weshalb ich eine wenig tricksen muss. In diesem Fall setze ich eine Option, die Versions 3.0 spezifisch ist und ermittele umgehend seinen Wert. Wenn ich den Wert ermitteln kann, nehme ich an, dass das Steuerelement in der Version 3.0 vorliegt.
Wenn Sie das RichEdit Steuerelement in der Version 3.0 benutzen, werden Sie feststellen, dass das aktuallisieren der Schrift-Farbe für eine größere Datei ziemlich lange dauert. Dieses Problem scheint Versions 3.0 spezifisch. Ich habe dazu einen Trick gefunden: lassen Sie das Steuerelement das Verhalten des System Edit Steuerelementes emulieren, in dem Sie eine EM_SETEDITSTYLE Nachricht senden.
Danach können wir die Versions-Information erhalten, wir fahren fort und leiten das RichEdit Steuerelement ab (subclassing). Nun werden wir die neue Fenster-Prozedur für das RichEdit Steuerelement unter die Lupe nehmen.
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 eaxWir behandeln die WM_PAINT Nachricht. Als erstes verstecken wir den Cursor, um so einige unschöne gfx nach dem Hilighting zu vermeiden. Danach übergeben wir die Nachricht der original RichEdit Prozedur um sie das Fenster aktuallisieren zu lassen. Wenn CallWindowProc zurückkehrt, wird der Text mit seiner gewöhnlichen Farbe/Hintergrund aktualisiert. Nun ist unsere Möglichkeit um das Syntax-Hilighting zu machen
mov edi,offset ASMSyntaxArray invoke GetDC,hWnd mov hdc,eax invoke SetBkMode,hdc,TRANSPARENTSpeichern Sie die Adress von ASMSyntaxArray in EDI. Dann erhalten wir das Handle des Device Kontextes und setzen den Text Hintergrund auf transparent, so dass der Text, den wir schreiben werden, die Standard-Hintergrund-Farbe benutzt.
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,0Wir wollen den sichtbaren Text ermitteln, weshalb wir als erstes das formatierende Rechteck ermitteln müssen, in dem wir eine EM_GETRECT Nachricht an das RichEdit Steuerelement senden. Nun, wo wir das begrenzende Rechteck haben, ermitteln wir den nächsten Zeichen-Index zu linken oberen Ecke des Rechtecks mit EM_CHARFROMPOS. Wenn wir einmal den Zeichen-Index haben (das erste sichtbare Zeichen im Steuerelement), können wir mit dem Syntax Hilighting von dieser Position aus starten. Aber der Effekt mag nicht so gut sein, als wenn wir mit dem ersten Buchstaben des Zeile, in dem das Zeichen ist, beginnen. Das ist der Grund, warum ich die Zeilen-Nummer von diesem ersten Zeichen ermittele, indem eine EM_LINEFROMCHAR Nachricht gesendet wird. Um das erste Zeichen dieser Linie zu erhalten, sende ich eine EM_LINEINDEX Nachricht.
mov txtrange.chrg.cpMin,eax mov FirstChar,eax invoke SendMessage,hWnd,EM_CHARFROMPOS,0,addr rect.right mov txtrange.chrg.cpMax,eaxWenn wir den ersten Zeichen-Index haben, speichern wir ihn für zukünftige Referenzen in der FirstChar Variable. Als nächstes ermitteln wir den zu letzt sichtbaren Zeichen-Index, in dem wir eine EM_CHARFROMPOS Nachricht senden, und die untere rechte Ecke des Rechtecks in lParam übergeben.
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,eaxWährend des Syntax Hilightings, habe ich einen unansehnlichen Seiteneffekt dieser Methode bemerkt: wenn das RichEdit Steuerelement eine Begrenzung (Margin) hat (sie können eine Begrenzung spezifizieren, in dem Sie eine EM_SETMARGINS Nachricht an das RichEdit Steuerelement senden), DrawText schreibt über diese Begrenzung. Deshalb muss ich eine Clipping-Region erzeugen, die Größe des formatierenden Rechtecks, indem CreateRectRgn aufgerufen wird. Die Ausgabe der GDI Funktionen werden auf die "schreibbare" Fläche geclippt.
Als nächstes müssen wir die Kommentare hilighten und sie aus dem Weg räumen. Meine Methode ist nach einem ";" zu suchen und den Text mit der Kommentar-Farbe zu hilighten, bis ein Carriage Return gefunden wird. Ich werde die Routine hier nicht analysieren: sie ist ziemlich lang und kompliziert. Soviel sei hier gesagt, dass, wenn alle Kommentare gehilighted sind, wir sie mit nullen im Buffer ersetzen, so dass die Wörter in den Kommentaren später nicht bearbeitet / gehilighted werden.
mov ecx,BufferSize lea esi,buffer .while ecx0 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 .endwNachdem die Kommentare erst einmal aus dem Weg sind, seperieren wir die Wörter im Buffer, indem wir die "Seperatoren" mit 0 ersetzen. Mit dieser Methode müssen wir uns nicht um das Seperator-Zeichen kümmern, während wir die Wörter in dem Buffer weiterbearbeiten: Es gibt nur ein Seperator-Zeiche, NULL.
lea esi,buffer mov ecx,BufferSize .while ecx0 mov al,byte ptr [esi] .if al!=0Durchsuchen Sie den Buffer nach dem ersten Zeichen, der nicht NULL ist, d.h. das erste Zeichen eines Wortes.
push ecx invoke lstrlen,esi push eax mov edx,eaxErmitteln Sie die Länge des Wortes und speichern Sie sie in EDX
movzx eax,byte ptr [esi] .if al="A" && al<="Z" sub al,"A" add al,"a" .endifKonvertiere den Buchstaben in einen Kleinbuchstaben (wenn es ein Großbuchstabe ist)
shl eax,2 add eax,edi ; edi enthält den Zeiger auf das WORDINFO Zeiger Array .if dword ptr [eax]!=0Danach, springen wir zum korrespondierenden DWord in ASMSyntaxArray und überprüfen, ob der Wert in diesem DWord 0 ist. Wenn ja, können wir zum nächsten Wort springen.
mov eax,dword ptr [eax] assume eax:ptr WORDINFO .while eax!=0 .if edx==[eax].WordLenWenn der Wert im DWord ungleich null ist, zeigt es auf die verkettete Liste der WORDINFO Strukturen. Wir überprüfen die Länge des Wortes in unserem lokalen Buffer mit dem Wort in der WORDINFO Struktur. Das ist ein schneller Test, bevor wir die Wörter vergleichen. Sollte einigen Rechenzyklen sparen.
pushad invoke lstrcmpi,[eax].pszWord,esi .if eax==0Wenn die Länge beider Wörter gleich ist, fahren wir fort und vergleichen Sie mit lstrcmpi.
popad mov ecx,esi lea edx,buffer sub ecx,edx add ecx,FirstCharWir konstruieren den Zeichen-Index aus der Adresse des ersten Buchstbens des ersten übereinstimmenden Wortes aus dem Buffer. Wir emitteln als erstes seinen relativen Offset von der Start-Adresse des Buffers us und addieren dann den Zeichen-Index des ersten sichtbaren Buchstabens hinzu.
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 popadWenn wir erst einmal den Zeichen-Index des ersten Buchstabens des ersten Wortes, das gehilighted werden soll, kennen, fahren wir fort und ermitteln die Koordinaten, in dem wir eine EM_POSFROMCHAR Nachricht senden. Diese Nachricht wird allerdings von RichEdit 2.0 und 3.0 unterschiedlich interpretiert. Bei RichEdit 2.0 enthält wParam den Zeichen-Index und lParam wird nicht benutzt. Die Koordinaten werden in EAX zurückgegeben. Bei RichEdit 3.0 ist wParam der Zeiger auf eine POINT Struktur, die mit den Koordinaten gefüllt wird und lParam enthält den Zeichen-Index.
Wie Sie sehen, kann die übergabe falscher Parameter bei EM_POSFROMCHAR verheerende Folgen für Ihr System haben. Das ist der Grund warum ich zwichen den RichEdit Steuerelement Versionen unterscheiden muss.
mov edx,[eax].pColor invoke SetTextColor,hdc,dword ptr [edx] invoke DrawText,hdc,esi,-1,addr rect,0Wenn wir erst einmal die Koordinaten des Anfangs haben, setzen wir die Text-Farbe, die in der WORDINFO Struktur spezifiziert ist. Und dann überschreiben wir das Wort mit der neuen Farbe.
Als Schlusswort: diese Methode kann in verschiedene Dingen verbessert werden. Ich ermittle zum Beispiel den ganzen Text von der ersten bis zur letzten sichtbaren Zeile. Wenn diese Zeilen sehr lang sind, kann die Performance leiden, indem Wörter bearbeitet werden, die nicht sichtbar sind. Sie können das optimieren, indem Sie nur den wirklich sichtbaren Text ermitteln. Auch der Such-Algorithmus kann verbessert werden, indem eine effizientere Methode benutzt wird. Verstehen Sie mich nicht falsch: die Syntax-Hilighting Methode die ich in diesem Beispiel verwendet habe ist SCHNELL, aber sie kann SCHNELLER sein. :)
Deutsche Übersetzung: Joachim Rohde
Die original Win32Asm-Tutorials stammen von Iczelion's Win32 Assembly HomePage