AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Thema durchsuchen
Ansicht
Themen-Optionen

Alternative zu PosEx

Ein Thema von Amateurprofi · begonnen am 19. Nov 2024 · letzter Beitrag vom 4. Dez 2024
Antwort Antwort
Seite 2 von 6     12 34     Letzte »    
Benutzerbild von Sinspin
Sinspin

Registriert seit: 15. Sep 2008
Ort: Dubai
691 Beiträge
 
Delphi 10.3 Rio
 
#11

AW: Alternative zu PosEx

  Alt 20. Nov 2024, 08:09
Nochmal zu "Wie sieht es denn mit einer 64Bit Variante aus?"
Warum fragst du dass wenn du sagst "Ich habe aber bisher noch keinen Anwendungsfall gehabt bei dem die Untersuchung eines Strings zeitkritisch gewesen wäre."
Warum soll ich für meinen üblichen Kleinkram das Alte weiter verwenden wenn ich was Neues haben könnte?
Dann muss der Kopp sich auch nicht merken das es da mal was schnelles gegeben hat was man verwenden könnte. Es geht einfach.

Aber ich brauche eben 64Bit, da alle meine Komponenten auf 64Bit umgestellt sind und ich nur noch 64Bit Programme schreibe.
Stefan
Nur die Besten sterben jung
A constant is a constant until it change.
  Mit Zitat antworten Zitat
Amateurprofi

Registriert seit: 17. Nov 2005
Ort: Hamburg
1.077 Beiträge
 
Delphi XE2 Professional
 
#12

AW: Alternative zu PosEx

  Alt 21. Nov 2024, 04:34
Nochmal zu "Wie sieht es denn mit einer 64Bit Variante aus?"
Warum fragst du dass wenn du sagst "Ich habe aber bisher noch keinen Anwendungsfall gehabt bei dem die Untersuchung eines Strings zeitkritisch gewesen wäre."
Warum soll ich für meinen üblichen Kleinkram das Alte weiter verwenden wenn ich was Neues haben könnte?
Dann muss der Kopp sich auch nicht merken das es da mal was schnelles gegeben hat was man verwenden könnte. Es geht einfach.

Aber ich brauche eben 64Bit, da alle meine Komponenten auf 64Bit umgestellt sind und ich nur noch 64Bit Programme schreibe.
Werde die Tage 64 Bit-Versionen schreiben.
Gruß, Klaus
Die Titanic wurde von Profis gebaut,
die Arche Noah von einem Amateur.
... Und dieser Beitrag vom Amateurprofi....
  Mit Zitat antworten Zitat
Amateurprofi

Registriert seit: 17. Nov 2005
Ort: Hamburg
1.077 Beiträge
 
Delphi XE2 Professional
 
#13

AW: Alternative zu PosEx

  Alt 23. Nov 2024, 13:33
Hier sind auch die 64bit-Versionen von StrPos und StrPosEx.
Der Vollständigkeit halber und wegen marginaler Änderungen auch noch mal die 32bit-Versionen.

StrPos

Delphi-Quellcode:
{$IFDEF CPUX86}
{------------------------------------------------------------------------------}
{ StrPos 32bit                                                                 }
{ Prüft, ob SearchFor in SearchIn innerhalb des Bereiches First..Last          }
{ vorkommt und gibt die gefundene Position. bzw 0 für 'nicht gefunden' zurück. }
{ First<1 heißt ab Anfang von SearchIn suchen.                                 }
{ Last<1 oder Last>Length(SearchIn) heißt bis Ende von SearchIn suchen.        }
{------------------------------------------------------------------------------}
FUNCTION StrPos(const SearchFor,SearchIn:String; First,Last:NativeInt):Integer;
asm
               // EAX=@SearchFor, EDX=@SearchIn, ECX=First, Stack=Last
               test eax,eax
               jz @Nil // SearchFor leer
               test edx,edx
               jz @Nil // SearchIn leer
               dec ecx // First 0-Based
               jge @FirstOk // First>=1
               xor ecx,ecx
@FirstOK: push ebx
               push edi
               push esi
               mov ebp,Last
               mov ebx,[edx-4] // Length(SearchIn)
               test ebp,ebp
               jle @LastOK // Last<=0 heißt bis Ende suchen
               cmp ebp,ebx
               jge @LastOK // Last>Length(SearchIn), bis Ende suchen
               mov ebx,ebp // Nur bis Last suchen
@LastOK: // EAX=@SearchFor, EDX=@SearchIn, ECX=First-1, EBX=Last
               mov edi,[eax-4] // Length(SearchFor)
               lea esi,[ecx+edi] // First+Length(SearchFor)-1
               cmp esi,ebx
               jg @Past // First>Last oder First+Length(SearchFor)>Last
               lea eax,[eax+edi*2-2] // @SearchFor[Length(SearchFor)]
               lea ebp,[edx+ebx*2] // @SearchIn[Last+1]
               lea edx,[edx+esi*2-2] // @SearchIn[First+Length(SearchFor)-1]
               lea edi,[edi*2-2] // 2*(Length(SearchFor)-1)
               neg edi // -(2*(Length(SearchFor)-1))
               add ecx,ecx // 2*(First-1)
               sub ecx,edx // 2*(First-1)-@SearchIn[First+Length(SearchFor)-1]
               push ecx // Für spätere Positionsberechnung
               movzx ecx,word[eax] // letztes Zeichen von SearchFor
               // --------------------------------------------------------------
               // CX = Letztes Zeichen von SearchFor
               // EDX = SearchIn[First+Length(SearchFor)
               // EBP = @SearchIn[Last+1 Zeichen]
               // EDI = -(2*(Length(SearchFor)-1))
               // ESI = First+Length(SearchFor)-1
@Loop: cmp cx,[edx] // Letzes Zeichen von SearchFor an EDX?
               jz @Test0 // Ja, SearchIn auf voriges Zeichen
@AfterTest0: cmp cx,[edx+2] // Letzes Zeichen von SearchFor an EDX+1 Zeichen
               jz @TestT // J,
@AfterTestT: add edx,8 // SearchIn+4 Zeichen
               cmp edx,ebp // SearchIn noch im zu durchsuchenden Bereich
               jb @Continue // Ja
