CSharp 调用动态链接库 DLL

本文总结如何在 CSharp 调用动态链接库

托管代码调用非托管代码的 2 种方式方式?

  • 利用 P/Invoke (平台调用) 实现直接调用: 使用该方法在托管代码内引入非托管 DLL,但是问题是 PInvoke 不能简单的实现 C++ 类的调用
  • 利用 C++/CLI 作为代理中间层:直接使用 C++/CLI 编写托管 DLL,可以实现 C# 调用 C++ 所写的类,但是问题是 MONO 架构不支持 C++/CIL 功能,因此无法实现脱离 Microsoft.NET Framework 跨平台运行

什么是 P/Invoke (平台调用)?

  • P/Invoke 的全称是 Platform Invoke (平台调用) 它实际上是一种函数调用机制,通过 P/Invoke 允许从托管代码访问非托管库中的结构、回调和函数的技术。大多数 P/Invoke API 包含在两个命名空间中:System 和 System.Runtime.InteropServices
  • PInvoke 最简单,但只能调用函数,不能直接调用类, 但有一个折衷的办法,就是在 C++ 里面定义一系列函数,里面调用相应的类,暴露给调用方(托管语言)的只有一系列的函数接口(API)
    1
    2
    3
    4
    extern "C"
    {
    CPLUSPLUSTEST_API int adjustBrightnessContrast(unsigned char* src, int w, int h, int channel);
    }

托管代码通过 DllImport 导入 C++ 的 DLL 的过程?

  • C# 通过 P/Invoke (平台调用) 使用 C++ 的 DLL 时,需要使用 DllImport 导入
  • DllImport:用来标识该方法是非托管的代码方法,在编译器编译的时候它能够正确的认识出被该 特性标记的是外来代码段。当到达程序运行的时候,也能够正确的认识出该代码是引用非托管的 代码,这样 CLR 会去加载非托管 DLL 文件,然后查找到入口点进行调用
    1
    2
    3
    4
    5
    // CharSet 指示用在入口点中的字符集,如:CharSet=CharSet.Ansi;
    // SetLastError 指示方法是否保留 Win32"上一错误",如:SetLastError=true;
    // ExactSpelling 指示 EntryPoint 是否必须与指示的入口点的拼写完全匹配,如:ExactSpelling=false;
    // CallingConvention指示入口点的调用约定, 如:CallingConvention=CallingConvention.Winapi;
    [DllImport("convertPython.dll", EntryPoint="_startLedCheck", CallingConvention=llingConvention.Cdecl)]

