🐱 算神的小窝 🤓

仓颉语言与net9互调用.md


CreationTime:7/4/2025 9:10:02 PM LastAccessTime:8/21/2025 3:43:57 PM


仓颉语言与.net9 C#互调用

仓颉语言简介

仓颉编程语言是由华为打造一款面向全场景智能的新一代编程语言,主打原生智能化、天生全场景、高性能、强安全。主要应用于鸿蒙原生应用及服务应用等场景中,为开发者提供良好的编程体验。

小试牛刀

作为一名dotnet开发人员,最近又接触到了仓颉语言。突发奇想能不能将两者联动一下,于是有了本篇内容。

仓颉安装

仓颉官网下载SDK包,解压后添加一下Path环境变量即可,详细的可看安装指南,此处不多加赘述。

之后,再到VSCode的插件商店搜索Cangjie安装,再配置一下插件的SDK路径,环境就算是配置完成了。

准备C#的类库项目

  1. 新建一个类库项目CSLibrary
  2. 写点代码Hello.cs
namespace CSLibrary;

public static class Hello
{
    public static string SayHello(string name)
    {
        return $"Hello, {name}! My name is CSLibrary.Hello";
    }
}

这里的思路是通过P/Invoke手法导出原生C接口,让仓颉调用,所以Hello.cs要这样修改下

using System.Runtime.InteropServices;
namespace CSLibrary;

public static class Hello
{
    [UnmanagedCallersOnly(EntryPoint = "sayhello")]
    public static IntPtr SayHello(IntPtr namePtr)
    {
        // 将非托管字符串指针转换为C#字符串
        string? name = Marshal.PtrToStringUTF8(namePtr);
        string greeting = $"Hello, {name}! My name is CSLibrary.Hello";

        // 将C#字符串转换为非托管UTF-8字符串指针
        IntPtr resultPtr = Marshal.StringToCoTaskMemUTF8(greeting);
        return resultPtr;
    }
}

之所以如此修改是因为非托管函数只能使用非托管兼容的类型(如基本数值类型、指针或结构体),而C#中的string是托管类型。

  1. 修改CSLibrary.csproj启用AOT编译
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <OutputType>Library</OutputType>
    <PublishAot>true</PublishAot>    <!-- 启用AOT编译 -->
  </PropertyGroup>

</Project>
  1. 发布为原生DLL
dotnet publish -c Release -r win-x64

这样我们就得到了一个CSLibrary.dll,放着备用。

编写仓颉代码

在之前配置好的VSCode环境中,按下ctrl+shift+p,输入Create Cangjie Project,按照提示一路下去就可以创建一个仓颉工程项目

修改main.cj里面的代码

package CJinvokedotnet
// declare the function by `foreign` keyword
foreign func sayhello(namePtr: CString): CString

main(): Int64 {
    println("hello world")
    // call this function by `unsafe` block
    unsafe {
        var name = LibC.mallocCString("算神")
        var greeting = sayhello(name)
        println(greeting)
        LibC.free(name)
    }
    return 0
}

把刚才得到的CSLibrary.dll放到main.cj的同级目录下

使用以下命令进行编译

cjc -L . -l CSLibrary ./main.cj

运行生成的main.exe可得到以下结果

hello world
Hello, 算神! My name is CSLibrary.Hello

C#调用仓颉的dll

  1. 新建一个文件mylib.cj
package CJinvokedotnet
// 定义C可见方法
@C
func myHello(): Unit {
    println("您好,欢迎使用仓颉!")
}
  1. 使用下面的命令编译可以得到一个libCJinvokedotnet.dll
cjc mylib.cj --output-type=dylib
  1. C#中通过DllImport导入libCJinvokedotnet.dllmyHello方法,并调用它
// 导入仓颉库的自定义方法
[DllImport("libCJinvokedotnet.dll", EntryPoint = "myHello")]
private static extern void myHello();

// 调用仓颉自定义库中的myHello方法
myHello();

一键体验(Windows系统)

  1. Clone本项目https://github.com/lishewen/CJinvokedotnet
  2. 使用build.bat一键生成C#和仓颉的项目
  3. 运行main.exe

Linux系统下的互调用

  1. Hello.cs中的
[DllImport("libCJinvokedotnet.dll", EntryPoint = "myHello")]

修改为:

[DllImport("libCJinvokedotnet.so", EntryPoint = "myHello")]

仓颉在Linux系统默认编译出来的库为 .so 文件 2. CSLibrary.csproj中的AssemblyName需要加上lib前缀,即

<AssemblyName>libCSLibrary</AssemblyName>

这样就能让编译出来的文件名为libCSLibrary.so

这是cjc编译器的一个约定lib[arg].so,为了让Linux下的cjc编译器能正确地识别出文件 3. 最终编译出来的main文件,需要给与其可执行权限

chmod +x main

Linux下的build.sh脚本使用

  1. 赋予可执行权限
chmod +x build.sh
  1. 可加架构参数,如笔者使用的是树莓派4B,所以在后面加arm64,不加则默认为x64
./build.sh arm64
An unhandled error has occurred. Reload 🗙