2009-09-11 9 views
6

So habe ich eine native 3rd-Party-C++ - Code-Basis Ich arbeite mit .lib und .hpp-Dateien, die ich verwendet, um einen Wrapper in C++/CLI für die eventuelle Verwendung zu erstellen in C#.Zugriffsverletzung Ausnahme/Absturz von C++ - Callback zu C# -Funktion

Ich habe ein bestimmtes Problem beim Wechsel vom Debug- zum Freigabemodus festgestellt, in dem ich eine Zugriffsverletzung-Ausnahme erhalte, wenn der Code eines Rückrufs zurückkehrt.

der Code aus den ursprünglichen HPP-Dateien für Callback-Funktion Format:

typedef int (*CallbackFunction) (void *inst, const void *data); 

-Code aus dem C++/CLI Wrapper für Callback-Funktion Format: (ich erklären würde, warum ich zwei in einem Moment erklärt)

public delegate int ManagedCallbackFunction (IntPtr oInst, const IntPtr oData); 
public delegate int UnManagedCallbackFunction (void* inst, const void* data); 

--Quickly, der Grund, erklärte ich eine zweite „UnManagedCallbackFunction“ ist, dass ich versuchte, einen „Vermittler“ Rückruf in der Hülle zu schaffen, so dass die Kette geändert von native C++> C# zu einer Version von native C++> C++/CLI Wrapper> C# ... Vollständige Beschreibung ure, das Problem lebt immer noch, es wurde gerade auf die gleiche Zeile in den C++/CLI Wrapper geschoben (die Rückkehr).

Und schließlich, das Krachen Code von C#:

public static int hReceiveLogEvent(IntPtr pInstance, IntPtr pData) 
    { 
     Console.WriteLine("in hReceiveLogEvent..."); 
     Console.WriteLine("pInstance: {0}", pInstance); 
     Console.WriteLine("pData: {0}", pData); 

     // provide object context for static member function 
     helloworld hw = (helloworld)GCHandle.FromIntPtr(pInstance).Target; 
     if (hw == null || pData == null) 
     { 
      Console.WriteLine("hReceiveLogEvent: received null instance pointer or null data\n"); 
      return 0; 
     } 

     // typecast data to DataLogger object ptr 
     IntPtr ip2 = GCHandle.ToIntPtr(GCHandle.Alloc(new DataLoggerWrap(pData))); 
     DataLoggerWrap dlw = (DataLoggerWrap)GCHandle.FromIntPtr(ip2).Target; 

     //Do Logging Stuff 

     Console.WriteLine("exiting hReceiveLogEvent..."); 
     Console.WriteLine("pInstance: {0}", pInstance); 
     Console.WriteLine("pData: {0}", pData); 
     Console.WriteLine("Setting pData to zero..."); 
     pData = IntPtr.Zero; 
     pInstance = IntPtr.Zero; 
     Console.WriteLine("pData: {0}", pData); 
     Console.WriteLine("pInstance: {0}", pInstance); 

     return 1; 
    } 

Alle schreiben an die Konsole sind fertig und dann sehen wir den gefürchteten Absturz auf der Rückkehr:

Unbehandelte Ausnahme bei 0x04d1004c in helloworld.exe: 0xC0000005: Zugriff Verletzung Leseort 0x04d1004c.

Wenn ich in die Debugger Schritt von hier, alles was ich sehe ist, dass der letzte Eintrag auf dem Call-Stack ist:> „04d1004c()“, die von einem Dezimalwert auswertet: 80805964

Welche ist nur dann interessant, wenn Sie an der Konsole sehen, die zeigt:

entering registerDataLogger 
pointer to callback handle: 790848 
fp for callback: 2631370 
pointer to inst: 790844 
in hReceiveLogEvent... 
pInstance: 790844 
pData: 80805964 
exiting hReceiveLogEvent... 
pInstance: 790844 
pData: 80805964 
Setting pData to zero... 
pData: 0 
pInstance: 0 

Nun, ich weiß, dass zwischen Debug und Release einige Dinge in der Microsoft-Welt ganz anders sind. Ich bin natürlich besorgt über Byte-Padding und die Initialisierung von Variablen, wenn also etwas hier nicht zur Verfügung steht, lassen Sie es mich wissen und ich werde den (schon langen) Beitrag hinzufügen. Ich denke auch, dass der verwaltete Code NICHT alle Eigentumsrechte freigibt und dann versucht das native C++ - Zeug (für das ich nicht den Code habe) das pData-Objekt zu löschen oder zu töten, wodurch die App zum Absturz gebracht wird.

