Partager via


Comment : marshaler des rappels et des délégués à l’aide de l’interopérabilité C++

Cette rubrique illustre le marshaling des rappels et délégués (version managée d’un rappel) entre le code managé et non managé à l’aide de Visual C++.

Les exemples de code suivants utilisent les directives de #pragma managées et non managées 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 distincts. Les fichiers contenant uniquement les fonctions non managées n’ont pas besoin d’être compilés avec / clr (Compilation Common Language Runtime).

Exemple : Configurer l’API non managée pour déclencher un délégué managé

L’exemple suivant montre comment configurer 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é est GetFunctionPointerForDelegateutilisé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 connaissance du fait qu’elle est implémentée en tant que fonction managée.

Notez qu’il est possible, mais pas nécessaire, d’épingler le délégué à l’aide de pin_ptr (C++/CLI) pour l’empêcher d’être relocalisé ou supprimé par le garbage collector. La protection contre le garbage collection prématuré est nécessaire, mais l’épinglage offre plus de protection que nécessaire, car elle empêche le regroupement, mais empêche également la réinstallation.

Si un délégué est relocalisé par un garbage collection, il n’affecte pas le rappel managé sous-jacent. Il est donc Alloc utilisé pour ajouter une référence au délégué, ce qui permet la réinstallation du délégué, mais empêche l’élimination. L’utilisation de GCHandle au lieu 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();
}

Exemple : pointeur de fonction stocké par l’API non managée

L’exemple suivant est similaire à l’exemple précédent, mais dans ce cas, le pointeur de fonction fourni est stocké par l’API non managée, de sorte qu’il peut être appelé à tout moment, exigeant que le garbage collection soit supprimé pendant une durée arbitraire. Par conséquent, l’exemple suivant utilise une instance globale permettant GCHandle d’empêcher le délégué d’être déplacé, indépendamment de l’étendue de la fonction. Comme indiqué dans le premier exemple, l’utilisation de pin_ptr n’est pas nécessaire pour ces exemples, mais dans ce cas ne fonctionne pas de toute façon, car l’étendue d’une pin_ptr est limitée à une seule fonction.

// 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();
}

Voir aussi

Utilisation de l’interopérabilité C++ (PInvoke implicite)