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


Практическое руководство. Маршалирование структур с помощью PInvoke

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

По умолчанию собственные и управляемые структуры размещаются по-разному в памяти, поэтому для успешного передачи структур через управляемую или неуправляемую границу требуются дополнительные шаги для сохранения целостности данных.

В этом документе описываются шаги, необходимые для определения управляемых эквивалентов собственных структур и способа передаче результирующей структуры в неуправляемые функции. В этом документе предполагается, что используются простые структуры , которые не содержат строк или указателей. Сведения о неизменяемом взаимодействии см. в разделе "Использование взаимодействия C++ (неявное PInvoke)". P/Invoke не может содержать типы, не допускающие переключаемость, в качестве возвращаемого значения. Типы Blittable имеют одинаковое представление в управляемом и неуправляемом коде. Дополнительные сведения см. в разделе "Blittable" и "Не blittable Types".

Маршалинг простых и неуправляемых структур в пределах управляемой или неуправляемой границы сначала требует определения управляемых версий каждой собственной структуры. Эти структуры могут иметь любое юридическое имя; Между собственной и управляемой версией двух структур, отличных от их макета данных, нет связи. Поэтому крайне важно, чтобы управляемая версия содержала поля с одинаковым размером и в том же порядке, что и собственная версия. (Нет механизма обеспечения эквивалентности управляемых и собственных версий структуры, поэтому несовместимость не станет очевидной до времени выполнения. Это ответственность программиста, чтобы гарантировать, что две структуры имеют один и тот же макет данных.)

Поскольку элементы управляемых структур иногда переупорядочены для целей производительности, необходимо использовать StructLayoutAttribute атрибут, чтобы указать, что структура размещается последовательно. Кроме того, рекомендуется явно задать параметр упаковки структуры так же, как и для собственной структуры. (Хотя по умолчанию Visual C++ использует 8-байтовую структуру упаковки для обоих управляемых кода.)

  1. Затем используйте DllImportAttribute для объявления точек входа, которые соответствуют любым неуправляемым функциям, принимаюющим структуру, но используйте управляемую версию структуры в подписях функций, которая является спорным, если используется одно и то же имя для обеих версий структуры.

  2. Теперь управляемый код может передать управляемую версию структуры неуправляемых функций, как будто они фактически управляемые функции. Эти структуры можно передавать по значению или по ссылке, как показано в следующем примере.

Неуправляемые и управляемые модули

Следующий код состоит из неуправляемого и управляемого модуля. Неуправляемый модуль — это библиотека DLL, которая определяет структуру с именем Location и функцию GetDistance, которая принимает два экземпляра структуры location. Второй модуль — это управляемое приложение командной строки, которое импортирует функцию GetDistance, но определяет ее с точки зрения управляемого эквивалента структуры Location, MLocation. На практике одно и то же имя, вероятно, будет использоваться для обеих версий структуры; Однако другое имя используется здесь для демонстрации того, что прототип DllImport определен с точки зрения управляемой версии.

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

Пример: неуправляемый модуль DLL

// TraditionalDll3.cpp
// compile with: /LD /EHsc
#include <iostream>
#include <stdio.h>
#include <math.h>

#define TRADITIONALDLL_EXPORTS
#ifdef TRADITIONALDLL_EXPORTS
   #define TRADITIONALDLL_API __declspec(dllexport)
#else
   #define TRADITIONALDLL_API __declspec(dllimport)
#endif

#pragma pack(push, 8)
struct Location {
   int x;
   int y;
};
#pragma pack(pop)

extern "C" {
   TRADITIONALDLL_API double GetDistance(Location, Location);
   TRADITIONALDLL_API void InitLocation(Location*);
}

double GetDistance(Location loc1, Location loc2) {
   printf_s("[unmanaged] loc1(%d,%d)", loc1.x, loc1.y);
   printf_s(" loc2(%d,%d)\n", loc2.x, loc2.y);

   double h = loc1.x - loc2.x;
   double v = loc1.y = loc2.y;
   double dist = sqrt( pow(h,2) + pow(v,2) );

   return dist;
}

void InitLocation(Location* lp) {
   printf_s("[unmanaged] Initializing location...\n");
   lp->x = 50;
   lp->y = 50;
}

Пример: модуль приложения управляемой командной строки

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

[StructLayout(LayoutKind::Sequential, Pack=8)]
value struct MLocation {
   int x;
   int y;
};

value struct TraditionalDLL {
   [DllImport("TraditionalDLL3.dll")]
   static public double GetDistance(MLocation, MLocation);
   [DllImport("TraditionalDLL3.dll")]
   static public double InitLocation(MLocation*);
};

int main() {
   MLocation loc1;
   loc1.x = 0;
   loc1.y = 0;

   MLocation loc2;
   loc2.x = 100;
   loc2.y = 100;

   double dist = TraditionalDLL::GetDistance(loc1, loc2);
   Console::WriteLine("[managed] distance = {0}", dist);

   MLocation loc3;
   TraditionalDLL::InitLocation(&loc3);
   Console::WriteLine("[managed] x={0} y={1}", loc3.x, loc3.y);
}
[unmanaged] loc1(0,0) loc2(100,100)
[managed] distance = 141.42135623731
[unmanaged] Initializing location...
[managed] x=50 y=50

См. также

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