C#与CC++的交互.doc_第1页
C#与CC++的交互.doc_第2页
C#与CC++的交互.doc_第3页
C#与CC++的交互.doc_第4页
C#与CC++的交互.doc_第5页
已阅读5页,还剩32页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

/kf/201112/113590.htmlC#与C/C+的交互warensoft 中科院计算所培训中心欢迎转载,请注明出处及作者最近在编写Warensoft3D游戏引擎,并预计明年年初发布测试版本,底层引擎使用DirectX和MONO来编写,上层的逻辑使用C#来编写,因此编写了大量C#与C+互调的代码,现在经验写出来与大家分享,并希望后来者少走弯路。C#与C+交互,总体来说可以有两种方法: 利用C+/CLI作为代理中间层 利用PInvoke实现直接调用第一种方法:实现起来比较简单直观,并且可以实现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+方式导出,那个函数的名字就会变得乱七八糟,我们的程序就无法找到入口点了。本文将说明以下几点: 互调的基本原理 基本数据类型的传递 指针的传递 函数指针的传递 结构体的传递1. 互调的基本原理首先,我们来看一个再常规不过的概念数据类型我们知道在大多数的静态语言中定义变量的时候都要先指定其数据类型,所谓数据类型,都是人们强加的一个便于记忆的名称,究其本质就是指明了这个数据在内存里到底是占用了几个字节,程序在运行的时候,首先找到这个数据的地址,然后再按着该类型的长度,读取相对应的内存,然后再处理。了解了前面这个事儿,所有编程语言之间进行互调就有点门道儿了。对于不同语言之间的互调,只要将该数据的指针(内存地址)传递给另一个语言,在另一个语言中根据通信协议将指针所指向的数据存储入长度对应的数据类型即可,当然要满足以下几点:1. 对于像Java,.NET这样有运行时虚拟机编程语言来讲,由于虚拟机会让堆内存来回转移,因此,在进行互调的时候,要保证正在被互调的数据所在的内存一定要固定,不能被转移。2. 有一些编程语言支持指针,有一些语言不支持指针(如Java),这个问题并不重要,所谓指针,其实就是一个内存地址,对于32位OS的指针是一个32位整数,而对于64位机OS的指针是一个64位整数。因为大多数语言中都有整型数,所以可以利用整型来接收指针。2. 基本数据类型的传递互调过程中,最基本要传递的无非是数值和字符,即:int,long,float,char等等,但是此类型非彼类型,C/C+与C#中有一些数据类型长度是不一样的,下表中列出常见数据类型的异同:C/C+C#长度shortshort2Bytesintint4Byteslong(该类型在传递的时候常常会弄混)int4Bytesboolbool1Bytechar(Ascii码字符)byte1Bytewchar_t(Unicode字符,该类型与C#中的Char兼容)char2Bytesfloatfloat4Bytesdoubledouble8Bytes最容易弄混的是就是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等同于在函数前面添加externC,意思是该函数在编译和连接时使用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方法是一个外部调用。该方法声明完毕之后,就可以像调用一个普通的静态方法一样去使用了。下面是示例程序:classProgramDllImport(E:exTestCPPDLLDebugTestCPPDLL.dll, EntryPoint =Add)externstaticintAdd(inta,intb);staticvoidMain(string args)intc = Add(1,2);Console.WriteLine(c);Console.Read();在运行C#程序之前,先要修改C#的项目属性,如下图所示:将platform target设置为x86,并且允许非安全代码(后面有用)。然后运行该C#程序,其结果如下图所示:第五步:前面的Add方法中传递的是数值类型(int),其他的数据类型,如float,double,和bool类型的传递方式是一样的,下面演示如何传递字符串。在TestCPPDLL.h中添加一个新的函数声明,代码如下:EXTERN_C TESTCPPDLL_APIvoid_stdcallWriteString(wchar_t*content);这里的参数是wchar_t类型的指针,对应着C#中的char类型。TestCPPDLL.cpp中添加如下代码:TESTCPPDLL_APIvoid_stdcallWriteString(wchar_t*content)coutcontent;该代码的功能就是将输入的字符串通过C+在控制台上输出。下面是在C#中的声明:DllImport(E:exTestCPPDLLDebugTestCPPDLL.dll, EntryPoint =WriteString)externunsafestaticvoidWriteString(char*c);调用过程如下所示:/因为使用指针,因为要声明非安全域unsafe/在传递字符串时,将字符所在的内存固化,/并取出字符数组的指针fixed(char* p = &(hello.ToCharArray()0)/调用方法WriteString(p);其运行效果如下图所示:3.指针的传递根据前面介绍的数据类型对照表,我们可以直接在方法中传递指针,但是要注意的是我们常常需要将数组的指针(数据入口地址,第一个元素的地址),数据从C/C+到C#时问题不大,但是如果从C#到C/C+时一定要将数组先固化,然后再传递处理。下面演示如何传递指针,首先在TestCPPDLL.h中添加下列声明:/传入一个整型指针,将其所指向的内容加1EXTERN_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+; coutendl;int *arrPtr;TESTCPPDLL_API int* _stdcall GetArrayFromCPP() arrPtr=new int10; for (int i = 0; i 10; i+) arrPtri=i; return arrPtr;对应调用的C#代码如下所示:DllImport(E:exTestCPPDLLDebugTestCPPDLL.dll, EntryPoint = AddInt)extern unsafe static void AddInt(int* i);DllImport(E:exTestCPPDLLDebugTestCPPDLL.dll, EntryPoint = AddIntArray)extern unsafe static void AddIntArray(int* firstElement, int arraylength);DllImport(E:exTestCPPDLLDebugTestCPPDLL.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 int10;for (int iArr = 0; iArr 10; iArr+)CSArrayiArr = iArr;fixed (int* pCSArray = &CSArray0)AddIntArray(pCSArray, 10);/调用C+中的GetArrayFromCPP方法获取一个C+中建立的数组int* pArrayPointer = null;pArrayPointer = GetArrayFromCPP();for (int iArr = 0; iArr 10; iArr+)Console.WriteLine(*pArrayPointer);pArrayPointer+;4. 函数指针的传递前面说明的都是简单数据类型的及其指针的传递,利用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:exTestCPPDLLDebugTestCPPDLL.dll, EntryPoint = SetCallback)extern static void SetCallback(CSCallback callback);在C#中的调用过程如下所示:/让委托指向将被回调的方法callback = CSCallbackFunction;/将委托传递给C+SetCallback(callback);SetCallback方法被执行后,在C#中定义的CSCallbackFunction就会被C+所调用。5. 结构体的传递传递结构体的想法和传递一个int类型数据类似,struct中的数据是在内存中顺序排列的,只要保证保证以下几点,就可以直接传递结构体,甚至是结构体的指针:要传递的成员为公有的值类型字段C#中结构体字段类型与C+结构体中的字段类型相兼容C#结构中的字段顺序与C+结构体中的字段顺序相同,要保证该功能,需要将C#结构体标记为StructLayout( LayoutKind.Sequential)下面通过代码进行说明,首先在C#中添加一个结构体,代码如下所示:StructLayout( LayoutKind.Sequential)struct Vector3public 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) coutgot vector3 in cpp,x:; coutvector.X; cout,Y:; coutvector.Y; cout,Z:; coutvector.Z;在C#中添加对SendStructFromCSToCPP函数的声明,代码如下所示:DllImport(E:exTestCPPDLLDebugTestCPPDLL.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);/传入一个整型指针,将其所指向的内容加1EXTERN_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.husing 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+; coutendl;int *arrPtr;TESTCPPDLL_API int* _stdcall GetArrayFromCPP() arrPtr=new int10; for (int i = 0; i 10; i+) arrPtri=i; return arrPtr;TESTCPPDLL_API void _stdcall SetCallback(CPPCallback callback) int tick=100; /下面的代码是对C#中委托进行调用 callback(tick);TESTCPPDLL_API void _stdcall SendStructFromCSToCPP(Vector3 vector) coutgot vector3 in cpp,x:; coutvector.X; cout,Y:; coutvector.Y; cout,Z:; coutvector.Z;完整的C#代码如下所示:using System;using System.Collections.Generic;using System.Runtime.InteropServices;using System.Text;namespace ConsoleApplication1class ProgramDllImport(E:exTestCPPDLLDebugTestCPPDLL.dll, EntryPoint = Add)extern static int Add(int a, int b);DllImport(E:exTestCPPDLLDebugTestCPPDLL.dll, EntryPoint = WriteString)extern unsafe static void WriteString(char* c);DllImport(E:exTestCPPDLLDebugTestCPPDLL.dll, EntryPoint = AddInt)extern unsafe static void AddInt(int* i);DllImport(E:exTestCPPDLLDebugTestCPPDLL.dll, EntryPoint = AddIntArray)extern unsafe static void AddIntArray(int* firstElement, int arraylength);DllImport(E:exTestCPPDLLDebugTestCPPDLL.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:exTestCPPDLLDe

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论