在C#我們可以透過PInvoke(Platform Invocation Services)來access unmanaged DLL中的function, structs甚至是callbacks function。以下是一個簡單的例子,示範如何在C#中呼叫Windows DLL user32.dll。
MessageBox在user32.dll宣告長這樣:
MessageBox(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);
C#範例程式:
using System;
using System.Runtime.InteropServices; // For StructLayout, DllImport
class MsgBoxTest
{
[DllImport("user32.dll"]
static extern int MessageBox(IntPtr hWnd, String text, String caption, int type);
public static void Main()
{
MessageBox(IntPtr.Zero, "Text", "Caption", 0);
}
}
有三個地方要注意。DllImport定義function位於哪個dll中,static定義這是一個global method,extern則是告訴CLR此function是實做在外部。
看到這邊好像覺得P/Invoke沒什麼,繼續看下去就知道P/Invoke為什麼這麼讓人頭痛。
如果Native function的參數是struct該怎麼處理?讓我們看一個例子:
void GetSystemTime(LPSYSTEMTIME lpSystemTime);
typedef struct _SYSTEMTIME {
WORD wYear;
WORD wMonth;
WORD wDayOfWeek;
WORD wDay;
WORD wHour;
WORD wMinute;
WORD wSecond;
WORD wMilliseconds;
} SYSTEMTIME, *PSYSTEMTIME;
要呼叫這個function,我們必須定義一個C#的Class和C Struct有一樣的結構。
using System;
using System.Runtime.InteropServices;
[ StructLayout( LayoutKind.Sequential )]
public class SystemTime
{
public ushort year;
public ushort month;
public ushort weekday;
public ushort day;
public ushort hour;
public ushort minute;
public ushort second;
public ushort millisecond;
}
StructLayout告訴marshaler如何把每個C# class中的field對應到C Struct中,LayoutKind.Sequential表示每個field是根據pack-size aligned sequentially。預設的pack-size是8,也就是說所有的field都會aligned成8 bytes。我們可以在StructLayout中加入Pack attribute來修改pack-size。
[StructLayout(LayoutKind.Sequential, Pack = 1)]
GetSystemTime範例由於剛好是8 byte aligned所以不需更改pack size。
要呼叫GetSystemTime很簡單:
using System;
using System.Runtime.InteropServices;
class MsgBoxTest
{
[DllImport("Kernel32.dll")]
public static extern void GetSystemTime(SystemTime st);
public static void Main()
{
SystemTime t = new SystemTime();
GetSystemTime(t);
Console.WriteLine(t.Year);
}
}
如果不知道native function如何在C#中宣告,可以去
PINVOKE.NET搜尋。此網站也提供一個方便的Visual Studio add-in可讓你在VS中直接查詢。