C# Marshal : 데이터 변환
대부분에 있어 Managed 의 string 을 Unmanaged 의 CString 이나 LPSTR 로 변환하는 경우가 많았는데 오늘 다른 타입으로 변경해야 되는 경우가 생겼다... 시간좀 많이 소비한것 같다..
■ 윈도우 핸들(HWND) 변환
Win32API나 MFC를 보면 함수 파라미터로 윈도우 핸들 즉, HWND 를 많이 사용한다. Managed Code 에서 HWND에 대응하는 타입은 IntPtr 이다.
HWND ↔ IntPtr
IntPtr 은 void * 또는 핸들타입에 사용된다.
호출하고자 하는 함수 선언
Managed Code delegate
[UnmanagedFunctionPointer(CallingConvention.Cdel)]
private delegate void deleFunc(IntPtr hWnd);
참고로 Control을 상속받는 클래스에서는 Handle 프로퍼티로 쉽게 핸들값을 얻을 수 있다. Handle 프로퍼티는 컨트롤 창의 핸들값을 IntPtr로 리턴한다.
[BrowsableAttribute(false)]public IntPtr Handle { get; }
■ out 타입 포인터 변수 전달
Unmanaged Code 의 함수가 인수로 DWORD * 타입을 갖으며 해당 포인터 변수에 값을 저장한다. 따라서 이 함수를 호출하고 난 뒤에 포인터 변수에 저장된 값을 조사해서 사용하는 경우이다.
호출하고자 하는 함수 선언
void Func(DWORD *pdwListenPort);
Func 함수 호출결과 pdwListenPort 포인터 변수에 값을 얻게 된다. 즉, out 속성.
Managed Code delegate
[UnmanagedFunctionPointer(CallingConvention.Cdel)]
private delegate void deleFunc(out IntPtr pdwListenPort);
마샬링에서 void * 는 IntPtr 로 매칭된다.
void * ↔ IntPtr
DWORD * 이므로 일단 IntPtr 을 사용하였고, 메소드 결과 포인터 변수로 값을 얻으므로 out 키워드를 사용하였다.
IntPtr 은 void * 이므로 DWORD * 형식의 데이터를 얻기 위해선 적절한 타입 캐스팅을 해주면 된다.
IntPtr 변수의 메소드로 To- 관련 메소드를 사용하면 쉽게 변환이 가능하다. 원래는 DWORD 는 32bit Unsigned int 타입이다. 그렇다면 Managed Code쪽에서는 uint 또는 UInt32 타입으로 변환해 주어야 한다. 올바르지 않지만 ToInt32() 메소드를 이용하여 변환해 주었다.
DWORD * (out 속성의 포인터 변수) ↔ out IntPtr
// deleFunc Func 에 연결
IntPtr value = IntPtr.Zero;
Func(out value);
Int32 tmp = 0;
tmp = value.ToInt32();
■ in 타입 포인터 변수 변환
C++ 에서 포인터 변수는 기본적으로 값을 읽고 쓸 수 있다. 물론 const 키워드를 통해 읽기만 가능하게 할 수는 있다. 이렇게 Managed Code 쪽에서 포인터 변수값을 읽고 쓸 수 있게 넘기는 법은 아직 구현해보지 못했다...
포인터 변수를 전달하여 값을 전달 받는 것은 위에 방법을 사용하면 되고 (■ out 타입 포인터 변수 전달),
포인터 변수의 값을 저장하여 전달하는 방법을 알아보자.
호출하고자 하는 함수 선언
void Func(DWORD *dwPort);
Managed Code delegate
[UnmanagedFunctionPointer(CallingConvention.Cdel)]
private delegate void deleFunc([MarshalAs(UnmanagedType.AsAny)] Object pdwPort);
우선 이 변환은 IntPtr 타입을 사용하지 않았다. MarshalAs 프로퍼티를 사용하였으며, AsAny 를 사용하였다.
여기서 AsAny 설명은 다음과 같다.
런타임에서 개체의 형식을 결정하고 해당 형식으로 개체를 마샬링하는 동적 형식입니다. 플랫폼 호출 메서드에만 유효합니다.
사용 예
uint port = 9100;
Func(port);
즉, uint 값이 런타임시에 적잘하게 포인터를 만들고 port 값을 그 포인터 변수에 저장하여 Unmananged 코드에 전달하게 된다.
■ in/out 타입 포인터 변수 변환
왠만한 경우 위의 in 또는 out 타입 포인터 변수 변환으로 사용할 수 있는데 in, out 타입의 포인터 변수를 사용해야 할 때 어떻게 해야 할까? 아.. 근데 이 마샬링 변환을 매번 사용하는게 아니라 간혹가다 사용하니 할때마다 새롭다...
호출하고자 하는 함수 선언
void Func(DWORD *dwPort);
dwPort 포인터 변수에 값을 설정하여 Func함수에 전달한다. Func함수 호출 후 dwPort에 새로운 값이 저장된다.
Managed Code delegate
[UnmanagedFunctionPointer(CallingConvention.Cdel)]
private delegate void deleFunc(ref uint pdwPort);
DWORD 는 Unsigned int 32bit 타입이브로 C#의 uint 별칭을 사용한다.
uint portNum = 0;
Func(ref portNum);
Func함수 호출 후 새로운 값이 portNum에 저장되어 있다.
■ LPCTSTR
LPCTSTR ↔ [MarshalAs(UnmanagedType.LPWStr] string
참조)
@