Procedura: Effettuare il marshalling dei puntatori di funzione tramite P/Invoke
I delegati gestiti possono essere usati al posto dei puntatori a funzione durante l'interoperabilità con funzioni non gestite usando le funzionalità P/Invoke di .NET Framework. Tuttavia, è consigliabile usare le funzionalità di interoperabilità C++ quando possibile. P/Invoke fornisce una segnalazione di errori in fase di compilazione, non è indipendente dai tipi e può essere noiosa da implementare. Se l'API non gestita viene inserita in un pacchetto come DLL e il codice sorgente non è disponibile, P/Invoke è l'unica opzione. In caso contrario, vedere questi articoli:
Le API non gestite che accettano puntatori a funzioni come argomenti possono essere chiamate dal codice gestito usando un delegato gestito al posto del puntatore a funzione nativo. Il compilatore effettua automaticamente il marshalling del delegato alle funzioni non gestite come puntatore a funzione. Inserisce il codice di transizione gestito/non gestito necessario.
Esempio
Il codice seguente è costituito da un modulo non gestito e gestito. Il modulo non gestito è una DLL che definisce una funzione denominata TakesCallback
che accetta un puntatore a funzione. Questo indirizzo viene usato per eseguire la funzione.
// TraditionalDll5.cpp
// compile with: /LD /EHsc
#include <iostream>
#define TRADITIONALDLL_EXPORTS
#ifdef TRADITIONALDLL_EXPORTS
#define TRADITIONALDLL_API __declspec(dllexport)
#else
#define TRADITIONALDLL_API __declspec(dllimport)
#endif
extern "C" {
/* Declare an unmanaged function type that takes two int arguments
Note the use of __stdcall for compatibility with managed code */
typedef int (__stdcall *CALLBACK)(int);
TRADITIONALDLL_API int TakesCallback(CALLBACK fp, int);
}
int TakesCallback(CALLBACK fp, int n) {
printf_s("[unmanaged] got callback address, calling it...\n");
return fp(n);
}
Il modulo gestito definisce un delegato sottoposto a marshalling al codice nativo come puntatore a funzione. Usa l'attributo DllImportAttribute per esporre la funzione nativa TakesCallback
al codice gestito. main
Nella funzione viene creata un'istanza del delegato e passata alla TakesCallback
funzione . L'output del programma dimostra che questa funzione viene eseguita dalla funzione nativa TakesCallback
.
La funzione gestita elimina l'operazione di Garbage Collection per il delegato gestito per impedire la rilocazione del delegato di .NET Framework durante l'esecuzione della funzione nativa.
// MarshalDelegate.cpp
// compile with: /clr
using namespace System;
using namespace System::Runtime::InteropServices;
public delegate int GetTheAnswerDelegate(int);
public value struct TraditionalDLL {
[DllImport("TraditionalDLL5.dll")]
static public int TakesCallback(GetTheAnswerDelegate^ pfn, int n);
};
int GetNumber(int n) {
Console::WriteLine("[managed] callback!");
static int x = 0;
++x;
return x + n;
}
int main() {
GetTheAnswerDelegate^ fp = gcnew GetTheAnswerDelegate(GetNumber);
pin_ptr<GetTheAnswerDelegate^> pp = &fp;
Console::WriteLine("[managed] sending delegate as callback...");
int answer = TraditionalDLL::TakesCallback(fp, 42);
}
Nessuna parte della DLL viene esposta al codice gestito usando la direttiva tradizionale #include
. Infatti, la DLL è accessibile solo in fase di esecuzione, quindi i problemi con le funzioni importate tramite DllImportAttribute non possono essere rilevati in fase di compilazione.