방법: PInvoke를 사용하여 구조체 마샬링

이 문서에서는 P/Invoke를 사용하여 관리되는 함수에서 C 스타일 구조체를 허용하는 네이티브 함수를 호출하는 방법을 설명합니다. P/Invoke는 컴파일 시간 오류 보고를 거의 제공하지 않으며, 형식이 안전하지 않으며, 구현이 번잡할 수 있으므로 P/Invoke 대신 C++ Interop 기능을 사용하는 것이 좋지만, 관리되지 않는 API가 DLL로 패키지되고 소스 코드를 사용할 수 없는 경우 P/Invoke가 유일한 옵션입니다. 그렇지 않으면 다음 문서를 참조하세요.

기본적으로 네이티브 구조와 관리되는 구조는 메모리에 다르게 배치되므로 관리/관리되지 않는 경계를 넘어 구조를 성공적으로 전달하려면 데이터 무결성을 유지하기 위한 추가 단계가 필요합니다.

이 문서에서는 네이티브 구조의 관리되는 동등한 구조를 정의하는 데 필요한 단계와 결과 구조를 관리되지 않는 함수에 전달하는 방법을 설명합니다. 이 문서에서는 문자열이나 포인터가 포함되지 않은 단순 구조체가 사용된다고 가정합니다. Blittable이 아닌 상호 운용성에 대한 자세한 내용은 C++ Interop 사용(암시적 PInvoke)을 참조하세요. P/Invoke는 Blittable이 아닌 형식을 반환 값으로 사용할 수 없습니다. Blittable 형식은 관리 코드와 관리되지 않는 코드에서 동일한 표현을 갖습니다. 자세한 내용은 Blittable 및 Blittable이 아닌 형식을 참조 하세요.

관리/관리되지 않는 경계를 넘어 단순하고 Blittable 구조체를 마샬링하려면 먼저 각 네이티브 구조의 관리되는 버전을 정의해야 합니다. 이러한 구조는 모든 법적 이름을 가질 수 있습니다. 데이터 레이아웃 이외의 두 구조체의 네이티브 버전과 관리되는 버전 간에는 관계가 없습니다. 따라서 관리되는 버전에는 네이티브 버전과 크기가 동일하고 순서가 같은 필드가 포함되어야 합니다. (구조체의 관리 버전과 네이티브 버전이 동일한지 확인하는 메커니즘은 없으므로 런타임까지 비호환성이 명백해지지 않습니다. 두 구조체의 데이터 레이아웃이 동일한지 확인하는 것은 프로그래머의 책임입니다.)

관리되는 구조체의 멤버는 경우에 따라 성능 목적으로 다시 정렬되기 때문에 특성을 사용하여 StructLayoutAttribute 구조체가 순차적으로 배치됨을 나타내야 합니다. 또한 구조체 패킹 설정을 네이티브 구조에서 사용하는 것과 동일하게 명시적으로 설정하는 것이 좋습니다. (기본적으로 Visual C++는 두 관리 코드 모두에 대해 8 바이트 구조 패킹을 사용합니다.)

  1. 다음으로, DllImportAttribute 구조를 수락하는 관리되지 않는 함수에 해당하는 진입점을 선언하지만, 두 버전의 구조체에 동일한 이름을 사용하는 경우 함수 서명에서 구조체의 관리되는 버전을 사용합니다.

  2. 이제 관리 코드는 실제로 관리되는 함수인 것처럼 관리되는 버전의 구조를 관리되지 않는 함수에 전달할 수 있습니다. 이러한 구조체는 다음 예제에 설명된 대로 값 또는 참조로 전달할 수 있습니다.

관리되지 않는 모듈 및 관리되는 모듈

다음 코드는 관리되지 않는 모듈과 관리되는 모듈로 구성됩니다. 관리되지 않는 모듈은 Location이라는 구조와 위치 구조의 두 인스턴스를 허용하는 GetDistance라는 함수를 정의하는 DLL입니다. 두 번째 모듈은 GetDistance 함수를 가져오지만 위치 구조인 MLocation에 해당하는 관리되는 측면에서 정의하는 관리되는 명령줄 애플리케이션입니다. 실제로 구조체의 두 버전 모두에 동일한 이름이 사용될 수 있습니다. 그러나 DllImport 프로토타입이 관리되는 버전 측면에서 정의되어 있음을 보여 주는 데는 다른 이름이 사용됩니다.

기존 #include 지시문을 사용하여 관리 코드에 DLL 부분이 노출되지 않습니다. 실제로 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

참고 항목

C++에서 명시적 PInvoke 사용(DllImport 특성)