Hallo Zusammen,
ich habe hier eine Windows-DLL in C++ erstellt (Visual Studio 2010,
x86), bei welcher der Erfolg der Funktionen über einen boolschen
Rückgabewert gemeldet wird. Beispielsweise:
1
bool DLL_FUNCTION foo()
2
{
3
if (doSomething)
4
{
5
lastErr = "OK";
6
// here
7
return(true);
8
}
9
lastErr = "ERR: Errortext";
10
// here
11
return(false);
12
}
Nun tritt beim Aufruf der DLL-Funktion aus einer C#-Assembly der Fehler
auf, dass der Wert "true" zurückgemeldet wird, aber nachweislich der
False-Pfad durchlaufen wurde (erkennbar am Inhalt von lastError).
Interessanterweise tritt dieses Verhalten aber nicht auf, wenn ich an
die mit "here" gekennzeichneten Stellen einen printf()-Anweisung
einfüge.
Ich habe die Optimierung beim C++-Compiler bereits abgeschaltet (/Od).
Kann sich jemand erklären, wie so ein Verhalten zustande kommen kan? Ich
würde mich doch gerne auf die korrektheit der Rückgabewerte verlassen
können...
Danke und Grüße,
Peter
Das hört sich schonmal interessant an. Die DLL exportiert die Funktionen
mit
1
__declspec(dllexport) __stdcall
Bisher habe ich die selbe Definition (allerdings mit "import") auch über
eine Header-Datei in meine Projekte eingebunden, allerdings unter C#
lediglich mit:
1
[DllImport("name.dll")]
2
public static extern bool foo();
Ich bin davon ausgegangen, dass ich mich da dank Marshalling aber nicht
mehr weiter drum kümmer muss, oder bin ich da auf dem Holzweg?
Peter
So, ich kann die CallingConvention beim DllImport tatsächlich angeben,
allerdings entspricht der Standardwert ebenfalls StdCall, es ergibt sich
somit erwartungsgemäß auch keine Veränderung. Hat vielleicht noch jemand
einen anderen Tipp, was da schieflaufen könnte?
Offtopic @ Klaus Wachtler: Von plagen kann keine Rede sein. Wenn man von
der Plattformabhängigkeit absieht (die mir als Linux-User ebenfalls
nicht sonderlich gefällt), finde ich die Sprache sehr gelungen.
Insbesondere Sprachkonstrukte wie "using(){}" oder "foreach" nehmen
einem tatsächlich einige potentielle Fehlerquellen ab. Ich persönlich
finde einiges durchdachter als Java. Und wenn nicht gerade Echtzeit oder
Ressorcen dagegen sprechen, finde ich eine automatische
Speicherverwaltung definitiv zeitgemäß...
Peter
So, ich habe etwas im Assemplercode weitergeforscht:
1
bool__stdcallfoo()
2
{
3
if(doSomething)
4
{
5
lastErr="OK";
6
// here
7
return(true);
8
}
9
lastErr="ERR: Errortext";
10
// here2
11
return(false);
12
}
Die Funktion gibt ihren Rückgabewert über das eax-Register zurück. Dazu
setzt der Compiler die Anweisungen folgendermaßen um:
return(true) => mov al,1
return(false) => xor al,al
Dadurch werden aber nur die acht niederwertigsten Bits im eax-Register
(=al) verändert.
Nach der Zuweisung "lastErr = "ERR: Errortext";" ist jedoch eax ungleich
null. Wenn ich an der mit "here2"-bezeichneten Stelle eine
printf("Bla");-Anweisung einfüge, ist eax danach wieder null, und alles
funktioniert wie es soll. Wenn nicht, sind nur die untersten 8 Bits von
eax korrekt, was die fehlerhafte Auswertung des Rückgabewertes
verursacht.
Was ich allerdings nicht verstehe ist, dass nach dem Funktionsaufruf der
Inhalt von eax scheinbar nochmal umkopiert wird, um die höherwertigen
Bits auf null zu setzten (was aber aus irgendeinem Grund nicht
funktioniert):
bool ret = foo(128) =>
1
mov ecx,80h
2
call FFAFC198 // Nach dem Aufruf ist eax z.B. 0x1001b600
3
mov dword ptr [ebp+FFFFFF38h],eax
4
movzx eax,byte ptr [ebp+FFFFFF38h]
5
mov dword ptr [ebp-4Ch],eax // ret ist nun fälschlicherweise true
Vielleicht hat ja noch irgendjemand eine Idee, was hier schief läuft...
Peter
Nachtrag:
Mit "Assemplercode" ist natürlich "Assemblercode" gemeint...
Also ich habe nun innerhalb der Funktion mit einer
Inline-Assembler-Instruktion das eax-Register auf Null gesetzt, und
damit ist der Fehler verschwunden. Damit stellt sich mir die Frage:
- Muss ein return(false) nur die niederwertigsten 8 Bits oder das
komplette eax-Register auf null setzten?
- Warum wird mit folgendem Code ret trotzdem true zugewiesen, obwohl
meines Erachtens nach nur die niederwertigsten 8 Bits (=0) kopiert
werden?
1
mov dword ptr [ebp+FFFFFF38h],eax // eax ist 0x??????00
2
movzx eax,byte ptr [ebp+FFFFFF38h]
3
mov dword ptr [ebp-4Ch],eax
- Gibt es weitere Fallen beim Aufruf von nativen DLLs aus C# (die
Calling Convention passt)
Peter
das meine ich ja mit verschienden Bool typen. Wenn ich C der bool 8bit
ist und ein C# 32bit dann könnte es diesen verhalten erklären. Du
solltst es ja auch mal mit BOOL testen.
@Peter II:
Volltreffer, vielen Danke! Ich habe dich erst so verstanden, dass bool
innerhalb c++ falsch definiert sein sollte, aber mit deinem neuen Post
war mir klar was du meinst.
Es ist tatsächlich so, dass mein C++-Compiler lediglich die untersten 8
Bits auf null setzt, weil ein bool-Wert für ihn nur aus einem Byte
besteht.
Der C#-InteropService jedoch betrachtet alle 4 Byte, und da diese
ungleich null sind, ist der Wert folglich für ihn "true".
Mögliche Lösungen, die ich bisher entdeckt habe sind zum einen, das
eax-Register vor dem return(false) vollständig nullzusetzen:
1
__asm mov eax,0
2
return(false);
oder alternativ eine Marshalling-Diektive beim DLL-Import angeben: