1、http:/ 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 是不能跨平台的),所以使用了本方法,下面将对本方法展开详细的说明。测试平台:Windo
2、ws7 64 位,VS2010,.NET4.0注意事项:PInvoke 从功能上来说,只支持函数调用,在被导出的函数前面一定要添加extern C来指明导出函数的时候使用 C 语言方式编译和连接,这样保证函数定义的名字和导出的名字相同,否则如果默认按 C+方式导出,那个函数的名字就会变得乱七八糟,我们的程序就无法找到入口点了。本文将说明以下几点: 互调的基本原理 基本数据类型的传递 指针的传递 函数指针的传递 结构体的传递1. 互调的基本原理首先,我们来看一个再常规不过的概念数据类型我们知道在大多数的静态语言中定义变量的时候都要先指定其数据类型,所谓数据类型,都是人们强加的一个便于记忆的名称,究其本质就是指明了这个数据在内存里到底是占用了几个字节,程序在运行的时候,首先找到这个数据的地址,然后再按着该类型的长度,读取相对应的内存,然后再处理。了解了前面这个事儿,所有编程语言之间进行互调就有点门道儿了。对于不同语言之间的互调,只要将该数据的指针(内存地址)传递给另一个语言,在另一个语言中根据通信协议将指针所指向的数据存储入长度对应的数据类型即可,当然要满足以下几点:1. 对于像 Java
3、,.NET 这样有运行时虚拟机编程语言来讲,由于虚拟机会让堆内存来回转移,因此,在进行互调的时候,要保证正在被互调的数据所在的内存一定要固定,不能被转移。2. 有一些编程语言支持指针,有一些语言不支持指针(如 Java),这个问题并不重要,所谓指针,其实就是一个内存地址,对于 32 位 OS 的指针是一个 32 位整数,而对于 64 位机 OS 的指针是一个 64 位整数。因为大多数语言中都有整型数,所以可以利用整型来接收指针。2. 基本数据类型的传递互调过程中,最基本要传递的无非是数值和字符,即:int,long,float,char 等等,但是此类型非彼类型,C/C+ 与 C#中有一些数据类型长度是不一样的,下表中列出常见数据类型的异同:C/C+ C# 长度short short 2Bytesint int 4Byteslong(该类型在传递的时候常常会弄混) int 4Bytesbool bool 1Bytechar(Ascii 码字符 ) byte 1Bytewchar_t(Unicode 字符,该类型与 C#中的 Char 兼容) char 2Bytesfloat float
4、 4Bytesdouble 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的宏,该宏对应的内容是_d
5、eclspec(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 文件的位置,第二个
6、参数EntryPoint用来指明对应的 C/C+中的函数名称是什么。extern关键字表明该处声明的这个 Add 方法是一个外部调用。该方法声明完毕之后,就可以像调用一个普通的静态方法一样去使用了。下面是示例程序:class ProgramDllImport(E:exTestCPPDLLDebugTestCPPDLL.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 中添加一个新的函数声
7、明,代码如下: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#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
8、)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#
9、代码如下所示:using System;using System.Collections.Generic;using System.Runtime.InteropServices;using System.Text;namespace ConsoleApplication1 class 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) e
《c#与cc++的交互》由会员第***分享,可在线阅读,更多相关《c#与cc++的交互》请在金锄头文库上搜索。