前不久昊问起来如何让c++和c#相互调用dll,后来专门去搜索了下,现在衣服自己洗分享出来,可能对大家有帮助。
首先我们来说c#调用c++编译的dll。这个网上已经很多了,c++里和普通写法一样,设置导出函数等等,例如c++里有一个函数 int cplusplus(int a,int b),那么在C#里,可以先申明:
[DllImport (abc.dll)]
public extern static int cplusplus(int a,int b);
然后就可以像普通的C#函数一样调用了。有几个需要注意的地方,
1、DllImport 后括号里输入c++ 的dll 的文件名,如果该dll不是在系统目录或者环境变量的目录,或者是c#程序的bin目录里,这里就应该输入完整的路径。建议放到c#的bin目录里。
2、函数必须声明为public的,不要忘记了extern关键字,当然static也所必需的。
反过来,c++是如何调用c#的dll呢,例如c#里有一个函数 public int csharp(int a,int b)在类Pipe下,而Pipe类属于ABC命名空间,那么对于c++项目,首先应该设置项目属性,打开公共语言运行时支持,即/clr标记,然后c++项目中添加c#的dll文件引用,接下来,在cpp文件里添加c#对应函数的命名空间 using namespace ABC;最后就是在需要调用的地方,使用托管代码的方式调用:
Pipe ^p = gcnew Pipe();
int a = p.csharp(3,4);
网上有同学提及编译失败,有一种可能是没有添加dll的引用,如果不喜欢添加引用,那么在cpp 文件里,使用 #using “..\Debug\csharp.dll” 也是一样可以的,不过这里就是用using,而非 include关键字,当然绝对路径也是不可少的。
现在c++和c#相互调用dll的方式就算完成了,有的同学对c++调用c#的dll的方式不太认可。为什么呢,因为要用托管代码的方式。其实上面描述的方法,本质上是使用的COM调用来完成的,微软也给出了直接COM调用的示例,喜欢这种方式的同学不妨琢磨下。
上一个星期,都在解决一个问题,c# 调用 c++ 的 dll 。
问题出现在字符串上,在 win8 64位+vs2012 环境下,64位的c#去调用64位的 c++ dll。在调试的时候,总是自动退出调试,没有进入异常,也没有什么输出。dll 方法是接收2个参数,并返回一个字符串。c# 通过调用获得返回的字符串。
一开始的时候,我以为是由于 c# 和 c++ 之间不同的类型转换时,我给设置错了类型导致,然后又仔细拿网上的好几篇文章做对比,发现这块没有问题。dll 文件并不是我们这边提供的,没有源码所以也不好联调。我就给 dll 那边发邮件,描述情况,并期待他们的反馈。
对方那边就只会c++,我提供的c#代码他们看不太懂,再加上对方没有64位环境,同时也不愿意提供dll的源码给我这边调试,进度比较慢。白天就是邮件来邮件去,晚上的时候我就各种安装系统,发现这问题只在64 位系统上出现,在 32 位系统上是可以正确调用的。
后来突然想到,如果我这边新建一个 c++ 项目的话,能不能够复现呢?果然,问题复现了,现在抛开对方的 dll 文件,也可以进行调试了。经过跟踪,发现 c++ 代码完全成功执行了,但是在 c# 调用这边就崩溃了。这么看起来,似乎和 c++ 代码方面的关系不大。于是又仔细检查了 c++ 的导出函数设置,没有问题。我以为是我c#项目的问题,删掉重建了个简单的,还是不行。
接下来,我又怀疑是不是 vs 本身的问题,因为是用的 2012 的版本,恰好这个时候,vs2012 sp1补丁包出来了,果断地升级。升级后发现还是崩溃了。
到这里似乎没有进展了,最后我请教了 c++ 方面的救兵。雪飞同学果然很是强悍,不多会就给出了我一个解决方法。在 c# 这边的函数申明这里设置返回类型为 IntPtr 类型,然后使用 Marshal.PtrToStringAuto 方法进行转换为托管字符串。问题就解决了。
我们一起还讨论这个问题出现的原因,一个可能的原因是在64位平台上,c++ 返回的是一个64位的指针,但是c#在调用的时候,并没有作为64位指针来获取,或者说拿了一个错误的指针,导致字符串获取失败。有可能在 c# 64位平台上字符串的初始化的时候,从非托管代码到托管代码的构造函数里存在缺陷。当然这个问题,是不是这样的,我们就不清楚了。获取给 vs 的开发team发个邮件可以得到更详细的信息。
在今天的时候,我越想越愤懑,为什么别的语言可以,c# 就非得这样,在32位和64位平台上还要做区分代码编写。碰巧下午我测试时先申明了字符串,然后再调用,发现问题居然解决了。似乎验证了和雪飞同学一起讨论的结果。
可是,最好的一个解决方法居然是这样的。这一刻,我觉得太蛋疼了。真的。
string s=””;s=GetInfo(); 的写法和 string s=GetInfo(); 之间对于托管代码和非托管代码居然有这样的区别。
还有另外一个问题,就是结构体转换成字节时,进入了异常。也是字符串的问题,后来根据 google 上面的搜索解决了。如果结构体里包含字符数组,在转换成字节时,需要先定义长度,不然会转换失败。具体来说就是在结构体里字符数组前面添加属性定义。例如
[MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 6)] public char[] device ; //这里的 device 为 “iphone”,长度为6,所以 SizeConst=6