在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中有Array的型態, 可以利用MarshalAs 屬性來表示, 如下所示
C | C# |
---|---|
|
|
下面我以一個用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 - 使用平台叫用封送處理資料
沒有留言:
張貼留言