Laden...

PChar als Result aus Delphi DLL

Erstellt von BeGreen vor 18 Jahren Letzter Beitrag vor 18 Jahren 5.742 Views
B
BeGreen Themenstarter:in
6 Beiträge seit 2005
vor 18 Jahren
PChar als Result aus Delphi DLL

Hallo alle zusammen,

ich habe zwar geschaut ob es eine vergleichbare Frage gibt, habe aber nichts gefunden. Also nicht schlagen falls ich was übersehen habe.

Ich habe eine DLL die in Delphi geschrieben wurde, und diese möchte ich in mein C# Programm einbinden. Die meisten Funktionen stellen kein Problem dar, ausser dieser einen:

DELPHI:

function GetMessage: PChar; stdcall; export;

Diese Delphi DLL-Funktion gibt einen PChar zurück. Ich glaube das entspricht dem char* von C# aber mit absoluter Sicherheit weiss ich es nicht.

Jedenfalls schaffe ich es nicht erfolgreich diese Funktion aufzurufen. Ich habe verschiedene DLLImport Aufrufe probiert und bin jetzt völlig verwirrt. Das ich am Ende gerne den String hinter diesen PChar hätte ist noch einmal eine andere Sache, da ich schon bei einem erfolgreichen Aufruf scheitere. Ich hoffe Ihr könnt mir helfen oder einen Tip geben. Bin mit meinem Latein am Ende.

PS: Ich habe zwar Zugriff auf die Delphi-DLL, kann Sie aber nicht ändern, weil andere Systeme schon erfolgreich mit dieser DLL arbeiten.

Gruß, BeGreen

S
8.746 Beiträge seit 2005
vor 18 Jahren

PChar wird automatisch nach "string" umgesetzt. Wichtig ist, dass du im DLLImport "CharSet=CharSet.Ansi" angibst.

B
BeGreen Themenstarter:in
6 Beiträge seit 2005
vor 18 Jahren

Hallo und danke erstmal für die Antwort.

Aber .... es funktioniert nicht 🙂

Hier ist der DLLImport:


[DllImport(@"c:\\mydll.dll", CharSet = CharSet.Ansi)]
public static extern string GetMessage();

und hier der Aufruf:


private void Button1_Click(object sender, System.EventArgs e) {
  string Msg;
  Msg = GetMessage();
 }

und das ist die Fehlermeldung:

System.NullReferenceException: Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt.
at MyProject.Form1.GetMessage()

Bestimmt habe ich irgendwas dummes übersehen. Aber was?

Gruß, BeGreen

4.221 Beiträge seit 2005
vor 18 Jahren

müsste das nicht CharSet.Unicode sein ??

Edit:

Wobei Svenson schreibt ja explizit es müsse Ansi sein.....

Früher war ich unentschlossen, heute bin ich mir da nicht mehr so sicher...

S
8.746 Beiträge seit 2005
vor 18 Jahren

PChar (oder LPCHAR) ist char* (pointer to char), das ist ANSI. Wenn es Unicode wäre, hiesse der Typ LPWCHAR/PWCHAR (W für wide string [nicht zu verwechseln mit den langen Delphi-Strings]).

Probier nochmal:


[DllImport(@"c:\mydll.dll", CharSet = CharSet.Ansi)]
[return: MarshalAs(UnmanagedType.LPStr)]
public static extern string GetMessage();

Ich würde übrigens keine absoluten Pfade verwenden. Aber daran wirds nicht liegen.

B
BeGreen Themenstarter:in
6 Beiträge seit 2005
vor 18 Jahren

Danke erstmal,

aber das Ergebnis ist das gleiche.
Vielleicht liegt es ja an der DLL? Sie funktioniert in Zusammenarbeit mit Delphi Programmen. Aber nicht mit meinem C# Testprogramm. Vielleicht weil in der DLL kein Speicher allociert wird?

Hier mal der Code der Delphi DLL-Funktion:


function GetMessage: PChar; stdcall; export;
begin
  if Module.IncomingList.Count > 0 then
  begin
    Result := PChar(Module.IncomingList[Module.IncomingList.Count - 1]);
    Module.IncomingList.Delete(Module.IncomingList.Count - 1);
  end
  else Result := '';
end;

Mit Module.IncomingList ist eine Delphi TStringList gemeint. Grob gesagt nimmt diese Funktion einen String aus dieser Liste, castet diesen nach PChar und gibt ihn als Result zurück. Könnte hier ein Problem enstehen?

Denn sonst kann ich es mir nicht mehr erklären. Ich hoffe du hast noch eine Idee? Ach ja, und das mit den absoluten Pfaden beim DllImport kommt natürlich weg sobald ich es zum Laufen bekommen habe.

Gruß, BeGreen

S
8.746 Beiträge seit 2005
vor 18 Jahren

