Compartilhar via


Como realizar marshal de retornos de chamadas e delegados usando o C++ Interop

Este tópico demonstra o marshalling de retornos de chamada e delegados (a versão gerenciada de um retorno de chamada) entre código gerenciado e não gerenciado usando o Visual C++.

Os exemplos de código a seguir usam as diretivas de #pragma managed, unmanaged para implementar funções gerenciadas e não gerenciadas no mesmo arquivo, mas as funções também poderia ser definidas em arquivos separados. Os arquivos que contêm apenas funções não gerenciadas não precisam ser compilados com o /clr (compilação do Common Language Runtime).

Exemplo: Configurar a API não gerenciada para disparar o delegado gerenciado

O exemplo a seguir demonstra como configurar uma API não gerenciada para disparar um delegado gerenciado. Um delegado gerenciado é criado e um dos métodos de interoperabilidade, GetFunctionPointerForDelegate, é usado para recuperar o ponto de entrada subjacente do delegado. Esse endereço é então passado para a função não gerenciada, que o chama sem conhecimento do fato de que ele é implementado como uma função gerenciada.

Observe que é possível, mas não necessário, fixar o delegado usando pin_ptr (C++/CLI) para impedir que ele seja relocalizado ou descartado pelo coletor de lixo. A proteção contra coleta de lixo prematura é necessária, mas a fixação fornece mais proteção do que necessária, pois impede a coleta e a realocação.

Se um delegado for localizado novamente por uma coleta de lixo, ele não afetará o retorno de chamada gerenciado subjacente, portanto Alloc é usado para adicionar uma referência ao delegado, permitindo a realocação do delegado e impedindo o descarte. Usar o GCHandle em vez de pin_ptr reduz o potencial de fragmentação do heap gerenciado.

// MarshalDelegate1.cpp
// compile with: /clr
#include <iostream>

using namespace System;
using namespace System::Runtime::InteropServices;

#pragma unmanaged

// Declare an unmanaged function type that takes two int arguments
// Note the use of __stdcall for compatibility with managed code
typedef int (__stdcall *ANSWERCB)(int, int);

int TakesCallback(ANSWERCB fp, int n, int m) {
   printf_s("[unmanaged] got callback address, calling it...\n");
   return fp(n, m);
}

#pragma managed

public delegate int GetTheAnswerDelegate(int, int);

int GetNumber(int n, int m) {
   Console::WriteLine("[managed] callback!");
   return n + m;
}

int main() {
   GetTheAnswerDelegate^ fp = gcnew GetTheAnswerDelegate(GetNumber);
   GCHandle gch = GCHandle::Alloc(fp);
   IntPtr ip = Marshal::GetFunctionPointerForDelegate(fp);
   ANSWERCB cb = static_cast<ANSWERCB>(ip.ToPointer());
   Console::WriteLine("[managed] sending delegate as callback...");

// force garbage collection cycle to prove
// that the delegate doesn't get disposed
   GC::Collect();

   int answer = TakesCallback(cb, 243, 257);

// release reference to delegate
   gch.Free();
}

Exemplo: Ponteiro de função armazenado pela API não gerenciada

O exemplo a seguir é semelhante ao exemplo anterior, mas nesse caso, o ponteiro de função fornecido é armazenado pela API não gerenciada, portanto, ele pode ser invocado a qualquer momento, exigindo que a coleta de lixo seja suprimida por um período arbitrário. Como resultado, o exemplo a seguir usa uma instância global de GCHandle para impedir que o delegado seja realocado, independentemente do escopo da função. Conforme discutido no primeiro exemplo, usar pin_ptr é desnecessário para esses exemplos, mas nesse caso não funcionaria de qualquer maneira, pois o escopo de uma pin_ptr está limitado a uma única função.

// MarshalDelegate2.cpp
// compile with: /clr
#include <iostream>

using namespace System;
using namespace System::Runtime::InteropServices;

#pragma unmanaged

// Declare an unmanaged function type that takes two int arguments
// Note the use of __stdcall for compatibility with managed code
typedef int (__stdcall *ANSWERCB)(int, int);
static ANSWERCB cb;

int TakesCallback(ANSWERCB fp, int n, int m) {
   cb = fp;
   if (cb) {
      printf_s("[unmanaged] got callback address (%d), calling it...\n", cb);
      return cb(n, m);
   }
   printf_s("[unmanaged] unregistering callback");
   return 0;
}

#pragma managed

public delegate int GetTheAnswerDelegate(int, int);

int GetNumber(int n, int m) {
   Console::WriteLine("[managed] callback!");
   static int x = 0;
   ++x;

   return n + m + x;
}

static GCHandle gch;

int main() {
   GetTheAnswerDelegate^ fp = gcnew GetTheAnswerDelegate(GetNumber);

   gch = GCHandle::Alloc(fp);

   IntPtr ip = Marshal::GetFunctionPointerForDelegate(fp);
   ANSWERCB cb = static_cast<ANSWERCB>(ip.ToPointer());
   Console::WriteLine("[managed] sending delegate as callback...");

   int answer = TakesCallback(cb, 243, 257);

   // possibly much later (in another function)...

   Console::WriteLine("[managed] releasing callback mechanisms...");
   TakesCallback(0, 243, 257);
   gch.Free();
}

Confira também

Usando interop do C++ (PInvoke implícito)