Win32 API 사용하기
C#에서 Win32 API 사용하기
개요
Win32 API를 불러올 때, 함수의 명칭, 인자, 리턴 값을 가지고 불러오게 되어 있다. 하지만, C#에서 타입들이 모두 객체(Object)의 형식이며, 일반적인 C 의 데이터 형과 상이한 모양을 가진다. 이러한 문제들을 해결할 수 있는 것이 PInvoke 기능이다.
PInvoke( Platform Invocation Service)는 관리화 코드에서 비관리화 코드를 호출할 방법을 제공한다. 일반적인 용도는 Win32 API의 호출을 위해 사용한다.
namespace PinvokeExample { using System; using System.Runtime.InteropServices; // 반드시 입력해야 한다. public class Win32 { [DllImport("user32.dll")] public static extern int FindWindow(string a, string b);
} } |
위 예제는 FindWindow라는 user32.dll의 C함수를 사용하는 모습을 보여주고 있다. 실제 FindWindow의 선언은 다음과 같다.
HWND FindWindow(LPCSTR swClassName, LPCSTR swTitle);
HWND는 윈도우 핸들을 표현하는 32비트 정수 이므로, int형으로 치환되고 LPCSTR 형은 NULL로 끝나는 문자열을 표현한다. 이때 PInvoke는 string을 자동으로 LPCSTR로 치환해 주는 역할을 하게 된다.
이 문서에서는 이처럼 Win32 API 함수의 여러 유형들을 어떻게 C#에서 사용 할 것인지에 대하여 알아보자.
WIN32 데이터형의 치환
Win32 API에서 일반적으로 사용하고 있는 데이터형은 모두 C#의 데이터 형으로 치환될 수 있다.
WIN32 | 비관리C데이타타입 | C# |
HANDLE | int | int |
BYTE | unsigned char | byte |
SHORT | short | short |
WORD | unsigned short | ushort |
INT | int | int |
UINT | unsigned int | uint 또는 int |
LONG | long | int |
BOOL | long | int |
DWORD | unsigned long | uint |
ULONG | unsigned long | uint |
CHAR | char | char |
LPSTR | char* | string 또는 StringBuilder |
LPCSTR | const char* | string |
LPWSTR | wchar_t* | string 또는 StringBuilder |
LPCWSTR | const wchar_t* | string |
FLOAT | float | float |
DOUBLE | double | double |
| char * | char[] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Structure 의 전달
예를 들어 POINT 형의 경우,
typedef struct t_Point { int x; int y; } POINT; |
이것은 기본적으로 다음과 같이 선언될 수 있다.
[순차적]
[StructLayout(LayoutKind.Sequential)]
public struct Point {
public int x
public int y;
}
[명시적]
[StructLayout(LayoutKind.Explicit)]
public struct Point {
[FieldOffset(0)] public int x;
[FieldOffset(4)] public int y;
}
일차적으로 할당되는 메모리 레이아웃이 동일하다면, C#에서 바로 받아 들이 수 있다.
// BOOL SetWindowPos(POINT pos); 이런 함수가 있다고 가정하면 ^^
[DllImport ("user32.dll")]
public static extern bool SetWindowPos(Point pos);
사용할 함수 이름 바꾸기
여기서 함수의 이름을 바꿔서 사용하고 싶다면 다음과 같이 변경하면 된다.
// BOOL SetWindowPos(POINT pos);
[DllImport ("user32.dll", EntryPoint = "SetWindowPos")]
public static extern bool ShowAt(Point pos);레퍼런스형 전달하기
LPPOINT형은 POINT의 포인터 형이므로 ref Point와 같이 사용 할 수 있다. 실제 사용하는 형식은 다음과 같다.
C 언어의 포인터의 경우 레퍼런스로 사용하려고 하면, ref 키워드를 사용하는 방법이 있다.
// BOOL SetWindowPos(HWND hWnd, LPRECT lpRect);
[DllImport("user32.dll")]
public static extern bool SetWindowPos(int hWnd, ref Rect lpRect);
Out형 함수 인자 사용하기
MSDN 같은곳에서 함수의 선언을 살펴보면 다음과 같은 형식의 함수를 볼 수 있을 것이다. 이러한 형식은 레퍼런스 형으로 결과를 함수의 인자에 보내겠다는 말이다. 이러한 형식은 Win32 API에서 많이 쓰이고 있고, 포인터를 사용하므로, 많은 주의를 기울여야 한다.
BOOL GetWindowRect(
HWND hWnd, // handle to window
LPRECT lpRect // window coordinates
);
Parameters
hWnd
[in] Handle to the window.
lpRect
[out] Pointer to a RECT structure that receives the screen coordinates of the upper-left and lower-right corners of the window.
여기서 LPRECT는 앞 절에서 설명한 Structure의 전달을 참고하여 치환 될 수 있다.
여기서 lpRect는 RECT의 포인터이며, GetWindowRect 함수 내에서 이 포인터에 직접 값을 쓰게 되어 있다. 즉 이 포인터는 값을 기록하기 위한 인자이지, 값을 전달하기 위한 인자는 아닌 것이다. 이것은 또 다른 C# 레퍼런스 연산자인 out 키워드를 사용하여 쉽게 해결 할 수 있다.
public static extern bool GetwindowRect(int hWnd, out Rect lpRect);
실제 사용하는 모습은 다음과 같다.
public static extern bool GetWindowRect(int hWnd, out Rect lpRect);
public static void UseFunction() {
Rect _rect; // 값을 대입하지 않아도 된다.
Win32.GetWindowRect(hwnd, out _rect);
}
참고로 ref 키워드는 입력과 출력 둘 다 사용 할 수 있다. 그러나 ref를 사용하는 변수가 값이 설정되어 있다는 가정을 하고 있으므로, 이전에 반드시 어떠한 값을 입력해야 한다.
실제 사용 예는 다음과 같다.
public static extern bool GetWindowRect(int hWnd, ref Rect lpRect);
public static void UseFunction() {
Rect _rect = new Rect(); // 꼭 값을 대입해야 한다.
_rect.top = 20; _rect.left = 30;
_rect.bottom = 50; _rect.right = 60;
Win32.GetWindowRect(hwnd, ref _rect);
}
여기서 잠깐
대중없이 Rect라는 구조체가 나오는데 이는 API에서 RECT형을 C#으로 바꾸어 사용하는 structure이다. 앞의 예제들은 다음과 같은 선언을 하였다고 가정한다.
[StructLayout(LayoutKind.Explicit)] public struct Point { [FieldOffset(0)] public int top [FieldOffset(4)] public int left [FieldOffset(8)] public int bottom; [FieldOffset(12)] public int right; } |
CALLBACK 함수의 선언
C 언어에서 콜백 함수는 함수 포인터로 존재하게 된다. 이것은 함수 인스턴스의 포인터로, 함수 자체를 전달하게 되는 방식이다. 대표적으로 사용되는 부분은 EnumWindows 함수이다.
// BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARMAM IParam) |
이 함수는 현재 열려 있는 모든 윈도우 핸들을 열거하기 위한 함수로 실제 처리하는 부분은 함수 포인터, 즉 콜백함수인 lpEnumFunc에서 처리하게 되어 있다. WNDENUMPROC 타입의 선언은 다음과 같다.
// typedef BOOL (CALLBACK* WNDENUMPROC)(HWND, LPARAM);
public delegate bool Callback(int hWnd, long lParam);이러한 콜백 함수 역할을 하는 C#의 프리미티브는 delegate이다. CALLBACK은 delegate로 지환된다.
결과적으로 다음과 같이 사용하게 된다.
namespace ada.appshare
{
public delegate bool Callback(int hwnd, int lParam);
internal class Win32
{
internal static extern int EnumWindows(CallBack x, int y);
[DllImport("user32.dll")]
public static bool EnumWindowsCallback
(int hWnd, int lParam)
{
System.Console.WriteLine(""+ hWnd);
return true;
}
}
public static void Main(String []args)
{
Win32.Callback call=
new Win32.Callback(Win32.EnumWindowsCallback);
Win32.EnumWindows(call, 0);
}
}
Win32추가설명
DllImport 애트리뷰트는 DLL익스포트 함수를 호출하는데 필요한 정보를 지정하는
몇가지 중 요한 필드 제공
[DllImport("DLL명", CharSet=CharSet열거형값)]
ChartSet
이필드에 CharSet.UniCode값이 지정되면 모든 문자열 인수는 유니코드문자로 변
환됩니다. 윈도우 98에서는 기본적으로 ANSI 문자를 사용합니다. 윈도우 2000에
서는 CharSet.Unicode 기본입니다. 지정하지 않으면 기본값으로 사용합니다.
MarshalAs 애트리뷰트
관리코드와 비관리코드사이에 메서드 호출이 이루어 질 때 , 이들 사이의
데이터에 대해서 마샬링이 발생합니다. 여기에는 인수로 넘겨주는 값 뿐만이
아니라 반환되는 값도 포함합 니다. 마샬링 방법을 지정하기 위해서
사용합니다. MashalAs 애트리뷰트는 다른 타입의 데이터를 마샬링 하는
방법을 결정합니다.
[MarshalAs(UnmanagedType열거형값)]
멤버 이름 | 설명 |
AnsiBStr | 싱글 바이트 길이 접두사가 있는 ANSI 문자열 |
AsAny | 런타임에서 개체의 Type을 결정하고 해당 Type으로 개체를 마샬링하는 동적 형식 |
Bool | 4바이트 부울 값(true != 0, false = 0) |
BStr | 더블바이트 길이 접두사가 있는 유니코드 문자열 |
ByValArray | MarshalAsAttribute.Value가 ByValArray로 설정된 경우, SizeConst는 배열에 있는 요소의 숫자를 나타내도록 설정되어야 합니다. ArraySubType 필드는 문자열 형식 사이에서 구별을 지을 필요가 있는 경우 배열 요소의 UnmanagedType을 포함할 수도 있습니다. 또한 이 UnmanagedType은 구조의 필드로 나타나는 배열에서만 사용될 수 있습니다. |
ByValTStr | 구조 내에 나타나는 인라인 고정 길이 문자 배열에 사용됩니다. 포함하는 구조체에 적용된 StructLayoutAttribute의 CharSet 인수에 의해 결정된 ByValTStr와 함께 사용된 문자 형식입니다. |
Currency | Decimal 대신 COM 통화 형식으로 10진 값을 마샬링하기 위해 System.Decimal에서 사용됩니다. |
CustomMarshaler | MarshalAsAttribute.MarshalType 또는 MarshalAsAttribute.MarshalTypeRef와 함께 사용될 때 사용자 지정 마샬러 클래스를 지정합니다. MarshalCookie 필드는 사용자 지정 마샬러에게 추가 정보를 전달하는 데 사용할 수 있습니다. |
Error | I4 또는 U4와 관련된 기본 형식은 매개 변수가 내보낸 형식 라이브러리에 있는 HRESULT로 내보내지도록 합니다. |
FunctionPtr | 함수 포인터 |
I1 | 1바이트 부호 있는 정수 |
I2 | 2바이트 부호 있는 정수 |
I4 | 4바이트 부호 있는 정수 |
I8 | 8바이트 부호 있는 정수 |
IDispatch | COM IDispatch 포인터 |
Interface | COM 인터페이스 포인터. 인터페이스의 Guid는 클래스 메타데이터에서 얻을 수 있습니다. |
IUnknown | COM IUnknown 포인터 |
LPArray | A C 스타일 배열입니다. 관리되는 배열에서 관리되지 않는 배열로 마샬링하면 배열의 길이는 관리되는 배열의 길이에 의해 결정됩니다. 관리되는 배열에서 관리되지 않는 배열로 마샬링하면 배열의 길이는 MarshalAsAttribute.SizeConst와 MarshalAsAttribute.SizeParamIndex 필드에 의해 결정됩니다. 문자열 형식 사이의 구별이 필요할 경우 배열에 있는 요소의 관리되지 않는 형식이 따라옵니다. |
LPStr | 싱글 바이트 ANSI 문자열 |
LPStruct | C 스타일 구조에 대한 포인터. 형식이 지정된 관리되는 클래스를 마샬링하는 데 사용됩니다. |
LPTStr | 플랫폼 종속 문자열, 즉 Windows 98에서는 ANSI, Windows NT에서는 유니코드입니다. LPTStr 형식의 문자열을 내보낼 수 없으므로 이 값은 플랫폼 호출에 대해서만 지원되고 COM interop에 대해서는 지원되지 않습니다. |
LPWStr | 더블바이트 유니코드 문자열 |
R4 | 4바이트 부동 소수점 수 |
R8 | 8바이트 부동 소수점 수 |
SafeArray | SafeArray는 형식, 차수, 관련된 배열 데이터의 범위를 전달하는 자동 기술 배열입니다. |
Struct | 형식이 지정된 관리되는 클래스와 값 형식을 마샬링하는 데 사용되는 C 스타일 구조 |
SysInt | 플랫폼에 종속적인 부호 있는 정수. 32비트 Windows에서는 4바이트, 64비트 Windows에서는 8바이트입니다. |
SysUInt | 하드웨어에 종속적인 부호 없는 정수 |
TBStr | 길이 접두사가 있는 플랫폼에 종속적인 Char 문자열. Windows 98에서는 ANSI, Windows NT에서는 유니코드입니다. |
U1 | 1바이트 부호 없는 정수 |
U2 | 2바이트 부호 없는 정수 |
U4 | 4바이트 부호 없는 정수 |
U8 | 8바이트 부호 없는 정수 |
VariantBool | 2바이트 OLE 정의 부울 값(true != -1, false = 0) |
VBByRefStr | Visual Basic에서 관리되지 않는 코드의 문자열을 변경할 수 있으며 결과가 관리되는 코드에 반영되게 합니다. |
예제
[C#]
using System;
using System.Runtime.InteropServices;
namespace MyModule
{
// If you do not have a type library for an interface
// you can redeclare it using ComImportAttribute.
// This is how the interface would look in an idl file.
//[
//object,
//uuid("73EB4AF8-BE9C-4b49-B3A4-24F4FF657B26"),
//dual, helpstring("IMyStorage Interface"),
//pointer_default(unique)
//]
//interface IMyStorage : IDispatch
//{
// [id(1)]
// HRESULT GetItem([in] BSTR bstrName, [out, retval] IDispatch ** ppItem);
// [id(2)]
// HRESULT GetItems([in] BSTR bstrLocation, [out] SAFEARRAY(VARIANT)* pItems);
// [id(3)]
// HRESULT GetItemDescriptions([in] BSTR bstrLocation, [out] SAFEARRAY(VARIANT) ** ppItems);
// [id(4), propget]
// HRESULT get_IsEmpty([out, retval] BOOL * pfEmpty);
//};
// This is the managed declaration.
[ComImport]
[Guid("73EB4AF8-BE9C-4b49-B3A4-24F4FF657B26")]
public interface IMyStorage
{
[DispId(1)]
[return : MarshalAs( UnmanagedType.Interface )]
Object GetItem( [In, MarshalAs( UnmanagedType.BStr )] String bstrName );
[DispId(2)]
void GetItems( [In, MarshalAs( UnmanagedType.BStr )] String bstrLocation,
[Out, MarshalAs( UnmanagedType.SafeArray,
SafeArraySubType = VarEnum.VT_VARIANT )] out Object[] Items );
[DispId(3)]
void GetItemDescriptions( [In] String bstrLocation,
[In, Out, MarshalAs( UnmanagedType.SafeArray )] ref Object[] varDescriptions );
bool IsEmpty
{
[DispId(4)]
[return : MarshalAs( UnmanagedType.VariantBool )]
get;
}
}
}
고정길이 문자열 버퍼의 사용
경우에 딸사서는 고정길이 문자열 버퍼를 DLL 익스포트 함수의 인수에 넘겨주어야 하는 경
우가 있습니다. 예를 들어 윈도우 운영체제를 설치한 시스템 디렉토리의 경로명을 구하는
GetSystemDirectory API함수는 다음과 같은 원형을 갖습니다.
UNIT GetSystemDerictory (
LPSTR lpBuffer, // 경로병을 저장할 버퍼
UINT uSize); // 디렉토리 버퍼의 크기
이때 GetSystemDirecotry 함수의 첫 번째 인수에는 256 바이트 크리글 갖는 시스템 디렉토
리 경로명을 저장할 버퍼가 지정되어야 합니다. 이때 우리는 이 인수에 StringBuffer클래스
타입을 지정할 수 있습니다.
[ DllImport("kernel32.dll", CharSet=CharSet.Auto)]
public static extern int GetSystemDirectory(
[MarshalAs(UnmanagedType.LPWstr, SizeConst=256)]
StringBuilder buffer,
int size);
우리는 이 함수를 다음과 같이 호출할 수 있습니다.
SrgingBuilder buffer = new StringBuilder(256);
Win32API.GEtSystemDirectory(buffer, buffer.Capacity);
Console.WriteLine(buffer);
'C#' 카테고리의 다른 글
해쉬테이블 (Hashtable) (0) | 2018.11.08 |
---|---|
DLL을 C#에서 사용하기 (0) | 2018.10.25 |
[MFC] LPSTR,LPCSTR,LPCTSTR .. 과연 무엇인가? (0) | 2018.10.23 |
opos visual studio 적용.... (0) | 2018.10.15 |
C#으로 LS PLC와 RS232C 통신하기 (0) | 2018.04.05 |