Практическое руководство. Маршал указателей функции с помощью 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 не могут быть обнаружены во время компиляции.