C#调用C++dll方法和注意事项

在实际C#开发项目中,存在如下两种情况

  1. C#调用第三方库,而第三方库是使用C++编写的;
  2. 牵涉到项目源代码保密,C#代码容易被反编译,因此抽取核心算法部分使用C++编写

这时候就涉及C#托管代码与C++非托管代码互相调用。

本文介绍C#调用C++的方法以及在C#调用C++产生问题的排查过程和经验总结。

下面介绍C#如何静态和动态调用C++库;

一、C#中静态调用C++动态链接

1. 建立 CppDemo,建立的时候选择DLL动态库。

2. 在DllDemo.cpp文件中添加一个Add函数

extern "C" __declspec(dllexport) int Add(int a,int b)

{

    return a+b;

}

3. 编译生成DllDem.dll提供C#使用.

4. 新建C#工程,选择控制台测试程序InteropDemo

5. 在Program.cs中添加引用:using System.Runtime.InteropServices;

6. 在pulic class Program添加如下代码:

using System;

using System.Collections.Generic;

using System.Text;

using System.Runtime.InteropServices;

namespace InteropDemo

{

    class Program

    {

        [DllImport("CppDemo.dll", EntryPoint = "Add", ExactSpelling = false, CallingConvention = CallingConvention.Cdecl)]

        public static extern int Add(int a, int b); //

        static void Main(string[] args)

        {

            Console.WriteLine(Add(1, 2));

            Console.Read();

        }

    }

}

C#中导入C++库并导出需要使用的函数,这样就可以实现C#静态调用C++动态链接库了。。

二、C# 中动态调用C++动态链接

C#除了可以静态调用C++dll以外,通过简单的封装也可以实现C++一样动态加载调用dll的功能,只要封装C++中LoadLibrary, GetProcess, FreeLibrary这几个函数就可以实现动态调用动态链接库。实现过程如下:

1. 将kernel32中的几个方法封装成本地调用类LoadLibraryMethod Code
using System;

using System.Collections.Generic;

using System.Text;

using System.Runtime.InteropServices;

 

namespace InteropDemo

{

    public static class LoadLibraryMethod

    {

        [DllImport("kernel32.dll", EntryPoint = "LoadLibrary")]

        public static extern int LoadLibrary(

            [MarshalAs(UnmanagedType.LPStr)] string lpLibFileName);

 

        [DllImport("kernel32.dll", EntryPoint = "GetProcAddress")]

        public static extern IntPtr GetProcAddress(int hModule,

            [MarshalAs(UnmanagedType.LPStr)] string lpProcName);

 

        [DllImport("kernel32.dll", EntryPoint = "FreeLibrary")]

        public static extern bool FreeLibrary(int hModule);

    }

}

2. 使用LoadLibraryMethod类动态加载C++动态链接库,获得函数指针,并且将指针封装成C#中的委托。原因很简单,C#中已经不能使用指针了,如下:

using System;

using System.Collections.Generic;

using System.Text;

using System.Runtime.InteropServices;

 

namespace InteropDemo

{

class Program

{

static void Main(string[] args)

{

//1. 动态加载C++ Dll

int hModule = LoadLibraryMethod.LoadLibrary(@"c:\CppDemo.dll");

if (hModule == 0) return;

 

//2. 读取函数指针

IntPtr intPtr = LoadLibraryMethod.GetProcAddress(hModule, "Add");

 

//3. 将函数指针封装成委托

Add addFunction = (Add)Marshal.GetDelegateForFunctionPointer(intPtr, typeof(Add));

 

//4. 测试

Console.WriteLine(addFunction(1, 2));

Console.Read();

}

 

///

/// 函数指针

///

///

///

///

delegate int Add(int a, int b);

 

}

}

三、 C#调用C++问题排查案例

在实际应用出现一个问题,我们提供一个C++DLL给第三方使用,第三方使用C#调用,C#在调用C++的时候总是崩溃,解决该问题经历了如下排查过程:

  1. 使用Windbug进行调试,由于C#托管调用C++导致C++dll库崩溃堆栈找不到,无法确认崩溃位置。
  2. 在提供的DLL中的各个导出函数入口和出口增加日志,该方案可行,最终运行发现在DLL中回调函数ResultCallback4C 调用前后的日志有开始没结束,确定是回调函数返回时崩溃,由于自己demo调用没有问题,怀疑ResultCallback4C回调函数内部处理有问题。
  3. 确定可能是回调函数有问题后,将回调注释掉,发现正常,初步断定是调用方回调内部处理有问题。
  4. 第三方调用为确认调用没问题,将C#中回调函数处理逻辑全部注释,发现仍然有问题,排除了第三方回调内部问题。
  5. 经过上述排查后仍然未能解决问题,之后整理了下思路和思考了C++ C#的一些性质,怀疑到可能是函数调用约定的问题,果断将调用约定由cdeclcall改为stdcall,编译重新提供给第三方,更新之后发现没有问题,最终确定第三方在定义回调函数时候CallingConvention = CallingConvention.stdcall)使用的stdcall,而和我们我们定义的不一致,导致崩溃。

四、 注意事项

1. C#调用C++调用约定必须一致,当然C++本身也是一样。

2. C++和c#中对应的数据结构大小一致.

3. c#引用函数的参数类型和c++函数中的参数类型一致。

你可能感兴趣的:(编程语言)