Cómo: Calcular las referencias de devoluciones de llamadas y delegados mediante la interoperabilidad de C++
En este tema se muestra la serialización de devoluciones de llamada y delegados (la versión administrada de una devolución de llamada) entre código administrado y no administrado mediante Visual C++.
En los ejemplos de código siguientes se usan las directivas de #pragma managed, unmanaged para implementar funciones administradas y no administradas en el mismo archivo, pero las funciones también se podrían definir en archivos independientes. Los archivos que contienen solo funciones no administradas no tienen que compilarse con /clr (compilación de Common Language Runtime).
Ejemplo: Configuración de la API no administrada para desencadenar un delegado administrado
En el ejemplo siguiente se muestra cómo configurar una API no administrada para desencadenar un delegado administrado. Se crea un delegado administrado y se usa uno de los métodos de interoperabilidad, GetFunctionPointerForDelegate, para recuperar el punto de entrada subyacente para el delegado. A continuación, esta dirección se pasa a la función no administrada, que la llama sin conocimiento del hecho de que se implementa como una función administrada.
Tenga en cuenta que es posible, pero no necesario, anclar el delegado mediante pin_ptr (C++/CLI) para evitar que el recolector de elementos no utilizados lo vuelva a ubicar o eliminar. Se necesita protección contra la recolección prematura de elementos no utilizados, pero el anclaje proporciona más protección de la necesaria, ya que evita la recolección, pero también evita la reubicación.
Si una recolección de elementos no utilizados vuelve a ubicar un delegado, no afectará a la devolución de llamada administrada subyacente, por lo que Alloc se usa para agregar una referencia al delegado, lo que permite la reubicación del delegado, pero evita la eliminación. El uso de GCHandle en lugar de pin_ptr reduce el potencial de fragmentación del montón administrado.
// 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();
}
Ejemplo: Puntero de función almacenado por la API no administrada
El ejemplo siguiente es similar al ejemplo anterior, pero en este caso el puntero de función proporcionado se almacena mediante la API no administrada, por lo que se puede invocar en cualquier momento, lo que requiere que se suprima la recolección de elementos no utilizados durante un período arbitrario de tiempo. Como resultado, en el ejemplo siguiente se usa una instancia global de GCHandle para evitar que el delegado se reubique, independientemente del ámbito de la función. Como se describe en el primer ejemplo, el uso de pin_ptr no es necesario para estos ejemplos, pero en este caso no funcionaría de todos modos, ya que el ámbito de pin_ptr se limita a una sola función.
// 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();
}