Gewusst wie: Marshallen von Rückrufen und Delegaten mit C++-Interop
In diesem Thema wird das Marshalling von Rückrufen und Delegaten (der verwalteten Version eines Rückrufs) zwischen verwaltetem und nicht verwaltetem Code mithilfe von Visual C++ veranschaulicht.
In den folgenden Codebeispielen werden die verwalteten, nicht verwalteten #pragma Direktiven verwendet, um verwaltete und nicht verwaltete Funktionen in derselben Datei zu implementieren, die Funktionen können jedoch auch in separaten Dateien definiert werden. Dateien, die nur nicht verwaltete Funktionen enthalten, müssen nicht mit der /clr (Common Language Runtime Compilation) kompiliert werden.
Beispiel: Konfigurieren der nicht verwalteten API zum Auslösen des verwalteten Delegaten
Im folgenden Beispiel wird veranschaulicht, wie Sie eine nicht verwaltete API konfigurieren, um einen verwalteten Delegaten auszulösen. Ein verwalteter Delegat wird erstellt, und eine der Interopmethoden wird verwendet, GetFunctionPointerForDelegateum den zugrunde liegenden Einstiegspunkt für den Delegaten abzurufen. Diese Adresse wird dann an die nicht verwaltete Funktion übergeben, die sie ohne Kenntnis der Tatsache aufruft, dass sie als verwaltete Funktion implementiert wird.
Beachten Sie, dass es möglich, aber nicht erforderlich ist, den Delegat mithilfe von pin_ptr (C++/CLI) anzuheften, um zu verhindern, dass er vom Garbage Collector erneut gefunden oder gelöscht wird. Der Schutz vor vorzeitiger Garbage Collection ist erforderlich, aber das Anheften bietet mehr Schutz als nötig, da es die Sammlung verhindert, aber auch die Verlagerung verhindert.
Wenn sich eine Stellvertretung durch eine Garbage Collection erneut befindet, wirkt sie sich nicht auf den zugrunde liegenden verwalteten Rückruf aus, sondern Alloc wird verwendet, um einen Verweis auf die Stellvertretung hinzuzufügen, um die Verlagerung der Stellvertretung zu ermöglichen, aber die Entsorgung zu verhindern. Die Verwendung von GCHandle anstelle von pin_ptr reduziert das Fragmentierungspotenzial des verwalteten Heaps.
// 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();
}
Beispiel: Funktionszeiger, der von nicht verwalteter API gespeichert wird
Das folgende Beispiel ähnelt dem vorherigen Beispiel, aber in diesem Fall wird der bereitgestellte Funktionszeiger von der nicht verwalteten API gespeichert, sodass es jederzeit aufgerufen werden kann, sodass diese Garbage Collection für eine beliebige Zeit unterdrückt werden muss. Daher wird im folgenden Beispiel eine globale Instanz GCHandle verwendet, mit der verhindert wird, dass der Delegat unabhängig vom Funktionsbereich verschoben wird. Wie im ersten Beispiel erläutert, ist die Verwendung von pin_ptr für diese Beispiele unnötig, würde aber in diesem Fall nicht funktionieren, da der Umfang einer pin_ptr auf eine einzelne Funktion beschränkt ist.
// 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();
}