托管代码通过 P/Invoke (平台调用) 使用 C++ 函数的过程?

  • (1) 定义预定于宏:用于声明 dll 中开放的函数
    1
    2
    3
    4
    5
    #ifndef EXPORTDLL_EXPORTS
    #define EXPORTDLL_API extern "C" _declspec(dllimport)
    #else
    #define EXPORTDLL_API extern "C" _declspec(dllexport)
    #endif//!EXPORTDLL_EXPORTS
  • (2) 确定调用约定:参考 DLL 函数的调用约定?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //1.1 标准调用约定
    EXPORTDLL_API void _stdcall CallingCvt_Stdcall()
    {
    wprintf(L"CallingCvt_Stdcall\n");
    }

    //1.2 C调用约定
    EXPORTDLL_API void _cdecl CallingCvt_Cdecl()
    {
    wprintf(L"CallingCvt_Cdecl\n");
    }
  • (3) C# 导入:使用 DllImport 导入 dll
    1
    2
    3
    4
    5
    6
    7
    //1.1 标准调用约定
    [DllImport("ExportDll.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode)]
    public static extern void CallingCvt_Stdcall();

    //1.2 C调用约定
    [DllImport("ExportDll.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
    public static extern void CallingCvt_Cdecl();
  • (4) 测试
    1
    2
    3
    4
    5
    //1.1 标准调用约定
    CExportDll.CallingCvt_Stdcall();

    //1.2 C调用约定
    CExportDll.CallingCvt_Cdecl();

什么是数据封送?

  • 托管代码使用 P/Invoke (平台调用) 使用非托管代代码函数时,设计参数的传递,但是由于他们分别基于公共语言运行时 (CLR) 、本机代码(Native Code),两者数据存储机制不一样,需要在托管代码一方进行数据封送处理。 封送指的就是托管内存和非托管内存之间传递数据的过程
  • 封送是双向的,由封送拆收器完成,其主要任务是: 1. 数据类型转换。非托管数据类型到托管数据类型的相互转换(输出),或者,托管数据类型到非托管数据类型的转换(输入); 2. 内存搬运。非托管内存复制到托管内存,或者,托管内存复制到非托管内存; 3. 内存释放
  • 对于每个 .NET Framework 类型均有一个默认非托管类型,可以使用 MarshalAs 重写默认封送处理: string 类型默认非托管类型是 LPTSTR,可以在非托管函数的 C# 声明中使用 MarshalAs 属性重写默认封送处理
    1
    2
    3
    // puts 函数的参数的默认封送处理已从默认值 LPTSTR 重写为 LPSTR
    [DllImport("msvcrt.dll")]
    public static extern int puts([MarshalAs(UnmanagedType.LPStr)] string m);

托管代码和非托管代码的数据传递为什么要进行封送?

  • 主要是数据存储顺序不一致
  • 在 C/C++ 中,struct 类型中的成员的一旦声明,则实例中成员在 内存中的布局 (Layout) 顺序就定下来了
  • 在.net 托管环境中, CLR 提供了更自由的方式来控制 struct 中 Layout:我们可以在定义 struct 时,在 struct 上运用 StructLayoutAttribute 特性来控制成员的内存布局。默认情况下,struct 实例中 的字段在栈上的布局 (Layout) 顺序与声明中的顺序相同,即在 struct 上运用 StructLayoutAttribute (LayoutKind.Sequential)] 特性

通过 P/Invoke 如何封送 “基础数据类型”?

  • 对于 P/Invoke (平台调用),具有相同格式的托管和 c 本机基元类型之间不需要封送处理,但是必须对不具有相同形式的类型进行封送处理。例如在 C/C 中 long 和 int 都是 4 个字节,都对应着 C# 中的 int 类型,而 C/C++ 中的 char 类型占一个字节,用来表示一个 ASCII 码字符,在 C# 中能够表示一个字节的是 byte 类型
  • C++ 定义接口
    1
    void func1(int a, double b, char c,int& d, double& e, char& f)
  • C# 调用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    [DllImport("CPPDLL.dll", EntryPoint = "func1", SetLastError = true, CharSet = CharSet.Ansi)]
    public static extern unsafe void func1(int a, double b, char c, out int d,out double e, out char f);

    public static void test_func1()
    {
    int a = 1; double b = 2.2;char c = 'H';
    int d=0; double e=0; char f=' ';
    Console.WriteLine("[i n]C# :"+a + "-" + b + "-" + c + "-" + d + "-" + e + "-" + f);
    func1(a, b, c, out d, out e, out f);
    Console.WriteLine("[out]C# :" + a + "-" + b + "-" + c + "-" + d + "-" + e + "-" + f);
    }

通过 P/Invoke 方式如何封送数组 / 指针?

  • 1)- C++ 定义为指针,C# 使用 fxied 将数组转为指针封送;2)如果 C# 需要拿到 C ++ 的不定长的数组返回,需要使用 Marshal 解析封装;3)C# 可使用指针向 C ++ 发送数据,也可直接发送数组
  • C++ 定义开放函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    void func2(int* a, int aNum,int* b,int*& c, int& cNum, int* d, int dNum) {
    // 打印C#传递过来的数组
    std::cout << "[in]C++/a:";
    for (int i = 0; i < aNum; i++)
    {
    std::cout << a[i] << "-";
    }
    std::cout << "" << endl;

    b[0] = 10;
    b[1] = 12;
    std::cout << "[out]C++/b:" << b[0] << "-" << b[1] << endl;

    cNum = 10;
    c = new int[cNum];
    std::cout << "[out]C++/c:";
    for (int i = 0; i < cNum; i++)
    {
    c[i] = 100 + i;
    std::cout << c[i] << "-";
    }
    std::cout << "" << endl;

    std::cout << "[in]C++/d:";
    for (int i = 0; i < dNum; i++) {
    std::cout << d[i] << "-";
    d[i] = i * 10 + 2;
    }
    std::cout << "" << endl;
    };
  • C# 传入数据
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    [DllImport("CPPDLL.dll", EntryPoint = "func2", SetLastError = true, CharSet = CharSet.Ansi)]
    public static extern unsafe void func2(int* a, int aNum,int* b,out IntPtr c, out int cNum,int[] d,int dNum);

    public static void test_func2()
    {
    int aNum = 3;
    int[] a=new int[aNum];
    for(int i=0;i<aNum;i++){a[i] = i + 10;}

    int[] b = new int[2];
    int dNum = 3;
    int[] d = { 1, 2, 3 };

    int cNum = 0;
    unsafe
    {
    fixed (int* a_ptr = a)
    {
    fixed (int* b_ptr = b)
    {
    IntPtr c;
    func2(a_ptr, aNum, b_ptr, out c, out cNum,d,dNum);

    Console.WriteLine("[out]C#/b:" + b[0]+","+b[1]);

    int[] arr = new int[cNum];
    Marshal.Copy(c, arr, 0, cNum);
    for (int i = 0; i < cNum; i++)
    {
    Console.Write(arr[i] + "-");
    }

    Console.WriteLine("");
    for (int i = 0; i < dNum; i++)
    {
    Console.Write(d[i] + "-");
    }
    }
    }
    }
    }
    //1. 数组a:C#向C++传递数组,并使用aNum确定数量
    //2. 数组b:C#申请空间,C++按约定空间赋值,然后C#打印
    //3. 数组c:C#向C++传递IntPtr指针,C++动态返回不定长的数据,然后C#使用Marshal解析数据
    //4. 数组d:类似数组b,C#声明不一样,使得不用使用fixed

通过 P/Invoke 方式如何封送结构体?

    • C# 和 C++ 定义相同的数据结构,然后将数据结构当作基础数据类型使用
  • 需要接收 C++ 返回时,使用 out 修饰符
  • C++ 定义函数
    1
    2
    3
    4
    5
    6
    7
    void func3(point a, point& b) {
    std::cout << "[in]C++/a: " << a.x << "," << a.y << endl;

    b.x = 10;
    b.y = 100;
    std::cout << "[out]C++/b: " << b.x << "," << b.y << endl;
    }
  • C# 调用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    [DllImport("CPPDLL.dll", EntryPoint = "func3", SetLastError = true, CharSet = CharSet.Ansi)]
    public static extern unsafe void func3(point a,out point b);

    public static void test_func3()
    {
    point a=new point(1,2);
    Console.WriteLine("[in]C#/a:" + a.x + "," + a.y);

    point b;
    func3(a, out b);
    Console.WriteLine("[out]C#/b:" + b.x + "," + b.y);
    }
    //1. 结构a:C#向C++传递结构数据
    //2. 结构b:C++声明结构变量,C++赋值,C#可以拿到其值

通过 P/Invoke 方式如何封送结构数组(单层指针)?

  • 结构数组和基础数据类型的数组传递方法类似
  • C++ 定义函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    void func4(point* a, int aNum, point* b, int bNum) {

    for (int i = 0; i < aNum; i++)
    {
    std::cout << a[i].x << "," << a[i].y << endl;
    }

    for (int i = 0; i < bNum; i++)
    {
    b[i].x = (i+1)*10+i;
    b[i].y = (i + 2) * 10 + i;
    }
    }
  • C# 调用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    [DllImport("CPPDLL.dll", EntryPoint = "func4", SetLastError = true, CharSet = CharSet.Ansi)]
    public static extern unsafe void func4(point* a, int aNum,point* b,int bNum);

    public static void test_func4()
    {
    point[] a =
    {
    new point(1,2),
    new point(3,4),
    new point(5,6)
    };
    int aNum = a.Length;

    int bNum = 2;
    point[] b = new point[bNum];

    unsafe
    {
    fixed(point* a_ptr=a)
    {
    fixed (point* b_ptr = b)
    {
    func4(a_ptr, aNum, b_ptr, bNum);

    for (int i = 0; i < bNum; i++)
    {
    Console.WriteLine(b[i].x + "," + b[i].y);
    }
    }
    }
    }
    }
    // 1. 结构指针a:既可向C++传递数据,也可向C#传递数据
    // 2. 结构指针b:既可向C++传递数据,也可向C#传递数据

通过 P/Invoke 方式如何封送结构数组(嵌套指针)?

  • 多重指针,最外层需要在 C# 指定空间,或者使用 IntPtr 传递
  • C++ 定义
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    void func6(point** ployPoints, int ployNum, int* ployPointsNum)
    {
    ployPoints[0] = new point[1];
    ployPoints[0][0].x = 1.1;
    ployPoints[0][0].y = 2.1;

    ployPoints[1] = new point[2];
    ployPoints[1][0].x = 3.1;
    ployPoints[1][0].y = 3.2;
    ployPoints[1][1].x = 4.1;
    ployPoints[1][1].y = 4.2;

    ployPoints[2] = new point[3];
    ployPoints[2][0].x = 5.1;
    ployPoints[2][0].y = 5.2;
    ployPoints[2][1].x = 6.1;
    ployPoints[2][1].y = 6.2;
    ployPoints[2][2].x = 7.1;
    ployPoints[2][2].y = 7.2;

    ployNum = 3;
    ployPointsNum[0] = 1;
    ployPointsNum[1] = 2;
    ployPointsNum[2] = 3;
    }
  • C# 调用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    [DllImport("CPPDLL.dll", EntryPoint = "func6", SetLastError = true, CharSet = CharSet.Ansi)]
    public static extern unsafe void func6(point** ployPoints, int ployNum, int[] ployPointsNum);

    // C#申请第一层空间,C++申请第二层空间
    public static void Test_func6()
    {
    unsafe
    {
    int ployNum = 3;
    point*[] ployPointsList = new point*[ployNum];
    int[] ployPointsNum = new int[ployNum];
    fixed (point** ployPoints = ployPointsList)
    {
    func6(ployPoints, ployNum, ployPointsNum);
    Console.WriteLine(ployNum);
    for (int i = 0; i < ployNum; i++)
    {
    for (int j = 0; j < ployPointsNum[i]; j++)
    {
    Console.WriteLine(ployPointsList[i][j].x + "," + ployPointsList[i][j].y);
    }
    }
    }
    }
    }
    // 1. 结构指针ployPoints:双重指针,先在C#申请第一重空间,然后在C++申请第二重空间,并返回每个二重空间的大小
    // 2. 指针指向空间长度指针ployPointsNum:第二层空间的大小,是一个列表

通过 P/Invoke 方式 C# 如何接受 C++ 的” 结构体 " 函数返回?

  • C++ 定义开放函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    typedef struct _testStru5
    {
    int iVal;
    }testStru5;
    testStru5 g_stru5;

    EXPORTDLL_API testStru5* Struct_Return()
    {
    g_stru5.iVal = 5;
    wprintf(L"Struct_Return \n");
    return(&g_stru5);
    }
  • C# 接受数据
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
    public struct testStru5
    {
    public int iVal;
    };
    [DllImport("ExportDll.dll", CharSet = CharSet.Unicode)]
    public static extern IntPtr Struct_Return();

    IntPtr struIntPtr = Struct_Return();
    testStru5 stru5 = (testStru5)(Marshal.PtrToStructure(struIntPtr, typeof(testStru5)));

通过 P/Invoke 方式 C# 如何互送 C++ 的” 结构体数组 (内含基础类型一维数组)“?

  • C++ 定义开放函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    struct cmppe_submit
    {
    char user[10];
    };

    extern "C" __declspec(dllexport) void GetUser(cmppe_submit* lpSubit)
    {
    pData->data[0] = 1;
    pData->data[1] = 2;
    pData->data[2] = 3;

    }
  • C# 接受数据
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    struct MyData
    {
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
    public int[] data;
    };

    [DllImport("DLL.dll", EntryPoint = "GetData", CallingConvention = CallingConvention.Cdecl)]
    private extern static void GetData(ref MyData pData);//用ref声明结构

    MyData sd = new MyData();
    sd.data = new int[10];
    GetData(ref sd);

通过 P/Invoke 方式 C# 如何互送 C++ 的” 结构体数组 (内含基础类型二维数组)“?

  • C++ 定义开放函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    struct cmppe_submit
    {
    char user[10][200];
    };

    extern "C" __declspec(dllexport) void GetUser(cmppe_submit* lpSubit)
    {
    strcpy(lpSubit->user[0], "waqerqwdsewf");
    }
  • C# 接受数据
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    // 使用一维数组转写对应结构
    struct cmppe_submit
    {
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2000)]
    public byte[] dst_addr;
    }

    [DllImport(@"Application3.dll", EntryPoint = "GetUser", CallingConvention = CallingConvention.Cdecl)]
    private extern static void GetUser(ref cmppe_submit lpSubmit);//用ref声明结构

    cmppe_submit submit;
    submit.dst_addr = new byte[2000];
    GetUser(ref submit);
    string str = System.Text.Encoding.Default.GetString(submit.dst_addr, 0, 25);

    // 使用2个1维数组转写对应结构
    [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
    public struct cmppe_submit_pre
    {
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=200)]
    public int []m;
    };
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct cmppe_submit_now
    {
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
    public cmppe_submit_pre[]m;
    };

    [DllImport(@"Application3.dll", EntryPoint = "GetUser", CallingConvention = CallingConvention.Cdecl)]
    private extern static void GetUser(ref cmppe_submit_now lpSubmit);//用ref声明结构
    cmppe_submit_now submit=new cmppe_submit_now();

    GetUser(ref submit);