@EndLoop: add edx,-4 // SearchIn-2 Zeichen
               cmp edx,ebp // SearchIn noch im zu durchsuchenden Bereich
               jb @Loop // Ja
               jmp @False // Nicht gefunden
@Continue: cmp cx,[edx-4] // Letzes Zeichen von SearchFor an EDX-2 Zeichen?
               jz @Test2 // Ja, SearchIn -3 Zeichen
               cmp cx,[edx-2] // Letzes Zeichen von SearchFor an EDX-1 Zeichen?
               jnz @Loop // Nein, nächste Position prüfen
@Test1: add edx,2 // SearchIn + 1 Zeichen, durch folgende Adds - 2 Zeichen
@Test2: add edx,-4 // SearchIn - 2 Zeichen, durch folgendes Add - 3 Zeichen
@Test0: add edx,-2 // SearchIn - 1 Zeichen
@TestT: mov esi,edi
               test esi,esi // Alle Zeichen von SearchFor gefunden?
               jz @Found // Ja, gefunden
@String: mov ebx,[eax+esi] // 2 Zeichen aus SearchFor
               cmp ebx,[edx+esi+2] // In SearchIn?
               jnz @AfterTestT // Nein, SearchIn+4 Zeichen
               cmp esi,-4 // Alle Zeichen gefunden?
               jge @Found // Ja
               mov ebx,[eax+esi+4] // Nächste 2 Zeichen aus SearchFor
               cmp ebx,[edx+esi+6] // In SearchIn?
               jnz @AfterTestT // Nein, SearchIn+4 Zeichen
               add esi,8 // Zeichenzahl + 4 Zeichen
               jl @String // Nächste 4 Zeichen prüfen
               //---------------------------------------------------------------
@Found: lea eax,[edx+4] // Fundstelle
               cmp eax,ebp // Im zu durchsuchenden Bereich?
               ja @False // Nein, nicht gefunden.
               add eax,[esp] // Endgültige Position in Bytes
               shr eax,1 // Endgültige Position in Zeichen
               jmp @End // Stack bereinigen, Register wieder herstellen
               //---------------------------------------------------------------
@False: xor eax,eax // Nicht gefunden
               jmp @End // Stack bereinigen, Register wieder herstellen
               //---------------------------------------------------------------
@Nil: xor eax,eax // Nicht gefunden
               jmp @Ret // Return
               //---------------------------------------------------------------
@Past: xor eax,eax // Nicht gefunden
               jmp @Pop // Register wieder herstellen
               //---------------------------------------------------------------
@End: add esp,4 // Stack bereinigen
@Pop: pop esi // ESI wieder herstellen
               pop edi // EDI wieder herstellen
               pop ebx // EBX wieder herstellen
@Ret:
end;
{$ENDIF}
Delphi-Quellcode:
{$IFDEF CPUX64}
{------------------------------------------------------------------------------}
{ StrPos 64Bit                                                                 }
{ Prüft, ob SearchFor in SearchIn innerhalb des Bereiches First..Last          }
{ vorkommt und gibt die gefundene Position. bzw 0 für 'nicht gefunden' zurück. }
{ First<1 heißt ab Anfang von SearchIn suchen.                                 }
{ Last<1 oder Last>Length(SearchIn) heißt bis Ende von SearchIn suchen.        }
{------------------------------------------------------------------------------}
FUNCTION StrPos(const SearchFor,SearchIn:String; First,Last:NativeInt):Integer;
asm
               // RCX=@SearchFor, RDX=@SearchIn, R8=First, R9=Last
               test rcx,rcx
               jz @Nil // SearchFor leer
               test edx,edx
               jz @Nil // SearchIn leer
               dec r8 // First 0-Based
               jge @FirstOk // First>=1
               xor r8,r8
@FirstOK: movsx r10,dword[rdx-4] // Length(SearchIn)
               test r9,r9
               jle @LastOK // Last<=0 heißt bis Ende suchen
               cmp r9,r10
               jge @LastOK // Last>Length(SearchIn), bis Ende suchen
               mov r10,r9 // Nur bis Last suchen
@LastOK: movsx r9,dword[rcx-4] // Length(SearchFor)
               add r8,r9 // First+Length(SearchFor)-1
               cmp r8,r10
               jg @Nil // First>Last oder First+Length(SearchFor)>Last
               push rbx // RBX retten
               lea rax,[rcx+r9*2-2] // @SearchFor[Length(SearchFor)]
               lea r11,[rdx+r10*2] // @SearchIn[Last+1]
               lea rdx,[rdx+r8*2-2] // @SearchIn[First+Length(SearchFor)-1]
               lea r9,[r9*2-2] // 2*(Length(SearchFor)-1)
               neg r9 // -(2*(Length(SearchFor)-1))
               add r8,r8 // 2*(First-1)
               sub r8,rdx // 2*(First-1)-@SearchIn[First+Length(SearchFor)-1]
               mov cx,[rax] // Letztes Zeichen von SearchFor
               // --------------------------------------------------------------
               // RAX @SearchFor[Length(SearchFor)]
               // RDX = Aktuelle Fundstelle für letztes Zeichen von SearchFor
               // CX = Letztes Zeichen von SearchFor
               // R8 = 2*(First-1)-@SearchIn[First+Length(SearchFor)-1]
               // R9 = -(2*(Length(SearchFor)-1))
               // R11 = Terminator
@Loop: cmp cx,[rdx] // Letzes Zeichen von SearchFor an EDX?
               jz @Test0 // Ja, SearchIn auf voriges Zeichen
@AfterTest0: cmp cx,[rdx+2] // Letzes Zeichen von SearchFor an EDX+1 Zeichen
               jz @TestT // J,
@AfterTestT: add rdx,8 // SearchIn+4 Zeichen
               cmp rdx,r11 // SearchIn noch im zu durchsuchenden Bereich
               jb @Continue // Ja
@EndLoop: add rdx,-4 // SearchIn-2 Zeichen
               cmp rdx,r11 // SearchIn noch im zu durchsuchenden Bereich
               jb @Loop // Ja
               jmp @NotFound // Nicht gefunden
@Continue: cmp cx,[rdx-4] // Letzes Zeichen von SearchFor an EDX-2 Zeichen?
               jz @Test2 // Ja, SearchIn -3 Zeichen
               cmp cx,[rdx-2] // Letzes Zeichen von SearchFor an EDX-1 Zeichen?
               jnz @Loop // Nein, nächste Position prüfen
