C#与C/C++的交互
warensoft 中科院策画所培训中间
迎接转载,请注明出处及作者
比来在编写Warensoft3D游戏引擎,并估计来岁年初公布测试版本,底层引擎应用DirectX和MONO来编写,上层的逻辑应用C#来编写,是以编写了多量C#与C++互调的代码,如今经验写出来与大师分享,并欲望后来者少走弯路。
C#与C++交互,总体来说可以有两种办法:
第一种办法:实现起来斗劲简单直观,并且可以实现C#调用C++所写的类,然则题目是MONO构架不支撑C++/CLI功能,是以无法实现离开Microsoft .NET Framework跨平台运行。
第二种办法:简单的实现并不麻烦,只要添加DllImportAttribute特点即可以导入C/C++的函数,然则题目是PInvoke不克不及简单的实现对C++类的调用。在Warensoft3D中为了可以应用MONO实现跨平台(当然DirectX是不克不及跨平台的),所以应用了本办法,下面将对本办法展开具体的申明。
测试平台:
Windows7 64位,VS2010,.NET4.0
重视事项:
PInvoke从功能上来说,只支撑函数调用,在被导出的函数前面必然要添加extern "C"来指明导出函数的时辰应用C说话体式格式编译和连接,如许包管函数定义的名字和导出的名字雷同,不然若是默认按C++体式格式导出,那个函数的名字就会变得东倒西歪,我们的法度就无法找到进口点了。
本文将申明以下几点:
起首,我们来看一个再常规不过的概念—"数据类型"
我们知道在大多半的静态说话中定义变量的时辰都要先指定其数据类型,所谓数据类型,都是人们强加的一个便于记忆的名称,究其本质就是指了然这个数据在内存里到底是占用了几个字节,法度在运行的时辰,起首找到这个数据的地址,然后再按着该类型的长度,读取相对应的内存,然后再处理惩罚。
懂得了前面这个事儿,所有编程说话之间进行互调就有点门道儿了。对于不合说话之间的互调,只要将该数据的指针(内存地址)传递给另一个说话,在另一个说话中按照通信和谈将指针所指向的数据存储入长度对应的数据类型即可,当然要满足以下几点:
互调过程中,最根蒂根基要传递的无非是数值和字符,即:int,long,float,char等等,然则此类型非彼类型,C/C++与C#中有一些数据类型长度是不一样的,下表中列出常见数据类型的异同:
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个字节,都对应着C#中的int类型,而C/C++中的char类型占一个字节,用来默示一个ASCII码字符,在C#中可以或许默示一个字节的是byte类型。与C#中char类型对应的应当是C/C++中的wchar_t类型,对应的是一个2字节的Unicode字符。
下面经由过程实例来申明调用过程:
第一步:
建树一个C++的Win32DLL,如下图所示:
这里要重视选择"Export symbols"导出符号。点击完成。
第二步:
因为项目标名称是"TestCPPDLL",是以,会主动生成TestCPPDLL.h和TestCPPDLL.cpp两个文件,.h文件是要导出内容的声明文件,为了能清楚的申明题目,我们将TestCPPDLL.h和TestCPPDLL.cpp两个文件中的所有内容都删除,然后在TestCPPDLL.h中添加如下内容:
第一行代码中定义了一个名为"TESTCPPDLL_API"的宏,该宏对应的内容是"__declspec(dllexport)"意思是将后面润饰的内容定义为DLL中要导出的内容。当然你也可以不应用这个宏,可以直接将"__declspec(dllexport)"写在要导出的函数前面。
第二行中的"EXTERN_C",是在"winnt.h"中定义的宏,在函数前面添加"EXTERN_C"等同于在函数前面添加extern
"C",意思是该函数在编译和连接时应用C说话的体式格式,以包管函数名字不变。
第二行的代码是一个函数的声明,申明该函数可以被模块外部调用,其定义实如今TestCPPDLL.cpp中,TestCPPDLL.cpp的代码如下所示:
第三步:
在编译C++DLL之前,须要做以下设备,在项目属性对话框中选择"C/C++"|"Advanced",将Compile AS 选项的值改为"C++"。然后断定,并编译。
生成的DLL文件如下图所示:
第四步:
起首,添加一个C#的应用法度,若是要在C#中调用C++的DLL文件,先要在C#的类中添加一个静态办法,并且应用DllImportAttribute对该办法进行润饰,代码如下所示:
DllImport中的第一个参数是指明DLL文件的地位,第二个参数"EntryPoint"用来指明对应的C/C++中的函数名称是什么。"extern"关键字注解该处声明的这个Add办法是一个外部调用。
该办法声明完毕之后,就可以像调用一个通俗的静态办法一样去应用了。
下面是示例法度:
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();
}
}
在运行C#法度之前,先要批改C#的项目属性,如下图所示:
将platform target设置为x86,并且容许非安然代码(后面有效)。
然后运行该C#法度,其成果如下图所示:
第五步:
前面的Add办法中传递的是数值类型(int),其他的数据类型,如float,double,和bool类型的传递体式格式是一样的,下面演示如何传递字符串。
在TestCPPDLL.h中添加一个新的函数声明,代码如下:
EXTERN_C TESTCPPDLL_API void
__stdcall WriteString(wchar_t*content);
这里的参数是wchar_t类型的指针,对应着C#中的char类型。TestCPPDLL.cpp中添加如下代码:
TESTCPPDLL_API
void
__stdcall WriteString(wchar_t*content)
{
cout<
}
该代码的功能就是将输入的字符通同过C++在把握台上输出。下面是在C#中的声明:
[DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "WriteString")]
extern
unsafe
static
void WriteString(char*c);
调用过程如下所示:
//因为应用指针,因为要声明非安然域
unsafe
{
//在传递字符串时,将字符地点的内存固化,
//并取出字符数组的指针
fixed (char* p = &("hello".ToCharArray()[0]))
{
//调用办法
WriteString(p);
}
}
其运行结果如下图所示:
3. 指针的传递
按照前面介绍的数据类型对比表,我们可以直接在办法中传递指针,然则要重视的是我们经常须要将数组的指针(数据进口地址,第一个元素的地址),数据从C/C++到C#时题目不大,然则若是从C#到C/C++时必然要将数组先固化,然后再传递处理惩罚。
下面演示如何传递指针,起首在TestCPPDLL.h中添加下列声明:
//传入一个整型指针,将其所指向的内容加1
EXTERN_C
TESTCPPDLL_API
void
__stdcall AddInt(int *i);
//传入一个整型数组的指针以及数组长度,遍历每一个元素并且输出
EXTERN_C
TESTCPPDLL_API
void
__stdcall AddIntArray(int *firstElement,int arraylength);
//在C++中生成一个整型数组,并且数组指针返回给C#
EXTERN_C TESTCPPDLL_API int* __stdcall GetArrayFromCPP();
其实现写在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<
}
int *arrPtr;
TESTCPPDLL_API
int* __stdcall GetArrayFromCPP()
{
arrPtr=new
int[10];
for (int i = 0; i < 10; i++)
{
arrPtr[i]=i;
}
return arrPtr;
}
对应调用的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();
调用过程如下所示:
unsafe
{
// 调用C++中的AddInt办法
int i = 10;
AddInt(&i);
Console.WriteLine(i);
//调用C++中的AddIntArray办法将C#中的数据传递到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);
}
//调用C++中的GetArrayFromCPP办法获取一个C++中建树的数组
int* pArrayPointer = null;
pArrayPointer = GetArrayFromCPP();
for (int iArr = 0; iArr < 10; iArr++)
{
Console.WriteLine(*pArrayPointer);
pArrayPointer++;
}
}
前面申明的都是简单数据类型的及其指针的传递,哄骗PInvoke我们也可以实现函数指针的传递,C#中并没有函数指针的概念,然则可以应用委托(delegate)来庖代函数指针,关于C#中委托的申明,可以参考笔者前面的一个文章:《C#委托及事务》
大师可能会问,为什么要传递函数指针呢?哄骗PInvoke可以实现C#对C/C++函数的调用,反过来,我们能不克不及在C/C++法度运行的某一时刻,来调用一个C#对应的函数呢?(例如在C++中存在一个自力线程,该线程可能在随便率性时刻触发一个事务,并且须要通知C#)。这个时辰,我们就有须要将一个C#中已经指向某一个函数的函数指针(委托)传递给C++。
想要传递函数指针,起首要在C#中定义一个委托,并且在C++中定义一个函数指针,同时要包管委托和函数指针具备雷同的函数原型,我们起首编写C#的代码,如下所示:
//定义一个委托,返回值为空,存在一个整型参数
public
delegate
void
CSCallback(int tick);
//定义一个用于回调的办法,与前面定义的委托的原型一样
//该办法会被C++所调用
static
void CSCallbackFunction(int tick)
{
Console.WriteLine(tick.ToString ());
}
//定义一个委托类型的实例,
//在主法度中该委托实例将指向前面定义的CSCallbackFunction办法
static
CSCallback callback;
在CS的主法度中让callback指向CSCallbackFunction办法,代码如下所示:
//调用委托所指向的办法
callback = CSCallbackFunction;
然后在C/C++中定义一个函数指针,并且添加一个用于设置函数指针的函数,TestCPPDLL.h中的代码如下所示:
//定义一个函数指针
typedef
void (__stdcall *CPPCallback)(int tick);
//定义一个用于设置函数指针的办法,
//并在该函数中调用C#中传递过来的委托
EXTERN_C
TESTCPPDLL_API
void SetCallback(CPPCallback callback);
SetCallback函数的实如今TestCPPDLL.cpp中,代码如下所示:
TESTCPPDLL_API
void SetCallback(CPPCallback
callback)
{
int tick=rand();
//下面的代码是对C#中委托进行调用
callback(tick);
}
在C#中添加SetCallback函数的声明,代码如下所示:
//这里应用CSCallback委托类型来兼容C++里的CPPCallback函数指针
[DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "SetCallback")]
extern
static
void SetCallback(CSCallback callback);
在C#中的调用过程如下所示:
//让委托指向将被回调的办法
callback = CSCallbackFunction;
//将委托传递给C++
SetCallback(callback);
SetCallback办法被履行后,在C#中定义的CSCallbackFunction就会被C++所调用。
传递布局体的设法和传递一个int类型数据类似,struct中的数据是在内存中次序分列的,只要包管包管以下几点,就可以直接传递布局体,甚至是布局体的指针:
下面经由过程代码进行申明,起首在C#中添加一个布局体,代码如下所示:
[StructLayout( LayoutKind.Sequential)]
struct
Vector3
{
public
float X, Y, Z;
}
该布局体默示一个3D向量,包含X,Y,Z三个float类型的分量。
然后在TestCPPDLL.h中也定义一个雷同布局的布局体,代码如下所示:
struct
Vector3
{
float X,Y,Z;
};
在TestCPPDLL.h中声明一个用于传递Vector3布局体的一个函数,代码如下所示:
EXTERN_C
TESTCPPDLL_API
void
__stdcall SendStructFromCSToCPP(Vector3 vector);
在TestCPPDLL.cpp中将其实现,代码如下所示:
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函数的声明,代码如下所示:
[DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "SendStructFromCSToCPP")]
extern
static
void SendStructFromCSToCPP(Vector3 vector);
C#中的调用过程如下所示:
//建树一个Vector3的实例
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); //传入一个整型指针,将其所指向的内容加1 EXTERN_C TESTCPPDLL_API void __stdcall AddInt(int *i); //传入一个整型数组的指针以及数组长度,遍历每一个元素并且输出 EXTERN_C TESTCPPDLL_API void __stdcall AddIntArray(int *firstElement,int arraylength); //在C++中生成一个整型数组,并且数组指针返回给C# EXTERN_C TESTCPPDLL_API int* __stdcall GetArrayFromCPP(); //定义一个函数指针 typedef void (__stdcall *CPPCallback)(int tick); //定义一个用于设置函数指针的办法, //并在该函数中调用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#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<
完全的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(); //定义一个委托,返回值为空,存在一个整型参数 public delegate void CSCallback(int tick); //定义一个用于回调的办法,与前面定义的委托的原型一样 //该办法会被C++所调用 static void CSCallbackFunction(int tick) { Console.WriteLine(tick.ToString()); } //定义一个委托类型的实例, //在主法度中该委托实例将指向前面定义的CSCallbackFunction办法 static CSCallback callback; //这里应用CSCallback委托类型来兼容C++里的CPPCallback函数指针 [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); //因为应用指针,因为要声明非安然域 unsafe { //在传递字符串时,将字符地点的内存固化, //并取出字符数组的指针 fixed (char* p = &("hello".ToCharArray()[0])) { //调用办法 WriteString(p); } } unsafe { // 调用C++中的AddInt办法 int i = 10; AddInt(&i); Console.WriteLine(i); //调用C++中的AddIntArray办法将C#中的数据传递到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); } //调用C++中的GetArrayFromCPP办法获取一个C++中建树的数组 int* pArrayPointer = null; pArrayPointer = GetArrayFromCPP(); for (int iArr = 0; iArr < 10; iArr++) { Console.WriteLine(*pArrayPointer); pArrayPointer++; } } //让委托指向将被回调的办法 callback = CSCallbackFunction; //将委托传递给C++ SetCallback(callback); //建树一个Vector3的实例 Vector3 vector = new Vector3() { X = 10, Y = 20, Z = 30 }; //将vector传递给C++并在C++中输出 SendStructFromCSToCPP(vector); Console.Read(); } } }
文献来源:http://www.mysjtu.com/page/M0/S652/652679.html