출처: http://ljh131.tistory.com/45


C#에서 DLL의 Unmanaged function을 호출하려면 다음과 같은 방법으로 함수를 만들어야 합니다. 아래 글의 출처는 msdn입니다.

To consume exported DLL functions

1. Identify functions in DLLs.
Minimally, you must specify the name of the function and name of the DLL that contains it.

2. Create a class to hold DLL functions.
You can use an existing class, create an individual class for each unmanaged function, or create one class that contains a set of related unmanaged functions.

3. Create prototypes in managed code.
[C#] Use the DllImportAttribute to identify the DLL and function. Mark the method with the static and extern modifiers.

4. Call a DLL function.
Call the method on your managed class as you would any other managed method. Passing structures and implementing callback functions are special cases.

가장 일반적인 방법이죠. 좀 복잡할테니 예를 들어서 설명하겠습니다.

우리가 필요한 함수는 Win32API함수인 EnumDisplayDevice()라는 함수입니다. 함수의 프로토타입은 다음과 같습니다.

BOOL EnumDisplayDevices(
  LPCTSTR lpDevice,                // device name
  DWORD iDevNum,                   // display device
  PDISPLAY_DEVICE lpDisplayDevice, // device information
  DWORD dwFlags                    // reserved
);
이제 이 함수 프로토타입을 C#용으로 고쳐야 합니다.

가장 먼저 할 일은 함수가 있는 DLL파일입니다. 이건 MSDN을 통해 쉽게 찾을 수 있죠. User32.dll에 있다는 것이 바로 확인됩니다.

두번째는 본격적으로 함수의 프로토타입을 C#으로 만드는 과정입니다.

우선 첫번째 인자를 볼까요? 문자열을 나타내는 LPCTSTR입니다. long pointer to const string이죠. 이 타입을 어떻게 바꿔야 할까요?

여기서 반드시 참고해야 할 것이 MSDN의 Platform Invoke Data Types 표입니다.

http://msdn2.microsoft.com/en-us/library/ac7ay120(vs.80).aspx

이 표를 보면 Win32API나 C스타일 함수에서 사용되는 데이터 타입들에 대응되는 Managed class type들이 나와있습니다. 표를 보면서 C#에서의 파라매터 타입을 정해주면 됩니다. 표에 따르면 - LPCTSTR은 직접 나와있지 않지만 - 얼추 string에 대응됩니다.

마찬가지로 DWORD는 UInt32입니다. 문제는 세번째 인자인 PDISPLAY_DEVICE인데요, 구조체의 포인터죠.

아쉽게도 이 경우에 대해서는 MSDN에서 명확한 해답을 찾을 수 없었습니다. 하지만 제가 알아본 바로는 대략 구조체를 클래스로 변환한 뒤, In, Out Attribute을 결정해주면 됩니다.

구조체인 DISPLAY_DEVICE에 대응되는 클래스를 만들기 전에 프로토타입 부터 설정해봅시다. MSDN을 보면 이 파라매터는 out 타입입니다. 따라서 In과 Out Attribute를 넣어줍니다. 완성된 프로토타입은 다음과 같습니다.

  [DllImport("user32.dll")]
  public static extern bool EnumDisplayDevices(string lpDevice, UInt32 iDevNum,
             [In, Out] DisplayDevice lpDisplayDevice, UInt32 dwFlags);
이제 할일은 구조체 DISPLAY_DEVICE를 클래스로 변환하는 작업입니다. 구조체의 프로토타입을 봅시다.

typedef struct _DISPLAY_DEVICE {
  DWORD cb;
  TCHAR DeviceName[32];
  TCHAR DeviceString[128];
  DWORD StateFlags;
  TCHAR DeviceID[128];
  TCHAR DeviceKey[128];
} DISPLAY_DEVICE, *PDISPLAY_DEVICE;
여기서 문제는 배열이죠. C#의 배열은 C의 배열과 메모리 할당 방식이 다릅니다. 이 문제는 MarshalAs Attribute를 이용해서 해결할 수 있습니다.

완성된 클래스는 아래와 같습니다.

 [StructLayout(LayoutKind.Sequential)]
 public class DisplayDevice
 {
  public UInt32 cb;  
  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
  public string DeviceName;  
  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
  public string DeviceString;  
  public UInt32 StateFlags;  
  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
  public string DeviceID;  
  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
  public string DeviceKey;
 }
구조체가 선언된 순서대로 할당되는 것처럼 클래스 Attribute도

 [StructLayout(LayoutKind.Sequential)]

로 만들어서 구조체와 마찬가지로 순차적으로 변수를 할당한다고 선언을 해주면 됩니다.

드디어 프로토타입이 완성되었습니다!

하지만 실제로 함수를 호출하려면 아직 남은 것이 한가지 있습니다.

'Before calling EnumDisplayDevices, you must initialize the cb member of DISPLAY_DEVICE to the size, in bytes, of DISPLAY_DEVICE.'

함수를 호출하기 전에 구조체(여기서는 클래스)의 cb멤버를 구조체의 크기로 설정해야 한다는 것입니다. C에선 sizeof(DISPLAY_DEVICE)로 가능합니다. 하지만 C#에서는 어떻게 해야할까요?

물론 C#에도 sizeof키워드가 있습니다만 여기서는 동작하지 않습니다. 이 경우에도 마샬을 이용해야 하는데, 코드는 아래와 같습니다.

   DisplayDevice dd = new DisplayDevice();
   dd.cb = (UInt32)Marshal.SizeOf(dd);
이제 EnumDisplayDevices함수를 C#에서 호출할 수 있게 되었습니다.

기타 함수들에서 사용되는 상수들은 직접 헤더 파일에서 찾아서 넣어주면 됩니다.

자세한 설명이 필요하면 아래 링크를 참고하세요

http://www.canaware.com/default.aspx?page=ed51cde3-d979-4daf-afae-fa6192562ea9&article=cfd2e407-d20f-4dfc-8c34-e7d29c771b05

AND