@Test1: add rdx,2 // SearchIn + 1 Zeichen, durch folgende Adds - 2 Zeichen
@Test2: add rdx,-4 // SearchIn - 2 Zeichen, durch folgendes Add - 3 Zeichen
@Test0: add rdx,-2 // SearchIn - 1 Zeichen
@TestT: test r9,r9 // Hat SearchFor nur 1 Zeichen?
               jz @Found // Ja, gefunden
               mov r10,r9
@String: mov ebx,[rax+r10] // 2 Zeichen aus SearchFor
               cmp ebx,[rdx+r10+2] // In SearchIn?
               jnz @AfterTestT // Nein, SearchIn+4 Zeichen
               cmp r10,-4 // Alle Zeichen gefunden?
               jge @Found // Ja
               mov ebx,[rax+r10+4] // Nächste 2 Zeichen aus SearchFor
               cmp ebx,[rdx+r10+6] // In SearchIn?
               jnz @AfterTestT // Nein, SearchIn+4 Zeichen
               add r10,8 // Zeichenzahl + 4 Zeichen
               jl @String // Nächste 4 Zeichen prüfen
               //---------------------------------------------------------------
@Found: lea rax,[rdx+4] // Fundstelle
               cmp rax,r11 // Im zu durchsuchenden Bereich?
               ja @NotFound // Nein, nicht gefunden.
               add rax,r8 // Endgültige Position in Bytes
               shr rax,1 // Endgültige Position in Zeichen
               pop rbx // RBX wieder herstellen
               ret
               //---------------------------------------------------------------
@Nil: xor eax,eax
               ret
@NotFound: //---------------------------------------------------------------
               xor eax,eax // Nicht gefunden
               pop rbx // RBX wieder herstellen
end;
{$ENDIF}
StrPosEx

Delphi-Quellcode:
{$IFDEF CPUX86}
{------------------------------------------------------------------------------}
{ StrPosEx 32bit                                                               }
{ Sucht alle Vorkommen von SearchFor in SearchIn und stellt die Positionen     }
{ der Fundstellen in Positions.                                                }
{ Es ist Sache der aufrufenden Stelle, sicherzustellen, daas Positions lang    }
{ genug ist, alle Fundstellen zu speichern.                                    }
{ Parameter                                                                    }
{    SearchFor  : String, nach dem gesucht wird.                               }
{    SearchIn   : String, in dem gesucht wird.                                 }
{    Positions  : Array zur Speicherung der Fundstellen.                       }
{    Exceptions : Gibt an, in welchen Fällen Exceptions ausgelöst werden.      }
{                 0 = Keine, -1 = Alle.                                        }
{                    Bit 0 = 1 : Wenn SearchFor leer ist.                      }
{                    Bit 1 = 1 : Wenn SearchIn leer ist.                       }
{                    Bit 2 = 1 : Wenn SearchFor länger ist, als SearchIn.      }
{                    Bit 3 = 1 : Wenn Positions nicht assigned ist.            }
{                    Bit 4 = 1 : Wenn Positions zu kurz ist.                   }
{                 Wenn ein Fehler auftritt, und das korrespondierende Bit in   }
{                 Exceptions nicht gesetzt ist, werden folgende Fehlercodes    }
{                 zurückgegeben:                                               }
{                    -1 SearchFor leer.                                        }
{                    -2 SearchIn leer.                                         }
{                    -3 SearchFor länger als SearchIn.                         }
{                    -4 Positions nicht assigned.                              }
{                    -5 Positions zu kurz.                                     }
{ Wenn kein Fehler auftritt, wird die Anzahl der Fundstellen zurückgegeben.    }
{------------------------------------------------------------------------------}
FUNCTION StrPosEx(const SearchFor,SearchIn:String;
   Positions:TIntegerDynArray; Exceptions:NativeInt=0):Integer;
const
   sSearchForEmpty:String='StrPosEx:'#13'SearchFor ist leer';
   sSearchInEmpty:String='StrPosEx:'#13'SearchIn ist leer';
   sSearchForTooLong:String='StrPosEx:'#13'Searchfo ist länger als SearchIn';
   sPositionsEmpty:String='StrPosEx:'#13'Positions ist nicht assigned';
   sPositionsLength:String='StrPosEx:'#13'Das Array "Positions" ist zu kurz '+
                           'um alle Fundstellen zu speichern';
   pExClass:ExceptClass=(Exception);
asm
               // EAX=@SearchFor, EDX=@SearchIn, ECX=Positions
               // Register retten und Platz für lokale Variablen reservieren
               push ebp
               push ebx
               push edi
               push esi
               sub esp,12 // Platz für 3 Integers
               // Prüfen, ob SearchFor und SearchIn nicht leer sind
               test eax,eax // SearchFor leer?
               jz @SFNil // Ja, Fehlermedung
               test edx,edx // SearchIn leer?
               jz @SINil // Ja, Fehlermeldung
               test ecx,ecx // Positions leer?
               jz @PosNil // Ja, Fehlermeldung
               // Längen von SearchFor und SearchIn laden und prüfen ob
               // SearchFor nicht länger ist, als SearchIn
               mov edi,[eax-4] // Length(SearchFor)
               mov ebx,[edx-4] // Length(SearchIn)
               cmp edi,ebx // SearchFor länger als SearchIn?
               ja @SFTooLong // Ja, Fehlermeldung
               cmp edi,1 // Hat SearchFor nur 1 Zeichen
               je @Char // Ja
               // Positions retten, Anzahl Fundstellen auf 0
               mov [esp+8],ecx // Positions
               mov [esp+4],0 // Anzahl Fundstellen
               // Zeiger und Länge von SearchIn initialsieren
               lea eax,[eax+edi*2-2] // EAX auf letztes Zeichen in SearchFor
               lea ebp,[edx+ebx*2] // EBP hinter letztes Zeichen von SearchIn
               lea edx,[edx+edi*2-2] // EDX auf Ende der ersten potentiellen Fundstelle
               lea edi,[edi*2-2] // EDI = 2*(Length(SearchFor)-1)
               neg edi // EDI = -(2*(Length(SearchFor)-1))
               xor ecx,ecx
               sub ecx,edx // ECX = -SearchIn[Length(SearchFor)-1]
               mov [esp],ecx // Für spätere Positionsberechnung
               // --------------------------------------------------------------
               // EAX = Zeigt auf letztes Zeichen in SearchFor
               // EDX = Zeigt auf Ende der nächsten potentiellen Fundstelle
               // EBP = Zeigt hinter letztes Zeichen von SearchIn
               // EDI = -(2*(Length(SearchFor)-1))
               // [ESP-8] = Postions
               // [ESP-4] = Anzahl Fundstellen
               // [ESP] = -SearchIn[Length(SearchFor)-1]
