Share via


방법: C++ Interop를 사용하여 콜백 및 대리자 마샬링

이 항목에서는 Visual C++를 사용하여 관리 코드와 관리되지 않는 코드 간에 콜백 및 대리자(콜백의 관리되는 버전)를 마샬링하는 방법을 보여 줍니다.

다음 코드 예제에서는 관리되는 관리되지 않는 #pragma 지시문을 사용하여 동일한 파일에서 관리되는 함수와 관리되지 않는 함수를 구현하지만 함수를 별도의 파일에 정의할 수도 있습니다. 관리되지 않는 함수만 포함하는 파일은 /clr(공용 언어 런타임 컴파일)을 사용하여 컴파일할 필요가 없습니다.

예: 관리되는 대리자를 트리거하도록 관리되지 않는 API 구성

다음 예제에서는 관리되는 대리자를 트리거하도록 관리되지 않는 API를 구성하는 방법을 보여 줍니다. 관리되는 대리자가 만들어지고 interop 메서드 GetFunctionPointerForDelegate중 하나가 대리자의 기본 진입점을 검색하는 데 사용됩니다. 그런 다음, 이 주소는 관리되지 않는 함수로 전달되며, 관리되는 함수로 구현된다는 사실을 알지 못하고 이 주소를 호출합니다.

가비지 수집기에서 pin_ptr(C++/CLI)를 사용하여 대리자를 다시 배치하거나 삭제하지 못하도록 하는 것은 가능하지만 필요하지는 않습니다. 조기 가비지 수집으로부터 보호가 필요하지만 고정은 수집을 방지하지만 재배치를 방지하기 때문에 필요한 것보다 더 많은 보호를 제공합니다.

대리자가 가비지 수집에 의해 다시 배치되는 경우 기본 관리 콜백에는 영향을 미치지 않으므로 Alloc 대리자의 재배치를 허용하지만 삭제를 방지하는 대리자 참조를 추가하는 데 사용됩니다. pin_ptr 대신 GCHandle을 사용하면 관리되는 힙의 조각화 가능성이 줄어듭니다.

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

참고 항목

C++ Interop 사용(암시적 PInvoke)