在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 - 使用平台叫用封送處理資料
沒有留言:
張貼留言