通过 P/Invoke 方式 C# 如何互送 C++ 的” 结构体 (内含字符串指针)?

  • C++ 定义开放函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    typedef struct _testStru9
    {
    WCHAR *pWChArr;
    CHAR *pChArr;
    bool IsCbool;
    BOOL IsBOOL;
    }testStru9;

    EXPORTDLL_API void Struct_ChangePtr( testStru9 *pStru )
    {
    if (NULL == pStru)
    {
    return;
    }

    pStru->IsBOOL = true;
    pStru->IsBOOL = TRUE;
    pStru->pWChArr = (WCHAR*)CoTaskMemAlloc(8*sizeof(WCHAR));
    pStru->pChArr = (CHAR*)CoTaskMemAlloc(8*sizeof(CHAR));

    lstrcpynW(pStru->pWChArr, L"ghj", 8);
    lstrcpynA(pStru->pChArr, "ghj", 8);

    wprintf(L"Struct_ChangePtr \n");
    }
  • C# 接受数据
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    # 定义成string即可,注意BOOL与bool的不同
    [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
    public struct testStru9
    {
    [MarshalAs(UnmanagedType.LPWStr)]
    public string pWChArr;
    [MarshalAs(UnmanagedType.LPStr)]
    public string pChArr;
    [MarshalAs(UnmanagedType.U1)]
    public bool IsCbool;
    [MarshalAs(UnmanagedType.Bool)]
    public bool IsBOOL;
    };
    [DllImport("ExportDll.dll", CharSet = CharSet.Unicode)]
    public static extern void Struct_ChangePtr(ref testStru9 pStru);

    testStru9 stru9 = new testStru9();
    Struct_ChangePtr(ref stru9);

通过 P/Invoke 方式 C# 如何互送 C++ 的” 结构体二维指针 "?

  • C++ 定义开放函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

    typedef struct _testStru8
    {
    int m;
    }testStru8;

    EXPORTDLL_API void Struct_ParameterOut( testStru8 **ppStru )
    {
    if (NULL == ppStru){
    return;
    }

    *ppStru = (testStru8*)CoTaskMemAlloc(sizeof(testStru8));

    (*ppStru)->m = 8;
    wprintf(L"Struct_ParameterOut \n");
    }
  • C# 接受数据
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
    public struct testStru8
    {
    public int m;
    };
    [DllImport("ExportDll.dll", CharSet = CharSet.Unicode)]
    public static extern void Struct_ParameterOut(ref IntPtr ppStru);

    IntPtr outPtr = IntPtr.Zero;
    Struct_ParameterOut(ref outPtr);
    testStru8 stru8 = (testStru8)(Marshal.PtrToStructure(outPtr, typeof(testStru8)));
    Marshal.FreeCoTaskMem(outPtr); // 释放非托管的内存

通过 P/Invoke 如何封送” 嵌套的结构体 “?

  • C++ 定义开放函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    typedef struct _testStru10Pre
    {
    int iVal;
    }testStru10Pre;
    typedef struct _testStru10
    {
    testStru10Pre *pPre;
    long lVal;
    _testStru10()
    {
    pPre = NULL;
    }
    }testStru10;

    EXPORTDLL_API void Struct_NestStruct( testStru10 *pStru )
    {
    if (NULL == pStru)
    {
    return;
    }

    pStru->lVal = 10;
    if (NULL != pStru->pPre)
    {
    pStru->pPre->iVal = 9;
    }

    wprintf(L"Struct_NestStruct \n");
    }
  • C# 接受数据
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

    public struct testStru10Pre
    {
    public int iVal;
    };
    public struct testStru10
    {
    public IntPtr pPre;
    public int lVal;
    };
    [DllImport("ExportDll.dll", CharSet = CharSet.Unicode)]
    public static extern void Struct_NestStruct(ref testStru10 pStru);

    testStru10Pre str10Pre = new testStru10Pre();
    IntPtr intPtrStru10Pre = Marshal.AllocCoTaskMem(Marshal.SizeOf(str10Pre));
    Marshal.StructureToPtr(str10Pre, intPtrStru10Pre, false);

    testStru10 stru10 = new testStru10();
    stru10.pPre = intPtrStru10Pre;
    Struct_NestStruct(ref stru10);
    testStru10Pre str10Pre2 = (testStru10Pre)Marshal.PtrToStructure(stru10.pPre, typeof(testStru10Pre));

    Marshal.DestroyStructure(intPtrStru10Pre, typeof(testStru10Pre));

通过 P/Invoke 方式 C# 如何封送 “字符串” 到 C++?

  • C++ 定义开放函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    EXPORTDLL_API int Str_Output( WCHAR *pStr )
    {
    if (NULL == pStr)
    {
    return(-1);
    }

    wprintf(L"Str_Output %s\n", pStr);

    return(0);
    }
  • C# 接受数据
    1
    2
    3
    4
    5
    6
    [DllImport("ExportDll.dll", CharSet=CharSet.Unicode)]
    public static extern int Str_Output([MarshalAs(UnmanagedType.LPWStr)]string pStr);


    string str = "hjkl;";
    CExportDll.Str_Output(str);

通过 P/Invoke 方式 C# 如何接受 C++“字符串” 作为参数返回?

  • C++ 定义开放函数
    1
    2
    3
    4
    5
    6
    7
    8
    EXPORTDLL_API int Str_Change( WCHAR *pStr, int len )
    {
    if (NULL == pStr)
    {
    return(-1);
    }

    for (int ix=0; ix
  • C# 接受数据
    1
    2
    3
    4
    5
    6
    7
    8
    9
    [DllImport("ExportDll.dll", CharSet = CharSet.Unicode)]
    public static extern int Str_ChangeArr([In, Out]string[] ppStr, int len);


    string[] strArr = new string[4] {new string('\0', 10),
    new string('\0', 10),
    new string('\0', 10),
    new string('\0', 10) };
    CExportDll.Str_ChangeArr(strArr, 4);

通过 P/Invoke 如何封送” 函数指针 “?

  • C# 中并没有函数指针的概念, 想要传递函数指针,首先要在 C# 中定义一个委托 (delegate),并且在 C++ 中定义一个函数指针,同时要保证 委托和函数指针具备相同的函数原型
  • (1) C++ 返回函数指针
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 定义函数指针
    typedef int (*PCALLBACKFUN)(int i);

    // 定义返回函数指针的函数
    int PrintInt( int i )
    {
    return(wprintf(L"CallBack_GetFunPtr %d\n", i));
    }

    EXPORTDLL_API PCALLBACKFUN CallBack_GetFunPtr()
    {
    return(PrintInt);
    }
  • (2) C# 定义对应 C++ 函数指针的委托
    1
    public delegate int DelegateGetFunPtrType(int i);
  • (3) C# 导入 C++ 的函数
    1
    2
    3
    [DllImport("ExportDll.dll", CharSet = CharSet.Unicode)]
    [return:MarshalAs(UnmanagedType.FunctionPtr)]
    public static extern DelegateGetFunPtrType CallBack_GetFunPtr();
  • (4) 测试
    1
    2
    CExportDll.DelegateGetFunPtrType printInt = CExportDll.CallBack_GetFunPtr();
    printInt(100);

C# 读取图片如何传递给 C++ 的 DLL?

  • C++ 定义接口
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    int CPLUSPLUSTEST_API opencvtest(unsigned char* src, int w, int h, int channel, float contrastValue, int brightValue)
    {
    if (!src)
    {
    return 0;
    }
    int format = CV_8UC3;
    switch (channel)
    {
    case 1:
    format = CV_8UC1;
    break;
    case 2:
    format = CV_8UC2;
    break;
    case 3:
    format = CV_8UC3;
    break;
    default:
    return 0;
    break;
    }

    Mat img(h, w, format, src);
    }
  • C# 读图
    1
    2
    Bitmap bmp;
    bmp = new Bitmap("1.bmp");
  • C# 传递图片数据到 DLL
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    int stride;
    byte[] source = GetBGRValues(bmp, out stride);
    int channel = Image.GetPixelFormatSize(bmp.PixelFormat) / 8;
    // 在原来的基础上,对比度和亮度加多少
    opencvtest(source, bmp.Width, bmp.Height, channel, (float)(1), 0); //C++ opencv对内存操作

    public static byte[] GetBGRValues(Bitmap bmp, out int stride)
    {
    var rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
    var bmpData = bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);
    stride = bmpData.Stride;
    //int channel = bmpData.Stride / bmp.Width;
    var rowBytes = bmpData.Width * Image.GetPixelFormatSize(bmp.PixelFormat) / 8;
    var imgBytes = bmp.Height * rowBytes;
    byte[] rgbValues = new byte[imgBytes];
    IntPtr ptr = bmpData.Scan0;
    for (var i = 0; i < bmp.Height; i++)
    {
    Marshal.Copy(ptr, rgbValues, i * rowBytes, rowBytes); //对齐
    ptr += bmpData.Stride; // next row
    }
    bmp.UnlockBits(bmpData);
    return rgbValues;
    }

C# 调用托管的 DLL 时,出现 “引用无效或不支持该引用” 的问题?

  • 原因:编译得到的 DLL 是非托管的,不能使用引用导入

C# 静态调用 DLL 中非托管函数时,出现 “System.EntryPointNotFoundException” ?

  • 原因: DllImport 的 EntryPoint 属性和 DLL 的函数不一致
  • 方法一:在 VS2019 的终端上,使用命令 dumpbin /exports convertPython.dll 查看 DLL 对外开放的函数名,然后将查到的名字填到 EntryPoint 上
  • 方法二:在生成 DLL 时,按以下方式编写,设定函数对外开放接口命名格式,其他格式看 DLL 函数的调用约定?
    1
    2
    3
    4
    5
    extern "C"
    {
    _declspec(dllexport) char _great_function(const char* a, int b);
    _declspec(dllexport) void _startLedCheck(byte* ImageBuffer, int imageWedth, int imageHeight);
    }

什么是 Marshal 类?

  • 提供了一个方法集合,这些方法用于分配非托管内存、复制非托管内存块、将托管类型转换为非托管类型,此外还提供了在与非托管代码交互时使用的其他杂项方法

Marshal 类的 AllocHGlobal () 方法?

  • 从进程的非托管内存中分配内存
    1
    2
    3
    4
    5

    // Demonstrate how to call GlobalAlloc and
    // GlobalFree using the Marshal class.
    IntPtr hglobal = Marshal.AllocHGlobal(100);
    Marshal.FreeHGlobal(hglobal);

Marshal 类的 Copy () 方法?

  • 将数据从托管内存复制到非托管内存指针
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    using System;
    using System.Runtime.InteropServices;

    class Example
    {
    static void Main()
    {
    // Create a managed array.
    byte[] managedArray = { 1, 2, 3, 4 };

    // Initialize unmanaged memory to hold the array.
    int size = Marshal.SizeOf(managedArray[0]) * managedArray.Length;

    IntPtr pnt = Marshal.AllocHGlobal(size);
    try
    {
    // Copy the array to unmanaged memory.
    Marshal.Copy(managedArray, 0, pnt, managedArray.Length);

    // Copy the unmanaged array back to another managed array.
    byte[] managedArray2 = new byte[managedArray.Length];

    Marshal.Copy(pnt, managedArray2, 0, managedArray.Length);
    Console.WriteLine("The array was copied to unmanaged memory and back.");
    }
    finally
    {
    // Free the unmanaged memory.
    Marshal.FreeHGlobal(pnt);
    }
    }
    }

Marshal 类的 FreeHGlobal () 方法?

  • 释放以前从进程的非托管内存中分配的内存
    1
    2
    3
    4
    5

    // Demonstrate how to call GlobalAlloc and
    // GlobalFree using the Marshal class.
    IntPtr hglobal = Marshal.AllocHGlobal(100);
    Marshal.FreeHGlobal(hglobal);

Marshal 类的 PtrToStringAnsi () 方法?

  • 将非托管 ANSI 或 UTF-8 字符串中第一个空字符之前的所有字符复制到托管 String,并将每个字符扩展为 UTF-16 字符
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    using namespace System;
    using namespace System::Runtime::InteropServices;

    void main()
    {
    // Create an unmanaged c string.
    const char * myString = "Hello managed world (from the unmanaged world)!";

    // Convert the c string to a managed String.
    String ^ myManagedString = Marshal::PtrToStringAnsi((IntPtr) (char *) myString);

    // Display the string to the console.
    Console::WriteLine(myManagedString);
    }

Marshal 类的 PtrToStructure () 方法?

  • 将数据从非托管内存块封送到托管对象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42

    using System;
    using System.Runtime.InteropServices;

    public struct Point
    {
    public int x;
    public int y;
    }

    class Example
    {
    static void Main()
    {
    // Create a point struct.
    Point p;
    p.x = 1;
    p.y = 1;
    Console.WriteLine("The value of first point is " + p.x + " and " + p.y + ".");

    // Initialize unmanged memory to hold the struct.
    IntPtr pnt = Marshal.AllocHGlobal(Marshal.SizeOf(p));
    try
    {
    // Copy the struct to unmanaged memory.
    Marshal.StructureToPtr(p, pnt, false);

    // Create another point.
    Point anotherP;

    // Set this Point to the value of the
    // Point in unmanaged memory.
    anotherP = (Point)Marshal.PtrToStructure(pnt, typeof(Point));
    Console.WriteLine("The value of new point is " + anotherP.x + " and " + anotherP.y + ".");
    }
    finally
    {
    // Free the unmanaged memory.
    Marshal.FreeHGlobal(pnt);
    }
    }
    }

Marshal 类的 SizeOf () 方法?

  • 返回对象的非托管大小(以字节为单位)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42

    using System;
    using System.Runtime.InteropServices;

    public struct Point
    {
    public int x;
    public int y;
    }

    class Example
    {
    static void Main()
    {
    // Create a point struct.
    Point p;
    p.x = 1;
    p.y = 1;
    Console.WriteLine("The value of first point is " + p.x + " and " + p.y + ".");

    // Initialize unmanged memory to hold the struct.
    IntPtr pnt = Marshal.AllocHGlobal(Marshal.SizeOf(p));
    try
    {
    // Copy the struct to unmanaged memory.
    Marshal.StructureToPtr(p, pnt, false);

    // Create another point.
    Point anotherP;

    // Set this Point to the value of the
    // Point in unmanaged memory.
    anotherP = (Point)Marshal.PtrToStructure(pnt, typeof(Point));
    Console.WriteLine("The value of new point is " + anotherP.x + " and " + anotherP.y + ".");
    }
    finally
    {
    // Free the unmanaged memory.
    Marshal.FreeHGlobal(pnt);
    }
    }
    }

Marshal 类的 StringToHGlobalUni () 方法?

  • 将托管 String 的内容复制到非托管内存

Marshal 类如何将 void * 指针转为 IntPtr?

  • 规定 void 类型的指针可以指向任何数据类型,只不过在 c 函数实体内强制为你的数据类型即可,比如
    1
    2
    // 函数:int fnAdd(struct mybuf *p_mydata),或者写成int fnAdd(void *p_mydata)
    struct mybuf*p = (structmybuf*)p_mydata;
  • C++:接口声明
    1
    2
    3
    4
    5
    6
    7
    struct A            
    {
    wchar_t osdbuffer[100];
    unsigned short ix;
    unsigned short iy;
    };
    int SetConfig(int type, void* p); // 声明接口
  • C#:声明调用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode,Pack = 4)]
    public struct A {
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]
    public string osdbuffer;
    public ushort ix; //显示坐标x
    public ushort iy; //显示坐标y
    }
    A s_a = new A();
    int lenght = Marshal.SizeOf(s_a);
    IntPtr pA= Marshal.AllocHGlobal(lenght);
    Marshal.StructureToPtr(s_a, pA, true);
    int type = 1;
    int ret = SetConfig( type, pA);
    Marshal.FreeHGlobal(pA);

Marshal 类如何将 byte [] 转 IntPtr?

  • 不使用 Marshal 创建新的堆,节省内存开销,也避免忘记释放导致的问题
    1
    System.Runtime.InteropServices.Marshal.UnsafeAddrOfPinnedArrayElement(array, 0);

Marshal 类如何将 IntPtr 转 byte []?

  • Copy :使用 Marshal 对数据进行拷贝,该操作速度较慢并且会引入新空间的开辟
    1
    2
    3
    IntPtr intPtr = GetBuff();
    byte[] b = new byte[length];
    Marshal.Copy(intPtr, b, 0, length);
  • byte*:该操作不会开辟新的空间,速度极快,但必须在 unsafe 模块下使用
    1
    2
    IntPtr pRet = GetBuff();
    byte* memBytePtr = (byte*)pRet.ToPointer();

通过 Marshal 方式 C# 封送 “基础数据类型 (一维数组)” 到 C++ 指针?

  • C++ 端定义接口
    1
    CPLUSPLUSTEST_API int ParsePolygonMarkNum(int w, int h, int drawPointNum, double* drawPoints)
  • C# 调用
    1
    2
    3
    double[] allDrawPoint = { 326, 96, 141, 618, 536, 300, 127, 297, 491, 613 };
    double* pointPtr = (double*)Marshal.UnsafeAddrOfPinnedArrayElement(allDrawPoint, 0);
    int rtnRs = ParsePolygonMarkNum(w, h, nowDrawPoint.Length / 2, pointPtr)