首页 > 编程语言 >C#调用C++ dll教程

C#调用C++ dll教程

时间:2024-10-11 11:02:38浏览次数:7  
标签:ClibMath C# LIBMATH C++ dll int

C#调用C++ dll教程

文章目录

在使用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);
}
  • 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

注意: 如果想导出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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

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);
}
  • 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

二、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);
        }
    }
}
  • 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

注意:由于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);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

使用C#包装类:

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

MyClassWrapper myInstance = new MyClassWrapper();
myInstance.MyMethod();
  • 1
  • 2

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

三、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);
  • 1

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

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

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

struct Person
{
    char* name;
    int age;
};
  • 1
  • 2
  • 3
  • 4
  • 5

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

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public class Person
{
    public string name;
    public int age;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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

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

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

在处理更复杂的情况,如回调函数,复杂的数据结构,和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);
  • 1

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

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

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

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

原文链接:https://blog.csdn.net/yao_hou/article/details/134471487

标签:ClibMath,C#,LIBMATH,C++,dll,int
From: https://www.cnblogs.com/sunny3158/p/18457984

相关文章

  • avue-crud常用配置说明
    avue-crud 是一个基于Vue.js的开源组件库,用于快速构建表单、表格和数据管理的前端界面。它简化了常见的CRUD(创建、读取、更新、删除)操作的开发过程,特别适用于管理系统或后台管理平台。它是一个基于Element-plus低代码前端框架,它使用JSON配置来生成页面,可以减少页面开发工作......
  • 最新WeCam虚拟摄像头无人直播神器,支持全平台自动抽帧硬改直播间
    功能介绍:wecam专注无人直播......
  • 最新WeCam虚拟摄像头无人直播神器,支持全平台自动抽帧硬改直播间
    功能介绍:wecam专注无人直播......
  • CSS篇二:其他选择器与权重/范围
    一、其他选择器简单聊聊:篇一提到的三种基础选择器其实并不能完全覆盖商业项目的开发场景,所以其他类型选择器的使用频率也非常高,所以还是建议记住,避免某一场景下为难。1、通配选择器简述:使用较少,优缺点都很明显,优→统一设置;缺→样式覆盖,降低代码执行效率。推荐度:一星2、组......
  • ArgoCD + ArgoCD Image Updater 部署实现
    部署思路踩坑整理1、ArgoCD和ArgoCDImageUpdater是2个不同的程序。"ArgoCDImageUpdater小工具"没有集成在ArgoCD中需要单独部署。2、单独的ArgoCD能够实现基于git仓库变更作为应用部署的事实来源[参考子页:argocd根据镜像tag变化实现自动发布的2种方式];ArgoCD+"ArgoCD......
  • C# await 高级用法
    本文告诉大家await的高级用法,包括底层原理。昨天看到太子写了一段代码,我开始觉得他修改了编译器,要不然下面的代码怎么可以编译通过await"林德熙逗比";需要知道,基本可以添加await都是可以等待的类型,如Task。如果一个类需要可以被等待,那么这个类必须满足以下条件类里有一个Ge......
  • Navicat Premium v17 中文注册版
    Navicat是一款强大的数据库管理和开发工具,它支持多种数据库系统,包括MySQL、MariaDB、Oracle、PostgreSQL、SQLite和SQLServer等。Navicat提供了一个直观的用户界面,使得用户可以轻松地创建、组织、访问和共享数据库信息。该版本已注册,可以使用全部功能。使用说明:1、......
  • AIGC算法3:Attention及其变体
    1.AttentionAttention是Transformer的核心部分,Attention机制帮助模型进行信息筛选,通过Q,K,V,对信息进行加工1.1attention计算公式1.2attention计算流程1.3SoftmaxattentionSelf-attention的Q,K,V同源,都是输入序列X的线性变化,在实际生产过程中K与V相同。F为token维度,DM为投影维......
  • DATAGERRY REST API身份验证绕过漏洞(CVE-2024-46627)
    0X01产品描述:        ‌DATAGERRY是一个灵活的开源CMDB和资产管理工具,它完全将数据模型的定义留给用户。‌用户只需在一个易于使用的webfrontend中定义自己的对象类型(如服务器、路由器、租赁线路、位置等)。通过DATAGERRY的导出API,存储在DATAGERRY中的CMDB对象可以轻......
  • 最新WeCam虚拟摄像头无人直播神器,支持全平台自动抽帧硬改直播间
    功能介绍:wecam专注无人直播......