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

   2023-03-08 学习力0
核心提示:简介上一篇文章使用C#编写一个.NET分析器文章发布以后,很多小伙伴都对最新的NativeAOT函数导出比较感兴趣,今天故写一篇短文来介绍一下如何使用它。在以前,如果有其他语言需要调用C#编写的库,那基本上只有通过各种RPC的方式(HTTP、GRPC)或者引入一层C++

简介

上一篇文章使用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;
}

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

附录

 
反对 0举报 0 评论 0
 

免责声明:本文仅代表作者个人观点,与乐学笔记(本网)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
    本网站有部分内容均转载自其它媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,若因作品内容、知识产权、版权和其他问题,请及时提供相关证明等材料并与我们留言联系,本网站将在规定时间内给予删除等相关处理.

  • 使用C#编写一个.NET分析器(一) 使用csv模块的什么方法可以一次性将一行数据写入文件
    使用C#编写一个.NET分析器(一) 使用csv模块的
    译者注这是在Datadog公司任职的Kevin Gosse大佬使用C#编写.NET分析器的系列文章之一,在国内只有很少很少的人了解和研究.NET分析器,它常被用于APM(应用性能诊断)、IDE、诊断工具中,比如Datadog的APM,Visual Studio的分析器以及Rider和Reshaper等等。之前
    03-08
  • 我比较了 Go 和 C# 的速度
    我比较了 Go 和 C# 的速度
    我在 Go 和 C# 之间进行了速度比较。我通常使用 C#,但我有机会使用 Go,并且由于传闻 Go 速度很快,所以我实际测量了它。测量内容我在 Go 和 C# 中执行了一个简单的循环和判断过程,以查看整数 2 到 N 是否为质数。来源是Github参考。测量模式 逻辑内核 8 Wi
    03-08
  • [C#]使用 AltCover 获得代码覆盖率 - E2E Test 和 Unit Test
    [C#]使用 AltCover 获得代码覆盖率 - E2E Test
    背景在 CI/CD 流程当中,测试是 CI 中很重要的部分。跟开发人员关系最大的就是单元测试,单元测试编写完成之后,我们可以使用 IDE 或者 dot cover 等工具获得单元测试对于业务代码的覆盖率。不过我们需要一个独立的 CLI 工具,这样我们才能够在 Jenkins 的 CI
  • C#中LINQ的Select与SelectMany函数如何使用 c反应蛋白高说明什么
    C#中LINQ的Select与SelectMany函数如何使用 c反
    本篇内容主要讲解“C#中LINQ的Select与SelectMany函数如何使用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C#中LINQ的Select与SelectMany函数如何使用”吧!LINQ的Select与SelectMany函数使用Select扩展函
    02-09 linqselect
  • PerfView专题 (第三篇):如何寻找 C# 中的 VirtualAlloc 内存泄漏
    PerfView专题 (第三篇):如何寻找 C# 中的 Virt
    一:背景上一篇我们聊到了如何用 PerfView 去侦察 NTHeap 的内存泄漏,这种内存泄漏往往是用 C 的 malloc 或者 C++ 的 new 分配而不释放所造成的,这一篇我们来聊一下由 VirtualAlloc 方法造成的泄漏如何去甄别?了解 VirtualAlloc 的朋友肯定说, C# 这种高
    02-09
  • Blazor和Vue对比学习(知识点杂锦3.04):Blazor中C#和JS互操作(超长文)
    Blazor和Vue对比学习(知识点杂锦3.04):Blazo
    C#和JS互操作的基本语法是比较简单的,但小知识点特别多,同时,受应用加载顺序、组件生命周期以及参数类型的影响,会有比较多坑,需要耐心的学习。在C#中调用JS的场景会比较多,特别是在WASM模式下,由于WebAssembly的限制,很多时候,还是需要借助JS去控制D
    02-09
  • 的键">C#怎么使用struct类型作为泛型Dictionary
    本文小编为大家详细介绍“C#怎么使用struct类型作为泛型DictionaryTKey,TValue的键”,内容详细,步骤清晰,细节处理妥当,希望这篇“C#怎么使用struct类型作为泛型DictionaryTKey,TValue的键”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学
  • C#如何实现折半查找算法 彩票查询
    C#如何实现折半查找算法 彩票查询
    本篇内容主要讲解“C#如何实现折半查找算法”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C#如何实现折半查找算法”吧!折半查找,也叫二分查找,当在一个数组或集合中查找某个元素时,先定位出中间位置元素
    02-09
  • C#如何实现选择排序 c罗
    C#如何实现选择排序 c罗
    本篇内容主要讲解“C#如何实现选择排序”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C#如何实现选择排序”吧!选择排序是一种低效的排序算法,大致过程是:遍历数组的每一个元素,先假设0号位置上的元素是最
    02-09
  • PerfView专题 (第四篇):如何寻找 C# 中程序集泄漏
    PerfView专题 (第四篇):如何寻找 C# 中程序集
    一:背景前两篇我们都聊到了非托管内存泄漏,一个是 HeapAlloc ,一个是 VirtualAlloc,除了这两种泄漏之外还存在其他渠道的内存泄漏,比如程序集泄漏,这一篇我们就来聊一聊。二: 程序集也会泄漏?在我分析的一百多dump中,程序集方面的泄漏主要有 XmlSeria
    02-09
点击排行