当前位置: 代码网 > it编程>编程语言>Asp.net > C#调用C++ dll教程

C#调用C++ dll教程

2024年07月28日 Asp.net 我要评论
在使用C#开发客户端时,有时需要调用C++ dll,本篇博客来介绍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);
}

注意: 如果想导出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方法来分配和释放非托管内存。

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com