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 값
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
댓글