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 ASMSyntaxArray
Initialisiere 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 WordFileName
Konstruiere 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!=-1
Ich benutze diese Methode als schnelle überprüfung, ob eine Datei existiert.
mov BlockSize,1024*10
invoke HeapAlloc,hMainHeap,0,BlockSize
mov pTemp,eax
Alloziiere 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!=0
Ich 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,FALSE
InProgress 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,edi
ESI 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 EndOfWord
Scanne 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 SearchLoop
Wenn 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 SkipIt
Wenn 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 WORDINFO
Wenn 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,eax
Wir 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].pColor
Speichern 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 buffer
Alloziieren 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,edx
pArray 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 SkipIt
Nachdem 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
.endif
Nachdem 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 eax
Wir 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,TRANSPARENT
Speichern 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,0
Wir 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,eax
Wenn 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,eax
Wä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
.endw
Nachdem 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!=0
Durchsuchen 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,eax
Ermitteln 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"
.endif
Konvertiere 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]!=0
Danach, 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].WordLen
Wenn 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==0
Wenn 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,FirstChar
Wir 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
popad
Wenn 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,0
Wenn 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