Comment : marshaler des rappels et des délégués à l'aide de l'interopérabilité C++
Cette rubrique illustre le marshaling de rappels et de délégués (version managée d'un rappel) entre du code managé et non managé à l'aide de Visual C++.
Les exemples de code suivants utilisent les directives #pragma managed, unmanaged pour implémenter des fonctions managées et non managées dans le même fichier, mais les fonctions peuvent également être définies dans des fichiers séparés. Les fichiers qui contiennent uniquement des fonctions non managées ne doivent pas être compilés avec /clr (Compilation pour le Common Language Runtime).
Exemple
L'exemple suivant illustre la configuration d'une API non managée pour déclencher un délégué managé. Un délégué managé est créé et l'une des méthodes d'interopérabilité, GetFunctionPointerForDelegate, est utilisée pour récupérer le point d'entrée sous-jacent du délégué. Cette adresse est ensuite passée à la fonction non managée qui l'appelle sans avoir connaissance du fait qu'elle est implémentée en tant que fonction managée.
Remarquez qu'il est possible, mais pas nécessaire, d'épingler le délégué à l'aide de pin_ptr pour l'empêcher d'être réadressé ou supprimé par le garbage collector. La protection contre un garbage collection prématuré est exigée, mais l'épinglage assure une protection plus élevée que ce qui est requis, car il empêche la collecte, mais également le réadressage.
Si un délégué est réadressé par un garbage collection, il n'affecte pas le rappel managé sous-jacent ; Alloc est donc utilisé pour ajouter une référence au délégué, en autorisant le réadressage du délégué, mais en empêchant sa suppression. L'utilisation de GCHandle plutôt que de pin_ptr réduit le potentiel de fragmentation du tas managé.
// 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();
}
L'exemple suivant est similaire à l'exemple précédent, mais dans ce cas, le pointeur fonction fourni est stocké par l'API non managée ; il peut donc être appelé à tout moment, en exigeant la suppression du garbage collection pendant une durée arbitraire. En conséquence, l'exemple suivant utilise une instance globale de GCHandle pour empêcher le délégué d'être réadressé, indépendamment de la portée de la fonction. Comme expliqué dans le premier exemple, l'utilisation de pin_ptr est inutile pour ces exemples, mais s'avère de toute manière inutile dans ce cas précis, car la portée de pin_ptr est limitée à une fonction unique.
// 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();
}