当前位置: 首页 > article >正文

【CPP 基础】如何把cpp库,分装给 c# 用。

  1. 基本方法

在C++中封装的方法(如在DLL中),如果输入参数是一个自定义类型,并且你想在C#中调用它,你需要做一些工作来确保数据结构在两种语言之间正确传递和解释。下面是详细的步骤。

    1. 场景描述

假设我们有一个C++函数,它接收一个自定义类型(结构体)作为参数,我们想在C#中调用这个函数。

    1. 创建C++库(DLL)

首先,我们创建一个C++ DLL项目,并定义一个自定义类型(结构体)以及一个使用该类型的方法。

示例:C++ 代码

头文件:

// MyLibrary.h
#pragma once

#ifdef MYLIBRARY_EXPORTS
#define MYLIBRARY_API __declspec(dllexport)
#else
#define MYLIBRARY_API __declspec(dllimport)
#endif

// 定义一个自定义类型
struct MyStruct
{
    int a;
    float b;
};

// 一个接收自定义类型的函数
extern "C" MYLIBRARY_API void ProcessStruct(MyStruct data);
// MyLibrary.cpp
#include "MyLibrary.h"
#include <iostream>

// 函数的实现
void ProcessStruct(MyStruct data)
{
    std::cout << "Processing Struct: " << data.a << ", " << data.b << std::endl;
}

  • MyStruct 是一个自定义类型的结构体。
  • ProcessStruct 是一个接受 MyStruct 类型参数的函数。
    1. 在C#中定义相同的数据结构

为了在C#中调用这个C++函数,需要使用[StructLayout]属性来定义一个与C++结构体相同的结构体。

示例:C# 代码

Csharp:

using System;
using System.Runtime.InteropServices;

class Program
{
    // 定义与C++相匹配的结构体
    [StructLayout(LayoutKind.Sequential)]
    public struct MyStruct
    {
        public int a;
        public float b;
    }