@Start: mov cx,[eax] // letztes Zeichen von SearchFor
@Loop: cmp cx,[edx] // CX an [EDX]?
               jz @Test0 // Ja, weitere Zeichen ab [EDX-1 Char] prüfen
@AfterTest0: cmp cx,[edx+2] // CX an [EDX + 1 Char]?
               jz @TestT // Ja, weitere Zeichen ab [EDX] prüfen
@AfterTestT: add edx,8 // SearchIn + 4 Chars
               cmp edx,ebp // SearchIn noch im zu durchsuchenden Bereich
               jb @Continue // Ja
@EndLoop: add edx,-4 // SearchIn - 2 Chars
               cmp edx,ebp // SearchIn noch im zu durchsuchenden Bereich
               jb @Loop // Ja
               jmp @NoFurther // Keine weiteren Fundstellen
@Continue: cmp cx,[edx-4] // CX an [EDX - 2 Chars]?
               jz @Test2 // Ja, SearchIn - 3 Chars
               cmp cx,[edx-2] // Letzes Zeichen von SearchFor an EDX-1 Zeichen?
               jnz @Loop // Nein, nächste Position prüfen
@Test1: add edx,2 // SearchIn + 1 Char, durch folgende Adds - 2 Chars
@Test2: add edx,-4 // SearchIn - 2 Chars, durch folgendes Add - 3 Chars
@Test0: add edx,-2 // SearchIn - 1 Char
@TestT: test edi,edi // Alle Zeichen von SearchFor gefunden?
               jz @Found // Ja, gefunden
               mov esi,edi // -(2*(Length(SearchFor)-1))
@String: mov ebx,[eax+esi] // 2 Zeichen aus SearchFor
               cmp ebx,[edx+esi+2] // In SearchIn?
               jnz @AfterTestT // Nein, SearchIn + 4 Chars
               cmp esi,-4 // Alle Zeichen gefunden?
               jge @Found // Ja
               mov ebx,[eax+esi+4] // Nächste 2 Zeichen aus SearchFor
               cmp ebx,[edx+esi+6] // In SearchIn?
               jnz @AfterTestT // Nein, SearchIn + 4 Chars
               add esi,8 // Zeichenzahl + 4 Chars
               jl @String // Nächste 4 Zeichen prüfen
               //---------------------------------------------------------------
               // Gefunden. EDX zeigt auf Fundstelle - 1 Zeichen
@Found: lea ecx,[edx+4] // Fundstelle + 1 Zeichen
               cmp ecx,ebp // Im zu durchsuchenden Bereich?
               ja @NoFurther // Nein, keine weiteren Fundstellen
               add ecx,[esp] // Position in Bytes
               shr ecx,1 // Position in Zeichen
               mov esi,[esp+8] // Positions
               mov ebx,[esp+4] // Bisherige Anzahl Fundstellen
               cmp ebx,[esi-4] // Noch Platz in Positions?
               jae @OutOfMem // Nein
               mov [esi+ebx*4],ecx // Fundstelle speichern
               inc ebx // Anzahl Fundstellen + 1
               mov [esp+4],ebx // Anzahl Fundstellen speichern
               // EDX auf nächste potentielle Fundstelle
               mov esi,edi // -(2*(Length(SearchFor)-1))
               neg esi // 2*(Length(SearchFor)-1)
               lea edx,[edx+esi+4] // EDX=nächste potentielle Fundstelle
               cmp edx,ebp // Noch im gültigen Bereich?
               jb @Start // Ja, weiter suchen
               jmp @NoFurther // Nein, keine weiteren Fundstellen
               // --------------------------------------------------------------
               // Suche nach nur einem Zeichen
               // EAX = SearchFor
               // EDX = SearchIn
               // ECX = Positions
               // EBX = Lenght(SearchIn)
@Char: mov ebp,ecx // Positions
               xor ecx,ecx // Anzahl Fundstellen
               lea edx,[edx+ebx*2] // Hinter letztes Zeichen von SearchIn
               lea edi,[ebx+1] // für spötere Positionsermittlung
               neg ebx
               mov ax,[eax] // gesuchtes Zeichen
@CharLoop: cmp ax,[edx+ebx*2] // AX an aktueller Position?
               jz @CharFound1 // Ja
               cmp ax,[edx+ebx*2+2] // AX an nächster Position?
               jz @CharFound2 // Ja
               add ebx,2 // 2 Zeichen weiter
               jl @CharLoop
               jmp @NoFurtherC // Keine weiteren Fundstellen
               // An [EDX+EBX*2] gefunden
@CharFound1: cmp ecx,[ebp-4] // Noch Platz in Positions?
               jae @OutOfMem // Nein
               lea esi,[edi+ebx] // Fundstelle
               mov [ebp+ecx*4],esi // In Positions speichern
               inc ecx // Anzahl Fundstellen
               inc ebx // 1 Zeichen weiter
               jl @CharLoop
               jmp @NoFurtherC // Keine weiteren Fundstellen
               // An [EDX+EBX*2+2] gefunden
@CharFound2: cmp ecx,[ebp-4] // Noch Platz in Positions?
               jae @OutOfMem // Nein
               lea esi,[edi+ebx+1] // Fundstelle * 2
               mov [ebp+ecx*4],esi // In Positions speichern
               inc ecx // Anzahl Fundstellen
               add ebx,2 // 2 Zeichen weiter
               jl @CharLoop
               jmp @NoFurtherC // Keine weiteren Fundstellen
               //---------------------------------------------------------------
               // SearchFor ist leer
@SFNil: mov eax,sSearchForEmpty
               mov ecx,1 // Fehlercode
               jmp @End
               //---------------------------------------------------------------
               // SearchIn ist leer
