2012-11-19 6 views
6

Dire, ho ottenuto che il prototipo di una funzione che viene esposta in una DLL:Come si chiama una funzione non gestita che ha un carattere [] come parametro OUT da C#?

int CALLBACK worker (char* a_inBuf, int a_InLen, 
        char** a_pOutBuf, int* a_pOutLen, 
        char** a_pErrBuf, int* a_pErrLen) 

Sono sicuro che è ridicolmente facile chiamare quella funzione DLL dal mio codice C#, ma non funziona con questo codice:

[DllImport("mydll.dll")] 
    public static extern int worker(
     [In, MarshalAs(UnmanagedType.LPArray)] byte[] inBuf, 
     int inputLen, 
     [Out, MarshalAs(UnmanagedType.LPArray)] byte[] outBuf, 
     out int outputLen, 
     [Out, MarshalAs(UnmanagedType.LPArray)] byte[] errBuf, 
     out int errorLen); 

... 

int outputXmlLength = 0; 
int errorXmlLength = 0; 

byte[] outputXml = null; 
byte[] errorXml = null; 
worker(input, input.Length, output, out outputLength, error, out errorLength); 

ricevo una violazione di accesso quando vado a prendere la memoria per output e error dentro la mia biblioteca non gestito (e quindi de-riferimento il puntatore passato):

*a_ppBuffer = (char*) malloc(size*sizeof(char)); 
  1. Come si scrive l'istruzione DLLIMPORT nel codice C# per questa funzione?

  2. Come posso effettivamente chiamare la funzione in modo che a_pOutBuf e a_pErrBuf sono accessibili e non null dall'interno worker (vale a dire utilizzare un vero e proprio doppio puntatore)?

+1

È possibile modificare la firma della funzione di lavoro? –

+0

@SimonMourier: no. è una DLL usata in un programma C++ (API stabilita) e dovrebbe ora essere chiamata anche da una applicazione .net appena sviluppata. Pertanto l'API è fissa. – eckes

risposta

7

La definizione corrente non funzionerà. La funzione worker sta allocando memoria all'interno della funzione e scrivendo su quella memoria.

P/Invoke layer non supporta il marshalling di array in stile C che vengono allocati in questo modo, in quanto non ha modo di sapere quanto grande sarà la matrice al momento della chiamata (a differenza, a SAFEARRAY).

Ecco anche il motivo per il ritorno puntatori ad array di funzioni API è generalmente una cattiva idea, e l'API di Windows è scritto in modo tale che l'allocazione di memoria è gestita dal chiamante.

Detto questo, si desidera modificare il P/Invoke dichiarazione di worker a questo:

[DllImport("mydll.dll")] 
public static extern int worker(
    [In, MarshalAs(UnmanagedType.LPArray)] byte[] inBuf, 
    int inputLen, 
    ref IntPtr outBuf, ref int outputLen, 
    ref IntPtr errBuf, ref int errorLen); 

Nel fare questo, si sta ad indicare che si sta andando a maresciallo manualmente (gli array la outBuf e I parametri errBuf saranno impostati per te); stai passando il riferimento al puntatore (double-indirection, che è il tuo char**) e quindi devi leggere da loro utilizzando altri indicatori per il controllo dei limiti (in questo caso, i parametri outputLen e errorLen).

Si potrebbe schierare i dati fuori dei puntatori al ritorno in questo modo:

int outputXmlLength = 0; 
int errorXmlLength = 0; 

IntPtr output = IntPtr.Zero; 
IntPtr error = IntPtr.Zero; 

worker(input, input.Length, ref output, ref outputLength, 
    ref error, ref errorLength); 

// Get the strings. 
string outputString = Marshal.PtrToStringAnsi(output, outputLength); 
string errorString = Marshal.PtrToStringAnsi(error, errorLength); 

Detto questo, hai un altro problema. Poiché la memoria è stata allocata all'interno della funzione, è necessario liberare la memoria. Dato che stai utilizzando malloc per allocare la memoria, devi passare le due istanze IntPtr al codice non gestito per fare in modo che free le invochi.

Se tu fossi l'allocazione della memoria in codice non gestito utilizzando LocalAlloc o CoTaskMemAlloc allora si potrebbe utilizzare i FreeHGlobal o FreeCoTaskMem metodi, rispettivamente, sul Marshal class per liberare la memoria sul lato gestito.

+0

Ho già un metodo 'cleanup (void)' che viene utilizzato per ripulire la memoria allocata. L'assunto (che funziona con 'C++' non gestito) è che lo stato della mia DLL non cambierà tra chiamare 'worker' e' cleanup'. Lo stato è detenuto internamente. – eckes

+0

@eckes Intendi 'cleanup (void *)', giusto? – casperOne

+0

no. Il metodo richiede esattamente ** no ** parametri perché ci sono altre cose memorizzate fino a quando viene chiamato 'cleanup'. – eckes