C#調用C++寫的非託管的DLL中匯出的函數
Platform invoke是一個使得Managed 程式碼(managed code)能夠調用DLL中實現的非託管函數(unmanaged functions)的服務(service),例如:那些Win32 API中的函數。它定位(locate)並且調用(invoke)匯出的函數,在需要的時候,跨越互動邊界列集(marshal)它的參數(integers, strings, arrays, structures等)。
Platform invoke 在運行時(run time)依賴中繼資料(metadata)來定位匯出函數並列集參數.說明了這個過程:
下面介紹調用DLL非託管函數的過程
1. 指定DLL中的函數
至少,你要制定一個函數的名字和包含這個函數的DLL
注意ANSI和Unicode版本函數的差別
還可以改變DLL中的函數的名字,例如把MessageBoxA改為MsgBox
using System.Runtime.InteropServices;
public class Win32
{
[DllImport("user32.dll", EntryPoint = "MessageBoxA")]
public static extern int MsgBox(int hWnd, String text, String caption,
uint type);
}
2. 建立一個類來包含DLL中的函數
你可以使用一個已存在的類,或者為每個非託管函數建立一個單獨的類,或者為一組非託管函數建立一個類
在一個託管類中封裝(Wrapping)常用的DLL函數是一種封裝平台相關功能的有效方法,雖然在某些情況下它(Wrapping)並不是必須的。提供一個封裝類是一種方便的方法,因為定義DLL的函數很麻煩又容易出錯。如果你使用Visual Basic 或者C#,你必須在C#的Class或者Visual Basic的module內聲明DLL的函數
在類的內部,為每一個你想調用的DLL函數定義一個static的方法。定義可以包含其他的資訊:例如字元集(character set)或者傳遞方法參數的調用規則(calling convention),如果忽略了這些資訊,將使用預設的設定。
一份完整的聲明和預設設定的列表 see Creating Prototypes in Managed Code.
一旦封裝完成,你可以像調用其他的static方法一樣調用這些方法。Platform invoke自動處理下面的調出函數
當為platform invoke設計一個託管的類的時候,要考慮類和DLL函數之間的關係。例如:
• 在一個已經存在的類中聲明DLL函數
• 為每一個DLL函數建立一個單獨的類,使得函數獨立並且容易找到
• 建立一個包含一系列相關DLL函數的類來邏輯分組並且減少開銷
3. 在Managed 程式碼中建立函數簽名(signature)
[C#] 使用 DllImportAttribute 來制定DLL和函數。把方法標記為static and extern.
4.
[C#]
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential)]
public struct Point {
public int x;
public int y;
}
[StructLayout(LayoutKind.Explicit)]
public struct Rect {
[FieldOffset(0)] public int left;
[FieldOffset(4)] public int top;
[FieldOffset(8)] public int right;
[FieldOffset(12)] public int bottom;
}
class Win32API {
[DllImport("User32.dll")]
public static extern bool PtInRect(ref Rect r, Point p);
}
[C#]
[StructLayout(LayoutKind.Sequential)]
public class MySystemTime {
public ushort wYear;
public ushort wMonth;
public ushort wDayOfWeek;
public ushort wDay;
public ushort wHour;
public ushort wMinute;
public ushort wSecond;
public ushort wMilliseconds;
}
class Win32API {
[DllImport("Kernel32.dll")]
public static extern void GetSystemTime(MySystemTime st);
}
回呼函數的理想用到的情形是一個任務重複的執行。另一個通常的用法是enumeration函數,例如:EnumFontFamilies,EnumPrinters, 和EnumWindows
實現一個回呼函數
下面的流程說明了怎樣在一個託管的應用中使用P/Invoke列印本機上的每一個視窗的handle。特別的是,這個例子使用EnumWindows函數來處理視窗的列表並且,一個託管的回呼函數(named CallBack)用來列印視窗的handle的值
[C#]
using System;
using System.Runtime.InteropServices;
public delegate bool CallBack(int hwnd, int lParam);
public class EnumReportApp {
[DllImport("user32")]
public static extern int EnumWindows(CallBack x, int y);
public static void Main()
{
CallBack myCallBack = new CallBack(EnumReportApp.Report);
EnumWindows(myCallBack, 0);
}
public static bool Report(int hwnd, int lParam) {
Console.Write("Window handle is ");
Console.WriteLine(hwnd);
return true;
}
}
實現一個回呼函數
(1). 在實現之間,查看一下EnumWindows函數. EnumWindows的(signature)如下:
BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARAM lParam)
一個線索是這個函數需要一個callback的參數lpEnumFunc
(2). 建立一個託管的函數。例子聲明了一個delegate類型(called CallBack),包含兩個參數(hwnd and lparam).
(3). 建立一個代理(delegate)然後把它作為EnumWindows的參數傳遞給EnumWindows,Platform invoke自動轉換delegate為回呼函數。
(4). 確定垃圾收集器在回呼函數完成之前不會回收delegate .當你把delegate當作參數傳遞時,或者傳遞一個包含delegate的結構時,在調用的過程中,他不會被回收,這樣,在下面這個enumeration的例子中,callback函數在函數調用返回之前完成,所以託管的調用者不需要額外的工作。
如果,callback在函數調用返回之後可以被調用,託管的調用者必須保證在回呼函數完成之前delegate沒有被垃圾收集。防止垃圾收集的資訊參見Interop Marshaling• 實現回呼函數(Implementing Callback Functions)
回呼函數是在託管應用中的代碼來協助非託管的DLL函數完成一個功能。下面的例子說明了回呼函數的元素和怎樣在Managed 程式碼中實現回調。
回呼函數基礎
從Managed 程式碼中調用DLL中的函數,建立一個DLL中函數的託管的定義然後調用它,這個過程是很直接的
使用DLL的需要回調的函數需要一些額外的步驟。首先,必須通過尋找協助確定一個函數是否需要回調,然後,在你的Managed 程式碼中建立回呼函數,最後,調用DLL的函數,傳遞指向回呼函數的指標。描述了這個過程聲明傳遞Classes
可以傳遞類的成員給一個非託管DLL的函數,由於類的成員是有固定的布局(layout)。下面的例子說明了怎樣傳遞MySystemTime類順序定義的成員給User32.dll中的GetSystemTime函數
GetSystemTime 的函數簽名(signature)如下
void GetSystemTime(SYSTEMTIME* SystemTime);調用DLL中的函數.
調用你的託管類的方法像你調用其他的方法一樣,傳遞結構體和實現回呼函數的情形例外
• 傳遞結構體(Passing Structures)
聲明傳遞結構
下面的例子顯示了怎樣在Managed 程式碼中定義一個Point和Rect 結構,然後把它們當作參數傳遞給User32.dll 中的PtInRect函數。
PtInRect的函數簽名(signature)如下
BOOL PtInRect(const RECT *lprc, POINT pt);
注意必須傳遞一個 Rect的引用,因為這個函數需要一個指向RECT類型的指標