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


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

В этом документе описываются способы вызова машинных функций, принимающих строки в стиле С из управляемых функций, предоставляющих экземпляр класса String с помощью вызова P/Invoke. Рекомендуется использовать функции взаимодействия С++ вместо метода P/Invoke, поскольку этот метод не предоставляет подробной отчетности об ошибках во время компиляции, не является типобезопасным, а его реализация может быть сопряжена с большими сложностями. Однако если неуправляемый API-интерфейс упакован в качестве библиотеки DLL, а исходный код недоступен, P/Invoke — единственный доступный метод. Дополнительные сведения по другим случаям см. в следующих документах.

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

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

Чтобы выполнить маршалинг простых преобразуемых структур и их перенос между неуправляемым и управляемым кодом, в первую очередь необходимо определить управляемые версии для каждой неуправляемой структуры. Такие структуры могут иметь любое допустимое имя, поскольку связь между неуправляемыми и управляемыми версиями структур реализуется исключительно на уровне макета данных. В связи с этим в управляемой версии должны содержаться поля того же размера и в том же порядке, что и в неуправляемой версии. Механизм проверки соответствия между управляемыми и неуправляемыми версиями структур не предусмотрен. Поэтому любые ошибки несовместимости могут быть обнаружены только во время выполнения. Ответственность за обеспечение соответствия макета данных для двух структур возлагается на разработчика.

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

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

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

Пример

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

Управляемый модуль компилируется с параметром /clr, также возможна компиляция с /clr:pure.

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

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

См. также

Другие ресурсы

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