百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

跨语言调用C#代码的新方式-DllExport

off999 2025-06-18 23:30 4 浏览 0 评论

简介

上一篇文章使用C#编写一个.NET分析器文章发布以后,很多小伙伴都对最新的NativeAOT函数导出比较感兴趣,今天故写一篇短文来介绍一下如何使用它。

在以前,如果有其他语言需要调用C#编写的库,那基本上只有通过各种RPC的方式(HTTP、GRPC)或者引入一层C++代理层的方式来调用。

自从微软开始积极开发和研究Native AOT以后,我们有了新的方式。那就是直接使用Native AOT函数导出的方式,其它语言(C++、Go、Java各种支持调用导出函数的语言)就可以直接调用C#导出的函数来使用C#库。

废话不多说,让我们开始尝试。

开始尝试

我们先来一个简单的尝试,就是使用C#编写一个用于对两个整数求和的Add方法,然后使用C语言调用它。

1.首先我们需要创建一个新的类库项目。这个大家都会了,可以直接使用命令行新建,也可以通过VS等IDE工具新建。

dotnet new classlib -o CSharpDllExport

2.为我们的项目加入Native AOT的支持,根据.NET的版本不同有不同的方式。

  • 如果你是.NET6则需要引入Microsoft.DotNet.ILCompiler这个Nuget包,需要指定为7.0.0-preview.7.22375.6,新版本的话只允许.NET7以上使用。更多详情请看hez2010的博客 https://www.cnblogs.com/hez2010/p/dotnet-with-native-aot.html
  • 如果是.NET7那么只需要在项目属性中加入<PublishAot>true</PublishAot>即可,笔者直接使用的.NET7,所以如下配置就行。

3.编写一个静态方法,并且为它打上UnmanagedCallersOnly特性,告诉编译器我们需要将它作为函数导出,指定名称为Add。

using System.Runtime.InteropServices;
namespace CSharpDllExport
{
    public class DoSomethings
    {
        [UnmanagedCallersOnly(EntryPoint = "Add")]
        public static int Add(int a, int b)
        {
            return a + b;
        }
    }
}

4.使用dotnet publish -p:NativeLib=Shared -r win-x64 -c Release命令发布共享库。共享库的扩展名在不同的操作系统上不一样,如.dll.dylib.so。当然我们也可以发布静态库,只需要修改为-p:NativeLib=Static即可。

5.使用DLL Export Viewer工具打开生成的.dll文件,查看函数导出是否成功,如下图所示,我们成功的把ADD方法导出了,另外那个是默认导出用于Debugger的方法,我们可以忽略。工具下载链接放在文末。

6.编写一个C语言项目来测试一下我们的ADD方法是否可用。

#define PathToLibrary "E:\\MyCode\\BlogCodes\\CSharp-Dll-Export\\CSharpDllExport\\CSharpDllExport\\bin\\Release\\net7.0\\win-x64\\publish\\CSharpDllExport.dll"
// 导入必要的头文件
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
int callAddFunc(char* path, char* funcName, int a, int b);
int main()
{
    // 检查文件是否存在
    if (access(PathToLibrary, 0) == -1)
    {
        puts("没有在指定的路径找到库文件");
        return 0;
    }
    // 计算两个值的和
    int sum = callAddFunc(PathToLibrary, "Add", 2, 8);
    printf("两个值的和是 %d \n", sum);
}
int callAddFunc(char* path, char* funcName, int firstInt, int secondInt)
{
    // 调用 C# 共享库的函数来计算两个数的和
    HINSTANCE handle = LoadLibraryA(path);
    typedef int(*myFunc)(int, int);
    myFunc MyImport = (myFunc)GetProcAddress(handle, funcName);
    int result = MyImport(firstInt, secondInt);
    return result;
}

7.跑起来看看

这样我们就完成了一个C#函数导出的项目,并且通过C语言调用了C#导出的dll。同样我们可以使用Go的syscall、Java的JNI、Python的ctypes来调用我们生成的dll,在这里就不再演示了。

限制

使用这种方法导出的函数同样有一些限制,以下是在决定导出哪种托管方法时要考虑的一些限制:

  • 导出的方法必须是静态方法。
  • 导出的方法只能接受或返回基元或值类型(即结构体,如果有引用类型,那必须像P/Invoke一样封送所有引用类型参数)。
  • 无法从常规托管C#代码调用导出的方法,必须走Native AOT,否则将引发异常。
  • 导出的方法不能使用常规的C#异常处理,它们应改为返回错误代码。

数据传递引用类型

如果是引用类型的话注意需要传递指针或者序列化以后的结构体数据,比如我们编写一个方法连接两个string,那么C#这边就应该这样写:

[UnmanagedCallersOnly(EntryPoint = "ConcatString")]
public static IntPtr ConcatString(IntPtr first, IntPtr second)
{
    // 从指针转换为string
    string my1String = Marshal.PtrToStringAnsi(first);
    string my2String = Marshal.PtrToStringAnsi(second);
    // 连接两个string 
    string concat = my1String + my2String;
    // 将申请非托管内存string转换为指针
    IntPtr concatPointer = Marshal.StringToHGlobalAnsi(concat);
    // 返回指针
    return concatPointer;
}

