2009年8月27日 星期四

如何在C#中使用Unmanaged dll

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的函式, 並用 staticextern 來標記函式. 此目的在告訴編譯器該方法與實體物件無關.
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();
    }
}


下面表格列出DllImport的屬性欄位

名稱說明
BestFitMapping將 Unicode 字元轉換成 ANSI 字元時, 設定true/false可啟用或停用自動對應行為
CallingConventionCallingConvention 列舉成員, 此欄位的預設值為 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 資料型別
Managed需要封裝Unmanaged的資料, 下面表格列出UnmanagedManaged的資料型態轉換:
Wtypes.h 中的 Unmanaged 型別Unmanaged C 語言型別Managed 類別名稱
HANALEvoid*System.IntPtr
BYTEunsigned charSystem.Byte
SHORTshortSystem.Int16
WORDunsigned shortSystem.UInt16
INTintSystem.Int32
UINTunsigned intSystem.UInt32
LONGlongSystem.Int32
BOOLlongSystem.Int32
DWORDunsigned longSystem.UInt32
ULONGunsigned longSystem.UInt32
CHARcharSystem.Char
LPSTRchar*System.String 或
System.Text.StringBuilder
LPCSTRConst char*System.String 或
System.Text.StringBuilder
LPWSTRwchar_t*System.String 或
System.Text.StringBuilder
LPCWSTRConst wchar_t*System.String 或
System.Text.StringBuilder
FLOATFloatSystem.Single
DOUBLEDoubleSystem.Double


如果要包裝一個資料結構,
在 C# 中可以使用 StructLayout屬性來表示的結構, 如下面範例說明:

CC#

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 屬性來表示, 如下所示

CC#

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 - 使用平台叫用封送處理資料

沒有留言:

張貼留言