C#调用非托管C++DLL:直接调用法

在实际软件开发过程中,由于公司使用了多种语言开发,在C#中可能需要实现某个功能,而该功能可能用其他语言已经实现了,那么我们可以调用其他语言写好的模块吗?还有就是,由于C#开发好的项目,我们可以利用reflector等反编译工具反编译出其源代码,所以对于一些核心算法,我们不希望被别人知道,因此为了增强代码的安全性,我们需要将一些核心算法用C或C++来编写,然后用C#来调用这些已经写好的接口。在面对以上情况时,我们该怎么做呢?


 方案一:重新实现
        针对第一种情况,我们可以将C或者C++功能用C#来重新实现,这样的话代码比较统一,维护比较方便,但是这样的话增加了软件开发的成本,把C++的代码功能改成C#涉及到指针和内存的操作比较繁琐,况且有开发好的模块为什么不重复利用呢?针对第二种情况就不能得到有效解决,虽然可以使用混淆器对代码进行混淆,但是任然不是很安全。

 方案二:封装COM组件
        我们可以将C或者C++的函数封装成COM组件,在C#中调用时比较方便,但是COM组件需要注册,而且多次注册可能也会导致一些问题,同时在处理C或者C++的类型与COM组件的类型转换的时候也可能有些麻烦。


 方案三:使用动态链接库
        我们可以直接调用C或者C++已经写好的动态链接库,对于托管的C++DLL,用C#直接调用即可,但是对于非托管的C++DLL,调用的时候有两种方法,一种是先用托管C++DLL封装非托管C++DLL,具体使用方法参见:C#调用非托管C++DLL:通过托管C++DLL间接调用,另一种是本文介绍的直接调用的方法。

一、C++dll生成

  1. 新建项目->Visual C++->Win32项目 MyDLL

    注意:C++编写的dll一般是不能直接拿来C#调用,需要先新建个C++的工程把dll里的方法重新封装成可被C#外部调用的函数。

    C#调用非托管C++DLL:直接调用法_第1张图片

    C#调用非托管C++DLL:直接调用法_第2张图片

  2. 注意:函数前一定要加extern "C"  _declspec(dllexport),可被外部引用

    //MyDLL.cpp的代码如下:
    
    extern "C"  _declspec(dllexport)int add(int a ,int b)  
    
    {  
    
    int sum=a+b;
    
    return sum;
    
    }

     

    C#调用非托管C++DLL:直接调用法_第3张图片

3、F5编译程序,在Debug文件夹中找到生成MyDLL.dll目标文件备用

C#调用非托管C++DLL:直接调用法_第4张图片

 二、C#调用C++dll

  1. 新建项目->Visual C#->控制台应用程序 dllConsoleApplication1

    C#调用非托管C++DLL:直接调用法_第5张图片

  2. 将步骤1生成的MyDLL.dll文件copy到dllConsoleApplication1工程目录\bin\Debug下

     

  3. using System;
    
    using System.Collections.Generic;
    
    using System.Linq;
    
    using System.Text;
    
    using System.Runtime.InteropServices;   //必须添加,不然DllImport报错
    
    
    namespace dllConsoleApplication1
    
    {
    
        class CPPDLL
    
        {
    
            [DllImport("MyDLL.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl))] //引入dll,并设置字符集
    
            public static extern int add(int a ,int b);
    
        }
    
        class Program
    
        {
    
            static void Main(string[] args)
    
            {
    
                int sum=CPPDLL.add(3, 4);
    
            }
    
        }
    
    }

     

  4. 编译程序,在程序中加断点,查看函数的计算结果

    C#调用非托管C++DLL:直接调用法_第6张图片

  5. 到这里,C++dll里的方法已经在C#里调用成功了。

 

 三、 可能遇到的问题:

        C#在调用动态库的过程中我也遇到了以下一些问题:

        1、C++中有指针,C#中需要使用指针吗?

        由于C++中的动态库中有指针参数,因此我也是用.NET的不安全代码,使用了C#的指针,但是也还是出现了一些问题,如在C#中传入的参数是一个二维数组时就出现了问题,最后只能改C++函数传入参数的参数类型。

        2、C#和C++中的类型如何转换呢?

        虽然C#和C++比较类似,但是其给我们的参数类型我们要与C#的参数类型一一对应起来,具体看后续说明。

        3、C++函数中的CallingConventionCharSet 怎么设置?

   调用C++函数之前一定要先确认,否则可能出现函数调用导致堆栈不对称。原因可能是托管的 PInvoke 签名与非托管的目标签名不匹配的问题。函数的CallingConvention和CharSet,可以查看动态库对应的 .h头文件。

        4、如何反编译C++的dll的名称,端口?

        可以通过Dependency Walker工具进行反编译查看别人写的动态库的信息

   5、指针函数如何传参?

   对于函数需要的指针函数,C# 调用时,可以定义委托来传入参数。 

   6、需要注意C++ dll 编译的平台是x86还是x64,是多字节的还是双字节的(Unicode)。
        7、C++写好的动态库放到那个位置呢?
        关于C++动态库的位置也是个问题,在应用中我们使用了相对路径和绝对路径进行测试,有的发现在VS中可以调用到,但是发布后发现无法调用到动态库,最后只要把动态的dll放到系统的目录system32下面才解决了改问题,目前还没找到其他的方法,如有其他的更好方法还请大家指点。
        8、如何反编译C++的dll的名称,端口?
        可以通过Dependency Walker工具进行反编译查看别人写的动态库的信息
 

 

  四. 如何调用

  c#调用c++动态库一般我们这样写:

[DllImport(SDK, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public static extern int IKSDK_Release();
 1. DllImport的第一个参数SDK是动态库dll的路径,此dll放在程序运行的根目录bin\Debug或者c:windows/sytem32下,建议在程序根目录创建一个子目录来放置相应的C++ 动态库文件,方便以后更新。 

  2. CallingConvention 参数是c#调用c++的方式 是个枚举 msdn解释如下:

Cdecl 调用方清理堆栈。这使您能够调用具有 varargs 的函数(如 Printf),使之可用于接受可变数目的参数的方法。 
FastCal 不支持此调用约定。
StdCall 被调用方清理堆栈。这是使用平台 invoke 调用非托管函数的默认约定。 
ThisCall 第一个参数是 this 指针,它存储在寄存器 ECX 中。其他参数被推送到堆栈上。此调用约定用于对从非托管 DLL 导出的类调用方法。 
Winapi 此成员实际上不是调用约定,而是使用了默认平台调用约定。例如,在 Windows 上默认为 StdCall,在 Windows CE.NET 上默认为 Cdecl。 

  3. CharSet参数是控制名称重整以及将字符串参数封送到函数中的方式。 默认值为 CharSet.Ansi。

  4. entrypoint参数用于标识函数在 DLL 中的位置。在托管对象中,目标函数的原名或序号入口点将标识跨越交互操作边界的函数。此外,您可以将入口点映射到一个不同的名称,这实际上是将函数重命名。一般默认不设置此参数。

  5. 其他参数,请查看MSDN对于 DllImportAttribute 的说明。

 

 

你可能感兴趣的:(C++,C#)