C# 封装 C++的dll

发布时间 2023-09-09 09:16:41作者: 流水若冰
C# 的程序引用C++的dll时,首先要保证两者基于的平台一致,比如都是x64,或者都是x86的程序,否者两者之间不能直接调用,然后,要保证两者的数据类型可以相互识别,相互通用。在此重点介绍几个常用的数据转换。
  1. C++的char* 和 char[] 数组,对应到C#的string 类型
  2. C++的Handle 类型,一般是一个很大的整数,C#可以使用 IntPtr
  3. 对于指针类型的要使用ref
  4. C++中的结构体,可以在C#中声明同样名称的结构体,但是要进行一定的设置
    1. 使用StructLayout 特性设置结构体,当有char时,要设置 CharSet = CharSet.Ansi
    2. 使用string时,要使用 [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)] 特性修饰,并分配与原c++中的char数组相同大小的内存
使用DllImportAttribute(string dllPath) 来构造C++的同名函数,此特性有几个常用字段
  • CharSet 指示如何向方法传送字符串参数,并控制名称重整
    • None 与 Ansi具有相同的行为
    • Ansi 以单字节的形式封装传送字符串
    • Auto针对操作系统自动封装字符串
    • Unicode 以双字节的形式封送字符串
  • CallingConvention 指示入口点的调用规则
    • Cdecl 调用方清理堆栈
    • StdCall 被调用方清理堆栈
    • ThisCall 第一个参数是this指针
    • Winapi 默认平台调用约定
 
结构体与函数举例 
 
结构体举例1:
C++结构体
struct SGP_ANALYTIC_TEMP
{
    int rule_id;     
    char rule_name[50];   
    float max_temp; 
}; 
 
C#对应的结构体声明
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct SGP_ANALYTIC_TEMP
{
    public int rule_id = 0;    

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]
    public string rule_name="123";   

    public float max_temp = 0;//最高温度值

}
结构体举例2:结构体中嵌套结构体,此时对于结构体数组,也要使用MarshalAs指定类型,并分配内存
C++结构体
struct SGP_GENERAL_INFO
{       
       int range_num;//测温范围数量
       
       SGP_RANGE range[3];//测温范围
       
       char vl_rtsp_url[50];//可见光主码流rtsp地址
};

C++ 函数类型 
int SGP_GetGeneralInfo(SGP_HANDLE handle, SGP_GENERAL_INFO *output);

C# 对应的结构体
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct SGP_GENERAL_INFO
{

    
    public int range_num ;//测温范围数量
   
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
    public SGP_RANGE[] range;//测温范围

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]
    public string vl_sub_rtsp_url;//可见光辅码流rtsp地址
}

C# 函数类型
[DllImport(sdkPath, EntryPoint = "SGP_GetGeneralInfo", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern int SGP_GetGeneralInfo(IntPtr handle, ref SGP_GENERAL_INFO output);

 

遇到的问题
问题1 :
在引入DLL中的方法时,有时会出现以下错误报告:“在使用托管代码调用非托管代码时,发生“ 对 PInvoke 函数的调用导致堆栈不对称。原因可能是托管的 PInvoke 签名与非托管的目标签名不匹配。请检查 PInvoke 签名的调用约定和参数与非托管的目标签名是否匹配。
解决方法:
在DllImport时添加 CallingConvention = CallingConvention.Cdecl 即调用方清理堆栈,注意,当调用的C++dll还依赖其他dll时,需要把所调用的dll文件都放在一起,否则会抛出 System.DllNotFoundException的异常
 
问题2:
0
 
解决方法:
主要是由于Dll编译的平台和调用它的工程的平台不一致导致的,可都统一打到X64平台下编译