2009-09-11 12 views
6

Quindi ho una base di codice C++ di terze parti nativa Sto lavorando con file (.lib e .hpp) che ho usato per costruire un wrapper in C++/CLI per l'uso finale in C#.Violazione di accesso Eccezione/crash dalla richiamata C++ alla funzione C#

Ho incontrato un problema particolare quando si passa da Debug a Release, in quanto ottengo un'eccezione di violazione di accesso quando viene restituito un codice di callback.

il codice dal file HPP originali per il formato funzione di callback:

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

Codice dal C++/CLI wrapper per il formato funzione di callback: (ti spiego perché ho dichiarato due in un attimo)

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

--Quickly, il motivo per cui ho dichiarato un secondo "UnManagedCallbackFunction" è che ho cercato di creare un callback "intermediario" nella confezione, in modo che la catena di cambiato da nativi C++> C# per una versione di Native C++> C++/CLI Wrapper> C# ... Full disclos Il problema persiste, è appena stato trasferito al C++/CLI Wrapper ora sulla stessa riga (il ritorno).

E, infine, il codice schiantarsi da 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; 
    } 

Tutto scrive alla console sono fatto e poi vediamo l'incidente temuta sul ritorno:

Eccezione non gestita a 0x04d1004c in helloworld.exe: 0xC0000005: accesso alla posizione di lettura della violazione 0x04d1004c.

Se io passo nel debugger da qui, tutto quello che vedo è che l'ultima voce sul stack di chiamate è il seguente:> "04d1004c()", che restituisce un valore decimale di: 80805964

Quale è solo interessante se si guarda alla console che mostra:

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 

Ora, so che tra il debug e rilasciare alcune cose sono molto diverse in tutto il mondo Microsoft. Ovviamente, sono preoccupato per il byte padding e l'inizializzazione delle variabili, quindi se c'è qualcosa che non sto fornendo qui, fammelo sapere e aggiungerò al post (già lungo). Penso inoltre che il codice gestito NON stia rilasciando tutta la proprietà e quindi il materiale nativo di C++ (di cui non ho il codice) potrebbe tentare di eliminare o uccidere l'oggetto pData, causando così il crash dell'applicazione.

Più informazioni complete, tutto funziona correttamente (apparentemente) in modalità Debug!

Un vero problema di graffio della testa che apprezzerebbe qualsiasi aiuto!

risposta

3

penso che la pila ha ottenuto schiacciato a causa del mismatching convenzioni di chiamata: provare a mettere l'attributo

[UnmanagedFunctionPointer(CallingConvention.Cdecl)] 

sulla dichiarazione di callback delegato.

+0

Per il supporto, questo era per lo più giusto. Dopo aver contattato il fornitore di terze parti, abbiamo scoperto che sono stati compilati utilizzando cdecl spec e non la stdcall necessaria per la conformità al codice gestito: http://msdn.microsoft.com/en-us/library/367eeye0%28VS.80%29.aspx . Ho aggiunto una domanda su StackOverflow per chiedere perché deve essere così? Si spera che qualcuno fornisca una spiegazione migliore rispetto all'articolo MSDN di riferimento. – TomO

+0

Nelle impostazioni del progetto è presente un valore predefinito per la convenzione di chiamata utilizzata (C/C++) se non specificato con __declspec(). Questa convenzione di chiamata non era visibile nel codice. Ciò che accade con le convenzioni di mismatching è chiaro: se la responsabilità per la pulizia dello stack non è corrispondente, essa distrugge lo stack (non viene ripristinato allo stato precedente alla chiamata a causa di una doppia pulizia o troppo pochi). Questo dipende dalla quantità di argomenti passati nello stack. http://en.wikipedia.org/wiki/Calling_convention – jdehaan

0

This doesn't directly answer your question, ma può portare nella giusta direzione per quanto riguarda la modalità di debug bene vs modalità di rilascio non va bene:

Dal momento che il debugger aggiunge un sacco di informazioni tenuta dei registri alla pila, in genere riempiendo le dimensioni e il layout del mio programma in memoria, stavo "diventando fortunato" in modalità debug scribacchiando oltre 912 byte di memoria che non erano molto importanti. Senza il debugger, però, stavo scrivendo sopra cose piuttosto importanti, finendo per uscire dal mio spazio di memoria, facendo sì che Interop cancellasse la memoria che non possedeva.

Qual è la definizione di DataLoggerWrap? Un campo char potrebbe essere troppo piccolo per i dati che stai ricevendo.

0

Non sono sicuro di cosa stiate cercando di raggiungere.

alcuni punti:

1) Il garbage collector è più aggressivo nella modalità di rilascio così male con la proprietà il comportamento descritto non è raro.

2) Non capisco cosa sta cercando di fare il codice seguente?

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

Si utilizza GCHandle.Alloc per bloccare un'istanza di DataLoggerWrap in memoria, ma poi non avete mai passa fuori per non gestito - e allora perché si fa a bloccarlo? Non lo hai mai liberato?

La seconda riga quindi richiama un riferimento - perché il percorso circolare? perché il riferimento - non lo usi mai?

3) Si imposta IntPtrs su null - perché? - questo non avrà alcun effetto al di fuori dell'ambito della funzione.

4) È necessario sapere qual è il contratto di richiamata. Chi possiede pData il callback o la funzione di chiamata?

0

Sono con @jdehaan, ad eccezione di CallingConvetion.StdCall potrebbe essere la risposta, soprattutto quando la libreria di terze parti è scritta in BC++, ad esempio.

Problemi correlati