背景
在Windows Mobile和Wince(Windows Embedded CE)產品開發中,有時候會使用C++封裝一些共用代碼,例如在我們項目中使用了C++封裝了一個對USB通訊的公開程式碼程式庫,這些共用代碼編譯成靜態庫,其他模式使用的時候,只需要include標頭檔,連結lib庫就可以了,但是.NET Compact Framework的程式沒有辦法使用C++編譯的靜態庫,所以產生封裝Native DLL提供給.NET Compact Framework程式調用的需求。
簡介
本文講述在Windows Mobile和Wince(Windows Embedded CE)下如何封裝Native DLL提供給.NET Compact Framework進行調用。
可選方案
在.NET framework下可以把C++編譯成託管的DLL,這樣.NET的程式可以直接調用這個DLL了,但是在.NET Compact Framework下,不支援managed c++,所以只能封裝成Native的DLL,然後.NET Compact Framework P/Invoke該Native DLL。 封裝Native的DLL有兩個可選的方案。
方案一: .DEF檔案
使用.DEF檔案,.DEF檔案可以定義輸出介面
LIBRARY NativeLib
EXPORTS
Insert @1
Delete @2
Member @3
Min @4
上面的.def檔案定義了四個輸出介面。如果使用.def檔案,需要在編譯選項進行配置,如:
使用.def檔案麻煩的地方是每次更新介面都要手工更新.def檔案,但是使用.def檔案有一個好處是當dll發生更新以後,如果定義的ordinal沒有發生改變的話,調用方不需要重新連結程式就可以使用新的DLL,Windows Mobile的今日組件就是使用.def檔案進行定義的,需要實現ordinal為240和241兩個介面:
InitializeCustomItem @240 NONAME
CustomItemOptionsDlgProc @241 NONAME
這樣shell32進程就可以載入實現了這兩個介面的DLL,不需要重新串連來支援新的組件。
方案二: __declspec(dllexport)
我個人偏向於使用__declspec(dllexport)的方案,因為P/Invoke不存在調用方重新連結的問題,所以我偏向於使用__declspec(dllexport)的方案。
使用__declspec(dllexport)的方案,在需要輸出的函數加入__declspec(dllexport)。這樣該函數就會產生輸出表。
#define EXPORT_API __declspec(dllexport)
EXPORT_API BOOL OpenIndicator()
{
return TRUE;
}
例如上述定義產生以下輸出。使用Dumpbin /exports進行查看。
Microsoft (R) COFF/PE Dumper Version 9.00.30729.01
Copyright (C) Microsoft Corporation. All rights reserved.
Dump of file NativeLib.dll
File Type: DLL
Section contains the following exports for NativeLib.dll
00000000 characteristics
4AF757EA time date stamp Sun Nov 08 10:44:42 2009
0.00 version
1 ordinal base
1 number of functions
1 number of names
ordinal hint RVA name
1 0 0000BA30 ?OpenIndicator@@YAHXZ = ?OpenIndicator@@YAHXZ (int __c
decl OpenIndicator(void))
Summary
1000 .data
2000 .pdata
2000 .rdata
2000 .reloc
19000 .text
有沒有發現輸出是?OpenIndicator@@YAHXZ 而不是 OpenIndicator。
如果.NET Compact Framework的調用方如下:
[System.Runtime.InteropServices.DllImport("DeviceCommsLib.dll", SetLastError = true]
private static extern bool OpenIndicator();
程式執行的時候會拋出找不到進入點的異常,如:
原因是產生輸出的時候使用C++的命名輸出了?OpenIndicator@@YAHXZ 而不是 OpenIndicator。如果要解決這個問題,有兩個辦法:方法一,在調用方指定進入點:
[System.Runtime.InteropServices.DllImport("DeviceCommsLib.dll", SetLastError = true, EntryPoint = "?OpenIndicator@@YAHXZ")]
private static extern bool OpenIndicator();
這個辦法不好,因為調用方需要使用Dumpbin /exports查看輸出進入點。
方法二,增加extern "C",如下:
#define EXPORT_API __declspec(dllexport)
extern "C" {
EXPORT_API bool OpenIndicator()
{
return true;
}
}
這樣使用了C的命名,使用Dumpbin /exports查看輸出如下:
Microsoft (R) COFF/PE Dumper Version 9.00.30729.01
Copyright (C) Microsoft Corporation. All rights reserved.
Dump of file NativeLib.dll
File Type: DLL
Section contains the following exports for NativeLib.dll
00000000 characteristics
4AF75999 time date stamp Sun Nov 08 10:51:53 2009
0.00 version
1 ordinal base
1 number of functions
1 number of names
ordinal hint RVA name
1 0 0000BA30 OpenIndicator = OpenIndicator
Summary
1000 .data
2000 .pdata
2000 .rdata
2000 .reloc
19000 .text
輸出介面變成OpenIndicator 了。
總結
總結一下,如果有Native C++的共用靜態庫需要提供給.NET Compact Framework調用,首先建立一個win32的DLL,把改靜態庫連結到這個DLL裡面,然後定義DLL通過extern "C" __declspec(dllexport) 來輸出出介面,最後在.NET Compact Framework程式就可以通過P/Invoke來調用原先在靜態庫中的功能模組來。
關於P/Invoke,我原先寫過些文章可以參考。
.NET Compact Framework 下Win32 API P/Invoke 的使用
開發P/Invoke的工具與Website