在合做開發時,C#時常須要調用C++DLL,當傳遞參數時時常遇到問題,尤爲是傳遞和返回字符串是,現總結一下,分享給你們:html
VC++中主要字符串類型爲:LPSTR,LPCSTR, LPCTSTR, string, CString, LPCWSTR, LPWSTR等
但轉爲C#類型卻不徹底相同。c++
主要有以下幾種轉換:數組
將string轉爲IntPtr:IntPtr System.Runtime.InteropServices.Marshal.StringToCoTaskMemAuto(string)安全
將IntPtr轉爲string:string System.Runtime.InteropServices.MarshalPtrToStringAuto(IntPtr)函數
類型對照:post
BSTR --------- StringBuilderui
LPCTSTR --------- StringBuilderthis
LPCWSTR --------- IntPtr.net
handle---------IntPtr設計
hwnd-----------IntPtr
char *----------string
int * -----------ref int
int &-----------ref int
void *----------IntPtr
unsigned char *-----ref byte
Struct須要在C#裏從新定義一個Struct
CallBack回調函數須要封裝在一個委託裏,delegate static extern int FunCallBack(string str);
注意在每一個函數的前面加上public static extern +返回的數據類型,若是不加public ,函數默認爲私有函數,調用就會出錯。
在C#調用C++ DLL封裝庫時會出現兩個問題:
1. 數據類型轉換問題
2. 指針或地址參數傳送問題
首先是數據類型轉換問題。由於C#是.NET語言,利用的是.NET的基本數據類型,因此其實是將C++的數據類型與.NET的基本數據類型進行對應。
例如C++的原有函數是:
int __stdcall FunctionName(unsigned char param1, unsigned short param2)
其中的參數數據類型在C#中,必須轉爲對應的數據類型。如:
[DllImport(「 COM DLL path/file 」)]
extern static int FunctionName(byte param1, ushort param2)
由於調用的是__stdcall函數,因此使用了P/Invoke的調用方法。其中的方法FunctionName必須聲明爲靜態外部函數,即加上 extern static聲明頭。咱們能夠看到,在調用的過程當中,unsigned char變爲了byte,unsigned short變爲了ushort。變換後,參數的數據類型不變,只是聲明方式必須改成.NET語言的規範。
咱們能夠經過下表來進行這種轉換:
Win32 Types
CLR Type
char, INT8, SBYTE, CHAR
System.SByte
short, short int, INT16, SHORT
System.Int16
int, long, long int, INT32, LONG32, BOOL , INT
System.Int32
__int64, INT64, LONGLONG
System.Int64
unsigned char, UINT8, UCHAR , BYTE
System.Byte
unsigned short, UINT16, USHORT, WORD, ATOM, WCHAR , __wchar_t
System.UInt16
unsigned, unsigned int, UINT32, ULONG32, DWORD32, ULONG, DWORD, UINT
System.UInt32
unsigned __int64, UINT64, DWORDLONG, ULONGLONG
System.UInt64
float, FLOAT
System.Single
double, long double, DOUBLE
System.Double
以後再將CLR的數據類型表示方式轉換爲C#的表示方式。這樣一來,函數的參數類型問題就能夠解決了。
如今,咱們再來考慮下一個問題,若是要調用的函數參數是指針或是地址變量,怎麼辦?
對於這種狀況可使用C#提供的非安全代碼來進行解決,可是,畢竟是非託管代碼,垃圾資源處理很差的話對應用程序是很不利的。因此仍是使用C#提供的ref以及out修飾字比較好。
同上面同樣,咱們也舉一個例子:
int __stdcall FunctionName(unsigned char ¶m1, unsigned char *param2)
在C#中對其進行調用的方法是:
[DllImport(「 file 」)]
extern static int FunctionName(ref byte param1, ref byte param2)
看到這,可能有人會問,&是取地址,*是傳送指針,爲什麼都只用ref就能夠了呢?一種可能的解釋是ref是一個具備重載特性的修飾符,會自動識別是取地址仍是傳送指針。
在實際的狀況中,咱們利用參數傳遞地址更多仍是用在傳送數組首地址上。
如:byte[] param1 = new param1(6);
在這裏咱們聲明瞭一個數組,如今要將其的首地址傳送過去,只要將param1數組的第一個元素用ref修飾。具體以下:
[DllImport(「 file 」)]
extern static int FunctionName(ref byte param1[1], ref byte param2)
文章出處:DIY部落(http://www.diybl.com/course/3_program/c++/cppjs/200886/134816.html)
爲了能用上原來的C++代碼,只好研究下從C# 中調用DLL
首先必需要有一個聲明,使用的是DllImport關鍵字:
包含DllImport所在的名字空間
using System.Runtime.InteropServices;
public class XXXX{
[DllImport(「MyDLL.dll")]
public static extern int mySum (int a,int b);
}
[DllImport(「MyDLL.dll")]
public static extern int mySum (int a,int b);
代碼中DllImport關鍵字做用是告訴編譯器入口點在哪裏,並將打包函數捆綁在這個類中
在調用的時候
在類中的時候 直接 mySum(a,b);就能夠了
在其餘類中調用: XXXX. mySum(a,b);
[DllImport(「MyDLL.dll」)]在申明的時候還能夠添加幾個屬性
[DllImport(「MyDLL.dll", EntryPoint=" mySum ",CharSet=CharSet.Auto,CallingConvention=CallingConvention.StdCall)
]
EntryPoint: 指定要調用的 DLL 入口點。默認入口點名稱是託管方法的名稱 。
CharSet: 控制名稱重整和封送 String 參數的方式 (默認是UNICODE)
CallingConvention指示入口點的函數調用約定(默認WINAPI)(上次報告講過的)
SetLastError 指示被調用方在從屬性化方法返回以前是否調用 SetLastError Win32 API 函數 (C#中默認false )
int 類型
[DllImport(「MyDLL.dll")]
//返回個int 類型
public static extern int mySum (int a1,int b1);
//DLL中申明
extern 「C」 __declspec(dllexport) int WINAPI mySum(int a2,int b2)
{
//a2 b2不能改變a1 b1
//a2=..
//b2=...
return a+b;
}
//參數傳遞int 類型
public static extern int mySum (ref int a1,ref int b1);
//DLL中申明
extern 「C」 __declspec(dllexport) int WINAPI mySum(int *a2,int *b2)
{
//能夠改變 a1, b1
*a2=...
*b2=...
return a+b;
}
DLL 需傳入char *類型
[DllImport(「MyDLL.dll")]
//傳入值
public static extern int mySum (string astr1,string bstr1);
//DLL中申明
extern 「C」 __declspec(dllexport) int WINAPI mySum(char * astr2,char * bstr2)
{
//改變astr2 bstr 2 ,astr1 bstr1不會被改變
return a+b;
}
DLL 需傳出char *類型
[DllImport(「MyDLL.dll")]
// 傳出值
public static extern int mySum (StringBuilder abuf, StringBuilder bbuf );
//DLL中申明
extern 「C」 __declspec(dllexport) int WINAPI mySum(char * astr,char * bstr)
{
//傳出char * 改變astr bstr -->abuf, bbuf能夠被改變
return a+b;
}
DLL 回調函數
BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARAM lParam)
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;
}
}
DLL 傳遞結構
BOOL PtInRect(const RECT *lprc, POINT pt);
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 XXXX {
[DllImport("User32.dll")]
public static extern bool PtInRect(ref Rect r, Point p);
}
DLL 回調函數,傳遞結構 想看的msdn裏面都有專題介紹,看的我都是暈暈的:)
其餘參考請搜索:
在C#程序設計中使用Win32類庫
C#中調用C++託管Dll
如何在C#中加載本身編寫的動態連接庫
相關文章:Creating a P/Invoke Library
能用上DLL之後感受仍是很好的,原來的C++代碼只要修改編譯經過就能夠了,
高興沒多久,發現.net2005竟然能夠用VB,VC開發智能設備項目,能夠建立MFC智能設備項目
暈暈,難道能夠直接用MFC來開發smartphone的程序了,趕忙看看,,,
Visual C++ 使用 __declspec(dllexport) 從 DLL 導出 (到C#)
因爲各類的緣由, 如何把unmanaged 的 c++ DLL 轉換成 managed C# 是一個問題。
方法有3個.
Ø 使用.def文件
Ø 能夠不用.def文件, 使用__declspec(dllexport)關鍵字, 特別是針對Visual C++編譯器的時候
Ø 直接用MC++寫
何時用.def文件?
.def的意思是module-definition, 這個純文本的文件定義了模塊的信息。 對於編譯器來講, 一個方法在編譯以後的DLL文件裏, 存在的形式名字可能不是做者當時起的那個,例如好好的函數名字function() 變成了?function2@@YAXXZ; 能夠用undname查看這個被編譯器修飾掉的名字, 原型是"void __cdecl function2(void)". 大概使用的時候就會遇到相似」連接錯誤,未決的外部符號…」 的錯誤.
.def文件主要的做用, 就是」標註」 出這個函數原來的樣子, 這樣編譯器在編譯的時候, 規則上就會以C編譯器的規則來處理, 修飾被去掉了, 另外同時能夠把導出函數的序號值手動的改高一點; 還有一個優勢(也是缺點) 就是能夠用NONAME來修飾函數, 這樣導出函數的序號值就變成了1~N, 即第N個函數. 因此調用GetProcAddress() 的時候, 能夠直接用定義的序號值, 而不用寫函數的名字(可是名字就徹底不可用了), 更好的是, 導出函數的這個DLL會變得比較小, 固然, MSDN強調了一點: 僅你能夠並有權更改這個.def文件內容的時候, 你才能夠用這個辦法.
那麼, 何時考慮用.def文件呢? 由於編譯器不一樣, 而產生的修飾名不一樣的話, 這個文件就是必須的.
注意若是文件沒有導出函數的話, 這個文件可能下降運行效率。
.def文件的格式
LIBRARY FileNameWithoutExtension
EXPORTS
Function1 @1
Function3 @2
Function2 @3 NONAME
啓用 Enable it: Property pages-> Configuration Properties->C/C++ -> Linker -> input -> Module Definition File
那不用.def呢? __declspec(dllexport)的做用
這個東西, 能夠給函數用, 也能夠給類用. 聲明大概這樣子:
1 |
__declspec(dllexport)int__stdcall GetMid(vector<type> ve); |
2 |
class__declspec(dllexport) TestClass{ |
3 |
public: |
4 |
TestClass(); |
5 |
} |
這牽扯到了一個東西就是__stdcall和__cdecl (還有__fastcall, 不過不多用), 其中__cdecl通常是C或者C++的缺省調用規範, 可是最大的一個區別就是__stdcall在返回前自身清除堆棧, 而__cdecl是調用方來作這個事情(可參考COM中的某些機制), 另外一個區別就是__stdcall對於可變參數的函數, 玩不轉.
反正今時今日, 你們都在用__stdcall, 因此這麼寫也沒什麼問題, 但不是沒有. VB裏調用標記着__cdecl的方法, 可能會獲得一個異常Bad DLL Calling Convention. 解決方法也很簡單:
原來的函數
1 |
long_cdecl PassStr(LPSTRp Str) |
2 |
{ return1; } |
新的函數
1 |
long_stdcall PassStrStdCall(LPSTRpStr) |
2 |
{ returnPassStr(pStr); } |
問題是, 若是這個函數原型, 參數是可變的, 那又怎麼弄呢?
調用的時候, C#都是這麼寫的:
1 |
[DllImport("", EntryPoint =@" ?GetMid@@YAXXZ", CharSet = CharSet.Auto)] |
2 |
privatestaticexternvoidGetMid(...); |
這個入口點名字還真彆扭, 看來去掉這個修飾仍是蠻須要的, 除了用.def文件, 另外一個辦法就是用 extern 「C」.
Extern 「C」
一句話總結:這個東西能夠去掉修飾名。在不用.def文件的前提下, 這個能夠保證你的函數function() 仍是這個名字.
可是,這個東西對類不太起做用!
這個東西是這麼用的: 放到函數聲明的最前面。 就相似這樣 extern 「C」 void __declspec(dllexport) function(void);
對於類, 通常的作法是, 把它的內部方法(特別是實例方法,或變量),wrap出一個方法來。 見下面的實例.
還要作什麼?
當一個DLL被初始化的時候, 它須要一個入口點, 通常對於非MFC DLL來講, 這樣寫就好了:
01 |
BOOLAPIENTRY DllMain(HANDLEhModule, |
02 |
DWORD ul_reason_for_call, |
03 |
LPVOID lpReserved |
04 |
) |
05 |
{ |
06 |
switch( ul_reason_for_call ) |
07 |
{ |
08 |
caseDLL_PROCESS_ATTACH: |
09 |
caseDLL_THREAD_ATTACH: |
10 |
caseDLL_THREAD_DETACH: |
11 |
caseDLL_PROCESS_DETACH: |
12 |
break; |
13 |
} |
14 |
returnTRUE; |
15 |
} |
要注意的是這個入口點的名字必須是DllMain, 若是不是須要修改linker的/entry 選項. 不然對於C的話可能會初始化失敗.
************************************************************************
開始用導出函數 PInvoke
爲了好看一點, 先約定一下:
1 |
#define EXT_C extern "C" |
2 |
#define DLLEXPORT __declspec(dllexport) |
3 |
#define EXT_C_DLLEXPORT EXT_C DLLEXPORT |
4 |
#define CALLBACK __stdcall |
5 |
#define WINAPI __stdcall |
6 |
#define APIENTRY WINAPI |
1. 普通的函數
1 |
EXT_C_DLLEXPORT void WINAPI Function(); |
2 |
3 |
[DllImport("filename.dll", EntryPoint =" Function")] |
4 |
privatestaticexternvoidFunc(); |
2. ref或者out
1 |
EXT_C_DLLEXPORT void WINAPI Function(Type** ty); |
2 |
3 |
[DllImport("filename.dll", EntryPoint =" Function")] |
4 |
privatestaticexternvoidFunc(outType ty); |
3. 指針函數和委託
1 |
Typedef void(CALLBACK *pFunc)(int); |
2 |
EXT_C_DLLEXPORT void WINAPI Compare(inta, intb, pFunc p); |
3 |
privatedelegateintCompareCallback(inta, intb); |
4 |
[DllImport("filename.dll",EntryPoint=」Compare」)] |
5 |
privatestaticexternintCompare(inta, intb, CompareCallback call); |
4. 類的處理. 其實不是說不能夠把類標記爲 DLLEXPORT, 若是能夠的話, 固然是wrap比較好
C++裏的原型
1 |
classDLLEXPORT Test |
2 |
{ |
3 |
public: |
4 |
Test(); |
5 |
~Test(); |
6 |
BOOLfunction(intpar) |
7 |
}; |
類被export, 函數調用時候注意用CallingConvention.ThisCall.
1 |
[DllImport("filename.dll", EntryPoint =@"??4Test@@QEAAAEAV0@AEBV0@@Z", CallingConvention = CallingConvention.ThisCall)] |
2 |
privatestaticexternintTestFunc(IntPtr hwnd, intpar); |
採用了」迂迴」策略, C++裏先這樣定義,同理, 添加構造函數等, 函數就變成了這個樣子:
01 |
EXT_C_DLLEXPORT BOOLWINAPI function_wrap(Test* t, int par) |
02 |
{ |
03 |
returnt->function(par); |
04 |
} |
05 |
EXT_C_DLLEXPORT Test* Test_ctor() |
06 |
{ |
07 |
Test* t =newTest(); |
08 |
returnt; |
09 |
} |
10 |
EXT_C_DLLEXPORT voidTest_dector(Test* t) |
11 |
{ |
12 |
if(NULL == t) |
13 |
{ |
14 |
deletet; |
15 |
t = NULL; |
16 |
} |
17 |
} |
在C#裏這樣寫, 那麼就和平時用沒什麼區別了.
01 |
publicclassTest : IDisposable |
02 |
{ |
03 |
privateIntPtr instance; |
04 |
publicTest() |
05 |
{ |
06 |
instance = CreateInstance(); |
07 |
} |
08 |
09 |
~Test() |
10 |
{ |
11 |
Dispose(false); |
12 |
} |
13 |
14 |
#region pinvoke |
15 |
[DllImport("filename.dll", EntryPoint =@"Test_ctor")] |
16 |
privatestaticexternIntPtr CreateInstance(); |
17 |
[DllImport("filename.dll", EntryPoint =@"Test_dector")] |
18 |
privatestaticexternvoidDestroyInstance(IntPtr hwnd); |
19 |
[return: MarshalAs(UnmanagedType.Bool)] |
20 |
[DllImport("filename.dll", EntryPoint =@"function_wrap")] |
21 |
privatestaticexternboolfunction_wrap(intpar); |
22 |
#endregion |
23 |
24 |
#region IDisposable Members |
25 |
26 |
publicvoidDispose() |
27 |
{ |
28 |
Dispose(true); |
29 |
} |
30 |
31 |
privatevoidDispose(boolbDisposing) |
32 |
{ |
33 |
if(instance != IntPtr.Zero) |
34 |
{ |
35 |
DestroyInstance(instance); |
36 |
} |
37 |
38 |
if(bDisposing) |
39 |
{ |
40 |
GC.SuppressFinalize(this); |
41 |
} |
42 |
} |
43 |
44 |
#endregion |
45 |
} |
5. Struct操做.
1 |
typedefstructObject_HANDLE { |
2 |
unsignedlong dbch_size; |
3 |
HANDLE dbch_handle; |
4 |
GUID dbch_eventguid; |
5 |
BOOLres_flag; |
6 |
} Object_Native_HANDLE ; |
首先在C#裏嚴格定義這個,LayoutKind.Sequential 用來保證內存分配的正常。
1 |
[StructLayout(LayoutKind.Sequential)] |
2 |
publicstructObject_Native_HANDLE |
3 |
{ |
4 |
publiculongdbch_size; |
5 |
publicIntPtr dbch_handle; |
6 |
publicGuid dbch_eventGuid; |
7 |
publicboolres_flag; |
8 |
} |
Marshal的使用以下:
1 |
Object_Native_HANDLE obj =newObject_Native_HANDLE(); //初始化 |
2 |
intamountToAllocate = Marshal.SizeOf(obj);//獲取大小 |
3 |
IntPtr objPtr = Marshal.AllocHGlobal(amountToAllocate);//分配並獲取空的空間地址 |
4 |
Marshal.StructureToPtr(obj, objPtr,false);// 值寫入分配的空間 |
5 |
//操做... |
6 |
Marshal.FreeHGlobal(objPtr);//釋放空間 |
最後提一句, unmaged code中的錯誤, 到managed 之後, 極大多是捕捉不到的。 因此錯誤須要分別處理。
花了很多時間, MC++平時用的很少, 不寫了。