Поделиться через


Практическое руководство. Маршал указателей функции с помощью P/Invoke

Управляемые делегаты можно использовать вместо указателей функций при взаимодействии с неуправляемыми функциями с помощью платформа .NET Framework функций P/Invoke. Тем не менее, мы рекомендуем использовать функции взаимодействия C++ вместо этого, когда это возможно. P/Invoke предоставляет мало отчетов об ошибках во время компиляции, не является типобезопасным и может быть небезопасным для реализации. Если неуправляемый API упакован в виде библиотеки DLL и исходный код недоступен, P/Invoke является единственным вариантом. В противном случае см. следующие статьи:

Неуправляемые API, которые принимают указатели функций в качестве аргументов, можно вызывать из управляемого кода с помощью управляемого делегата вместо указателя собственной функции. Компилятор автоматически маршалирует делегат в неуправляемые функции в качестве указателя функции. Он вставляет необходимый управляемый или неуправляемый код перехода.

Пример

Следующий код состоит из неуправляемого и управляемого модуля. Неуправляемый модуль — это библиотека DLL, которая определяет функцию, которая TakesCallback принимает указатель функции. Этот адрес используется для выполнения функции.

// TraditionalDll5.cpp
// compile with: /LD /EHsc
#include <iostream>
#define TRADITIONALDLL_EXPORTS
#ifdef TRADITIONALDLL_EXPORTS
#define TRADITIONALDLL_API __declspec(dllexport)
#else
#define TRADITIONALDLL_API __declspec(dllimport)
#endif

extern "C" {
   /* Declare an unmanaged function type that takes two int arguments
      Note the use of __stdcall for compatibility with managed code */
   typedef int (__stdcall *CALLBACK)(int);
   TRADITIONALDLL_API int TakesCallback(CALLBACK fp, int);
}

int TakesCallback(CALLBACK fp, int n) {
   printf_s("[unmanaged] got callback address, calling it...\n");
   return fp(n);
}

Управляемый модуль определяет делегат, который маршалируется в машинный код в качестве указателя функции. Он использует DllImportAttribute атрибут для предоставления собственной TakesCallback функции управляемому коду. main В функции создается и передается в функцию экземпляр делегатаTakesCallback. Выходные данные программы показывают, что эта функция выполняется собственной TakesCallback функцией.

Управляемая функция подавляет сборку мусора для управляемого делегата, чтобы предотвратить перемещение делегата платформа .NET Framework сборки мусора во время выполнения собственной функции.

// MarshalDelegate.cpp
// compile with: /clr
using namespace System;
using namespace System::Runtime::InteropServices;

public delegate int GetTheAnswerDelegate(int);
public value struct TraditionalDLL {
   [DllImport("TraditionalDLL5.dll")]
   static public int TakesCallback(GetTheAnswerDelegate^ pfn, int n);
};

int GetNumber(int n) {
   Console::WriteLine("[managed] callback!");
   static int x = 0;
   ++x;
   return x + n;
}

int main() {
   GetTheAnswerDelegate^ fp = gcnew GetTheAnswerDelegate(GetNumber);
   pin_ptr<GetTheAnswerDelegate^> pp = &fp;
   Console::WriteLine("[managed] sending delegate as callback...");

   int answer = TraditionalDLL::TakesCallback(fp, 42);
}

Часть библиотеки DLL не предоставляется управляемому коду с помощью традиционной #include директивы. На самом деле библиотека DLL доступна только во время выполнения, поэтому проблемы с функциями, импортированными с помощью, DllImportAttribute не могут быть обнаружены во время компиляции.

См. также

Использование явного P/Invoke в C++ (DllImport атрибут)