Der Aufruf einer Delphi-DLL aus C # führt zu unerwarteten Ergebnissen

8

Ich habe eine Delphi DLL, die ich nicht geschrieben habe, aber von einer C # ASP.NET 3.5 App aus aufrufen muss. Hier ist die Funktionsdefinition, die ich von den Entwicklern bekommen habe:

%Vor%

Und hier ist mein C # -Code:

%Vor%

Und schließlich, mein Aufruf zu dieser Methode:

%Vor%

Jedes Mal, wenn ich diesen genauen Code neu kompiliere und ihn ausführe, gibt er einen anderen Wert zurück. Der erwartete Wert ist ein zehnstelliger Code aus Zahlen. Der zurückgegebene Wert ist tatsächlich 12 Ziffern.

Die letzte wichtige Information ist, dass ich einen Test .EXE habe, der eine GUI hat, die es mir erlaubt, die DLL zu testen. Jeder Test, der das EXE verwendet, gibt dieselbe 10-stellige Nummer (das erwartete Ergebnis) zurück.

Ich muss also glauben, dass ich meinen Aufruf an die DLL falsch deklariert habe. Gedanken?

    
Doug Hays 05.02.2009, 21:14
quelle

7 Antworten

22

Delphi verwendet standardmäßig die sogenannte fastcall Aufrufkonvention. Dies bedeutet, dass der Compiler versucht, Parameter an eine Funktion in den CPU-Registern zu übergeben und den Stack nur verwendet, wenn mehr Parameter als freie Register vorhanden sind. Zum Beispiel verwendet Delphi (EAX, EDX, ECX) für die ersten drei Parameter zu einer Funktion.
In Ihrem C # -Code verwenden Sie tatsächlich die stdcall Aufrufkonvention, die den Compiler anweist, Parameter über den Stack zu übergeben (in umgekehrter Reihenfolge, dh der letzte Parameter wird zuerst gedrückt) und den Angerufenen den Stapel aufräumen zu lassen.
Im Gegensatz dazu zwingt der Aufruf cdecl , der von C / C ++ - Compilern verwendet wird, den Aufrufer, den Stapel zu bereinigen.
Stellen Sie nur sicher, dass Sie auf beiden Seiten die gleiche Aufrufkonvention verwenden. Stdcall wird meistens verwendet, da es fast überall verwendet werden kann und von jedem Compiler unterstützt wird (auch Win32-APIs verwenden diese Konvention).
Beachten Sie, dass fastcall von .NET sowieso nicht unterstützt wird.

    
newgre 05.02.2009, 21:36
quelle
16

jn ist richtig. Der Funktionsprototyp kann, wie angegeben, nicht einfach direkt von C # aufgerufen werden, solange er in Delphis register Aufrufkonvention ist. Sie müssen entweder eine stdcall -Wrapper-Funktion dafür schreiben - vielleicht in einer anderen DLL, wenn Sie keine Quelle haben - oder Sie müssen die Leute, die die Funktion pflegen, dazu bringen, ihre Aufrufkonvention in stdcall zu ändern.

Update: Ich sehe auch, dass das erste Argument eine Delphi-Zeichenfolge ist. Dies ist auch etwas, das C # nicht liefern kann. Es sollte stattdessen ein PChar sein. Außerdem ist es wichtig, dass Sie wissen, ob die Funktion Ansi oder Unicode ist. Wenn die DLL mit Delphi 2009 (oder später) geschrieben wird, ist es Unicode, andernfalls ist es Ansi.

    
Barry Kelly 05.02.2009 21:44
quelle
2

Der Rückgabewert könnte ein anderes Problem sein. Es ist wahrscheinlich entweder ein Speicherleck (Sie reservieren einen Puffer auf dem Heap und geben ihn niemals frei) oder einen Zugriff auf bereits freien Speicher (Sie geben eine lokale Stringvariable an PChar zurück).

Das Zurückgeben von Strings (oder Daten mit variabler Größe im Allgemeinen) von einer Funktion zu einem anderen Modul ist im Allgemeinen problematisch.

Eine Lösung (die von winapi verwendet wird) besteht darin, dass der Aufrufer einen Puffer und seine Größe übergeben muss. Der Nachteil davon ist, dass, wenn der Puffer zu klein ist, die Funktion fehlschlägt und der Aufrufer sie erneut mit einem größeren Puffer aufrufen muss.

Eine andere mögliche Lösung besteht darin, den Puffer vom Heap in der Funktion zuzuweisen und ihn zurückzugeben. Dann müssen Sie eine andere Funktion exportieren, die der Aufrufer verwenden muss, um den zugewiesenen Speicher wieder freizugeben. Dies stellt sicher, dass der Speicher von der gleichen Laufzeit freigegeben wird, die ihn zugewiesen hat.

Das Übergeben eines (Delphi-) Zeichenfolgenparameters zwischen verschiedenen Sprachen (nicht Borland) ist wahrscheinlich unmöglich. Und selbst zwischen Delphi-Modulen stellen Sie sicher, dass beide Module dieselbe Instanz des Speichermanagers verwenden. Normalerweise bedeutet das, dass "uses ShareMem" als erstes für alle Module verwendet wird. Ein weiterer Unterschied ist die Aufrufkonvention "register", die eine Fast-Call-Konvention ist, aber nicht identisch mit den Fast-Call-MS-Compilern.

Eine komplett andere Lösung könnte sein, die Delphi-DLL mit einem der Delphi.net-Compiler neu zu kompilieren. Wie viel Arbeit das ist hängt von ihrem Code ab.

    
MasterOfChaos 06.02.2009 17:10
quelle
1

Ich habe das noch nie gemacht, aber versuche, deinen Code in:

zu ändern %Vor%

Beachten Sie den zusätzlichen Stdcall.

Edit2: Wie Sie aus den anderen Antworten sehen können, müssen Sie entweder die obige Änderung vornehmen oder eine Wrapper-DLL schreiben, die dasselbe tut.

    
PetriW 05.02.2009 21:19
quelle
1

Erstellen Sie in Delphi einen COM-Wrapper und rufen Sie diesen über Interop in Ihrem C # -Code auf. Voila .. einfach von C # oder jeder anderen zukünftigen Plattform zu verwenden.

    
Ben Ark 06.02.2009 17:18
quelle
1

Ich habe den anderen Tag damit verbracht, etwas über Konventionen zu lernen, und ich habe einige Methoden geschrieben, um zwischen verschiedenen Konventionen zu konvertieren. Hier ist eine für StdCall- & gt; FastCall.

%Vor%

}

    
AbdElRaheim 26.01.2010 00:00
quelle
0

Während Sie sie auffordern, die Aufrufkonvention zu ändern, sollten Sie sie auch bitten, den ersten Parameter so zu ändern, dass es keine "Zeichenkette" ist. Lassen Sie sie stattdessen einen Zeiger auf ein (nullterminiertes) char- oder widechar-Array verwenden. Die Verwendung von Delphi-Strings als DLL-Parameter ist eine schlechte Idee, auch ohne die zusätzliche Komplexität beim Versuch, sprachübergreifende Kompatibilität zu erreichen. Zusätzlich enthält die String-Variable entweder ASCII- oder Unicode-Inhalt, abhängig davon, welche Version von Delphi sie verwenden.

    
frogb 06.02.2009 12:10
quelle

Tags und Links