在C#中可以透過PInvoke(Platform Invocation Services), 呼叫 DLL 內實作的Unmanaged函式,
其流程可以參考 MSDN - PInvok的流程圖
在Managed的程式中使用Unmanaged dll的方法有兩種:
- 呼叫DLL匯出(dllexport)的函式
- 呼叫COM元件上的介面
因為, 使用COM Interop的方式有點複雜, 所以本文章只介紹第一種方法, 若要使用匯出的 DLL 函式(C-Style function), 可分成下面幾個步驟:
- 引用DllImport所在的名子空間: using System.Runtime.InteropServices;
- 定義類別(例如class PlatformInvoke)包裝要被呼叫的DLL介面(也就是C/C++中的header檔)
- 使用 DllImport來引用DLL的函式, 並用 static 和 extern 來標記函式. 此目的在告訴編譯器該方法與實體物件無關.
DllImport的格式語法如下:
//將user32.dll換成要使用的DLL檔
[DllImport("user32.dll"), SetLastError = true]
//換成所需的方法及參數(需要將Unmanaged類型轉成Managed)
public static extern ReturnType FunctionName(type arg);
如下面範例所示:
using System; using System.Runtime.InteropServices; class WrapperTest { [DllImport("msvcrt.dll")] public static extern int puts(string c); [DllImport("msvcrt.dll")] publlic static extern int _flushall(); public static void Main() { puts("Test"); _flushall(); } }
名稱 | 說明 |
BestFitMapping | 將 Unicode 字元轉換成 ANSI 字元時, 設定true/false可啟用或停用自動對應行為 |
CallingConvention | CallingConvention 列舉成員, 此欄位的預設值為 WinAPI |
CharSet | 指示用在入口處中的字串參數, 如果未指定 CharSet, 則預設為 CharSet.Auto |
EntryPoint | 指示要呼叫的 DLL 進入點(Entry Point) 的名稱或序數 |
ExactSpelling | 控制 DllImportAttribute CharSet 欄位是否會導致 Common Language Runtime 搜尋 Unmanaged DLL 以取得不是指定名稱的進入點名稱. |
PreserveSig | 指定簽章是否為 Unmanaged 程式碼進入點的直接轉譯, 預設此欄位為 true |
SetLasrError | 指示自屬性方法傳回之前, 被呼叫端是否呼叫 SetLastError Win32 API 函式。 |
ThrowOnUnmappableChar | 在無法對應的 Unicode 字元轉換為 ANSI "?" 字元時, 啟用或停用例外狀況的擲回 |
- 以 Managed 資料型別代替 Unmanaged 資料型別
Wtypes.h 中的 Unmanaged 型別 | Unmanaged C 語言型別 | Managed 類別名稱 |
HANALE | void* | System.IntPtr |
BYTE | unsigned char | System.Byte |
SHORT | short | System.Int16 |
WORD | unsigned short | System.UInt16 |
INT | int | System.Int32 |
UINT | unsigned int | System.UInt32 |
LONG | long | System.Int32 |
BOOL | long | System.Int32 |
DWORD | unsigned long | System.UInt32 |
ULONG | unsigned long | System.UInt32 |
CHAR | char | System.Char |
LPSTR | char* | System.String 或 System.Text.StringBuilder |
LPCSTR | Const char* | System.String 或 System.Text.StringBuilder |
LPWSTR | wchar_t* | System.String 或 System.Text.StringBuilder |
LPCWSTR | Const wchar_t* | System.String 或 System.Text.StringBuilder |
FLOAT | Float | System.Single |
DOUBLE | Double | System.Double |
如果要包裝一個資料結構, 在 C# 中可以使用 StructLayout屬性來表示的結構, 如下面範例說明:
C | C# |
---|---|
struct tOTM_Point { int X; int Y; int R; }; | [Serializable, StructLayout(LayoutKind.Sequential)] public struct tOTM_Point { public int X; public int Y; public int R; } |
如果在Struct中有Array的型態, 可以利用MarshalAs 屬性來表示, 如下所示
C | C# |
---|---|
struct tOTM_Touch { int TouchType; int TouchState; tOTM_Point PointArray[2]; }; | [Serializable, StructLayout(LayoutKind.Sequential)] public struct tOTM_Touch { public tOTM_TouchType TouchType; public tOTM_TouchState TouchState; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] public tOTM_Point[] PointArray; }; |
下面我以一個用C寫的OTMUT.dll為例子, 說明如何將Unmanaged dll轉成Managed程式碼:
C
#ifdef OTMUT_EXPORTS #define OTMUT_API __declspec(dllexport) #else #define OTMUT_API __declspec(dllimport) #endif struct tOTM_Point { int X; int Y; int R; }; struct tOTM_Touch { int TouchType; int TouchState; tOTM_Point PointArray[2]; }; typedef void (_stdcall* TouchFunc) (tOTM_Touch Touch); OTMUT_API void RegisterTouchFunc(TouchFunc Func);
C#
using System; using System.Runtime.InteropServices; namespace OTMUTInterop { [Serializable, StructLayout(LayoutKind.Sequential)] public struct tOTM_Point { public int X; public int Y; public int R; }; [Serializable, StructLayout(LayoutKind.Sequential)] public struct tOTM_Touch { public tOTM_TouchType TouchType; public tOTM_TouchState TouchState; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] public tOTM_Point[] PointArray; }; delegate void TouchFunc(tOTM_Touch touch); class OTMUT { [DllImport("OTMUT.dll", CallingConvention = CallingConvention.StdCall)] public static extern void RegisterTouchFunc(TouchFunc func); } }
參考文章 :
MSDN - 使用 Unmanaged DLL 函式
MSDN - 使用平台叫用封送處理資料