구조체 포인터 인자에 대한 P/Invoke 정의
C/C++와의 연동 문제가 때때로 ^^ 어렵게 다가오기도 합니다. 결국 연동부분에서 직관적이지 않다는 것이 문제겠지요. 덕분에 다음과 같은 글들도 씌여졌었고. ^^
Win32 Interop - 크기가 정해지지 않은 배열을 C++에서 C#으로 전달하는 경우
; https://www.sysnet.pe.kr/2/0/737
C#에서 Union 구조체 다루기
; https://www.sysnet.pe.kr/2/0/728
How to Interop DISPPARAMS
; https://www.sysnet.pe.kr/2/0/617
[in,out] 배열을 C#에서 C/C++로 넘기는 방법 - 두 번째 이야기
; https://www.sysnet.pe.kr/2/0/811
[in,out] 배열을 C#에서 C/C++로 넘기는 방법
; https://www.sysnet.pe.kr/2/0/810
이번에는, 다음 질문에 대한 답변으로 씌여지게 되었습니다.
C# Interop 관련 질문입니다.
; https://www.sysnet.pe.kr/3/0/904
; https://www.sysnet.pe.kr/3/0/905
요점은, 아래의 코드 조각에서 보는 것처럼, P/Invoke된 API에서 "ref DX_CAP" 인자에 대해 NULL이 가능해야 한다는 것입니다.
public struct DX_CAP
{
public ushort c1;
public ushort c2;
public ushort c3;
};
[DllImport("...")]
public static extern int fnStructTest(ref DX_CAP cap);
해답 1: IntPtr 사용
일단, Null이 가능하도록 하기 위해서는 ref DX_CAP에 대한 처리를 IntPtr로 하는 수 밖에는 없습니다. 결국 Interop이 다음과 같이 지정되어야 한다는 것이고.
[DllImport("...")]
public static extern void fnStructTest(IntPtr cap);
그다음, DX_CAP은 IntPtr과의 변환을 편하게 할 수 있도록 class로 정의합니다.
[StructLayout(LayoutKind.Sequential)]
public class DXC_CAP
{
public ushort ca_nbrdna;
public ushort ca_stdely;
public ushort ca_cnosig;
};
그럼, Null이 넘겨져야 할 경우에는 다음과 같이 간단하게 호출이 가능하겠고,
fnStructTest(IntPtr.Zero);
값을 넘겨 받아야 할 경우에는, fnStructTest를 호출할 때는 IntPtr로 변환해 주고, 호출 후에는 다시 IntPtr로부터 값을 복원해 주면 됩니다. 이를 코드로 정리하면 다음과 같습니다.
DXC_CAP inst2 = new DXC_CAP();
int marshalSize = 0;
unsafe
{
marshalSize = sizeof(DX_CAP);
}
IntPtr ptrData = Marshal.AllocCoTaskMem(marshalSize);
// 클래스 값 ==> IntPtr
Marshal.StructureToPtr(inst2, ptrData, false);
// 호출하고,
fnStructTest(ptrData);
// 다시 ptrData로부터 클래스 값 복원
Marshal.PtrToStructure(ptrData, inst2);
// 메모리 해제
Marshal.FreeCoTaskMem(ptrData);
근데... 한 눈에 봐도... 너무 불편합니다. 좀 개선할 필요가 있어보입니다. ^^
해답 2: 포인터 사용
다시 생각해서. 어쨌든 NULL이 가능하도록 해야 하니, unsafe 쪽으로 생각해 보는 것도 답이 될 수 있습니다.
코드를 정리해서, 다음과 같이 아예 구조체를 받는 포인터를 정의해주는 것입니다.
public struct DX_CAP
{
public ushort ca_nbrdna;
public ushort ca_stdely;
public ushort ca_cnosig;
};
[DllImport("TestNativeAPI.dll")]
unsafe public static extern void fnStructTest(DX_CAP *cap);
그럼, 게임 끝이군요. ^^
NULL을 넘겨주어야 할 때는 다음과 같이 해주면 되고,
unsafe { fnStructTest(null); }
값을 전달받아야 할 때도 역시 unsafe와 포인터 구문을 사용해 주면 됩니다.
DX_CAP inst;
unsafe { fnStructTest(&inst); }
unsafe와 포인터를 써야 하는 것에 대한 부담감이 있는데, 어느 쪽이든 선택을 해야 합니다. 몇 개 안되는 경우라면 그냥 해당 구조체 인자를 IntPtr로 받아들이는 P/Invoke 메서드를 중복 정의해주는 것도 답이겠지요.
***
첨부된 프로젝트는 예제 코드입니다. ***
[이 토픽에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]