Procedura: effettuare il marshalling di callback e delegati utilizzando l'interoperabilità C++

In questo argomento viene illustrato il marshalling di callback e delegati (la versione gestita di un callback) tra codice gestito e codice non gestito utilizzando Visual C++.

Negli esempi di codice riportati di seguito vengono utilizzate le direttive #pragma managed, unmanaged per implementare funzioni gestite e non gestite nello stesso file. Queste funzioni, tuttavia, possono anche essere definite in file diversi.I file che contengono soltanto funzioni non gestite non richiedono necessariamente la compilazione con /clr (Compilazione Common Language Runtime).

Esempio

Nell'esempio di codice riportato di seguito viene illustrato come configurare un'API non gestita per attivare un delegato gestito.Viene creato un delegato gestito e viene utilizzato uno dei metodi di interoperabilità (GetFunctionPointerForDelegate) per recuperare il punto di ingresso sottostante per il delegato.Questo indirizzo viene quindi passato alla funzione non gestita, che lo utilizza per effettuare una chiamata senza essere a conoscenza del fatto che il delegato è implementato come una funzione gestita.

Anche se non si tratta di un'operazione indispensabile, è possibile bloccare il delegato utilizzando pin_ptr (C++/CLI) per impedire che venga rilocato o eliminato dal Garbage Collector.Sebbene sia necessario assicurare la sicurezza contro l'esecuzione prematura della Garbage Collection, il blocco fornisce un livello di sicurezza maggiore rispetto a quello richiesto, poiché impedisce sia la Garbage Collection che la rilocazione.

Se viene rilocato dalla Garbage Collection, un delegato non avrà alcun effetto sulla callback gestita sottostante, Di conseguenza, viene utilizzato il metodoAlloc per aggiungere un riferimento al delegato, consentendo la rilocazione del delegato ma impedendone l'eliminazione.L'utilizzo di GCHandle al posto di pin_ptr riduce la potenziale frammentazione dell'heap gestito.

// 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'esempio riportato di seguito è simile all'esempio precedente. In questo caso, tuttavia, il puntatore a funzione fornito viene memorizzato dall'API non gestita e può quindi essere richiamato in qualsiasi momento, richiedendo la soppressione della Garbage Collection per un periodo di tempo arbitrario.Per questo motivo, nell'esempio riportato di seguito viene utilizzata un'istanza globale di GCHandle per impedire che il delegato venga rilocato, indipendentemente dall'ambito della funzione.Come indicato nel primo esempio, l'utilizzo di pin_ptr non è necessario per questi esempi. Tuttavia, anche se utilizzato, non funzionerebbe correttamente poiché l'ambito di un pin_ptr è limitato a una singola funzione.

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

Vedere anche

Riferimenti

Utilizzo delle funzionalità di interoperabilità C++ (PInvoke implicito)