C#调用C++ dll教程

文章目录

  • 一、创建C++ dll项目
  • 二、C#程序员调用C++ dll
  • 三、C++与C#数据类型对应
    • 基本数据类型对应表
    • C++指针类型与C#类型

在使用C#开发客户端时,有时需要调用C++ dll,本篇博客来介绍C#程序如何调用C++ dll。

一、创建C++ dll项目

首先使用VS2022创建C++ dll项目,具体步骤如下:

(1)选择Windows桌面向导,点击下一步, 取项目名,例如我的dll项目名是libMath
在这里插入图片描述

(2)选择动态项目,勾选导出符号

在这里插入图片描述

(3)编写动态代码,代码如下:

libMath.h

// 下列 ifdef 块是创建使从 DLL 导出更简单的
// 宏的标准方法。此 DLL 中的所有文件都是用命令行上定义的 LIBMATH_EXPORTS
// 符号编译的。在使用此 DLL 的
// 任何项目上不应定义此符号。这样,源文件中包含此文件的任何其他项目都会将
// LIBMATH_API 函数视为是从 DLL 导入的,而此 DLL 则将用此宏定义的
// 符号视为是被导出的。
#ifdef LIBMATH_EXPORTS
#define LIBMATH_API __declspec(dllexport)
#else
#define LIBMATH_API __declspec(dllimport)
#endif// 此类是从 dll 导出的
class LIBMATH_API ClibMath {
public:ClibMath();int Add(int a, int b);int Sub(int a, int b);
};// 由于需要给C#调用,为了方便导出类,添加了函数进行导出,并且需要加extern "C"
extern "C" {LIBMATH_API ClibMath* CreateMyClass();LIBMATH_API void DeleteMyClass(ClibMath* obj);LIBMATH_API int CallAdd(ClibMath* obj, int num1, int num2);LIBMATH_API int CallSub(ClibMath* obj, int num1, int num2);
}

注意: 如果想导出C++类在C#中使用,由于语言语法差异,C++类在C#中无法使用,因为C++类通常包含成员函数、构造函数、析构函数等,而C#与C++在处理这些方面存在差异。一种可行的方法是在C++类中添加一些导出函数,这样它们可以通过C#调用。这些函数可以执行类的实例化、调用成员函数等操作。确保使用 extern “C” 以避免名称修饰, 因为C++函数在编译时,会在原有的函数名前后添加一些符号,例如add函数在编译后可能变成了@xxasd_sfdf_add_xxx之类的,但是使用extern "C" 后就是按照C语言的方式导出,函数名不变,例如我添加的一些类导出方法:

// 由于需要给C#调用,为了方便导出类,添加了函数进行导出,并且需要加extern "C"
extern "C" {LIBMATH_API ClibMath* CreateMyClass();LIBMATH_API void DeleteMyClass(ClibMath* obj);LIBMATH_API int CallAdd(ClibMath* obj, int num1, int num2);LIBMATH_API int CallSub(ClibMath* obj, int num1, int num2);
}

libMath.cpp

// libMath.cpp : 定义 DLL 的导出函数。
//#include "framework.h"
#include "libMath.h"// 这是已导出类的构造函数。
ClibMath::ClibMath()
{return;
}int ClibMath::Add(int a, int b)
{return a + b;
}int ClibMath::Sub(int a, int b)
{return a - b;
}LIBMATH_API ClibMath* CreateMyClass() {return new ClibMath();
}LIBMATH_API void DeleteMyClass(ClibMath* obj) {delete obj;
}LIBMATH_API int CallAdd(ClibMath* obj, int num1, int num2) {return obj->Add(num1, num2);
}LIBMATH_API int CallSub(ClibMath* obj, int num1, int num2) {return obj->Sub(num1, num2);
}

二、C#程序员调用C++ dll

生成dll后可以用命令拷贝到C#项目的exe目录,或者手动拷贝,然后在C#代码中使用import导入即可,如下图:
在这里插入图片描述

代码如下:

/*C# 调用 C++ dll 将libMath.dll放到CSharpCallCppDLL/bin/Debug目录下*/using System.Runtime.InteropServices;namespace CSharpCallCppDLL
{internal class Program{private static IntPtr myClassInstance;  // 定义C++类的实例,用于后面的调用[DllImport("libMath.dll", CallingConvention = CallingConvention.Cdecl)]private static extern IntPtr CreateMyClass();[DllImport("libMath.dll", CallingConvention = CallingConvention.Cdecl)]private static extern void DeleteMyClass(IntPtr obj);[DllImport("libMath.dll", CallingConvention = CallingConvention.Cdecl)]private static extern int CallAdd(IntPtr obj, int num1, int num2);[DllImport("libMath.dll", CallingConvention = CallingConvention.Cdecl)]private static extern int CallSub(IntPtr obj, int num1, int num2);static void Main(string[] args){Console.WriteLine("测试C#调用C++");myClassInstance = CreateMyClass();int nRet = CallAdd(myClassInstance, 1, 2);Console.WriteLine($"1 + 2 = {nRet}");// 清理C++内存DeleteMyClass(myClassInstance);}}
}

注意:由于C++的编译特点,C++项目有Debug、Release、x86、x64之分,特别需要注意dll的放置位置,放错了可能就链接失败了,以及C++在x64于x86下的指针4字节与8字节的区分,这些都会导致在C#调用C++ dll代码失败或者crash。此外,确保你的应用程序具有访问和加载DLL所需的适当权限。

运行结果如下:
在这里插入图片描述
在C#中可以创建一个对应C++类的C#包装类,使用 DllImport 属性声明导出函数,例如下面的代码:

public class MyClassWrapper {private IntPtr myClassInstance;[DllImport("YourCppLibrary.dll", CallingConvention = CallingConvention.Cdecl)]private static extern IntPtr CreateMyClass();[DllImport("YourCppLibrary.dll", CallingConvention = CallingConvention.Cdecl)]private static extern void DeleteMyClass(IntPtr obj);[DllImport("YourCppLibrary.dll", CallingConvention = CallingConvention.Cdecl)]private static extern void CallMyMethod(IntPtr obj);public MyClassWrapper() {myClassInstance = CreateMyClass();}~MyClassWrapper() {DeleteMyClass(myClassInstance);}public void MyMethod() {CallMyMethod(myClassInstance);}
}

使用C#包装类:

在C#中,可以实例化MyClassWrapper类,并调用其中的方法,这将转发调用到C++类的实例。

MyClassWrapper myInstance = new MyClassWrapper();
myInstance.MyMethod();

这是一个简单的例子,实际情况可能更为复杂,特别是在处理类的继承、虚函数等方面。确保在进行实际应用时进行充分的测试,以确保互操作性正常运作。

三、C++与C#数据类型对应

C#在调用C++ DLL时,需要通过P/Invoke技术来完成。P/Invoke是.NET Framework用于调用非托管代码库的一种方式。在这个过程中,我们需要处理两种语言之间的数据类型转换,因为它们的数据类型不完全一致。

基本数据类型对应表

以下是C++和C#之间的一些常见数据类型的对应表(请注意,这并不是一个完全的列表,只是一些常见类型的示例):

C++C#
boolbool
char / BYTEbyte
shortshort
intint
longint
floatfloat
doubledouble
char* (C-style string)string
wchar_t* (Unicode string)string

在C#中,我们使用DllImport特性来声明对C++ DLL的函数调用。例如,如果我们有一个C++函数如下:

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

在C#中,我们可以这样声明和使用它:

[DllImport("MyLibrary.dll")]
public static extern int Add(int a, int b);public void Main()
{int result = Add(2, 3);
}

对于更复杂的数据类型,如结构体和类,我们需要在C#中创建对应的类或结构体来匹配。例如,如果我们有一个C++结构体:

struct Person
{char* name;int age;
};

我们可以在C#中创建一个类来匹配它:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public class Person
{public string name;public int age;
}

注意我们使用了StructLayout特性并设置CharSetCharSet.Ansi,因为C++中的char*类型对应于ANSI字符串。

在处理C++ DLL中的函数时,也需要注意函数调用的约定(cdeclstdcall等)。默认情况下,C#假定被调用的函数使用stdcall调用约定。如果函数使用不同的调用约定,你需要在DllImport特性中指定它,例如:

[DllImport("MyLibrary.dll", CallingConvention = CallingConvention.Cdecl)]

在处理更复杂的情况,如回调函数,复杂的数据结构,和C++类时,可能需要更多的处理。处理这些情况通常需要对两种语言都有深入的理解,并且可能需要手动管理内存。

C++指针类型与C#类型

C++ 指针在C#中的相应类型取决于指针指向的数据类型和你如何打算使用它。这里有几种常见的情况:

  1. 指向简单类型的指针:如果指针指向一个简单的数值类型(如int*float*等),你可以在C#中使用IntPtr类型来表示它。你可以使用Marshal类中的方法(如 Marshal.ReadInt32Marshal.WriteInt32等)来读取或写入指针指向的数据。

  2. 指向字符串的指针:如果指针指向一个字符串(如char*wchar_t*),你可以在C#中使用string类型来表示它。在DllImport特性中,你可以使用MarshalAs特性来指定字符串的编码方式。

  3. 指向结构体的指针:如果指针指向一个结构体,你可以在C#中创建一个对应的类或结构体,并使用ref关键字或者IntPtr类型来表示指针。使用ref关键字时,.NET运行时会自动处理内存管理。使用IntPtr时,你需要手动管理内存。

例如,假设你有一个C++函数如下:

extern "C" __declspec(dllexport) void ModifyPerson(Person* person);

在C#中,你可以这样使用它:

[DllImport("MyLibrary.dll")]
public static extern void ModifyPerson(ref Person person);// 或者
[DllImport("MyLibrary.dll")]
public static extern void ModifyPerson(IntPtr personPtr);
  1. 指向数组的指针:如果指针指向一个数组,你可以在C#中使用数组类型来表示它。你也可以使用MarshalAs特性来指定数组的大小。

对于更复杂的情况,例如指向函数的指针(函数指针),你可能需要使用Marshal.GetDelegateForFunctionPointer方法将其转换为C#委托。

需要注意的是,当你使用IntPtr来管理指针时,你需要自己负责内存的分配和释放。你可以使用Marshal.AllocHGlobalMarshal.FreeHGlobal方法来分配和释放非托管内存。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/151466.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android设计模式--责任链模式

无善无恶心之体,有善有恶意之动。知善知恶是良知,为善去恶是格物。 一,定义 使多个对象都有机会处理请求,从而避免了请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直…

vscode 推送本地新项目到gitee

一、gitee新建仓库 1、填好相关信息后点击创建 2、创建完成后复制 https,稍后要将本地项目与此关联 3、选择添加远程存储库 4、输入仓库地址,选择从URL添加远程存储仓库 5、输入仓库名称,确保仓库名一致

AIGC创作系统ChatGPT源码,支持最新GPT-4-Turbo模型,支持DALL-E3文生图

一、AI创作系统 SparkAi创作系统是基于OpenAI很火的ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统,支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美,可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如…

鸿蒙4.0开发笔记之DevEco Studio之配置代码片段快速生成(三)

一、作用 配置代码片段可以让我们在Deveco Studio中进行开发时快速调取常用的代码块、字符串或者某段具有特殊含义的文字。其实现方式类似于调用定义好变量,然而这个变量是存在于Deveco Studio中的,并不会占用项目的资源。 二、配置代码段的方法 1、打…

052-第三代软件开发-系统监测

第三代软件开发-系统监测 文章目录 第三代软件开发-系统监测项目介绍系统监测 关键字: Qt、 Qml、 cpu、 内存、memory 项目介绍 欢迎来到我们的 QML & C 项目!这个项目结合了 QML(Qt Meta-Object Language)和 C 的强大功…

最强英文开源模型Llama2架构与技术细节探秘

prerequisite: 最强英文开源模型LLaMA架构探秘,从原理到源码 Llama2 Meta AI于2023年7月19日宣布开源LLaMA模型的二代版本Llama2,并在原来基础上允许免费用于研究和商用。 作为LLaMA的延续和升级,Llama2的训练数据扩充了40%,达到…

【JavaEE初阶】 CSS相关属性,元素显示模式,盒模型,弹性布局,Chrome 调试工具||相关讲解

文章目录 🎋字体属性🚩设置字体🚩字体大小🚩字体粗细🚩文字样式 🎍文本属性🚩文本颜色🎈认识 RGB🎈设置文本颜色 🚩文本对齐🚩文本装饰&#x1f6…

什么是Mock?为什么要使用Mock呢?

1、前言 在日常开发过程中,大家经常都会遇到:新需求来了,但是需要跟第三方接口来对接,第三方服务还没好,我们自己的功能设计如何继续呢?这里,给大家推荐一下Mock方案。 2、场景示例 2.1、场景一…

【数据结构(三)】单向环形链表和约瑟夫问题(3)

文章目录 1. 单向环形链表应用场景2. 思路分析3. 代码实现3.1. 实现单向环形链表3.2. 产生出队编号序列3.2.1. 思路分析3.2.2. 代码实现 1. 单向环形链表应用场景 Josephu(约瑟夫、约瑟夫环) 问题: 设编号为 1,2,… n 的 n 个人围坐一圈&…

C语言入门笔记—static、extern、define、指针、结构体

一、static static修饰局部变量的时候,局部变量出了作用域,不销毁。本质上,static修饰局部变量的时候,改变了变量的存储位置。详见下图,当a不被static修饰和被static修饰的时候。 C/C static关键字详解&#xff…

面试题-6

1.精灵图和base64的区别是什么? 精灵图:把多张小图整合到一张大图上,利用定位的一些属性把小图显示在页面上,当访问页面可以减少请求,提高加载速度 base64:传输8bit字节代码的编码方式,把原本二进制形式转为64个字符的单位,最后组成字符串 …

计算机毕业设计 基于SpringBoot的企业内部网络管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍:✌从事软件开发10年之余,专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ 🍅文末获取源码联系🍅 👇🏻 精…

vscode pandas无法使用

一、代码内容 import csv csv_reader csv.reader(open("data.csv")) for row in csv_reader:print(row) print(row[2]) 二、错误提示 ModuleNotFoundError: No module named pandas 三、安装pandas 然后我安装pandas,因为我的python的版本是python …

【高级程序设计】Week2-4Week3-1 JavaScript

一、Javascript 1. What is JS 定义A scripting language used for client-side web development.作用 an implementation of the ECMAScript standard defines the syntax/characteristics of the language and a basic set of commonly used objects such as Number, Date …

Kotlin学习之函数

原文链接 Understanding Kotlin Functions 函数对于编程语言来说是极其重要的一个组成部分,函数可以视为是程序的执行,是真正活的代码,为啥呢?因为运行的时候你必须要执行一个函数,一般从主函数入口,开始一…

网络运维与网络安全 学习笔记2023.11.20

网络运维与网络安全 学习笔记 第二十一天 今日目标 交换网路径选择、Eth-Trunk原理、动态Eth-Trunk配置 Eth-Trunk案例实践、MUX VLAN原理、MUX VLAN配置 交换网路径选择 STP的作用 在交换网络中提供冗余/备份路径 提供冗余路径的同时,防止环路的产生 影响同网…

【C#】字符串拼接相关

目录 1.字符串拼接方式1 用号进行字符串拼接 复合运算符 2.字符串拼接方式2 3.控制台打印拼 4.例子 1.字符串拼接方式1 之前的算数运算符 只是用来数值类型变量进行数学运算的而 string 不存在算数运算符 不能计算 但是可以通过号来进行字符串拼接 用号进行字符串拼接 …

Docker中的RabbitMQ已经启动运行,但是管理界面打不开

文章目录 前言一、解决方法方法一方法二 总结 前言 肯定有好多小伙伴在学习RabbitMQ的过程中,发现镜像运行,但是我的管理界面怎么进不去,或者说我第一天可以进去,怎么第二天进不去了,为什么每次重新打开虚拟机都进不去…

应试教育导致学生迷信标准答案惯性导致思维僵化-移动机器人

移动机器人课程群实践创新的困境与突围 一、引言 随着科技的快速发展,工程教育变得越来越重要。然而,传统的应试教育模式往往侧重于理论知识的传授,忽视了学生的实践能力和创新精神的培养。这在移动机器人课程群的教学中表现得尤为明显。本文…

JavaScript的学习,就这一篇就OK了!(超详细)

目录 Day27 JavaScript(1) 1、JS的引入方式 2、ECMAScript基本语法 3、ECMAScript 基本数据类型​编辑 3.1 数字类型 3.2 字符串 3.3 布尔值 3.4 空值(Undefined和Null) 3.5 类型转换 3.6 原始值和引用值 4、运算符 5、流程控制语句 5.1 分…