2012-04-12 6 views
6

Sto cercando di creare un involucro a una DLL C e sto provando a chiamare una funzione che ha prende una funzione di callback, riceve un oggetto come un puntatore che viene passato indietro.Come chiamare una funzione in un ++ Dll C da C# che ha void * richiamata e oggetto parametro

file h delares

extern int SetErrorHandler(void (*handler) (int, const char*, void*), 
           void *data_ptr); 

Il gestore è una funzione di callback che viene chiamata quando si verifica un errore e il data_ptr è un qualsiasi oggetto (stato) che viene passato di nuovo a voi, nel caso del mio app che sarà solo questo (oggetto corrente).

Sono in grado di chiamare le funzioni in una DLL che utilizza tipi costanti di tipo marshalling come semplici stringhe, int, ecc. Ma non riesco a capire come eseguire il marshalling di un puntatore su un oggetto C# che è lo stato.

Per passare il riferimento all'oggetto alla funzione C da quello che ho trovato cercando qui e altrimenti mi sembra di aver bisogno di un tipo di struttura per poter eseguire il marshalling alla funzione, così ho creato una struttura per contenere il mio oggetto :

[StructLayout(LayoutKind.Sequential)] 
struct MyObjectState 
{ 
    public object state; 
} 

EDIT: ho provato a mettere un attributo: [MarshalAs(UnmanagedType.Struct, SizeConst = 4)] sulla proprietà public object state, ma questo produce lo stesso errore, quindi rimosso, doesnt sembra che funzionerebbe comunque.

La struct contiene una proprietà oggetto singolo per contenere qualsiasi oggetto per la funzione di richiamata.

ho dichiarato il delegato in C# come segue:

delegate void ErrorHandler(int errorCode, IntPtr name, IntPtr data); 

Poi ho dichiarato la funzione di importazione in C# come segue:

[DllImport("somedll.dll", CallingConvention = CallingConvention.Cdecl)] 
static extern int SetErrorHandler handler, IntPtr data); 

Poi ho creato una funzione di callback nel mio codice C#:

Sono in grado di chiamare la funzione di libreria come indicato di seguito:

var state = new MyObjectState() 
{ 
    state = this 
}; 
IntPtr pStruct = Marshal.AllocHGlobal(Marshal.SizeOf(state)); 
Marshal.StructureToPtr(state, pStruct, true); 
int ret = SetErrorHandler(MyErrorHandler, pStruct); 

Le opere di chiamata e la funzione di callback viene chiamata, ma non sono in grado di accedere ai dati nella funzione di callback e quando provo Marshal.PtrToStructure ottengo un errore:

The structure must not be a value class.

ho fatto un sacco di ricerca qui e abbiamo trovato diverse cose su Marshall e void *, ma niente mi ha aiutato a farlo funzionare

Grazie.

risposta

3

Si sta rendendo questo più complicato di quanto deve essere. Il tuo client C# non ha bisogno di usare il parametro data_ptr perché un delegato C# ha già un meccanismo incorporato per mantenere il puntatore this.

Quindi è sufficiente passare IntPtr.Zero al delegato. All'interno del tuo gestore di gestori degli errori devi semplicemente ignorare il valore di data_ptr dal momento che this sarà disponibile.

Nel caso in cui non si segua questa descrizione, ecco un breve programma per illustrare cosa intendo. Si noti come MyErrorHandler è un metodo di istanza che funge da gestore degli errori e può modificare i dati di istanza.

class Wrapper 
{ 
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)] 
    delegate void ErrorHandler(int errorCode, string name, IntPtr data); 

    [DllImport("somedll.dll", CallingConvention = CallingConvention.Cdecl)] 
    static extern int SetErrorHandler(ErrorHandler handler, IntPtr data); 

    void MyErrorHandler(int errorCode, string name, IntPtr data) 
    { 
     lastError = errorCode; 
     lastErrorName = name; 
    } 

    public Wrapper() 
    { 
     SetErrorHandler(MyErrorHandler, IntPtr.Zero); 
    }    

    public int lastError { get; set; } 
    public string lastErrorName { get; set; } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     Wrapper wrapper = new Wrapper(); 
    } 
} 
+0

In questo caso è vero, infatti non ho davvero bisogno di passare questo ai dati come sarà disponibile nel mio callback dell'oggetto, ma volevo fornire la chiamata corretta alla funzione per quando l'utente vuole fornire un contesto. Immagino che la gestione dei dati possa essere gestita dall'oggetto C# per mantenere lo stato/contesto. Quindi immagino che questo risolva il mio problema, ma suppongo che la domanda originale su come o sia possibile passare l'oggetto rimanga senza risposta – Andre

+0

Non si vuole davvero passare un riferimento a un oggetto C# nella mia vista. Ricorda che gli oggetti gestiti possono essere spostati da GC. Hai intenzione di forzare tali oggetti a essere appuntati? E cosa può fare il codice nativo con una cosa del genere? È completamente opaco. Si potrebbe usare 'ObjectIDGenerator' per ottenere un ID univoco per ciascun oggetto, ma personalmente penso che la tua domanda sia affrontata meglio spostandola lateralmente! –

+1

Quello che dici ha senso, e credo che la risposta sia: non farlo/evitalo, potrebbe non essere impossibile ma non dovresti, grazie – Andre

2

Ci possono benissimo essere un modo per fare questo, ma ho rinunciato molto tempo fa. La soluzione che è venuta in mente è un po 'hacker, ma è molto efficace e funziona con tutto quello che ho gettato a questo:

C# -> Managed C++ -> Native chiama

Facendo in questo modo si finisce a scrivere un piccolo involucro in C++ gestito, che è una specie di dolore, ma ho trovato per essere più capaci e meno doloroso di tutto questo codice di marshalling.

Onestamente, anche se spero che qualcuno dia una risposta non evasiva, ho lottato per un po 'con me stesso.

+0

Grazie Chris, questo è utile, ma non ho gli strumenti o le conoscenze a questo punto per scrivere la libreria C++ gestita, forse riuscirò a capirlo qualche volta in futuro. Non credo che tu possa fornire un link ad un generico con documentazione come usarlo? Infine, se tutto questo non funziona per me, la soluzione è assicurarsi che tutti i dati di cui ho bisogno nel callback debbano essere aggiunti alla struct e impostati come semplici valori? Grazie – Andre

+0

PS: im anche avendo un problema aggiuntivo con il mio callback, funziona bene ma anche se rimuovo tutto il codice nel corpo della funzione di callback, alla fine della funzione ottengo una violazione di accesso (Tentativo di leggere o scrivere memoria protetta) – Andre

+0

In realtà ho appena capito: mi mancava l'attributo [UnmanagedFunctionPointer (CallingConvention.Cdecl)] sul mio delegato: http://stackoverflow.com/questions/4906931/nullreferenceexception-during-c-callback-to-c- sharp-function – Andre