    // 使用DllImport来导入C++库的函数
    [DllImport("MyLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern void ProcessStruct(MyStruct data);

    static void Main()
    {
        // 创建一个结构体实例
        MyStruct myStruct;
        myStruct.a = 10;
        myStruct.b = 3.14f;

        // 调用C++中的函数
        ProcessStruct(myStruct);
    }
}

  1. 关键点解释

1、 匹配的数据结构:

使用[StructLayout(LayoutKind.Sequential)]来确保C#结构体的内存布局与C++结构体的布局匹配。LayoutKind.Sequential确保字段按声明顺序排列。

2、数据类型一致性:

确保C#结构体中的数据类型与C++结构体中的数据类型匹配。例如,int 对应int,float 对应float等。

3、导入非托管代码:

使用[DllImport]特性导入C++库中的方法。指定CallingConvention为Cdecl,因为C++默认使用__cdecl调用约定。

4、调用方法:

在C#中创建MyStruct实例并为其字段赋值,然后将它传递给导入的C++方法ProcessStruct。

  1. 编译和运行
  • C++项目: 构建C++ DLL项目。生成的MyLibrary.dll文件需要复制到C#项目的输出目录(例如bin/Debug/net6.0/)。
  • C#项目: 构建并运行C#项目,你应该能够看到C++库中的ProcessStruct函数的输出结果。

  1. 测试代码链接

构建库

  1.  要求cpp 写成 c类型的接口,
    1. ldd 看 库文件,c类型的,名字稳定。反射能,稳定读取,库中函数名。

  1. 为什么要用C类型的接口

在C#中调用C++库时,需要将C++库封装成C风格的API接口,主要是因为以下原因:

    1.  互操作性(Interoperability)限制

C#(.NET)无法直接调用C++的类和成员函数,因为两者的底层调用约定和内存管理方式不同。

  • C++使用的类和对象模型:C++支持复杂的面向对象编程模型,包括继承、多态、虚函数等,这些特性在C#中没有直接的等效实现。
  • C++的ABI(Application Binary Interface)不稳定:不同的编译器(如MSVC、GCC、Clang等)生成的C++对象布局和调用约定可能不同,导致二进制不兼容。这会给直接调用C++类带来极大的困难。

相反,C风格的API使用简单的函数调用,并且使用固定的调用约定(如stdcallcdecl),这使得跨语言调用变得更加可靠和稳定

    1. P/Invoke(Platform Invocation Services)的限制

P/Invoke是C#中用于调用非托管代码(如C/C++库)的机制,但它只支持对非托管的C函数的调用。P/Invoke只支持以下类型的C函数接口:

  • 简单的C函数:这些函数不包含类和复杂对象,参数和返回值基本数据类型指针
  • 结构体和指针:可以使用StructLayout和MarshalAs特性进行数据布局和内存管理。

P/Invoke不支持直接调用C++的类成员函数或在C++中实现的虚函数。因此,必须将C++的类和复杂对象转换为简单的C函数接口

    1. 内存管理的差异

C++和C#在内存管理方面有显著差异:

  • C++使用手动内存管理:C++开发者通常负责显式地分配和释放内存。
  • C#使用垃圾回收(Garbage Collection)机制:在C#中,内存管理由CLR(Common Language Runtime)负责,开发者不直接管理内存释放。

为了避免内存管理方面的冲突和问题,通常将C++的复杂对象封装在C风格的接口中,并在C#中通过指针(IntPtr)进行操作。这种方式可以确保两种语言的内存管理方式不会互相干扰。传入参数,推荐都用指针

    1. 封装复杂类型和数据结构

C++的类和结构可能包含复杂的成员,如指针、引用、容器类等,这些在跨语言调用时不容易正确转换。通过使用C风格的接口,可以明确地控制传递的数据类型和数据结构布局,确保在调用时的安全性和正确性。

    1. 跨平台和跨编译器的兼容性

通过封装成C风格的API,可以确保生成的库在不同平台和不同编译器之间具有更好的兼容性和可移植性。C语言接口相对简单,ABI通常是稳定的,因此在跨平台调用时更容易实现。

    1. 总结

将C++库封装成C风格的接口是为了提高互操作性,简化跨语言调用的复杂性,并确保内存管理和数据传递的正确性。这种方法使得C#能够安全、可靠地调用C++库的功能,同时避免了跨语言调用时的诸多问题。


http://www.kler.cn/news/288651.html

相关文章:

  • 数据结构---线性表--栈和队列
  • ActiveMQ实战指南:实现发布/订阅(publish-subscribe)消息发送!
  • Unity Android 进阶之 【Android 添加一个启动动画】在Unity场景加载完之前,避免 【Unity 启动界面慢 黑屏时间长】的情况
  • 青远生态为云南林业规划院定制开发的自然保护地规划智能编制系统顺利通过验收
  • Golang | Leetcode Golang题解之第385题迷你语法分析器
  • Java图形用户界面之Applet设计
  • python django 使用教程
  • 使用 streamlink 把 m3u8 转为 mp4
  • 代码随想录 刷题记录-24 图论 (1)理论基础 、深搜与广搜
  • 保姆级Maven安装、配置、版本查询教程(包含配置本地仓库、阿里云私服、环境变量)
  • 射频指纹特征提取:揭秘无线通信设备的身份标识
  • 网络准入管理系统是什么?网络准入很重要,2024年国内外网络准入控制系统有哪些?(靠谱儿~)
  • filezilla使用教程(window下filezilla使用教程)
  • TF | SD 卡出现无法删除的文件,乱码文件该如何处理 macOS
  • 太速科技-基于Kintex-7 XC7K160T 的CameraLink转四路光纤数据转发卡(Full Camera Link图像转万兆以太网适配器 )
  • PostgreSQL + PostGIS:空间数据存储及管理解决方案
  • 【java入门】JDK的下载安装与配置,最新最详细教程!
  • vue3+vite+ts如何使用路由
  • pyecharts可视化数据大屏
  • 【Python】3.基础语法(3)函数
  • Python实现BASE64 算法
  • 网络安全售前入门09安全服务——安全加固服务
  • Django-debug-toolbar的作用
  • Java 入门指南:Java 并发编程 —— 单例模式
  • 在Nginx上部署前端Vue项目,超级简单!!
  • 浅谈C# 虚函数和重写
  • html+css+js网页设计 中秋节-作业1个页面
  • 使用 Pandas 进行数据可视化:全面指南(六)
  • 前端框架的演变与选择
  • 【unity实战】使用新版输入系统Input System+Rigidbody实现第三人称人物控制器(附项目源码)