@SINil: mov eax,sSearchInEmpty
               mov ecx,2 // Fehlercode
               jmp @End
               //---------------------------------------------------------------
               // SearchFor ist länger als SearchIn
@SFTooLong: mov eax,sSearchForTooLong
               mov ecx,3 // Fehlercode
               jmp @End
               //---------------------------------------------------------------
               // Positions ist nicht assigned
@PosNil: mov eax,sPositionsEmpty
               mov ecx,4 // Fehlercode
               jmp @End
               //---------------------------------------------------------------
               // Positions nicht lang genug um neue Fundstelle zu speichern
@OutOfMem: mov eax,sPositionsLength
               mov ecx,5 // Fehlercode
               jmp @End // Fehlermeldung
               //---------------------------------------------------------------
               // Keine weiteren Fundstellen
@NoFurther: mov ecx,[esp+4] // Anzahl Fundstellen
@NoFurtherC: xor eax,eax // ´Kein Fehler
               //---------------------------------------------------------------
               // Stack bereinigen und Register restaurieren
@End: add esp,12 // Stack bereinigen
               pop esi // ESI wieder herstellen
               pop edi // EDI wieder herstellen
               pop ebx // EBX wieder herstellen
               pop ebp // EBP wieder herstellen
               //---------------------------------------------------------------
               // Prüfen ob Fehler vorliegt und ggfs. Exception auslösen
               test eax,eax // Fehler ?
               jz @NoError // Nein, Anzahl Fundstellen zurückgeben
               mov edx,1
               shl edx,cl
               neg ecx // Fehlercode
               shr edx,1
               test [ebp+8],edx // Exception werfen?
               jz @NoError // Nein, nur Fehlercode zurückgeben
               mov ecx,eax // Fehlertext
               mov eax,pExClass // InstanceOrVMT
               mov edx,1 // Alloc
               call Exception.Create
               call System.@RaiseExcept
               //---------------------------------------------------------------
               // Anzahl Fundstellen bzw. Fehlercode in Result
@NoError: mov eax,ecx // Anzahl Fundstellen bzw. FehlerCode
end;
{$ENDIF}
Delphi-Quellcode:
{$IFDEF CPUX64}
{------------------------------------------------------------------------------}
{ StrPosEx 64bit                                                               }
{ Sucht alle Vorkommen von SearchFor in SearchIn und stellt die Positionen     }
{ der Fundstellen in Positions.                                                }
{ Es ist Sache der aufrufenden Stelle, sicherzustellen, daas Positions lang    }
{ genug ist, alle Fundstellen zu speichern.                                    }
{ Parameter                                                                    }
{    SearchFor  : String, nach dem gesucht wird.                               }
{    SearchIn   : String, inem gesucht wird.                                   }
{    Positions  : Array zur Speicherung der Fundstellen.                       }
{    Exceptions : Gibt an, in welchen Fällen Exceptions ausgelöst werden.      }
{                 0 = Keine, -1 = Alle                                         }
{                 In der aktuellen Version ist das Werfen von Exceptions       }
{                 deaktiviert, weil der Code hier nicht funktioniert.          }
{                 In anderen Anwendungen funktioniert er.                      }
{                    Bit 0 = 1 : Wenn SearchFor leer ist.                      }
{                    Bit 1 = 1 : Wenn SearchIn leer ist.                       }
{                    Bit 2 = 1 : Wenn SearchFor länger ist, als SearchIn.      }
{                    Bit 3 = 1 : Wenn Positions nicht assigned ist.            }
{                    Bit 4 = 1 : Wenn Positions zu kurz ist.                   }
{                 Wenn ein Fehler auftritt, und das korrespondierende Bit in   }
{                 Exceptions nicht gesetzt ist, werden folgende Fehlercodes    }
{                 zurückgegeben:                                               }
{                    -1 SearchFor leer.                                        }
{                    -2 SearchIn leer.                                         }
{                    -3 SearchFor länger als SearchIn.                         }
{                    -4 Positions nicht assigned.                              }
{                    -5 Positions zu kurz.                                     }
{ Wenn kein Fehler auftritt, wird die Anzahl der Fundstellen zurückgegeben.    }
{------------------------------------------------------------------------------}
FUNCTION StrPosEx(const SearchFor,SearchIn:String;
   Positions:TIntegerDynArray; Exceptions:NativeInt=0):Integer;
const
   sSearchForEmpty:String='StrPosEx:'#13'SearchFor ist leer';
   sSearchInEmpty:String='StrPosEx:'#13'SearchIn ist leer';
   sSearchForTooLong:String='StrPosEx:'#13'Searchfo ist länger als SearchIn';
   sPositionsEmpty:String='StrPosEx:'#13'Positions ist nicht assigned';
   sPositionsLength:String='StrPosEx:'#13'Das Array "Positions" ist zu kurz '+
                           'um alle Fundstellen zu speichern';
   pExClass:ExceptClass=(Exception);
asm
               // RCX=@SearchFor, RDX=@SearchIn, R8=Positions, R9=Exceptions
               // Exceptions = 0 (keine Exceptions) setzen, weil das hier
               // nicht funktioniert (In anderen Projektion funktionierts).
               xor r9,r9 // Keine Exceptions
               // Register retten
               push rbp
               push rbx
               push rdi
               push rsi
               push r12
               // Prüfen, ob SearchFor und SearchIn nicht leer sind
               test rcx,rcx // SearchFor leer?
               jz @SFNil // Ja, Fehlermedung
               test rdx,rdx // SearchIn leer?
               jz @SINil // Ja, Fehlermeldung
               test r8,r8 // Positions assigned?
               jz @PosEmpty // Nein, Fehlermeldung
               // Längen von Positons, SearchFor und SearchIn laden und
               // prüfen ob SearchFor nicht länger ist, als SearchIn
               movsx rdi,dword[rcx-4] // Length(SearchFor)
               movsx rbx,dword[rdx-4] // Length(SearchIn)
               cmp edi,ebx // SearchFor länger als SearchIn?
               ja @SFTooLong // Ja, Fehlermeldung
               mov r11,[r8-8] // Length Positions
               xor r10,r10 // Anzahl Fundstellen auf 0
               cmp edi,1 // Hat SearchFor nur 1 Zeichen
               je @Char // Ja
               // Zeiger und Länge von SearchIn initialsieren
               lea rax,[rcx+rdi*2-2] // RAX auf letztes Zeichen in SearchFor
               lea rbp,[rdx+rbx*2] // RBP hinter letztes Zeichen von SearchIn
               lea rdx,[rdx+rdi*2-2] // RDX auf Ende der ersten potentiellen Fundstelle
               lea rdi,[rdi*2-2] // RDI = 2*(Length(SearchFor)-1)
               neg rdi // RDI = -(2*(Length(SearchFor)-1))
               xor r12,r12
               sub r12,rdx // R12 = -SearchIn[Length(SearchFor)-1]
               // --------------------------------------------------------------
               // RAX = Zeigt auf letztes Zeichen in SearchFor
               // RDX = Zeigt auf Ende der nächsten potentiellen Fundstelle
               // RBP = Zeigt hinter letztes Zeichen von SearchIn
               // RDI = -(2*(Length(SearchFor)-1))
               // R8 = Postions
               // R9 = Exceptions
               // R10 = Anzahl Fundstellen
               // R11 = Length(Positions)
               // R12 = -SearchIn[Length(SearchFor)-1]
               // Für Ermittlung der Positionen von Fundstellen
