C#与C++ DLL的交互
C#與C++交互,總體來說可以有兩種方法:
1.利用C++/CLI作為代理中間層
2.利用PInvoke實(shí)現(xiàn)直接調(diào)用
?
? 第一種方法:實(shí)現(xiàn)起來比較簡單直觀,并且可以實(shí)現(xiàn)C#調(diào)用C++所寫的類,但是問題是MONO架構(gòu)不支持C++/CIL功能,因此無法實(shí)現(xiàn)脫離Microsoft.NET Framework跨平臺運(yùn)行。
? 第二種方法:簡單的實(shí)現(xiàn)并不麻煩,只要添加DllImportAttribute特性即可導(dǎo)入C++的函數(shù),但是問題是PInvoke不能簡單的實(shí)現(xiàn)C++類的調(diào)用。在Warensoft3D中為了可以使用MONO實(shí)現(xiàn)跨平臺(當(dāng)然DirectX是不能跨平臺的),所以使用了本方法,下面將對本方法展開詳細(xì)的說明。
?
注意事項:
? PInvoke從功能上來說,只支持函數(shù)調(diào)用,在被導(dǎo)出的函數(shù)前面一定要添加rxtern “C來指明導(dǎo)出函數(shù)的時候使用C語言方式編譯和鏈接的,這樣保證函數(shù)定義的名字相同,否則如果默認(rèn)按C++方式導(dǎo)出,那個函數(shù)名字就會變得亂七八糟,我們的程序就無法找到入口點(diǎn)了。
?
本文將說明一下幾點(diǎn):
?
1.互調(diào)的基本原理
? 首先,我們-來看一個在常規(guī)不過的概念“數(shù)據(jù)類型”。在大多數(shù)的靜態(tài)語言中定義變量的時候都要先指定其數(shù)據(jù)的類型,所謂數(shù)據(jù)類型,都是人們加強(qiáng)的一個便于記憶的名稱,究其本質(zhì)就是指明了這個數(shù)據(jù)在內(nèi)存中到底占用了幾個字節(jié),程序在運(yùn)行時,首先找到這個數(shù)據(jù)的地址,然后在按著該類型的長度,讀取相應(yīng)的內(nèi)存,然后再做處理。了解了前面,所有編程語言之間進(jìn)行互調(diào)就有眉目了,對于不同的語言之間的互調(diào),只要將該數(shù)據(jù)的指針(內(nèi)存地址)傳遞給另一個語言,在另一個語言中根據(jù)通信協(xié)議將指針?biāo)赶虻臄?shù)據(jù)存入長度對應(yīng)的數(shù)據(jù)類型即可,當(dāng)然要滿足以下幾點(diǎn)。
1.對于像Java,.NET這樣有運(yùn)行時虛擬機(jī)編程語言來講,由于虛擬機(jī)會讓堆內(nèi)存來回轉(zhuǎn)移,因此,在進(jìn)行互調(diào)的時候要保證正在被互調(diào)的數(shù)據(jù)所在的內(nèi)存一定要固定,不能被轉(zhuǎn)移。
2.有一些編程語言支持指針,有一些編程語言不支持指針(如Java),這個問題并不重要,所謂指針,其實(shí)就是一個內(nèi)存地址,對于32位os的指針是一個32位整數(shù),而對于64位機(jī)os的指針是一個64位整數(shù)。因?yàn)榇蠖鄶?shù)語言中都有整形數(shù),所以可以利用整形來接收指針。
?
2.基本數(shù)據(jù)類型的傳遞
? 互調(diào)過程中,最基本要傳遞的無非是數(shù)值和字符,即:int,long,float,char等等,但是此類型非彼類型,C/C++與C#中有一些數(shù)據(jù)類型長度是不一樣的,下表中列出常見數(shù)據(jù)類型的異同:
?
| C/C++? | C#? | 長度 |
| short? | short? | 2Bytes |
| int | int? | 4Bytes? |
| long(該類型在傳遞的時候常常會弄混) | int? | 4Bytes? |
| bool? | bool? | 1Byte? |
| char(Ascii碼字符) | byte? | 1Byte? |
| wchar_t(Unicode字符,該類型與C#中的Char兼容) | char? | 2Bytes? |
| float | float | 4Bytes |
| double? | double? | 8Bytes? |
? 最容易弄混的是就是long,char兩個類型,在C/C++中long和int都是4個字節(jié),都對應(yīng)著C#中的int類型,而C/C++中的char類型占一個字節(jié),用來表示一個ASCII碼字符,在C#中能夠表示一個字節(jié)的是byte類型。與C#中char類型對應(yīng)的應(yīng)該是C/C++中的wchar_t類型,對應(yīng)的是一個2字節(jié)的Unicode字符。
? 下面通過實(shí)例來說明調(diào)用過程:
? 由于項目的名稱是"TestCPPDLL",因此,會自動生成TestCPPDLL.h和TestCPPDLL.cpp兩個文件,.h文件是要導(dǎo)出內(nèi)容的聲明文件,為了能清楚的說明問題,我們將TestCPPDLL.h和TestCPPDLL.cpp兩個文件中的所有內(nèi)容都刪除,然后在TestCPPDLL.h中添加如下內(nèi)容:
? 第一行代碼中定義了一個名為"TESTCPPDLL_API"的宏,該宏對應(yīng)的內(nèi)容是"__declspec(dllexport)"意思是將后面修飾的內(nèi)容定義為DLL中要導(dǎo)出的內(nèi)容。當(dāng)然你也可以不使用這個宏,可以直接將"__declspec(dllexport)"寫在要導(dǎo)出的函數(shù)前面。
? 第二行中的"EXTERN_C",是在"winnt.h"中定義的宏,在函數(shù)前面添加"EXTERN_C"等同于在函數(shù)前面添加extern?"C",意思是該函數(shù)在編譯和連接時使用C語言的方式,以保證函數(shù)名字不變。第二行的代碼是一個函數(shù)的聲明,說明該函數(shù)可以被模塊外部調(diào)用,其定義實(shí)現(xiàn)在TestCPPDLL.cpp中,TestCPPDLL.cpp的代碼如下所示:
第三步:
? 在編譯C++DLL之前,需要做以下配置,在項目屬性對話框中選擇"C/C++"|"Advanced",將Compile AS 選項的值改為"C++"。然后確定,并編譯。
生成的DLL文件如下圖所示:
第四步:
首先,添加一個C#的應(yīng)用程序,如果要在C#中調(diào)用C++的DLL文件,先要在C#的類中添加一個靜態(tài)方法,并且使用DllImportAttribute對該方法進(jìn)行修飾,代碼如下所示:
DllImport中的第一個參數(shù)是指明DLL文件的位置,第二個參數(shù)"EntryPoint"用來指明對應(yīng)的C/C++中的函數(shù)名稱是什么。"extern"關(guān)鍵字表明該處聲明的這個Add方法是一個外部調(diào)用。
該方法聲明完畢之后,就可以像調(diào)用一個普通的靜態(tài)方法一樣去使用了。
下面是示例程序:
class Program { [DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "Add")] extern static int Add(int a, int b); static void Main(string[] args) { int c = Add(1,2); Console.WriteLine(c); Console.Read(); } }在運(yùn)行C#程序之前,先要修改C#的項目屬性,如下圖所示:
將platform target設(shè)置為x86,并且允許非安全代碼(后面有用)。
然后運(yùn)行該C#程序,其結(jié)果如下圖所示:
第五步:
前面的Add方法中傳遞的是數(shù)值類型(int),其他的數(shù)據(jù)類型,如float,double,和bool類型的傳遞方式是一樣的,下面演示如何傳遞字符串。
在TestCPPDLL.h中添加一個新的函數(shù)聲明,代碼如下:
EXTERN_C TESTCPPDLL_API void __stdcall WriteString(wchar_t*content);這里的參數(shù)是wchar_t類型的指針,對應(yīng)著C#中的char類型。TestCPPDLL.cpp中添加如下代碼:
TESTCPPDLL_API void __stdcall WriteString(wchar_t*content) { cout<<content; }該代碼的功能就是將輸入的字符串通過C++在控制臺上輸出。下面是在C#中的聲明:
[DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "WriteString")] extern unsafe static void WriteString(char*c);調(diào)用過程如下所示:
//因?yàn)槭褂弥羔?#xff0c;因?yàn)橐暶鞣前踩?/span> unsafe {//在傳遞字符串時,將字符所在的內(nèi)存固化,//并取出字符數(shù)組的指針fixed (char* p = &("hello".ToCharArray()[0])){ //調(diào)用方法 WriteString(p);} }其運(yùn)行效果如下圖所示:
3.?指針的傳遞
根據(jù)前面介紹的數(shù)據(jù)類型對照表,我們可以直接在方法中傳遞指針,但是要注意的是我們常常需要將數(shù)組的指針(數(shù)據(jù)入口地址,第一個元素的地址),數(shù)據(jù)從C/C++到C#時問題不大,但是如果從C#到C/C++時一定要將數(shù)組先固化,然后再傳遞處理。
下面演示如何傳遞指針,首先在TestCPPDLL.h中添加下列聲明:
//傳入一個整型指針,將其所指向的內(nèi)容加1 EXTERN_C TESTCPPDLL_API void __stdcall AddInt(int *i); //傳入一個整型數(shù)組的指針以及數(shù)組長度,遍歷每一個元素并且輸出 EXTERN_C TESTCPPDLL_API void __stdcall AddIntArray(int *firstElement,int arraylength); //在C++中生成一個整型數(shù)組,并且數(shù)組指針返回給C# EXTERN_C TESTCPPDLL_API int* __stdcall GetArrayFromCPP(); 其實(shí)現(xiàn)寫在TestCPPDLL.cpp中,代碼如下所示: TESTCPPDLL_API void __stdcall AddInt(int *i) { (*i)++; } TESTCPPDLL_API void __stdcall AddIntArray(int *firstElement,int arrayLength) { int*currentPointer=firstElement; for (int i = 0; i < arrayLength; i++) { cout<<*currentPointer; currentPointer++; } cout<<endl; } int *arrPtr; TESTCPPDLL_API int* __stdcall GetArrayFromCPP() { arrPtr=new int[10]; for (int i = 0; i < 10; i++) { arrPtr[i]=i; } return arrPtr; }對應(yīng)調(diào)用的C#代碼如下所示:
[DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "AddInt")] extern unsafe static void AddInt(int* i); [DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "AddIntArray")] extern unsafe static void AddIntArray(int* firstElement, int arraylength); [DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "GetArrayFromCPP")] extern unsafe static int* GetArrayFromCPP();調(diào)用過程如下所示:
unsafe { // 調(diào)用C++中的AddInt方法int i = 10; AddInt(&i); Console.WriteLine(i); //調(diào)用C++中的AddIntArray方法將C#中的數(shù)據(jù)傳遞到C++中,并在C++中輸出int[] CSArray = new int[10]; for (int iArr = 0; iArr < 10; iArr++) { CSArray[iArr] = iArr; } fixed (int* pCSArray = &CSArray[0]) { AddIntArray(pCSArray, 10); } //調(diào)用C++中的GetArrayFromCPP方法獲取一個C++中建立的數(shù)組int* pArrayPointer = null; pArrayPointer = GetArrayFromCPP(); for (int iArr = 0; iArr < 10; iArr++) { Console.WriteLine(*pArrayPointer); pArrayPointer++; } }4. 函數(shù)指針的傳遞
前面說明的都是簡單數(shù)據(jù)類型的及其指針的傳遞,利用PInvoke我們也可以實(shí)現(xiàn)函數(shù)指針的傳遞,C#中并沒有函數(shù)指針的概念,但是可以使用委托(delegate)來代替函數(shù)指針,關(guān)于C#中委托的說明,可以參考筆者前面的一個文章:《C#委托及事件》
大家可能會問,為什么要傳遞函數(shù)指針呢?利用PInvoke可以實(shí)現(xiàn)C#對C/C++函數(shù)的調(diào)用,反過來,我們能不能在C/C++程序運(yùn)行的某一時刻,來調(diào)用一個C#對應(yīng)的函數(shù)呢?(例如在C++中存在一個獨(dú)立線程,該線程可能在任意時刻觸發(fā)一個事件,并且需要通知C#)。這個時候,我們就有必要將一個C#中已經(jīng)指向某一個函數(shù)的函數(shù)指針(委托)傳遞給C++。
想要傳遞函數(shù)指針,首先要在C#中定義一個委托,并且在C++中定義一個函數(shù)指針,同時要保證委托和函數(shù)指針具備相同的函數(shù)原型,我們首先編寫C#的代碼,如下所示:
//定義一個委托,返回值為空,存在一個整型參數(shù) public delegate void CSCallback(int tick); //定義一個用于回調(diào)的方法,與前面定義的委托的原型一樣 //該方法會被C++所調(diào)用 static void CSCallbackFunction(int tick) { Console.WriteLine(tick.ToString ()); } //定義一個委托類型的實(shí)例, //在主程序中該委托實(shí)例將指向前面定義的CSCallbackFunction方法 static CSCallback callback; 在CS的主程序中讓callback指向CSCallbackFunction方法,代碼如下所示: //調(diào)用委托所指向的方法 callback = CSCallbackFunction; 然后在C/C++中定義一個函數(shù)指針,并且添加一個用于設(shè)置函數(shù)指針的函數(shù),TestCPPDLL.h中的代碼如下所示: //定義一個函數(shù)指針 typedef void (__stdcall *CPPCallback)(int tick); //定義一個用于設(shè)置函數(shù)指針的方法, //并在該函數(shù)中調(diào)用C#中傳遞過來的委托 EXTERN_C TESTCPPDLL_API void SetCallback(CPPCallback callback); SetCallback函數(shù)的實(shí)現(xiàn)在TestCPPDLL.cpp中,代碼如下所示: TESTCPPDLL_API void SetCallback(CPPCallback callback) { int tick=rand(); //下面的代碼是對C#中委托進(jìn)行調(diào)用 ????callback(tick); } 在C#中添加SetCallback函數(shù)的聲明,代碼如下所示: //這里使用CSCallback委托類型來兼容C++里的CPPCallback函數(shù)指針 [DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "SetCallback")] extern static void SetCallback(CSCallback callback);在C#中的調(diào)用過程如下所示:
//讓委托指向?qū)⒈换卣{(diào)的方法 callback = CSCallbackFunction; //將委托傳遞給C++ SetCallback(callback);SetCallback方法被執(zhí)行后,在C#中定義的CSCallbackFunction就會被C++所調(diào)用。
5. 結(jié)構(gòu)體的傳遞
?
傳遞結(jié)構(gòu)體的想法和傳遞一個int類型數(shù)據(jù)類似,struct中的數(shù)據(jù)是在內(nèi)存中順序排列的,只要保證保證以下幾點(diǎn),就可以直接傳遞結(jié)構(gòu)體,甚至是結(jié)構(gòu)體的指針:
- 要傳遞的成員為公有的值類型字段
- C#中結(jié)構(gòu)體字段類型與C++結(jié)構(gòu)體中的字段類型相兼容
- C#結(jié)構(gòu)中的字段順序與C++結(jié)構(gòu)體中的字段順序相同,要保證該功能,需要將C#結(jié)構(gòu)體標(biāo)記為[StructLayout( LayoutKind.Sequential)]?
下面通過代碼進(jìn)行說明,首先在C#中添加一個結(jié)構(gòu)體,代碼如下所示:
[StructLayout( LayoutKind.Sequential)] struct Vector3 { public float X, Y, Z; }該結(jié)構(gòu)體表示一個3D向量,包括X,Y,Z三個float類型的分量。
然后在TestCPPDLL.h中也定義一個相同結(jié)構(gòu)的結(jié)構(gòu)體,代碼如下所示:
struct Vector3 { float X,Y,Z; };在TestCPPDLL.h中聲明一個用于傳遞Vector3結(jié)構(gòu)體的一個函數(shù),代碼如下所示:
EXTERN_C TESTCPPDLL_API void __stdcall SendStructFromCSToCPP(Vector3 vector);在TestCPPDLL.cpp中將其實(shí)現(xiàn),代碼如下所示:
TESTCPPDLL_API void __stdcall SendStructFromCSToCPP(Vector3 vector) { cout<<"got vector3 in cpp,x:"; cout<<vector.X; cout<<",Y:"; cout<<vector.Y; cout<<",Z:"; cout<<vector.Z; }在C#中添加對SendStructFromCSToCPP函數(shù)的聲明,代碼如下所示:
[DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "SendStructFromCSToCPP")] extern static void SendStructFromCSToCPP(Vector3 vector);C#中的調(diào)用過程如下所示:
//建立一個Vector3的實(shí)例 Vector3 vector = new Vector3() { X =10,Y=20,Z=30 }; //將vector傳遞給C++并在C++中輸出 SendStructFromCSToCPP(vector);基輸出效果如下所示:
?
完整的TestCPPDLL.h代碼如下所示:
#define TESTCPPDLL_API __declspec(dllexport)EXTERN_C TESTCPPDLL_API int __stdcall Add(int a,int b);EXTERN_C TESTCPPDLL_API void __stdcall WriteString(wchar_t*content);//傳入一個整型指針,將其所指向的內(nèi)容加1 EXTERN_C TESTCPPDLL_API void __stdcall AddInt(int *i);//傳入一個整型數(shù)組的指針以及數(shù)組長度,遍歷每一個元素并且輸出 EXTERN_C TESTCPPDLL_API void __stdcall AddIntArray(int *firstElement,int arraylength);//在C++中生成一個整型數(shù)組,并且數(shù)組指針返回給C# EXTERN_C TESTCPPDLL_API int* __stdcall GetArrayFromCPP();//定義一個函數(shù)指針 typedef void (__stdcall *CPPCallback)(int tick);//定義一個用于設(shè)置函數(shù)指針的方法,//并在該函數(shù)中調(diào)用C#中傳遞過來的委托 EXTERN_C TESTCPPDLL_API void __stdcall SetCallback(CPPCallback callback);struct Vector3{float X,Y,Z;};EXTERN_C TESTCPPDLL_API void __stdcall SendStructFromCSToCPP(Vector3 vector);?
完整的TestCPPDLL.CPP代碼如下所示:
#include "stdafx.h"#include <iostream>#include "TestCPPDLL.h"using namespace std;TESTCPPDLL_API int __stdcall Add(int a,int b){return a+b;}TESTCPPDLL_API void __stdcall WriteString(wchar_t*content){wprintf(content);printf("\n");}TESTCPPDLL_API void __stdcall AddInt(int *i){(*i)++;}TESTCPPDLL_API void __stdcall AddIntArray(int *firstElement,int arrayLength){int*currentPointer=firstElement;for (int i = 0; i < arrayLength; i++){cout<<*currentPointer;currentPointer++;}cout<<endl;}int *arrPtr;TESTCPPDLL_API int* __stdcall GetArrayFromCPP(){arrPtr=new int[10];for (int i = 0; i < 10; i++){arrPtr[i]=i;}return arrPtr;}TESTCPPDLL_API void __stdcall SetCallback(CPPCallback callback){int tick=100;//下面的代碼是對C#中委托進(jìn)行調(diào)用 callback(tick);}TESTCPPDLL_API void __stdcall SendStructFromCSToCPP(Vector3 vector){cout<<"got vector3 in cpp,x:";cout<<vector.X;cout<<",Y:";cout<<vector.Y;cout<<",Z:";cout<<vector.Z;}?
完整的C#代碼如下所示:
?
using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Text;namespace ConsoleApplication1 {class Program {[DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "Add")] extern static int Add(int a, int b); [DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "WriteString")] extern unsafe static void WriteString(char* c); [DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "AddInt")] extern unsafe static void AddInt(int* i); [DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "AddIntArray")] extern unsafe static void AddIntArray(int* firstElement, int arraylength); [DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "GetArrayFromCPP")] extern unsafe static int* GetArrayFromCPP();//定義一個委托,返回值為空,存在一個整型參數(shù) public delegate void CSCallback(int tick); //定義一個用于回調(diào)的方法,與前面定義的委托的原型一樣 //該方法會被C++所調(diào)用 static void CSCallbackFunction(int tick) {Console.WriteLine(tick.ToString()); } //定義一個委托類型的實(shí)例, //在主程序中該委托實(shí)例將指向前面定義的CSCallbackFunction方法 static CSCallback callback; //這里使用CSCallback委托類型來兼容C++里的CPPCallback函數(shù)指針 [DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "SetCallback")] extern static void SetCallback(CSCallback callback); [StructLayout(LayoutKind.Sequential)] struct Vector3 {public float X, Y, Z; } [DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "SendStructFromCSToCPP")] extern static void SendStructFromCSToCPP(Vector3 vector); static void Main(string[] args) {int c = Add(1, 2);Console.WriteLine(c);//因?yàn)槭褂弥羔?#xff0c;因?yàn)橐暶鞣前踩?/span>unsafe{//在傳遞字符串時,將字符所在的內(nèi)存固化,//并取出字符數(shù)組的指針fixed (char* p = &("hello".ToCharArray()[0])){//調(diào)用方法 WriteString(p);}} unsafe{// 調(diào)用C++中的AddInt方法int i = 10;AddInt(&i);Console.WriteLine(i);//調(diào)用C++中的AddIntArray方法將C#中的數(shù)據(jù)傳遞到C++中,并在C++中輸出int[] CSArray = new int[10];for (int iArr = 0; iArr < 10; iArr++){CSArray[iArr] = iArr;} fixed (int* pCSArray = &CSArray[0]){AddIntArray(pCSArray, 10);}//調(diào)用C++中的GetArrayFromCPP方法獲取一個C++中建立的數(shù)組int* pArrayPointer = null;pArrayPointer = GetArrayFromCPP();for (int iArr = 0; iArr < 10; iArr++){Console.WriteLine(*pArrayPointer);pArrayPointer++;} } //讓委托指向?qū)⒈换卣{(diào)的方法callback = CSCallbackFunction; //將委托傳遞給C++ SetCallback(callback); //建立一個Vector3的實(shí)例 Vector3 vector = new Vector3() { X = 10, Y = 20, Z = 30 }; //將vector傳遞給C++并在C++中輸出 SendStructFromCSToCPP(vector); Console.Read(); }
?
轉(zhuǎn)載于:https://www.cnblogs.com/ye-ming/p/7883579.html
總結(jié)
以上是生活随笔為你收集整理的C#与C++ DLL的交互的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mock平台架构及实现
- 下一篇: 如何在线将pdf转换成ppt格式