DLL을 C#에서 사용하기

C# 2018. 10. 25. 11:19

__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로 해결을 보았다.


문자열이 포함된 구조체는 어찌 보내나???

char szMsg[128];
int nCnt;

이런 식으로 선언된 구조체 말이다...!!! 


못보낸다... ㅜㅡ

보내고 싶으면 정말 이렇게 해야 한다.
쌩 노가다!!! 


.NET Framework 개발자 가이드
플랫폼 호출 래퍼 예제

구조체에 단순 형식이 포함된 경우에는 개체를 네이티브 함수에 전달할 수 있으며, 이 경우 네이티브 루틴은 .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;
}

참조 : http://www.heart4u.co.kr/tblog/194


체크 할 것

 .Net 2.0 서비스 팩 버전 확인


http://bestofsky.com/48



0. (함수 인자에 참조형을 사용 할 경우?,) 

   해당 소스 코드 프로젝트 속성에서 '빌드 > 안전하지 않은 코드 허용'에 체크한 후,

   unsafe { } 구문으로 감싸준다.

http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=17&MAEULNo=8&no=125908&ref=125908



1. 선언한 배열의 크기보다 더 큰 데이터가 전달되지는 않았는지 확인한다.

http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=17&MAEULNo=8&no=61219&ref=61195



2. C#에서 생성하지 않았거나 크기가 명확하지 않은 포인터를 직접 참조하지 않았는지 확인한다.

http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=17&MAEULNo=8&no=150988&ref=150961







'C#' 카테고리의 다른 글

Label 투명하게!  (0) 2018.12.10
해쉬테이블 (Hashtable)  (0) 2018.11.08
c#에서 비관리코드를 호출하는 방법  (0) 2018.10.24
[MFC] LPSTR,LPCSTR,LPCTSTR .. 과연 무엇인가?  (0) 2018.10.23
opos visual studio 적용....  (0) 2018.10.15
블로그 이미지

벵거빠돌이

,