@Start: mov cx,[rax] // letztes Zeichen von SearchFor
@Loop: cmp cx,[rdx] // CX an [EDX]?
               jz @Test0 // Ja, weitere Zeichen ab [EDX-1 Char] prüfen
@AfterTest0: cmp cx,[rdx+2] // CX an [EDX + 1 Char]?
               jz @TestT // Ja, weitere Zeichen ab [EDX] prüfen
@AfterTestT: add rdx,8 // SearchIn + 4 Chars
               cmp rdx,rbp // SearchIn noch im zu durchsuchenden Bereich
               jb @Continue // Ja
@EndLoop: add rdx,-4 // SearchIn - 2 Chars
               cmp rdx,rbp // SearchIn noch im zu durchsuchenden Bereich
               jb @Loop // Ja
               jmp @NoFurther // Keine weiteren Fundstellen
@Continue: cmp cx,[rdx-4] // CX an [EDX - 2 Chars]?
               jz @Test2 // Ja, SearchIn - 3 Chars
               cmp cx,[rdx-2] // Letzes Zeichen von SearchFor an EDX-1 Zeichen?
               jnz @Loop // Nein, nächste Position prüfen
@Test1: add rdx,2 // SearchIn + 1 Char, durch folgende Adds - 2 Chars
@Test2: add rdx,-4 // SearchIn - 2 Chars, durch folgendes Add - 3 Chars
@Test0: add rdx,-2 // SearchIn - 1 Char
@TestT: test rdi,rdi // Alle Zeichen von SearchFor gefunden?
               jz @Found // Ja, gefunden
               mov rsi,rdi // -(2*(Length(SearchFor)-1))
@String: mov ebx,[rax+rsi] // 2 Zeichen aus SearchFor
               cmp ebx,[rdx+rsi+2] // In SearchIn?
               jnz @AfterTestT // Nein, SearchIn + 4 Chars
               cmp rsi,-4 // Alle Zeichen gefunden?
               jge @Found // Ja
               mov ebx,[rax+rsi+4] // Nächste 2 Zeichen aus SearchFor
               cmp ebx,[rdx+rsi+6] // In SearchIn?
               jnz @AfterTestT // Nein, SearchIn + 4 Chars
               add rsi,8 // Zeichenzahl + 4 Chars
               jl @String // Nächste 4 Zeichen prüfen
               //---------------------------------------------------------------
               // Gefunden. RDX zeigt auf Fundstelle - 1 Zeichen
@Found: lea rsi,[rdx+4] // Fundstelle + 1 Zeichen
               cmp rsi,rbp // Im zu durchsuchenden Bereich?
               ja @NoFurther // Nein, keine weiteren Fundstellen
               add rsi,r12 // Position in Bytes
               shr rsi,1 // Position in Zeichen
               cmp r10,r11 // Noch Platz in Positions?
               jae @OutOfMem // Nein
               mov [r8+r10*4],esi // Fundstelle speichern
               inc r10 // Anzahl Fundstellen + 1
               // RDX auf nächste potentielle Fundstelle
               mov rsi,rdi // -(2*(Length(SearchFor)-1))
               neg rsi // 2*(Length(SearchFor)-1)
               lea rdx,[rdx+rsi+4] // EDX=nächste potentielle Fundstelle
               cmp rdx,rbp // Noch im gültigen Bereich?
               jb @Loop // Ja, weiter suchen
               jmp @NoFurther // Nein, keine weiteren Fundstellen
               // --------------------------------------------------------------
               // Suche nach nur einem Zeichen
               // RCX = SearchFor
               // RDX = SearchIn
               // RBX = Lenght(SearchIn)
               // R8 = Positions
               // R9 = Exceptions
               // R10 = Anzahl Fundstellen
               // R11 = Length(Positions)
@Char: lea rdx,[rdx+rbx*2] // Hinter letztes Zeichen von SearchIn
               lea rdi,[rbx+1] // für spätere Positionsermittlung
               neg rbx
               mov ax,[rcx] // gesuchtes Zeichen
@CharLoop: cmp ax,[rdx+rbx*2] // AX an aktueller Position?
               jz @CharFound1 // Ja
               cmp ax,[rdx+rbx*2+2] // AX an nächster Position?
               jz @CharFound2 // Ja
               add rbx,2 // 2 Zeichen weiter
               jl @CharLoop
               jmp @NoFurther // Keine weiteren Fundstellen
               // An [EDX+EBX*2] gefunden
@CharFound1: cmp r10,r11 // Noch Platz in Positions?
               jae @OutOfMem // Nein
               lea rsi,[rdi+rbx] // Fundstelle
               mov [r8+r10*4],esi // In Positions speichern
               inc r10 // Anzahl Fundstellen
               inc rbx // 1 Zeichen weiter
               jl @CharLoop // Weiter, solange negativ
               jmp @NoFurther // Keine weiteren Fundstellen
               // An [EDX+EBX*2+2] gefunden
@CharFound2: cmp r10,r11 // Noch Platz in Positions?
               jae @OutOfMem // Nein
               lea rsi,[rdi+rbx+1] // Fundstelle
               mov [r8+r10*4],esi // In Positions speichern
               inc r10 // Anzahl Fundstellen
               add rbx,2 // 2 Zeichen weiter
               jl @CharLoop // Weiter, solange negativ
               jmp @NoFurther // Keine weiteren Fundstellen
               //---------------------------------------------------------------
               // SearchFor ist leer
@SFNil: mov r8,sSearchForEmpty
               mov ecx,1 // Fehlercode
               jmp @End
               //---------------------------------------------------------------
               // SearchIn ist leer
@SINil: mov r8,sSearchInEmpty
               mov ecx,2 // Fehlercode
               jmp @End
               //---------------------------------------------------------------
               // SearchFor ist länger als SearchIn
@SFTooLong: mov r8,sSearchForTooLong
               mov ecx,3 // Fehlercode
               jmp @End
               //---------------------------------------------------------------
               // Positions ist nicht assigned
@PosEmpty: mov r8,sPositionsEmpty
               mov ecx,4 // Fehlercode
               jmp @End // Fehlermeldung
               //---------------------------------------------------------------
               // Positions nicht lang genug um neue Fundstelle zu speichern
@OutOfMem: mov r8,sPositionsLength
               mov ecx,5 // Fehlercode
               jmp @End // Fehlermeldung
               //---------------------------------------------------------------
               // Keine weiteren Fundstellen
@NoFurther: xor r8,r8 // ´Kein Fehler
               mov rcx,r10 // Anzahl Fundstellen
               //---------------------------------------------------------------
               // Register restaurieren
@End: pop r12 // R12 wieder herstellen
               pop rsi // RSI wieder herstellen
               pop rdi // RDI wieder herstellen
               pop rbx // RBX wieder herstellen
               pop rbp // RBP wieder herstellen
               //---------------------------------------------------------------
               // Prüfen ob Fehler vorliegt und ggfs. Exception auslösen
               test r8,r8 // Fehler ?
               jz @NoError // Nein, Anzahl Fundstellen zurückgeben
               mov edx,1
               shl edx,cl
               neg ecx // Fehlercode
               shr edx,1
               test r9d,edx // Exception werfen?
               jz @NoError // Nein, nur Fehlercode zurückgeben
               // Exception auslösen
               push rcx // Fehlercode retten
               push rbp
               sub rsp,$20
               mov rbp,rsp
               mov rcx,pExClass // InstanceOrVMT
               mov dl,$01 // Alloc
               call Exception.Create
               mov rcx,rax
               call System.@RaiseExcept
               add rsp,$20
               pop rbp
               pop rcx // Fehlercode
               //---------------------------------------------------------------
               // Anzahl Fundstellen bzw. Fehlercode in Result
@NoError: mov eax,ecx // Anzahl Fundstellen bzw. FehlerCode
end;
{$ENDIF}
Gruß, Klaus
Die Titanic wurde von Profis gebaut,
die Arche Noah von einem Amateur.
... Und dieser Beitrag vom Amateurprofi....
  Mit Zitat antworten Zitat
Benutzerbild von Sinspin
Sinspin

Registriert seit: 15. Sep 2008
Ort: Dubai
691 Beiträge
 
Delphi 10.3 Rio
 
#14

AW: Alternative zu PosEx

  Alt 25. Nov 2024, 10:20
Krass. Danke.

Sieht so aus als wenn ich malwider ein Buch in die Hand nehmen sollte und ein bisschen was zum Thema Assembler lesen. Ich fühle mich gerade irgendwie outdated.
Delphi-Quellcode:
{    Exceptions : Gibt an, in welchen Fällen Exceptions ausgelöst werden.      }
{                 0 = Keine, -1 = Alle                                         }
{                 In der aktuellen Version ist das Werfen von Exceptions       }
{                 deaktiviert, weil der Code hier nicht funktioniert.          }
{                 In anderen Anwendungen funktioniert er.                      }
Was meinst du damit? Fehlererkennung geht nicht oder Exception auswerfen geht nicht in 64Bit?
Stefan
Nur die Besten sterben jung
A constant is a constant until it change.
  Mit Zitat antworten Zitat
Amateurprofi

Registriert seit: 17. Nov 2005
Ort: Hamburg
1.077 Beiträge
 
Delphi XE2 Professional
 
#15

AW: Alternative zu PosEx

  Alt 25. Nov 2024, 11:35
Krass. Danke.

Sieht so aus als wenn ich malwider ein Buch in die Hand nehmen sollte und ein bisschen was zum Thema Assembler lesen. Ich fühle mich gerade irgendwie outdated.
Delphi-Quellcode:
{    Exceptions : Gibt an, in welchen Fällen Exceptions ausgelöst werden.      }
{                 0 = Keine, -1 = Alle                                         }
{                 In der aktuellen Version ist das Werfen von Exceptions       }
{                 deaktiviert, weil der Code hier nicht funktioniert.          }
{                 In anderen Anwendungen funktioniert er.                      }
Was meinst du damit? Fehlererkennung geht nicht oder Exception auswerfen geht nicht in 64Bit?
Ich meinte damit genau das was ich schrieb:
"Das Werfen von Exceptions ist deaktiviert."
Selbstverständlich werden Fehler erkannt, aber es wird auch dann keine Exception ausgelöst, wenn der Parameter "Exception" vorgibt, dass eine Exception ausgelöst werden soll, sondern ein entsprechender Fehlercode zurückgegeben.
Der Funktion gibt ja die Anzahl der Fundstellen zurück, die >= 0 ist.
Bei Fehlern ist das Ergebnis der Funktion negativ und kann sein:
-1 SearchFor (der zu suchende Text) ist leer.
-2 SearchIn (der zu durchsuchende Text) ist leer.
-3 SearchFor ist länger als SearchIn.
-4 Positions ist Nil (nicht assigned)
-5 Positions ist zu kurz, um alle Fundstellen zu speichern.
Zu -4 und -5
Es wäre natürlich eleganter, wenn die Länge von Positions innerhalb der Funktion gesetzt würde, aber das, was in Delphi nur ein simples "SetLength(Positions,M)" ist, scheint in Assembler nicht so trivial zu sein.
Na klar könnte ich das mit einer ausgelagerten Delphi-Prozedur lösen.
Auch das Werfen von Exceptions wäre mit einer ausgelagerten Delphi-Prozedur leicht zu machen.
Aber meine Philosophie ist halt, dass eine Assembler-Prozedur keine externen Subroutinen aufruft.
Gruß, Klaus
Die Titanic wurde von Profis gebaut,
die Arche Noah von einem Amateur.
... Und dieser Beitrag vom Amateurprofi....
  Mit Zitat antworten Zitat
Benutzerbild von Sinspin
Sinspin

Registriert seit: 15. Sep 2008
Ort: Dubai
691 Beiträge
 
Delphi 10.3 Rio
 
#16

AW: Alternative zu PosEx

  Alt 25. Nov 2024, 12:52
Nene :
Delphi-Quellcode:
{
Exceptions ...  deaktiviert, weil der Code hier nicht funktioniert.
}
Ist aber eigentlich wurscht. Die meißten Exceptions enstehen eh durch unsaubere Programmierung. Sind also vermeidbar.
Ich arbeite aber eh lieber mit detaillierten Fehlercodes anstatt von Exceptions.

Setlength ist nicht trivial und sollte generell so selten wie möglich für das gleiche Array aufgerufen werden.
Verkettete Listen sind da deutlich praktischer. Kommen aber mit zusätzlichem Speicherverbrauch einher und sind beim kopieren hässlich.
Ich kann schon verstehen warum die Parameter sind wie sie sind.
Stefan
Nur die Besten sterben jung
A constant is a constant until it change.
  Mit Zitat antworten Zitat
Benutzerbild von Stevie
Stevie

Registriert seit: 12. Aug 2003
Ort: Soest
4.027 Beiträge
 
Delphi 10.1 Berlin Enterprise
 
#17

AW: Alternative zu PosEx

  Alt 26. Nov 2024, 13:25
64bit StrPos ist defekt.

Teste selbst: StrPos('world', 'hello world', 0, 11) liefert 12 und nicht 7
Stefan
“Simplicity, carried to the extreme, becomes elegance.” Jon Franklin

Delphi Sorcery - DSharp - Spring4D - TestInsight
  Mit Zitat antworten Zitat
Blup

Registriert seit: 7. Aug 2008
Ort: Brandenburg
1.477 Beiträge
 
Delphi 12 Athens
 
#18

AW: Alternative zu PosEx

  Alt 26. Nov 2024, 16:53
Ich habe StrPosEx (32Bit) getestet und einen Fehler gefunden:
Code:
SetLength(Positions, 3)
StrPosEx('PaPa', 'PaPaPa', Positions)
Result = 1
Positions = [1,0,0]
Man muss das Array vor dem Aufruf auf die Anzahl der Ergebnisse anpassen.
Wenn der Platz nicht reicht, liefert die Funktion aber nicht die tatsächliche Anzahl zurück.
Die Fehlercodes sind für eine so simple Funktion eigentlich überflüssig, entweder es wird gefunden oder nicht.
Simple Überprüfungen kann man davor selbst vornehmen.

Meiner Meinung nach sollte die Funtktion den benötigten Platz selbst reservieren, z.B.:
Delphi-Quellcode:
function StrPosEx(const SearchFor, SearchIn: string; out Positions: TIntegerDynArray): Integer;
function StrPosEx(const SearchFor, SearchIn: string): TIntegerDynArray;

Geändert von Blup (26. Nov 2024 um 16:56 Uhr)
  Mit Zitat antworten Zitat
Amateurprofi

Registriert seit: 17. Nov 2005
Ort: Hamburg
1.077 Beiträge
 
Delphi XE2 Professional
 
#19

AW: Alternative zu PosEx

  Alt 26. Nov 2024, 17:45
Ich habe StrPosEx (32Bit) getestet und einen Fehler gefunden:
Code:
SetLength(Positions, 3)
StrPosEx('PaPa', 'PaPaPa', Positions)
Result = 1
Positions = [1,0,0]
Man muss das Array vor dem Aufruf auf die Anzahl der Ergebnisse anpassen.
Wenn der Platz nicht reicht, liefert die Funktion aber nicht die tatsächliche Anzahl zurück.
Die Fehlercodes sind für eine so simple Funktion eigentlich überflüssig, entweder es wird gefunden oder nicht.
Simple Überprüfungen kann man davor selbst vornehmen.

Meiner Meinung nach sollte die Funtktion den benötigten Platz selbst reservieren, z.B.:
Delphi-Quellcode:
function StrPosEx(const SearchFor, SearchIn: string; out Positions: TIntegerDynArray): Integer;
function StrPosEx(const SearchFor, SearchIn: string): TIntegerDynArray;
Und wo ist da der Fehler?
"PaPa" wird in "PaPaPa" an Position 1 gefunden.
Dann wird geprüft, ob ab hinter der Fundstelle, also ab Position 1+Length("PaPa") der Text "PaPa" noch einmal gefunden wird.
Das ist nicht der Fall, also wird korrekt 1 zurückgegeben.

Zu "Meiner Meinung nach sollte die Funtktion den benötigten Platz selbst reservieren".
Hast Du einen Vorschlag, wie das zu realisieren ist?
Gruß, Klaus
Die Titanic wurde von Profis gebaut,
die Arche Noah von einem Amateur.
... Und dieser Beitrag vom Amateurprofi....
  Mit Zitat antworten Zitat
Benutzerbild von himitsu
himitsu

Registriert seit: 11. Okt 2003
Ort: Elbflorenz
44.184 Beiträge
 
Delphi 12 Athens
 
#20

AW: Alternative zu PosEx

  Alt 26. Nov 2024, 17:50
Zitat:
wie
Als VAR-Parameter und drinnen ein SetLength.


Per se ist OUT hier falsch, jedenfalls in Bezug auf Managed-Types, wie z.B. dynamische Arrays.
Zum Glück macht Delphi hier heimlich, und ohne was zu sagen, ein VAR daraus.

Bei OUT ist es möglich auch die Referenz zu ändern, was hier "eigentlich" zu einem Speicherleck führen würde, wenn vor dem Aufruf das Array einen Inhalt hätte.
$2B or not $2B
  Mit Zitat antworten Zitat
Antwort Antwort
Seite 2 von 6     12 34     Letzte »    


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 14:47 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz