__declspec( dllimport ) void SetTimeOut(int IN nTimeOut);
이 형태는 int 값을 DLL내부 함수에 넘겨주는 것이다.
C#에서 호출 할때는 다음과 같다.
[DllImport("DLL명")]
private static extern void SetTimeOut(int nTimeOut);
C++이나 C#이나 Type이 동일하므로 크게 볼 내용은 없다. 똑같은 변수 타입으로 해주면 된다.
다음은 조금(?) 어렵다.
__declspec( dllimport ) int Connect(
TCHAR IN *pszIp,
int IN nPort,
void OUT **ppContext
);
결과값으로 int형을 리턴하고 문자열과 숫자를 입력 받고 void **를 받는 형태이다.
여기서 주의 할것은 eVC는 VS6와는 다르게 유니코드가 기본값이라는 것이다.
Connect(이 함수는 서버에 접속한다고 가정한 함수)일경우 아이피와 포트 그리고 그 핸들의 포인터를 받아 처리를 많이 하는 경우를 가정한것인데..(아닌가?^^;)
C나 C++은 C#에 비하면 변수 타입에 대단히 관대하기 때문에 void *형을 개인적으로 많이 사용을 햇는데 C#에서는 그것이 발목을 많이 잡았다.
특히 **를 받아서 DLL내부에서 new나 malloc을 하는 경우와 문자열값을 UniCode에서 AnsiCode로 변경하는 경우는 eVC에서 부터 많이 접해봤을것이다.
[DllImport("DLL명", CharSet = CharSet.Unicode )]
private static extern int Connect(
[MarshalAs(UnmanagedType.LPWStr )] System.Text.StringBuilder szip
, int nport
, ref IntPtr pContext
);
TimeOut함수에 비하면 조금 복잡하다.
서버 아이피 값을 넘겨주는 경우 eVC에서 _T , L , _TEXT Macro를 사용했을 것이다.
정 확한 이유는 모르겠지만, C#으로 Win32가 아닌 Mobile프로젝트를 구성하는 경우 당연히 Unicode가 기본이 될것이므로 string Type을 쓰더라도 상관 없을것이라 생각했는데 ... TCHAR *로 되어 있는 경우 char *와는 다른게 UnmanagedType을 정확히 명시 해줘야 한다는 것이다.
StringBuilder의 경우 string으로 해도 무관하지만 string보다 장점이 많다고 하여 써본것이다.
StringBuilder는 string으로 대체해도 된다.
UnmanagedType은 다음과 같다.
Bool : 4바이트 불리언값
ByValArray : 고정길이 배열
FunctionPtr : 함수 포인터
I1 : 1바이트 부호화 정수
I2 : 2바이트 부호화 정수
I4 : 4바이트 부호화 정수
I8 : 8바이트 부호화 정수
LPStr : Ansi문자열
LPStruct : C언어 구조체 포인터
LPTStr : 플랫폼 독립적인 문자열. Windows98계열은 Ansi문자열 Windows2000계열은 Unicode 문자열
LPVoid : 타입이 없는 4바이트 포인터
LPWStr : 유니코드 문자열
R4 : 4바이트 부동 소숫점
R8 : 8바이트 부동 소숫점
Struct : C언어 구조체
SysInt : 플랫폼 독립 부동화 정수. 32비트 OS의 경우 4바이트 64비트 OS일 경우 8바이트
U1 : 1바이트 비부호화 정수
U2 : 2바이트 비부호화 정수
U4 : 4바이트 비부호화 정수
U8 : 8바이트 비부호화 정수
첫번째 인자가 TCHAR * 의 문자열을 받으므로 UnmanagedType을 LPWStr로 선언을 해주었다.
두번째 인자 숫자는 TimeOut과 마찬가지로 int로 쉽게 해결하면 된다.
세번째 인자는 void **를 받아서 DLL내부에서 malloc하는 경우 void **로 동일 하게 명시해줘도 상관 없지만, C#에서 void *의 &를 넘겨주기가 좀 애매하다.
C#에서 void *를 대신하는(맞는지 정확히는 모르겠다...이놈의 모래성 ㅠㅠ) IntPtr과 Call Reference의 기호 ref로 해결을 보았다.
구조체에 단순 형식이 포함된 경우에는 개체를 네이티브 함수에 전달할 수 있으며, 이 경우 네이티브 루틴은 .NET Compact Framework에서 구조체를 압축하는 방식을 따라야 합니다.
매개 변수, 구조체에 대한 포인터를 하나씩 사용하는 DoRequest라는 네이티브 함수가 있다고 가정합니다. 구조체는 다음과 같은 필드 두 개를 정의합니다.
NULL로 끝나는 ANSI 문자열이 포함된 문자 배열
정수
다음은 C++의 구조체입니다.
typedef struct { char *RequestName, int Count, } ALERT_REQUEST; int DoAlertRequest(ALERT_REQUEST *pRequest);
C#에서 이 구조체의 관리되는 버전은 다음과 같습니다.
struct AlertRequest { String RequestName; int Count; } class AlertClass { [DLLImport("ALERT.DLL", EntryPoint="DoAlertRequest", CharSet=CharacterSet.Ansi)] public static extern int DoRequest(ref AlertRequest Req); }
DllImportAttribute 특성은 네이티브 메서드 호출의 메타데이터를 나타내며, DoRequest 메서드의 위치(ALERT.DLL), 메서드의 이름(DoAllertRequest) 및 문자열을 ANSI로 마샬링(관리 코드의 모든 문자열은 유니코드임)해야 함을 공용 언어 런타임에 알립니다.
.NET Framework에서 C++를 사용하는 경우 이 메서드에 대한 호출은 다음과 같습니다.
AlertRequest Request = new AlertRequest() Request.RequestName = "beep"; Request.Count = 10; AlertClass.DoRequest(ref Request);
.NET Framework의 공용 언어 런타임에서는 DoRequest 호출을 발견하면 ALERT.DLL을 동적으로 로드하고DoAlertRequest의 주소를 가져온 다음 필요에 따라 문자열을 변환하여 구조체의 관리되지 않는 버전을 빌드합니다. 그런 후에 이 구조체에 포인터를 푸시하고 DoAlertRequest를 호출합니다. 이 구조체에는 포함 String 개체가 들어 있기 때문에 구조체에 대한 포인터를 전달할 수 없습니다.
이러한 제약 조건을 고려할 때 네이티브 DoRequest 메서드를 직접 호출할 수 없으며 썽킹 호출을 대신 사용하여 DoRequest를 래핑해야 합니다. 다음은 C# 코드 예제입니다.
class AlertClass { [DllImport("ALERT.DLL", EntryPoint="DoAlertRequestThunk")] private static extern int DoRequestThunk(String RequestName, int Count); public static int DoRequest(ref AlertRequst Req) { return DoRequestThunk(Req.RequestName, Req.Count); } }
AlertClass에는 .NET Framework에서 상응하는 클래스와 시그니처가 같은 메서드가 포함됩니다. .NET Compact Framework의DoRequest는 전용 네이티브 루틴을 호출하는 관리되는 루틴입니다. 위의 코드 예제에서 구조체 내의 String 개체는 마샬링될 수 없으므로 네이티브 루틴에 대한 호출에 대해 구조체의 각 필드가 별도의 인수로 분리됩니다. 썽크는 네이티브 DoRequest를 래핑하며 구조체의 관리되지 않는 버전을 빌드하고 문자열 변환을 수행합니다(아래의 C++ 코드 참조).
int DoRequestThunk(wchar_t *RequestNameW, int Count) { ALERT_REQUEST Req; int ReturnCode; // CreateAnsiFromUnicodeString allocates and builds an ANSI // version of the Unicode string. Req.RequestName = CreateAnsiFromUnicodeString(RequestNameW); if (Req.RequestName == NULL) Return 0; Req.Count = Count; // This is the native DoRequest, not to be confused // with the managed method of the same name. ReturnCode = DoAlertRequest(&Req); free(Req.RequestName) return ReturnCode; }