如何在C#代码中调用C++函数
目录
参考资料:
[REF1] https://zh.wikipedia.org/wiki/%E5%B9%B3%E5%8F%B0%E5%8F%AB%E7%94%A8%E6%9C%8D%E5%8B%99
[REF2] https://www.cnblogs.com/KevinSong/p/3383436.html
[REF3] https://www.cnblogs.com/mazhenyu/p/4995204.html
[REF4] https://blog.****.net/yanhuatangtang/article/details/78889321
[REF5] https://blog.****.net/sxd125/article/details/82350652
[REF6] https://blog.****.net/ddupd/article/details/17652583
[REF7] https://blog.****.net/qincode/article/details/12441711
背景介绍:
.NET平台下的互操作有三种方式:平台调用,C++ Interop和COM Interop。
平台调用:
平台调用服务(英语:Platform Invocation Services),或称P/Invoke,通常指微软的公共语言运行时提供的跨平台调用方式。
平台调用服务是公共语言基础设施相关实现的一个特性。这一特性与微软的公共语言运行时提供的较为类似,因此一般提到P/Invoke多数指微软的.NET实现方案。这一方案能够实现通过托管代码访问原生代码。使用P/Invoke可以通过CLR来控制DLL的加载,以及将非托管代码的数据类型转换为托管数据类型。
C++ Interop:使用P/Invoke可以封送大部分的操作,但是对于复杂的操作处理起来就非常麻烦,同时无法处理异常(无法获取原来异常的真实信息)。同时,一般来说Interop性能比较好。
COM Interop 是一种让 .NET Framework 的程序能够和 COM 的程序相互操作的一种桥接技术[维基百科]。它可以让 .NET Framework 的程序使用 COM 组件,也可以让 COM 程序使用 .NET Framework 的组件。它可以让C#去调用C++的方法,C++去调用C#的方法。在上面的参考资料中有给出.NET平台与COM组件互操作的例子。
这里我们以平台调用服务的方式实现在C#托管代码中调用C++原生函数。具体过程如下:
C++工程项目封装为DLL库:
C#代码是无法直接调用c++代码的,需要将C#代码封装成托管代码,编译成dll才可以使用。此处以VS2017 社区版为例:
1.新建C++工程项目DemoCsharpImportCplus
2.在cpp文件中写入C#要调用的函数Add
3.在头文件pch.h文件中加入编译信息
4 调试函数没问题后,选择1.文件编译后为DLL;2.编译为 C++ 代码 (/TP)。
具体调用:
将编译后的ExportCplusDll.DLL文件拷贝至C#工程Bin目录下。并增加相应的调用代码,如下所示:
运行该程序, 程序可能会报如下几种错误:
常见错误:
A.未处理System.BadImageFormatException,试图加载格式不正确的程序。 (异常来自 HRESULT:0x8007000B)
解决方案:将C#程序的平台目标改为X86.
其原因是该C++API是在32位系统下面开发的,在64位系统上面开发编译的时候需要将生成的目标平台设为X86.
B.无法在 DLL“ExportCplusDll”中找到名为“XXX”的入口点.
是由于C++代码中的函数在实际打包为DLL后,为了代码的安全实际的函数名变化了,需要利用微软的dumpbin.exe查看dll中的函数名。C/C++打包为DLL后的函数名以及C#代码中的调用方式还不一样,具体见参考资料[REF1]。
dumpbin查看DLL函数使用方式如下:
C.对PInvoke函数的调用导致堆栈不对称问题
解决方案:
添加属性:CallingConvention = CallingConvention.Cdecl
具体各属性含义见参考资料[REF7]