Weitere vollständige Offenlegung, es funktioniert alles (scheinbar) im Debug-Modus!

Ein echtes Kopf-Scratch-Problem, das jede Hilfe zu schätzen wissen würde!

Antwort

3

ich glaube, der Stapel wurde zerquetscht wegen Aufrufkonventionen unpassende: versuchen, das Attribut

[UnmanagedFunctionPointer(CallingConvention.Cdecl)] 

auf den Rückruf Delegatendeklaration zu setzen.

+0

Für die Unterstützung war dies meistens richtig. Nach der Kontaktaufnahme mit dem Drittanbieter haben wir festgestellt, dass sie mit cdecl spec und nicht mit dem für die Einhaltung von Managed Code erforderlichen stdcall kompiliert wurden: http://msdn.microsoft.com/en-us/library/367eey0%28VS.80%29.aspx . Ich habe eine Frage zu StackOverflow gestellt, um zu fragen, warum das so sein muss? Hoffentlich gibt jemand eine bessere Erklärung als der referenzierte MSDN-Artikel. – TomO

+0

In den Projekteinstellungen gibt es einen Standard für die verwendete Aufrufkonvention (C/C++), falls nicht mit __declspec() angegeben. Diese Aufrufkonvention war im Code nicht sichtbar. Was bei nicht übereinstimmenden Konventionen passiert, ist klar: Wenn die Verantwortung für die Stapelbereinigung nicht zusammenpasst, wird der Stapel zerquetscht (vor dem Aufruf wird er nicht auf seinen Status zurückgesetzt, da entweder doppelt aufgeräumt oder zu wenige vorhanden sind). Dies hängt von der Anzahl der Argumente ab, die auf dem Stack übergeben werden. http://en.wikipedia.org/wiki/Calling_convention – jdehaan

0

This doesn't directly answer your question, aber es kann Sie in die richtige Richtung, so weit als Debug-Modus in Ordnung vs. Release-Modus nicht in Ordnung führen:

Da der Debugger fügt eine Menge von Aufzeichnungspflichten Informationen auf den Stapel, in der Regel Ich habe die Größe und das Layout meines Programms im Speicher aufgefüllt und im Debug-Modus "Glück gehabt", indem ich über 912 Bytes Speicher geschrieben habe, die nicht sehr wichtig waren. Ohne den Debugger kritzelte ich jedoch über ziemlich wichtige Dinge hinaus und ging schließlich außerhalb meines eigenen Speicherbereichs, was Interop dazu brachte, Speicher zu löschen, den er nicht besaß.

Was ist die Definition von DataLoggerWrap? Ein Char-Feld ist möglicherweise zu klein für die Daten, die Sie empfangen.

0

Ich bin mir nicht sicher, was Sie erreichen möchten.

Einige Punkte:

1) Der Garbage Collector aggressiver im Release-Modus ist das Verhalten, das Sie so mit schlechtem Eigentum beschreiben, ist nicht ungewöhnlich.

2) Ich verstehe nicht, was der folgende Code versucht zu tun?

IntPtr ip2 = GCHandle.ToIntPtr(GCHandle.Alloc(new DataLoggerWrap(pData))); 
DataLoggerWrap dlw = (DataLoggerWrap)GCHandle.FromIntPtr(ip2).Target; 

Sie verwenden GCHandle.Alloc eine Instanz von DataLoggerWrap im Speicher zu sperren, aber es dann übergeben Sie nie auf nicht verwalteten out - also warum Sie sie sperren? Sie befreien es auch nie?

Die zweite Zeile greift dann eine Referenz zurück - warum die Kreisbahn? Warum die Referenz - du benutzt sie nie?

3) Sie setzen die IntPtrs auf null - warum? - Dies hat keine Auswirkungen außerhalb des Funktionsumfangs.

4) Sie müssen wissen, was der Vertrag des Rückrufs ist. Wem gehört pData der Callback oder die Calling-Funktion?

0

Ich bin mit @jdehaan, außer CallingConvetion.StdCall könnte die Antwort sein, vor allem, wenn die 3rd Party Lib in BC++ geschrieben wird, zum Beispiel.