Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Programmieren allgemein (https://www.delphipraxis.net/40-programmieren-allgemein/)
-   -   Stringübergabe an DLL (https://www.delphipraxis.net/194510-stringuebergabe-dll.html)

norwegen60 30. Nov 2017 22:45

Stringübergabe an DLL
 
Hallo,

ich habe mich mal näher damit beschäftigt wie man Strings sauber an eine DLL und wieder zurück gibt und habe festgestellt, dass ich damit wohl zu sorglos umgegangen bin wenn ich sie einfach nur als pChar deklariert habe und dann auf beiden Seiten geich wieder in Strings gewandelt habe.
In einem Artikel von Michale Puff, aber auch an anderen Orten fand ich die Lösung, dass man besser zuerst die Länge ermittelt, dann den Speicher reservier, den Wert abholt und den Speicher wieder freigibt. In etwa so
Delphi-Quellcode:
  // Funktion aufrufen, um Größe des Buffers zu ermitteln
  len := func1('bar', nil, 0);
  Str(len, s);
  Writeln('len: ' + s);
  try
    // Speicher anfordern
    GetMem(Buffer, len + 1);
    // Funktion mit Buffer aufrufen
    len := func1('bar', Buffer, len + 1);
    Str(len, s);
    writeln(String(Buffer)+ ' [' + s + ']');
  finally
    // Speicher wieder freigeben
    FreeMem(Buffer);
  end;
Damit habe ich aber ein Problem wenn ich damit eine Funktion ausführe und von der etwas zurückgeliefert bekomme. Die würde dann ja zwei mal ausgeführt werden. Erst nach dem Ausführen weiß ich die Länge der Antwort. Jetzt könnte ich das Ergebnis in einer globalen Variablen in der DLL zwischenspeichern und beim zweiten Aufruf nur noch den Wert abholen ohne die Funktion auszuführen. Wirklich elegant finde ich das aber nicht.

Gibt es andere Möglichkeiten ohne dass ich einen festen Bereich reserviere

Grüße
Gerd

Der schöne Günther 30. Nov 2017 22:55

AW: Stringübergabe an DLL
 
Zitat:

Zitat von norwegen60 (Beitrag 1387660)
Erst nach dem Ausführen weiß ich die Länge der Antwort.

Kleiner Hinweis am Rande: String-Operationen mit der Windows-API funktionieren in der Regel auch so. Zuerst gibt man der Routine einen Speicherbereich der Länge Null und bekommt außer dem (erwarteten) Fehler die Angabe, wie viel Speicher man bitte reservieren soll. Das tut man, und ruft die Funktion ein weiteres mal auf, diesmal mit einem Puffer der so groß ist wie gewünscht. Die Methode befüllt den Puffer und fertig.

Zacherl 1. Dez 2017 01:43

AW: Stringübergabe an DLL
 
Zitat:

Zitat von norwegen60 (Beitrag 1387660)
ich habe mich mal näher damit beschäftigt wie man Strings sauber an eine DLL und wieder zurück gibt und habe festgestellt, dass ich damit wohl zu sorglos umgegangen bin wenn ich sie einfach nur als pChar deklariert habe und dann auf beiden Seiten geich wieder in Strings gewandelt habe

Um den String in eine Funktion reinzugeben, ist das absolut unproblematisch, da der Speicher ja erst dann freigegeben wird, nachdem der Programmfluss die Funktion wieder verlassen hat. Beim Rausgeben von Strings aus einer Funktion an den Aufrufer funktioniert das natürlich nicht.

Zitat:

Zitat von norwegen60 (Beitrag 1387660)
Gibt es andere Möglichkeiten ohne dass ich einen festen Bereich reserviere

Nicht wirklich. Wenn du dieses Modell implementieren willst, dann würde ich für den ersten Aufruf jeweils schon einen Buffer "auf gut Glück" reservieren. Gegebenenfalls reicht Dieser dann aus und du sparst dir den zweiten Aufruf. Schön ist dieses Design allerdings generell nicht.

Die Windows API benutzt - wie schon erwähnt - auch gerne dieses Verfahren. Hier kann es aber unter Umständen auch passieren, dass sich die Daten zwischen zwei Aufrufen ändern (z.b. wenn man mit MSDN-Library durchsuchenNtQuerySystemInformation alle laufenden Prozesse oder Threads auflisten will, kann das schnell mal passieren). Hier kommt man dann sogar nicht drumrum die API in einer Schleife aufzurufen, bis der übergebene Buffer schließlich passt (die Anzahl der Durchläufe kann man natürlich ein wenig optimieren, indem man auf den initial zurückgelieferten Wert nochmal ein paar Kilo-/Byte aufaddiert).

Statt eines einfachen Aufrufs landest du somit bei einer Schleife und mindestens einer zusätzlichen Variable.

Eine Alternative, die mir spontan einfällt, wäre die Verwendung eines
Delphi-Quellcode:
IString
Interfaces, welches z.b. eine Methode
Delphi-Quellcode:
SetString(const S: String)
implementiert. Darin müsstest du dann zwar vermutlich trotzdem manuell einen Speicher erstellen und den String reinkopieren (die Referenzzählung funktioniert bei direkter Zuweisung nicht wirklich über DLL-Grenzen hinweg; es sei denn man verwendet Laufzeit Packages bzw. SharedMem), aber die Freigabe könnte automatisiert im Destructor erfolgen (der ja bei Interfaces sogar automatisch aufgerufen wird) und deine DLL-Funktion müsste auch nur einmalig ausgeführt werden.

himitsu 1. Dez 2017 09:50

AW: Stringübergabe an DLL
 
WideString

Im Gegensatz zu String/AnsiString/UnicodeString, welche vom Delphi-Memory-Manager verwaltet werden, wird er von der OleAuth32.dll verwaltet.
Intern wird Delphi-Referenz durchsuchenSysAllocStringLen, Delphi-Referenz durchsuchenSysReAllocStringLen, Delphi-Referenz durchsuchenSysFreeString und Delphi-Referenz durchsuchenSysStringLen verwendet, welches auch andere Programmiersprachen verstehen.


Also WideString auf beiden Seiten geht.
Genauso wie Delphi-Strings auf beiden Seiten gehen, wenn beide den selben Speichermanager nutzen (Shared Memory).
Bei Delphi muß nur aufgepasst werden, dass sich das Datenformat der Kontrollstruktur im Jahre 2009 geändert hat. (also Delphi 7 mit XE geht nicht)

Dann der ShortString, aber diesen Pascal/Delphi1-String will man nicht verwenden.
Oder die bekannten Char-Arrays, aber natürlich nur AnsiChar oder WideChar, damit das Datenformat nicht ändern kann und immer fest definiert ist.
Und dementsprechend auch PAnsiChar und PWideChar auf einseitig verwaltete Speicherbereiche.

norwegen60 1. Dez 2017 10:12

AW: Stringübergabe an DLL
 
Danke für die Rückinfo. Bei den diversen Test die ich zwischenzeitlich gemacht habe, weiß ich wieder warum ich Pointern immer aus dem Weg gegangen bin.
Bei mir hat die Umsetzung des Beispiels von Michael Puff zu unregelmässigen Exceptions geführt.
Letztlich konnte ich es auf folgendes reduzieren. Komischerweise tritt hier der Exception aber konsequent auf
Delphi-Quellcode:
procedure TForm5.Button1Click(Sender: TObject);
var
  sValue : String;
begin
  sValue := 'Nur ein Test';
  TuWas(sValue);
end;

function TForm5.DoWhat(sValue):String;
var
  pBuffer: pChar;
begin
  GetMem(pBuffer, sizeof(sValue)); // length(sValue) + 1  ändert nichts
  pBuffer := pChar(sValue);
  Result := string(pBuffer);       // Auch wenn das auskommentiert ist tritt Exception auf. So wäre es aber korrekt oder?
  FreeMem(pBuffer);                // Creates an Access Violation
end;
Was mache ich falsch? Ich verwende Delphi XE

DeddyH 1. Dez 2017 10:25

AW: Stringübergabe an DLL
 
Die Bytegröße eines Strings ergibt sich aus Length(String) * SizeOf(Char). Unter Ansi ist das gleichbedeutend mit Length(String), unter Delphi-Unicode(UTF-16) ist das Length(String) * 2.

Luckie 1. Dez 2017 10:32

AW: Stringübergabe an DLL
 
Man beachte die Entstehungszeit meiner Codes. Damals war Unicode bei Delphi noch in weiter Ferne.

norwegen60 1. Dez 2017 10:40

AW: Stringübergabe an DLL
 
Zitat:

Zitat von himitsu (Beitrag 1387682)
WideString

Und da kann ich dann einfach auf beiden Seiten zuweisen und abholen ohne mich um Speicher zu kümmern?
Zitat:

Zitat von Luckie (Beitrag 1387688)
Man beachte die Entstehungszeit meiner Codes. Damals war Unicode bei Delphi noch in weiter Ferne.

Ja schon, aber ich habe ähnliche Beispiele immer wieder gefunden. Wie macht man es denn richtig?
Zitat:

Zitat von DeddyH (Beitrag 1387687)
Die Bytegröße eines Strings ergibt sich aus Length(String) * SizeOf(Char). Unter Ansi ist das gleichbedeutend mit Length(String), unter Delphi-Unicode(UTF-16) ist das Length(String) * 2.

Egal ob ich es mit Length(String) * SizeOf(Char) oder (Length(String)+1) * SizeOf(Char)) probiere: Der Exception bleibt

norwegen60 1. Dez 2017 19:10

AW: Stringübergabe an DLL
 
Zitat:

Zitat von himitsu (Beitrag 1387682)
WideString

Hier ein Aufruf der funktioniert.
Delphi-Quellcode:
function SetCommandW(wCommand: WideString; iPrio: Integer): WideString; stdcall; external 'uv.dll';

implementation

uses
  SysUtils;

function SetCommand(sCommand: string; iPrio: Integer): string;
// Wandelt die übergebenen String in WideString und wieder zurück
var
  wCommand,
  wResult :String;
begin
  wCommand := sCommand;
  wResult := SetCommandW(wCommand, iPrio);
  Result := wResult
end;
Diese Lösung gefällt mir super. Ich könnte auch direkt
Delphi-Quellcode:
Result := SetCommandW(sCommand, iPrio);
schreiben oder? Damit könnte die Zwischen-Function ganz entfallen. Auf jeden Fall meldet der Compiler bei beiden Tests keine Warnung über riskante Stringwandlungen. Und da EXE und DLL imme mit dem gleichen Kompiler erstellt werden und auf denselben PC laufen sollten auch unterschiedliche CodePages kein Thema sein.
Ist das korrekt?

Zitat:

Zitat von Luckie (Beitrag 1387688)
Man beachte die Entstehungszeit meiner Codes. Damals war Unicode bei Delphi noch in weiter Ferne.

Dieser Code funktioniert jetzt auch über pChar
Delphi-Quellcode:
function SetCommandP(pCommand, pResult: pChar; iLen, iPrio: Integer): Integer; stdcall; external 'uv.dll';

implementation

uses
  SysUtils;

function SetCommand(sCommand: string; iPrio: Integer): string;
// Wandelt die übergebenen String in pChar und wieder zurück
var
  iLen: Integer;
  pCommand, pBuffer: pChar;
  sResult : String;
begin
  pCommand := pChar(sCommand);
  // Speicher anfordern
  SetLength(sResult, SetCommandP(pCommand, nil, 0, 0));
  if Length(sResult) > 0 then
    SetCommandP(pCommand, pChar(sResult), 0, 0);
  Result := sResult;
end;
Wenn ich an Zeiger denke, bin ich immer unsicher, wann ich Speicher freigeben muss. In den Beispielen die ich fand wurde aber bei SetLength nie freigegeben. Ist in dem pChar Code alles korrekt?

Wäre froh wenn ich die Bestätigung bekäme dass beide Vorgehen ab Delphi XE korrekt sind

Danke
Gerd

Luckie 2. Dez 2017 02:24

AW: Stringübergabe an DLL
 
Zitat:

Zitat von norwegen60 (Beitrag 1387750)
Zitat:

Zitat von Luckie (Beitrag 1387688)
Man beachte die Entstehungszeit meiner Codes. Damals war Unicode bei Delphi noch in weiter Ferne.

Dieser Code funktioniert jetzt auch über pChar

Ja solange man die Ansitypen explizit angibt. Nur ist String in aktuellen Delphi Versionen gleiche WideString. Hätte ich damals alles als AnsiString und AnsiChar deklariert, wäre es ohne Probleme auch unter Unicode-Delphi kompilierbar. Aber wie gesagt, damals war Unicode noch kein Thema. Soi nacgt Delphi aber aus String einen WideString und so weiter. Und dann kracht es natürlich.


Alle Zeitangaben in WEZ +1. Es ist jetzt 03:41 Uhr.
Seite 1 von 2  1 2      

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