Forum: PC-Programmierung Fehlerhafter Rückgabewert beim Aufruf von DLL-Funktion


von Peter (Gast)


Lesenswert?

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

von Klaus W. (mfgkw)


Lesenswert?

Vielleicht gehen der C++-Teil und der C#-Teil von verschiedenen 
Aufrufarten aus (_cdecl etc.)?

von Peter (Gast)


Lesenswert?

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

von Klaus W. (mfgkw)


Lesenswert?

Das weiß ich nicht; ich plage mich nicht mit C# rum.
Sollte aber dokumentiert sein, was bei gemischter Programmierung von C# 
erwartet wird.

von Peter (Gast)


Lesenswert?

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

von Peter II (Gast)


Lesenswert?

kann es sein das ein bool nicht einheitlich ist? teste mal mit int. Oder 
BOOL grossgeschrieben.

von Peter (Gast)


Lesenswert?

So, ich habe etwas im Assemplercode weitergeforscht:
1
bool __stdcall foo()
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

von Peter (Gast)


Lesenswert?

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

von Peter II (Gast)


Lesenswert?

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.

von gk (Gast)


Lesenswert?

Braucht man da eventuell auch noch die __export Definition ?
gk

von Peter (Gast)


Lesenswert?

@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:
1
[DllImport("file.dll", CallingConvention = CallingConvention.StdCall)]
2
[return: MarshalAs(UnmanagedType.I1)]
3
public static extern bool fool();

Beides recht unschön, da man es vergessen kann, aber ein Workaround fürs 
Erste...

Peter

von Peter II (Gast)


Lesenswert?

http://msdn.microsoft.com/en-us/library/aa383751%28v=vs.85%29.aspx

du solltest mal BOOL verwenden (GROßGESCHRIEBEN!!!) das ist als int 
definiert.

Das hatte ich doch schon oben geschrieben.

von Peter (Gast)


Lesenswert?

Ja, du hast recht, BOOL löst das Problem ebenfalls, sorry für die 
Begriffsstutzigkeit...

Peter

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.