Como realizar marshal de ponteiros de função usando P/Invoke
Delegados gerenciados podem ser usados no lugar de ponteiros de função ao interoperar com funções não gerenciadas usando os recursos P/Invoke do .NET Framework. No entanto, incentivamos você a usar os recursos de interoperabilidade de C++ quando possível. O P/Invoke fornece pouco relatório de erros em tempo de compilação, não é fortemente tipado e a implementação dele pode ser entediante. Se a API não gerenciada for empacotada como uma DLL e o código-fonte não estiver disponível, P/Invoke será a única opção. Caso contrário, consulte estes artigos:
APIs não gerenciadas que usam ponteiros de função como argumentos podem ser chamados do código gerenciado usando um delegado gerenciado no lugar do ponteiro de função nativo. O compilador realizar marshal automaticamente do delegado para funções não gerenciadas como um ponteiro de função. Ele insere o código de transição gerenciado/não gerenciado necessário.
Exemplo
O código a seguir consiste em um módulo gerenciado e um não gerenciado. O módulo não gerenciado é uma DLL que define uma função chamada TakesCallback
que aceita um ponteiro de função. Esse endereço é usado para executar a função.
// 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);
}
O módulo gerenciado define um delegado do qual é feito marshal para o código nativo como um ponteiro de função. Ele usa o atributo DllImportAttribute para expor a função TakesCallback
nativa ao código gerenciado. Na função main
, uma instância do delegado é criada e passada para a função TakesCallback
. A saída do programa demonstra que essa função é executada pela função TakesCallback
nativa.
A função gerenciada suprime a coleta de lixo para o delegado gerenciado para impedir que a coleta de lixo do .NET Framework realoque o delegado enquanto a função nativa é executada.
// 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);
}
Nenhuma parte da DLL é exposta ao código gerenciado usando a diretiva tradicional #include
. De fato, a DLL é acessada somente em runtime, de modo que problemas em funções importadas usando DllImportAttribute não podem ser detectados em tempo de compilação.