zoukankan      html  css  js  c++  java
  • [转]C#调用C++ DLL

    在开发过程中经常需要在C#中调用C++编写的DLL,中间碰到过一些问题,这里做个总结,方便以后参考。

    • 类型对照问题
    • 内存释放问题
    • 版本问题(x86与x64)
    • 编译问题(静态与动态)
    • 资源加载问题
    • 异常捕获与问题定位

    类型对照问题

      c#调用c++方法时,首先要在类中定义一个与c++方法对应的外部方法,因为该方法是用C#语言定义的,那么肯定要弄清楚C#类型与c++类型如何对应,否则会导致调用失败,关于这个问题其实不算什么问题,网上有很多类型对照的文章,都有很详细的对应列表,用的时候参考一下就可以了。还可以使用工具,自动根据c++方法签名生成对应的C# import方法签名,参考P/Invoke Interop Assistant

      这里有一个调用约定的问题:__cdecl是C/C++函数的默认调用约定,__stdcall是C#函数的默认调用约定,也是所有的Windows API的调用方式。

      有一个问题还是要注意的,在x86模式下c#中的int对应c++中的int,而在x64模式下C#中的int是对应c++中的long,就这么一个小小的变量类型,在不经意间可能就会导致c++代码出错。

      C#调用C++(Win32)时常用类型转换总结

      

    内存释放问题

      由于这个问题经常遇到,并且如果不能解决的话肯定不会再考虑使用该dll了,这是一个可用性的问题。所以我在调用c++方法的时候,通常都会先批量跑一边,通过日志记录下每调用一次方法后,当前进程所占用的内存大小,这样在运行一段时间以后,就能很清楚的看到内存是否持续增长,如果是的话就需要和编写该dll的同事进行沟通,给他们提供测试数据,确认产生问题的原因。

      有时即使C++中的方法进行了内存释放,并且在c++测试代码中已经没有内存增长问题了,但是在C#中调用的时候内存还是会持续增长,该问题可能跟使用的场景有关,我这里是因为调用了一个返回char *类型的c++方法,我直接用C#中的字符串类型的一个变量接收了,结果发现内存总是释放不了,后来让同事把c++的方法更改了一下参数,然后在C#中用StringBuilder类型的变量作为参数传入c++方法中来接收该方法的结果,这样该内存问题就解决了。

    C#
    // 在C#中声明与C++方法对应的dllimport方法
    [DllImport("dllname.dll", CharSet = CharSet.Ansi, EntryPoint = "Handle", CallingConvention = CallingConvention.Cdecl)]
    public static extern bool CPPMethod(string content,StringBuilder result);
    
    // 该变量用来接收c++方法的处理结果,作为传出参数传入c++方法,在构造的时候必须明确指定大小
    // 如果不指定或者指定的大小不足,会导致c++方法出现空间分配不够的异常
    StringBuilder resultSB = new StringBuilder(length);
    string cppParam = "some content";
    bool isSuccess = CPPMethod(cppParam,resultSB);  // CPPMethod是与C++方法对应的dllimport方法
    
    C++ 
    // C++中的DLL函数原型,即:C#中要调用的方法,此处不再返回char *类型的结果,而是将结果放到传出参数result中
    extern "C" __declspec(dllexport) bool Handle(char* content, char* result);  // result为传出参数

    版本问题(x86与x64)

      版本不匹配的话,在调试时会提示正在加载格式不正确的dll。

      如果使用的是32位的c++版dll,需要把C#项目的编译平台设置为x86,如果使用的是64位的c++版dll,则设置为any cpu和x64都可以,这个需要自己根据实际情况对应好就可以了。

      如果程序对内存的使用比较高,最好将程序编译为64位,因为32位程序对单进程的内存大小有限制,经测试最大不超过2G。因为我的程序刚开始使用的是32位的c++版dll,并且在运行时需要调用这些dll加载很多资源,加载完这些资源进程占用的内存就差不多快2G了,所以总会莫名其妙的崩掉,甚至在加载的过程中就直接崩掉了,当时预感到是32位的问题,后来让同事将dll重新编译为64位后就没有这个问题了。可以通过dumpbin命令判断一个dll是32位还是64位,打开vs开发人员命令提示,输入:dumpbin /headers 你的dll路径,例如:dumpbin /headers d: est.dll,如下图所示:

    如果是32位dll,红框那里会显示 

    这里有一个地方需要注意,默认asp.net项目在调试时会运行在32位下的iisexpress进程中,如果你的项目是64位的,那么需要在VS中将iisexpress配置为64位模式,如下图所示:

    编译问题(静态编译与动态编译)

      这个问题在运行时有时候会提示dll加载不成功,这个问题在不同的电脑上会有不同的体现,有的存在这个问题,有的就运行正常。而我本机就属于正常的,部署的服务器属于出问题的。出现这个问题后,在确认代码无误后,我用depends.exe这个工具查看了一下导致问题的那个c++版的dll都依赖什么程序集,在出问题的机器上会提示有一些依赖的dll不存在,而这些dll在运行正常的机器上是存在的。下图红色框中的为某些机器上可能会缺少的dll:

      如果缺少相关dll,该条目的左边会显示出一个黄色的问号。这个问题可以采用静态编译进行解决,关于什么是静态编译可以自行百度,总之就是将程序所依赖的dll编译到程序集中,这样即使其他机器不存在这些dll也可以正常运行了,静态编译可以在vs的项目属性中进行设置

      默认是多线程 DLL(/MD),即:动态编译,这里更改为 多线程(/MT),即:静态编译。

      刚才的配置只能解决缺少MSVCP120.DLL和MSVCR120.DLL这一类问题,对于缺少MFC相关的dll,还要经过下面的配置:

      默认是使用标准Windows库,这里改为在静态库中使用MFC

    资源加载问题(相对路径与绝对路径,dll中又调用其他dll加载资源)

      这个问题相对比较隐蔽,出现时不会抛出异常,只能通过c++方法返回的状态码来判断方法执行是否成功,要不是在这里放了一个断点,特意看了一下,可能就遗漏这个问题了。

      场景是这样的:
      我在webservice中调用c++版dll中的一个初始化方法,该方法会加载一些资源文件,我在vs中调试执行的时候没问题,发布以后居然无法加载资源,貌似是路径问题,我把资源文件放到w3wp.exe的根目录下倒是可以成功加载,放在其他目录中就不行,遇到这个问题首先想到的可能是资源所在的目录权限不够导致iis无法正常加载,因为之前有个同样的问题就是这样,但这次将资源所在的目录更改为Everyone用户的完全控制权限还是不行,并且该问题只出现在b/s项目中,c/s项目没有这个问题。并且该目录中存放了很多资源文件,有好几个c++版的dll都需要从这里加载,其他几个都没问题,就这一个dll不行,看来不是权限的问题。这时候又想是不是相对路径的问题,那我改成绝对路径吧,结果问题依旧,后来在技术群里有个大牛说试试Directory.SetCurrentDirectory,赶紧修改代码,测试了一下确实好使了。代码如下:

    // 保存当前工作目录
    string currWorkPath = Directory.GetCurrentDirectory();
    // 切换当前工作目录
    Directory.SetCurrentDirectory(resourcePath);
    // 初始化进行资源加载
    Init(resourcePath);  // 这里要注意,使用了SetCurrentDirectory方法后,resourcePath要用相对路径
    // 还原当前工作目录
    Directory.SetCurrentDirectory(currWorkPath);

      如注释所示,使用SetCurrentDirectory切换了当前工作目录后,方法中所用的路径要改为相对路径,一开始我用的是绝对路径,居然还是无法加载。

      后来发现了该问题的原因,在使用的dll中又调用另外一个dll进行资源加载,可能这样会导致那个间接调用的dll出现路径问题,所以出现资源加载失败。

    异常捕获与问题定位

      关于异常捕获,虽然在方法中添加了特性HandleProcessCorruptedStateExceptionsSecurityCritical但还是捕获不到c++中的异常,原因可能是c++在遇到某些异常时会造成程序直接退出,这样在C#中就自然捕获不到了,所以还是尽量保证c++代码的健壮性。
    如果在c#中调用了多个c++版dll中的方法,因为有时捕获不到异常,很难通过常规方法找到问题的原因,c++方法中一旦出现异常可能会直接导致进程退出了,这时可以借助操作系统中的事件查看器来找出异常是来自哪个dll,同时在原有代码中注释掉那段调用该c++方法的代码,或者mock一个方法调用,保证该段代码无异常,然后再进行测试,如果无异常,那么只要解决了那个c++方法的问题即可,如果还有异常那么就是其他dll的问题,然后可以编写测试代码单独测试曾经出问题的dll中的方法。异常捕获+事件查看器+日志可以帮助开发者发现程序的大部分问题与原因。

      转自

      https://www.cnblogs.com/neverstop/p/5901652.html#item1

      https://www.cnblogs.com/lidabo/archive/2012/06/05/2536737.html

  • 相关阅读:
    docker 安装ELK
    关于centOS安装配置mysql5.6那点事
    关于centOS安装配置xampp那点事
    PowerDesigner16.5连接Oracle数据库生成E-R图
    Microsoft Visual Studio 中工具箱不显示DevExpress控件的解决办法
    Linux 基础命令
    oracle数据库表空间创建&导入&导出
    weblogic10.3.6漏洞修改方案
    oracle数据库表空间追加数据库文件方法
    转移博客
  • 原文地址:https://www.cnblogs.com/YQ2014/p/9813198.html
Copyright © 2011-2022 走看看