对应的C代码也应该传递指针,如下所示:

// 拼接两个字符串
char* result = callConcatStringFunc(PathToLibrary, "ConcatString", ".NET", " yyds");
printf("拼接符串的结果为 %s \n", result);
....
char* callConcatStringFunc(char* path, char* funcName, char* firstString, char* secondString)
{
    HINSTANCE handle = LoadLibraryA(path);
    typedef char* (*myFunc)(char*, char*);
    myFunc MyImport = (myFunc)GetProcAddress(handle, funcName);
    // 传递指针并且返回指针
    char* result = MyImport(firstString, secondString);
    return result;
}

运行一下,结果如下所示:

附录

  • 本文代码链接:https://github.com/InCerryGit/BlogCodes/tree/main/CSharp-Dll-Export
  • DLL Export Viewer下载链接:https://www.nirsoft.net/utils/dllexp-x64.zip
  • NativeAOT文档:https://github.com/dotnet/runtime/tree/main/src/coreclr/nativeaot/docs

相关推荐

python入门到脱坑经典案例—清空列表

在Python中,清空列表是一个基础但重要的操作。clear()方法是最直接的方式,但还有其他方法也可以实现相同效果。以下是详细说明:1.使用clear()方法(Python3.3+推荐)...

python中元组,列表,字典,集合删除项目方式的归纳

九三,君子终日乾乾,夕惕若,厉无咎。在使用python过程中会经常遇到这四种集合数据类型,今天就对这四种集合数据类型中删除项目的操作做个总结性的归纳。列表(List)是一种有序和可更改的集合。允许重复...

Linux 下海量文件删除方法效率对比,最慢的竟然是 rm

Linux下海量文件删除方法效率对比,本次参赛选手一共6位,分别是:rm、find、findwithdelete、rsync、Python、Perl.首先建立50万个文件$testfor...

数据结构与算法——链式存储(链表)的插入及删除,

持续分享嵌入式技术,操作系统,算法,c语言/python等,欢迎小友关注支持上篇文章我们讲述了链表的基本概念及一些查找遍历的方法,本篇我们主要将一下链表的插入删除操作,以及采用堆栈方式如何创建链表。链...

Python自动化:openpyxl写入数据,插入删除行列等基础操作

importopenpyxlwb=openpyxl.load_workbook("example1.xlsx")sh=wb['Sheet1']写入数据#...

在Linux下软件的安装与卸载(linux里的程序的安装与卸载命令)

通过apt安装/协助软件apt是AdvancedPackagingTool,是Linux下的一款安装包管理工具可以在终端中方便的安装/卸载/更新软件包命令使用格式:安装软件:sudoapt...

Python 批量卸载关联包 pip-autoremove

pip工具在安装扩展包的时候会自动安装依赖的关联包,但是卸载时只删除单个包,无法卸载关联的包。pip-autoremove就是为了解决卸载关联包的问题。安装方法通过下面的命令安装:pipinsta...

用Python在Word文档中插入和删除文本框

在当今自动化办公需求日益增长的背景下,通过编程手段动态管理Word文档中的文本框元素已成为提升工作效率的关键技术路径。文本框作为文档排版中灵活的内容容器,既能承载多模态信息(如文字、图像),又可实现独...

Python 从列表中删除值的多种实用方法详解

#Python从列表中删除值的多种实用方法详解在Python编程中,列表(List)是一种常用的数据结构,具有动态可变的特性。当我们需要从列表中删除元素时,根据不同的场景(如按值删除、按索引删除、...

Python 中的前缀删除操作全指南(python删除前导0)

1.字符串前缀删除1.1使用内置方法Python提供了几种内置方法来处理字符串前缀的删除:#1.使用removeprefix()方法(Python3.9+)text="...

每天学点Python知识:如何删除空白

在Python中,删除空白可以分为几种不同的情况,常见的是针对字符串或列表中空白字符的处理。一、删除字符串中的空白1.删除字符串两端的空白(空格、\t、\n等)使用.strip()方法:s...

Linux系统自带Python2&amp;yum的卸载及重装

写在前面事情的起因是我昨天在测试Linux安装Python3的shell脚本时,需要卸载Python3重新安装一遍。但是通过如下命令卸载python3时,少写了个3,不小心将系统自带的python2也...

如何使用Python将多个excel文件数据快速汇总?

在数据分析和处理的过程中,Excel文件是我们经常会遇到的数据格式之一。本文将通过一个具体的示例,展示如何使用Python和Pandas库来读取、合并和处理多个Excel文件的数据,并最终生成一个包含...

【第三弹】用Python实现Excel的vlookup功能

今天继续用pandas实现Excel的vlookup功能,假设我们的2个表长成这样:我们希望把Sheet2的部门匹在Sheet1的最后一列。话不多说,先上代码:importpandasaspd...

python中pandas读取excel单列及连续多列数据

案例:想获取test.xls中C列、H列以后(当H列后列数未知时)的所有数据。importpandasaspdfile_name=r'D:\test.xls'#表格绝对...

取消回复欢迎 发表评论: