Entwickler-Ecke

Windows API - Probleme mit Stringverkettung


Peter18 - Di 02.06.15 10:34
Titel: Probleme mit Stringverkettung
Ein freundliches Hallo an alle,

ich habe einen seltsamen Effekt bei der Verkettung von Strings. Kurze Strings kommen an, bei längeren nur der erste. Weden sie noch länger kommt keiner an.

Eine API liefert zwei Strings in einer Struktur. Die werden Byte für Byte in je einen String kopiert und per Ereignisroutine einer Anwendung übergeben. Diese macht die Strings per "Label.Caption" sichtbar. Soweit ist alles in Ordnung. Dann werden die Strings mit einem Trennzeichen verkettet und per TCPIP versandt.


Delphi-Quelltext
1:
  TCPIP.Senden := A + '|' + B;                    

Ich weiß nun nicht, ob es zufällige Speichersituationen waren, die dafür sorgten, dass die Ergebnisse unterschiedlich ausfielen. Testen kann ich leider in der notwendigen Tiefe nicht, weil der Rechner, auf dem der Test läuft, über die notwendige Hardware, aber nicht über eine Entwicklungsumgebung verfügt.

Ich vermute, dass die Strings vor dem Versand in einen zusammenhängenden Speicherbereich kopiert werden müssen, damit sie von der Winsock korrekt verarbeitet werden. Wenn sie getrennt vesand werden ist alles ok, aber nicht gemeinsam. Da die Reihenfolge des Eintreffens nicht definiert ist möchte ich sie lieber mit einer Sendung übertragen.

Grüße von der wolkigen Nordsee

Peter


jaenicke - Di 02.06.15 13:23

Du hast keinen Einfluss darauf auf wie viele Pakete verteilt die Daten ankommen. Die Reihenfolge ist bei TCP aber definiert, dafür sorgt das Protokoll schon. Die Reihenfolge ist nur bei UDP nicht definiert. Bei TCP hingegen werden fehlende Pakete ggf. erneut angefordert und du bekommst sie erst nach der Sortierung.

Sprich dein Protokoll muss so aussehen, dass du eine Aufteilung deiner Daten auf mehrere Pakete korrekt verarbeiten kannst.

Zum Debuggen bietet sich OutputDebugString in Kombination mit DebugView [https://technet.microsoft.com/en-us/library/bb896647.aspx] an. Damit kannst du einfach Debugdaten ausgeben, aber störst den Programmablauf dabei nicht, sondern greifst diese von außen aus dem Programm ab.


Peter18 - Di 02.06.15 17:35

Hallo Sebastian,

Dank Dir für Deine Antwort. Es ist richtig, dass große Datenmengen auf verschiedene Packete aufgeteilt werden. Der Empfänger setzt sie dann wieder in der richtigen Reihenfolge zusammen, da die Teilpackete nummeriert sind. Ich bin mir aber nicht sicher ob das auch bei aufeinanderfolgenden Sendungen der Fall ist wie:

Delphi-Quelltext
1:
2:
  TCPIP.Senden := A; 
  TCPIP.Senden := B;

Aber das Problem bleibt: Müssen die Strings umkopiert werden und wenn ja, wie?

OutputDebugString kann aber nur vom Programm aus bedient werden. Mit dem "CPU-Fenster" kann ich sehen, was im Speicher geschieht. Geht das auch damit? Ich vermute nämlich, dass Pascal mit den typisierten Pointern ins schleudern kommt wenn sie in PChar umgewandelt werden oder bei der Übergabe an die Winsock.

Grüße von der immer noch wolkigen Nordsee

Peter


Sinspin - Mi 03.06.15 09:57

Du kannst ja ohne PChar Umwandlungen arbeiten. Du brauchst ja nur ein ausreichend großes Array (AnsiChar) erstellen (das geht ja auch dynamisch) und da alle Zeichen der Reihe nach reinschreiben. Beim Senden übergibst Du den Zeiger auf die erste Position des Arrays.

Natürlich kommen einzeln gesendete Strings in der gleichen Reihenfolge an wie Du sie losgesendet hast. Die Nummerierung endet erst mit schließen des Ports. Sendest Du also jede Nachricht auf einem neuen Port kann es schon sein das sich einzelne Nachrichten "überhohlen".

Du kannst via OutputDebugString alle Informationen nach außen reichen von denen Du denkst das sie fürs findes des Fehlers hilfreich sein könnten. Also auch die Inhalte von Puffern oder Strings, oder CPU Registern oder sonstwas. Das ist ja vollständig Dir überlassen.


Peter18 - Mi 03.06.15 11:51

Hallo Stefan,

Dank Dir für Deine Antwort. Die PChar-Umwandlung findet bei "TCPIP" statt, es wird dem entsprechend ein String erwartet. Der Client baut die Verbindung zum Server auf und läßt sie bestehen, bis Client oder Server sie beenden. Damit können beliebig viele Sendungen bei einer "Sitzung" übertragen werden. Vielleicht sollte ich mir das ganze mal mit einem Sniffer ansehen.

Du sagst mit "OutputDebugString" kann ich auch "CPU Register" ausgeben, doch wie? Wenn ich das richtig verstehe muß das alles programmiert sein. Ich kann doch keinen Breakpoint setzen und dann die Pointer verfolgen, um zu verstehen wo das Problem liegt, oder?

Ich habe den Verdacht das Problem hängt mit der Behandlung der Strings in Pascal zusammen und möchte eine saubere Lösung realisieren, die immer funktioniert und nicht nur durch Zufall. Irgendwann habe ich eine Funktion genannt bekommen wie Copytostack oder so ähnlich, doch ich finde sie nicht wieder.

Mit meinem Versuchen mit "AnsiChar" hat es jedenfalls nicht geklappt. Hab wohl was falsch gemacht. Das habe ich zur Probe (Quick and dirty) eingebaut:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
var
  A    : array of AnsiChar;
  I, J :Integer;

begin
  SetLength( A, Length( B ) + Length( C ) + 1 );                                                  
  I := 0;
  for J := 1 to Length( B ) do
  begin
    A[ I ] := B[ J ];
    I      := I + 1       ;
  end;
  A[ I ] := '|'  ;
  I      := I + 1;
  for J := 1 to Length( C ) do
  begin
    A[ I ] := C[ J ];
    I      := I + 1         ;
  end;
  TCPIP.Send := A[1];

Grüße von der wolkigen Nordsee, zwischen denen ab und zu die Sonne sichtbar wird

Peter


OlafSt - Mi 03.06.15 11:58

Das Array A[] wird von Element 0 an befüllt (i:=0; a[I]:=b[j]). Abgeschickt wird aber ab A[1].

Ist das erste Befüllen eine leere Zeichenkette, steht nur der Feldtrenner "|" als erstes im Array A. Diesen Trenner sendest du aber nicht mit (weil erst ab Element 1 gesendet wird) und deine ganze Kommunikation kommt durcheinander.
Übrigens: I:=i+1 kann man durch inc(i) ersetzen - ist nicht nur kürzer, auch schneller.


Peter18 - Mi 03.06.15 12:42

Hallo OlafSt,

auch Dir Dank für die Antwort. "A[1]" ist mir durch die Lappen gegangen. Der erste (B) und der zweite (C) String sind nicht leer, sie werden in Labels gezeigt. Von dem String "A" kommt in der Regel nichts oder der String "B" beim Client an. Der Trenner fehlt meist schon.

Grüße von der Nordsee

Peter


Sinspin - Mi 03.06.15 13:01

Hey Peter, dann zeig mal die Empfangsseite. Also dort wo die einzelnen Datenpakete wieder zu einem String zusammengefügt werden. Nach deiner Beschreibung hört es sich so an als wenn da was schief geht.


Peter18 - Mi 03.06.15 13:26

Hallo Stefan,

Dank Dir. Das geschieht vor dem Senden. Ich verwende ein Memofeld, in dem die Strings sichtbar gemacht werden. Manchmal sind die Quellstrings weg. Manchmal sehe ich im Summenstring den 1. String und manchmal nichts. Daher vermute ich, dass es mit der Stringverarbeitung bei Pascal zusammenhängt. Ich vermute es werden Pointer verkettet, die mit String richtig verarbeitet werden, aber bei der Übergabe an die API nicht.


Quelltext
1:
2:
3:
Nachtrag:   Die Routine erhält die Parameter: "( B, C : String );" .
      "MDebug.Lines.Add( 'Will senden: ' + B + '>' + C );" ergibt "Will senden: 0123456",
      wobei B = '0123456' ist. ????


Grüße von der Nordsee

Peter


baumina - Mi 03.06.15 14:42

Der Fehler liegt sicher mal wieder wo ganz anders als du uns hier zeigst. Denn dass ein Str := A + '>' + B; nichtmal ein '>' im Ergebnis zeigt, kann ich mir kaum Vorstellen, außer es handelt sich bei der Darstellung um eine seltsame Schriftart.


Peter18 - Mi 03.06.15 14:58

Hallo baumina,

auch Dir Dank für Deine Antwort. Ich verfolge gerade einen Verdacht. C verwendet 0 terminierte Strings. Wenn man in Pascal versucht solche zu verketten, erscheint nur der Erste. Ich habe den String byteweise dargestellt und ein 0-Byte gefuden. Nun ändere ich das gerade und werde es Testen.

Grüße von der Sonnigen Nordsee

Peter


OlafSt - Mi 03.06.15 15:50

Ich glaube, über diesen "Bug" bin ich auch schon gestolpert, ist aber Jahre her. Labels zeigen nur bis zum ersten Nullbyte an - der Rest fehlt. WinAPI in C geschrieben halt ;)


Peter18 - Mi 03.06.15 18:36

Hallo OlafSt,

Dank Dir für Deinen "Trost". So etwas hatte ich auch schon einmal im Zusammenhang mit dem Usernamen, doch das war leicht zu beheben.

Es hängt anscheinend daran, dass es 0 terminierte Strings sind. Bei jedem Zugriff darauf gab es seltsame Effekte. Da wurde die Routine scheinbar nicht aufgerufen, oder es gab Zugriffafehler. Michts nachvollziehbares. Und dann keine Möglichkeit den Programmablauf zu debugen.

In der "TAPI" werden die Strings folgendermaßen aufgesetzt:

Delphi-Quelltext
1:
2:
3:
4:
    Ar := Ptr( Integer( lpCallInfo ) );
    A  := '';                                  
    for I := lpCallInfo.dwCallerIDOffset to lpCallInfo.dwCallerIDOffset + lpCallInfo.dwCallerIDSize - 1 do
      A := A + Chr( Ar^[I] );
Das Byte [0] war 0, enthielt also nicht die Länge. Anscheinend kommt Delphi damit ins schleudern. Alle Versuche die Strings zu kopieren oder sonst wie zusammen zu bringen sind Fehlgeschlagen. Vielleicht kann das ja jemand erklären.

Grüße von der noch immer sonnigen Nordsee

Peter


jaenicke - Mi 03.06.15 19:35

Ich hatte auch daran gedacht, aber ich dachte da du in dem geposteten Teil nur Strings verwendest, wird das nicht das Problem sein...

Ein Nullzeichen gibt es auch bei Delphi am Stringende, nur hinter dem normalen String. Dadurch ist der Speicher eines Strings mit einem PChar kompatibel. Delphi kommt damit auch als String mit Nullzeichen in der Mitte prima klar, denn Delphi kennt ja die Stringlänge. Sobald du solch einen String aber irgendwo an die Windows API übergibst, zählt das Nullzeichen aber als Endezeichen. Zur API gehören auch Memos oder MessageBoxen usw., so dass du einen solchen String zwar in Delphi nutzen, aber nicht anzeigen kannst. ;-)


OlafSt - Do 04.06.15 11:22

Hmm... Wenn ich mir die Schleife an ansehe, dann muß das doch Rufnummer des Anrufers sein. Ist diese leer, würde das das erste Nullbyte erklären, aber nicht, wieso dann in lpCallInfo.dwCallerIDSize irgendetwas > 0 steht. Würde mein Mißtrauen wecken ;)

However, womöglich ist es sinnvoll, die Nullbytes durch Leerzeichen zu ersetzen - so ist immerhin eine Anzeige möglich, ohne das (komplett auf nullterminierten Strings basierende) WinAPI stetig zu überfordern.


Peter18 - Do 04.06.15 11:23

Hallo jaenicke,

Dank Dir für die Antwort.

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Zur API gehören auch Memos oder MessageBoxen usw., so dass du einen solchen String zwar in Delphi nutzen, aber nicht anzeigen kannst. ;-)

Es muß doch irgendwelche Konvertierungsmöglichkeiten geben!

Grüße von der sonnigen Nordsee

Peter

Moderiert von user profile iconNarses: Beiträge zusammengefasst

Hallo OlafSt,

auch Dir Dank. Inzwischen habe ich einen Weg gefunden. Gestern war wohl das System oder Delphi etwas angeschlagen. Bei den Tests war kein leerer String dabei. Umkopieren geht:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
  A, E : array[0..500of AnsiChar;
...
  FillChar( A, SizeOf( A ), #0);
  FillChar( E, SizeOf( A ), #0);
  StrLCopy( A, PChar( Anrufer ),   500 );
  StrLCopy( E, PChar( Angerufen ), 500 );
  S := copy( A, 1, Length( A ) -1 ) + '>' + copy( E, 1, Length( E ) -1 );

Damit kann ich verketten und senden.

Grüße von der Nordsee

Peter


Sinspin - Do 04.06.15 13:08

Hallo Peter,

jetzt nachdem Du weist das es an den #0 Zeichen liegt verstehe den Aufwand nicht mehr den Du dir da machst.
Einfach im ersten Schritt alle Nullzeichen raus oder ersetzen und anschließend die Strings wie bisher einfach von Delphi verketten lassen.

Ich kann mich erinnern mit #0 Zeichen auch schon meine Freude gehabt zu haben wenn es darum ging in die DB geschriebene Memofelder wieder auszulesen. Obwohl alles drin steht kam nur der Teil bis zum ersten #0 Zeichen wieder raus. Ich habe dann die #0 Zeichen ersetzt und schon ging es.


Peter18 - Fr 05.06.15 18:17

Hallo Stefan,

Dank Dir für die Antwort.

Es hat etwas gedauert, bis ich die Vermutung hatte, dass es #0 ist. Danach habe ich genau das versucht. Das Ergebnis war jedoch nicht stabil. Mal funktionierte es mel nicht. Es gab dabei so seltsame Effekte, das die ursprünglichen Strings plötzlich weg waren, oder es wurde nur ein String gezeigt. Da ich es nicht debugen kann stand ich etwas auf der Rolle.

Weil die Strings mit Pascal in eine Stringvariable kopiert und der Ereignisroutine übergeben wurden, bin ich zunächst nicht auf die Idee gekommen. Vor allem hat mich irritiert, dass es mal funktioniert hat und mal nicht: funktioniert, Programm ergänzt => funktioniert nicht, Ergänzung entfernt => funktioniert nicht.

Grüße von der sonnigen 26°C heißen Nordsee (schwitz)

Peter


Peter18 - Di 16.06.15 10:57

Hallo an alle,

Noch ein Nachtrag: Da immer wieder Probleme mit der Tapi auftraten habe ich Delphi auf dem Testrechner installiert und festgestellt, dass zum einen der Proceduraufruf ein Problem zu sein scheint und, dass bei der Länge der übergebenen Zeichenkette das #0-Byte mitgezählt wird und deshalb mit kopiert wurde. Daher die Probleme mit den Strings.

Grüße von der Nordsee

Peter


Peter18 - So 05.07.15 12:19

Ein freundliches Hallo an alle,

noch ein Nachtrag: Im weiteren konnte ich feststellen, dass die Sache mit dem #0-Byte etwas seltsam ist. Auch wenn es nicht mit kopiert wird ist es da. Mal ist es Bestandteil des String, mal nicht! Ohne erkennbare Regel! Der Versuch das letzte Zeichen mit Copy abzuschneiden führte dazu, dass manchmal der String in Ordnung war, aber manchmal gewünschte Zeichen abgeschnitten wurden. Erst mit "If Ord( S[ Length(S) ] ) = 0 then" gab es stabile Ergebnisse.

Grüße von der Nordsee

Peter