P/Invoke各种总结(十、C++调用C#代码的几种方法总结)

方法一、使用 C++/CLI 创建一个桥接库

返回基础数据类型

1.首先我们新建 一个C#的类库工程 CSharpLib

 

新建一个ExportClass类,增加一个GetID函数,如下:

1 public class ExplortClass
2     {
3         public int GetID()
4         {
5             return 1024;
6         }
7     }

 

2.新建一个CLR空工程CSBridge,这个库会作为中间桥接的库。将CSBridge工程的输出路径修改为CSharpLib工程的输出路径

说明:如果没有看到CLR Empty,可以到Visual Studio的安装程序中钩选并安装(直接搜索cli)

 

 新建一个bridge.cpp。输入以下代码

 1 #include <Windows.h>
 2 #include<msclr/marshal_cppstd.h>
 3 
 4 //引用C# dll
 5 #using "./CSharpLib.dll"
 6 
 7 //引用命名空间
 8 using namespace msclr::interop;
 9 using namespace System;
10 using namespace System::Runtime::InteropServices;
11 using namespace CSharpLib;
12 
13 #define lib_export
14 #ifdef lib_export
15 #define cs_lib_api extern "C" __declspec(dllexport)
16 #else
17 #define cs_lib_api __declspec(dllimport)
18 #endif
19 
20 typedef int(__stdcall* funGetId)();  //定义函数指针
21 
22 //导出函数 供C++调用
23 //在这个函数里调用 C#的函数,做为中转层
24 cs_lib_api int GetID()
25 {
26     CSharpLib::ExplortClass^ c = gcnew CSharpLib::ExplortClass();
27     auto id = c->GetID();
28     return id;
29 }

这样就拥有了一个桥接工程 。

 

3. 新建一个C++控制台应用程序,输入以下代码测试。

// CppInvoke.cpp : This file contains the 'main' function. Program execution begins and ends there.
//

#include <iostream>
#include<Windows.h>

typedef int(__stdcall* funGetId)();

int main()
{
    HMODULE hInstance = LoadLibrary(L"CSBridge.dll");
    if (hInstance)
    {
        funGetId getId = (funGetId)GetProcAddress(hInstance, "GetID");

        if (getId)
        {
            auto result = getId();
            std::cout << result << std::endl;
        }
    }
}

可以看到输出结果为:1024

 

复杂一点的情况,返回一个结构体:

在CSharpLib中增加一个结构体Computer:

1     [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
2     public struct Computer
3     {
4         public int cpuId; 
5         public string cpuName;
6         public int osVersion;
7     }

增加一个获取Computer的函数

1   public Computer GetComputer()
2         {
3             Computer computer = new Computer();
4             computer.cpuId = 100000000;
5             computer.cpuName = "Intel";
6             computer.osVersion = 11;
7             return computer;
8         }

 然后在CSBridge中增加一个用于和Computer交互的类型interop_Computer,这个类型是用于C++中调用时使用,使用C#中的Computer类型转换可以得到interop_Computer。

1 struct interop_Computer
2 {
3     int cpuId;
4     wchar_t* cpuName;
5     int osVersion;
6 };

 再定义一个函数指针和增加一个中转层函数

 1 typedef interop_Computer(__stdcall* funGetComputer)();
 2 
 3 cs_lib_api interop_Computer GetComputer()
 4 {
 5     CSharpLib::ExplortClass^ c = gcnew CSharpLib::ExplortClass();
 6     auto computer = c->GetComputer(); //调用C#中的函数
 7     System::IntPtr ptr = Marshal::AllocHGlobal(sizeof(interop_Computer));//需要提前分配空间
 8     System::Runtime::InteropServices::Marshal::StructureToPtr(computer, ptr, false);//将C#中的结构体拷贝到Intptr
 9     interop_Computer* rt = (interop_Computer*)(void*)(ptr.ToPointer());//将Intptr强制转换为interop_Computer
10     return *rt;
11 }

 然后在CppInvoke中添加测试代码

HMODULE hInstance = LoadLibrary(L"CSBridge.dll");
    if (hInstance)
    {
      
        funGetComputer getComputer = (funGetComputer)GetProcAddress(hInstance, "GetComputer");
         
        if (getComputer)
        {
            auto computer = getComputer();
            std::wcout << computer.cpuId << "\t" << computer.cpuName << "\t" << computer.osVersion << std::endl;
        }

        FreeLibrary(hInstance);
    }

输出结果为:

 

这里还有一种情况,就 是需要 将C++中的参数传到C#中。

 这种情况有两种方法可以实现:

1、将C++中的参数封送到C#中,转换方式和上面返回结构体的实现方式差不多。大概思路就是把C++结构体转换成IntPtr,再从IntPtr转换到C#中的结构体。

2、将C#中的函数转换到C++中的函数再调用。这样就可以直接使用C++中的结构体。

 

实现方法如下:

在C#中增加一个函数PrintComputer,需要传入一个Computer结构体。然后再增加对应的委托和获取委托的函数

1 public void PrintComputer(Computer computer)
2         {
3             Console.WriteLine(computer.cpuId);
4             Console.WriteLine(computer.cpuName);
5             Console.WriteLine(computer.osVersion);
6         }
1 public delegate void PrintComputerDelegate(Computer computer); //声明委托
2 
4 public PrintComputerDelegate GetComputerDelegate() => PrintComputer;  //定义返回委托的函数

 在CSBridge中定义一个函数指针,并增加一个导出函数

 1 typedef void(__stdcall* funPrintComputer)(interop_Computer computer);
 2 
 3 cs_lib_api void PrintComputer(interop_Computer computer)
 4 {
 5     CSharpLib::ExplortClass^ c = gcnew CSharpLib::ExplortClass();
 6     auto printDelegate = c->GetComputerDelegate();//获取委托 
 7     IntPtr ptr = Marshal::GetFunctionPointerForDelegate(printDelegate);//将委托转为IntPtr类型
 8     funPrintComputer funcPrint = (funPrintComputer)ptr.ToPointer();//将IntPtr转换为指针,再转换为funPrintComputer
 9     if (funcPrint)
10     {
11         funcPrint(computer);
12     }
13 }

 这样就可以在C++中的参数传递到C#中。CppInvoke中的调用 代码如下:

1 funPrintComputer printComputer = (funPrintComputer)GetProcAddress(hInstance, "PrintComputer");
2         interop_Computer testComputer;
3         testComputer.cpuId = 18;
4         testComputer.cpuName = _tcsdup(L"AMD");
5         testComputer.osVersion = 7;
6         if (printComputer)
7         {
8             printComputer(testComputer);
9         }

 输出结果为:

 

示例代码(需要Visual Studio 2022)

 

方法二、将.NET组件导出为COM

待完成

posted @ 2022-07-15 15:04  zhaotianff  阅读(2065)  评论(3编辑  收藏  举报