본문 바로가기
Programming/WPF

C# 마샬링 - 구조체

by 곰네Zip 2022. 7. 21.

C#에서 C++의 DLL을 호출할 경우 데이터를 주고받아야 한다.

그렇게 하기 위해서 여러가지 방법이 있지만, 우선 DLL을 만든 후, DllImport를 사용하여 전달하게 구성되었기에. 그에 맞는 방법으로..

 

C#에서

-메소드 선언

 [DllImport("$DLL파일명", CallingConvention=CallingConvention.Cdecl, Charset=CharSet.Ansi)]
 public static extern $반환타입 $메소드명($params)

위와 같이 선언하면 된다. 콜링컨벤션은 굳이 안해도 되는듯. charset은 나는 MBCS를 사용하느라 그냥 명시적으로 선언해 주었다.

 

C++에서

 #define DLLAPI extern "C" __declspec(dllexport)
 DLLAPI $반환타입 $메소드명($params)

이렇게 설정해 주면 된다.

 

이러면 함수는 호출이 된다. 근데 데이터를 주고 받아야지.. 그러기 위해서는 데이터를 정렬해야 하고 그것이 마샬링.

 

데이터를 주고 받을 때.. 파라미터에 [MarshalAs(...)]을 붙여주면 된다. 어디까지나 지원되는 타입에 한해.

예를들면 이렇게 int를 넘겨준다고 하면..

public static extern void MyFunc([MarshalAs(UnmanagedType.I4)] int nCount); 

 

만약.. 저 int를 C++ DLL에서 값을 할당해주고 그걸 C#에서 가져오려면

public static extern void MyFunc([MarshalAs(UnmanagedType.I4)] out int nCount); 

 

C++에서는.. 각각

 DLLAPI void MyFunc(int nCount);
 DLLAPI void MyFunc(int* nCount);

이렇게하면 된다.

 cf). long을 전달할 경우 C++과 C# 동일하게 데이터 형태만 맞추면 된다. 단, MarshalAs(UnmanagedType.I4)가 아닌 I8로 해줄것.

 

* 구조체를 넘겨주기 (C++ <-> C#)

C++에 있는 구조체를 C#으로 넘겨주는것.. (단일구조체는 ref 구조체명

 

 - C#에서

[DllImport("MYDLL.DLL")]
public static extern void MyFunc( ref MyStruct result); 


[StructLayout(LayoutKind.Sequential)]
public struct MyStruct{
 [MarshalAs(UnmanagedType.I4)]
 int nData;
 [MarshalAs(UnmanagedType.LPStr)]
 string strData;
}

 

 - C++코드

동일한 구조체를 선언한다.

DLLAPI MyFunc(MyStruct* objStruct);


typedef struct _stMyST{
 int nData;
 LPSTR strData;
} MyStruct;

 

* 구조체 배열을 넘기기...

 - 구조체 배열은 조금 다르게 동작하더라. 물론 C#의 구조체를 C++로 전달하는것은 쉽다. 같은 데이터 구조체를 선언하고 넘기면 된다.

 

 -C#에서 (MyStruct를 그대로 사용)

 [DllImport("MyDll.dll")]
 public static extern void MyFunc(MyStruct[] list,[MarshalAs(UnmanagedType.I4)] int nCount);


func(){
 IList<MyStruct> objList;
 ...
 MyFunc(objList.ToArray(), objList.Count);
}  

 - C++에서

 DLLAPI MyFunc(MyStruct* pObj, int nCount){
   pObj[nIndex]; //nIndex는 nCount보다 작을것.
 } 

 

이제 반대로 C++에서 데이터를 채워서 넘겨주는것.. (MyStruct를 그대로 사용)

-  C#에서

 [DllImport("MyDll.dll")]
 public static extern void MyFunc(out IntPtr list,[MarshalAs(UnmanagedType.I4)] out int nCount);


func(){
   int nCount = 0;
   IntPtr info = IntPtr.Zero;
   MyFunc(out info, out nCount);
   int nSize = Marshal.SizeOf(typeof(MyStruct));
   for( int nIndex = 0; nIndex < nCount ; nIndex++){
       MyStruct str = (MyStruct)Marshal.PtrToStructure(info, typeof(MyStruct));
       info = (IntPtr)(info.ToInt32() + nSize);
   }
}

 

 

 - C++에서

 DLLAPI void MyFunc(MyStruct** pStruct,  int* nCount){
    list<MyStruct> items;
    ....
    *nCount = items.Count();
    *pStruct = new MyStruct[*nCount];
     int nIndex = 0;
     for(list<MyStruct>::iterator itr = item.begin(); itr != item.end() ; itr++){ 
       (*pStruct)[nIndex].nData = itr->nData;
       (*pStruct)[nIndex].strData = itr->strData;
       nIndex++;
     }
 }

 

 위와 같이 해주면 구조체 배열을 전달해 줄수 있다. C++에서는 (*pStruct)[index]임에 주의하고, C#에서는 IntPtr로 받아야 함에 주의.

 

 *! 구조체를 C++에서 정의 시, #pragma pack(1)을 선언해 준 경우 C#에서 마샬링 할 구조체를 선언해 줄 때..

[(StructLayout..., Pack=1)]

즉, Pack=1을 선언해 주어야 한다. 아니면.. 구조체 크기가 틀려져서 데이터를 잘못 읽어오고 크래시 날 수 있다.

 

* C#에서 어떤 형태로 줄세우기(마샬링..)할거다 했을 때.

데이터 타입은 여기서 확인 : 마소

 

UnmanagedType 열거형 (System.Runtime.InteropServices)

매개 변수나 필드를 비관리 코드로 마샬링하는 방법을 식별합니다.

docs.microsoft.com

아래는 몇가지만 요약 UnmanagedType 값

BStr 유니코드 문자열. C++에서 다른 모듈로 스트링 넘기고 받을 때, ANSI도 좋지만 유니코드가 좀 더 덜 귀찮더라. (물론 내부 변환은 별개의 귀찮음)
HString Windows런타임 문자열. String데이터형식에 사용 가능하단다. (!!! 위에꺼 잠시만.)
I1 1바이트 정수. C style bool. C++의 bool. __int8
Bool 4 byte부울값. Win32 BOOL. C++의 BOOL
I2 2바이트 정수. __int16
I4 4바이트 정수. int, long
I8 8바이트 정수. __int64
U4 부호없는 4바이트 정수. unsigned int, unsigned long
U8 부호없는 8바이트 정수. unsigned __int64
R4 4바이트 부동소수. float
R8 8바이트 부동소수. double

 

 만약 마샬링이 필요한 이유가 궁금하다면 아래 포스트 참고하셔요.

https://gomnezip.tistory.com/458

 

C#에서 마샬링이 필요한 이유

C#에서 마샬링을 하는 방법은 요기.. https://gomnezip.tistory.com/372 C# 마샬링 - 구조체 C#에서 C++의 DLL을 호출할 경우 데이터를 주고받아야 한다. 그렇게 하기 위해서 여러가지 방법이 있지만, 우선 DLL

gomnezip.tistory.com

 

반응형

댓글