Das Problem ist, dass du das Element löscht und damit auch den String! Anders als bei string findet bei einer PChar-Zuweisung kein "Kopieren" des Inhalts statt (bzw. Erhöhung des Referenzzählers), sondern nur eine Zuweisung der Speicheradresse (de facto entspricht ein PChar-Cast dem "@"-Operator). Bei Zurückkopieren/UnMarshalling des Return-Wertes greift .NET dann auf freigegebenen Speicher zu und bringt NullReferenceException. PChar und String lassen sich zwar casten, aber nur String unterliegt der Delphi-GC und somit der Refernzzählung.

Kurz gesagt: Einem PChar kannst du den Inhalt "untern dem Hintern wegziehen". Bei String geht das nicht.

Am Besten du deklarierst eine modul-globale String-Variable in die du den String einkopierst. So hast du auch nicht das Problem, dass der Speicher wieder freigegeben muss, weil das Delphi mit seiner String-GC selbst erledigt. Auf keinen Fall kannst du eine lokale Variable verwenden, weil auch hier der Speicher nach Beendigung der GetMessage()-Funktion freigegeben werden würde.

Fazit: Möchte man Strings aus Delphi-Funktionen als PChar rausreichen, muss sichergestellt sein, dass dieser String auf den der PChar zeigt die gesamte Lebensdauer der Anwendung auch existiert. Im Prinzip kommen hier also nur gloable Variablen (hier braucht der Aufrufer keine Freigabe zu machen) oder explizit dynamisch allokierte Strings in Frage (mit New() und Freigabe durch den Aufrufer).

Übrigens: Dein Code ist auch unter Delphi fehlerhaft, nur fällt es hier nicht auf, bzw. es kommt nicht (oder praktisch nie) zum Absturz. Aber der Fehler ist auch hier existent.

B
BeGreen Themenstarter:in
6 Beiträge seit 2005
vor 18 Jahren

Ich krieg glatt Tränen in die Augen. Ich sitze da jetzt seit vier Tagen an dem Problem. Danke für die Hilfe. Als kleine Anmerkung dazu: Deine erste Variante ohne den Marshal hat funktioniert, nicht die zweite. Für die Nachwelt.

Was mich jetzt natürlich noch wundert ist, wieso hat es mit einem Delphi Programm bislang immer funktioniert? Aber das ist mir jetzt auch fast egal. Danke nochmal.

Gruß, BeGreen

[Edit] Ich werde die Delphi DLL mit einer globalen Variable versehen, um das Problem generell zu vermeiden. Ist auch keine große Sache.

B
BeGreen Themenstarter:in
6 Beiträge seit 2005
vor 18 Jahren

Dann habe ich noch eine kurze Frage, die du wahrscheinlich auch ganz schnell beantworten kannst. Funktioniert die ganze auch so wenn eine Funktion ein PChar als Übergabeparameter erwartet?

Gruß, BeGreen

S
8.746 Beiträge seit 2005
vor 18 Jahren

Das Gute ist, dass es auch praktisch keine Extra-Performance kostet. Wenn du den String kopierst, wird nur ein Referenzzähler erhöht und nicht der gesamte String kopiert. Bei der Freigabe des Listenelements bleibt der Speicher für den String bestehen, weil ja noch ein andere Referenz (deine globale Variable) "auf dem String sitzt". Erst wenn du diese Variable erneut einen anderen Wert zuweist (also beim nächsten GetMessage-Aufruf), dann wird der Speicher durch die Delphi-GC freigegeben.

S
8.746 Beiträge seit 2005
vor 18 Jahren

Original von BeGreen
Dann habe ich noch eine kurze Frage, die du wahrscheinlich auch ganz schnell beantworten kannst. Funktioniert die ganze auch so wenn eine Funktion ein PChar als Übergabeparameter erwartet?

Ich verstehe die Frage nicht ganz. Meinst du, dass in GetMessage() ein PChar reingesteckt wird, den die Funktion dann zu füllen hat oder willst du anstelle des Rückgabewertes einfach nur einen "out PChar" machen?

Letzteres ist quasi identisch mit der Return-Variante. Bei ersten Variante muss die Länge fest sein oder mit in die Funktion übergeben werden.

B
BeGreen Themenstarter:in
6 Beiträge seit 2005
vor 18 Jahren

Bei einer Funktion wie:

public static extern void SendMessage(string Command);

Wo die DLL ein PChar erwartet als Command. Und das habe ich jetzt auch schon ausprobiert und funktioniert ebenso wie die Variante von davor. Einfach nur als Übergabe, nicht als Referenzübergabe.

Na, jedenfalls danke nochmal. Meine DLL scheint jetzt zu laufen, und gelernt habe ich auch noch was.

Gruß, BeGreen