Практическое руководство. Маршалинг обратных вызовов и делегатов посредством C++ Interop
В этом разделе демонстрируется маршаллирование обратных вызовов и делегатов (управляемая версия обратного вызова) между управляемым и неуправляемным кодом с помощью Visual C++.
В следующих примерах кода используются управляемые неуправляемые директивы #pragma для реализации управляемых и неуправляемых функций в одном файле, но функции также могут быть определены в отдельных файлах. Файлы, содержащие только неуправляемые функции, не должны компилироваться с помощью /clr (компиляция clr (компиляция среды CLR).
Пример. Настройка неуправляемого API для активации управляемого делегата
В следующем примере показано, как настроить неуправляемый API для активации управляемого делегата. Создается управляемый делегат и один из методов GetFunctionPointerForDelegateвзаимодействия используется для получения базовой точки входа для делегата. Затем этот адрес передается неуправляемой функции, которая вызывает ее без знаний о том, что она реализована как управляемая функция.
Обратите внимание, что это возможно, но не обязательно, чтобы закрепить делегат с помощью pin_ptr (C++/CLI), чтобы предотвратить его повторное размещение или удаление сборщиком мусора. Защита от преждевременной сборки мусора необходима, но закрепление обеспечивает большую защиту, чем необходимо, так как она предотвращает сбор, но также предотвращает перемещение.
Если делегат перенаправится сборкой мусора, он не повлияет на базовый управляемый обратный вызов, поэтому Alloc используется для добавления ссылки на делегат, позволяя перемещению делегата, но предотвращать удаление. Использование GCHandle вместо pin_ptr снижает вероятность фрагментации управляемой кучи.
// 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();
}
Пример: указатель функции, хранящийся неуправляемым API
Следующий пример аналогичен предыдущему примеру, но в этом случае предоставленный указатель функции хранится неуправляемым API, поэтому его можно вызывать в любое время, требуя подавления сборки мусора в течение произвольного периода времени. В результате в следующем примере используется глобальный экземпляр для предотвращения перемещения делегата GCHandle независимо от области функции. Как описано в первом примере, использование pin_ptr не требуется для этих примеров, но в этом случае не будет работать в любом случае, так как область pin_ptr ограничена одной функцией.
// 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();
}