发布于2026-07-23 阅读(0)
扫一扫,手机访问
这确实是一个非常值得聊的话题。眼下国产化系统的浪潮已经铺开,适配鸿蒙(HarmonyOS Next)几乎成了每一款国产软件必须迈过的门槛。而在前端、后端都能打的.NET,作为最适合开发客户端编程的语言之一,怎么在鸿蒙系统上真正跑起来,成了很多.NET开发者心头最挂念的事。我来说说目前正在推进的情况——从去年在.NET Conf CN上分享过初步成果之后,利用业余时间一直在移植A valonia到HarmonyOS,如今又有了些实质性的突破,干脆把过程中遇到的所有问题整合起来,和朋友们一起聊聊。
当前阶段,.NET已经可以顺利在HarmonyOS Next上运行。而A valonia的移植项目也已经在真机上跑起来,不过这篇文章的重点还是放在.NET适配层面的具体工作上。
首先要弄清楚的是,从HarmonyOS 5.0.0(12)版本开始,系统禁止匿名内存申请可执行权限。这就意味着,除了系统内置的Ja vaScript引擎外,其他虚拟机想要使用Jit功能根本行不通。所以直接接入CoreCLR这条路是堵死了。最新的Mono虽然能走解释执行的路子,但性能上的牺牲太大,从实践来看也不现实。最后的结果非常明确:只有NativeAOT运行时才是唯一可行的接入方案。
那么,为什么NativeAOT能接进来?关键在于鸿蒙系统兼容libc,是musl那种Linux系统的动态库(.so)。而.NET本身就有针对linux-musl-arm64和linux-musl-x64的RID支持,这意味着理论上我们可以直接把.NET程序编译成原生的Linux动态库(.so),然后在鸿蒙的原生项目里用dlopen和dlsym来调用C#里的入口函数。
反过来说,C#调用鸿蒙的API,则是通过P/Invoke去调用鸿蒙的NDK。至于ArkUI里的TypeScript API,则是通过NDK中提供的napi来实现调用。这番联动逻辑清晰,可以看看我正在进行的A valonia移植项目:https://github.com/OpenHarmony-NET/OpenHarmony.A valonia
鸿蒙系统用上了seccomp机制来限制危险的syscall调用。听起来很专业,但它的做法相当“硬核”——不是宽松地返回错误码,而是发现非法调用就直接把进程干掉。.NET运行时初始化时会触发一个叫__NR_get_mempolicy的系统调用来检查numa支持,可惜这个调用不在鸿蒙的seccomp白名单里,所以程序就直接宕了。
鸿蒙的seccomp白名单在这里可以看到:https://gitee.com/openharmony/startup_init/blob/master/services/modules/seccomp/seccomp_policy/app.seccomp.policy
其实安卓里也有类似的限制,但.NET的NativeAOT之所以能在安卓平台下正常跑,是因为.NET针对安卓平台做了特殊处理。而鸿蒙这边,我们直接用了Linux平台的代码,自然没照顾到这些细节。解决办法倒也不复杂——自己动手修改源码,把numa这部分函数全部替换成空函数即可。
第一个问题解决完之后,运行时初始化还是崩溃。排查了很久才发现,原来GC初始化时会申请大约256G的虚拟内存,mmap直接返回了Out Of Memory错误。这显然有备而来,但解法也明确:要么设置环境变量DOTNET_GCHeapHardLimit,把虚拟内存申请控制在180G以下;要么直接修改源代码,把USE_REGIONS宏关掉即可。
鸿蒙本身不会自带ICU和OpenSSL这类库,只能另想办法。有两种路径可以走:方案一——从Alpine上“偷”包,因为Alpine的libc就是musl,所以Alpine的库在鸿蒙上基本都能用。arm64架构的包可以从阿里云镜像拿:https://mirrors.aliyun.com/alpine/edge/main/aarch64/;x64架构的则是:https://mirrors.aliyun.com/alpine/edge/main/x86_64/。方案二——如果那个库本身是cmake项目,直接通过鸿蒙的CMake工具链编译就完事了。

鸿蒙的ICU配置文件路径和默认路径不一样,需要手动调用修改环境变量的API,把ICU_DATA改成/system/usr/ohos_icu。而且鸿蒙使用的libICU大版本是72,必须用这个版本的库才能正常跑起来。
NativeAOT有个众所周知的弱点——不支持跨平台编译。我的方案需要发布到linux-musl平台,所以如果只用Windows开发,效率会大打折扣。好消息是,可以直接在项目中引用这个开源项目:https://github.com/OpenHarmony-NET/PublishAotCross,它专门解决了跨平台编译的问题。

Marshal.GetDelegateForFunctionPointer相关函数这个也值得多说两句。Marshal.GetDelegateForFunctionPointer的实现依赖动态生成汇编,但鸿蒙系统不支持动态生成汇编代码执行(即Jit),直接调用肯定会崩。替代方案是用函数指针直接调用,就能绕开这个障碍。
前面提到好几个问题需要改源码,具体操作可以参考以下步骤。在Linux平台下(需要搭建好编译环境),修改完代码后执行下面的命令进行编译:
./build.sh --subset clr.aot --configuration Release -arch arm64 --cross |
编译成功之后,打开目录 运行时/artifacts/bin/coreclr/linux.arm64.Release/aotsdk,把里面所有内容替换到本机nuget的缓存目录,比如C:\Users\用户名\.nuget\packages\runtime.linux-musl-arm64.microsoft.dotnet.ilcompiler\dotnet版本\sdk。

https://github.com/dotnet/runtime/issues/110074
https://github.com/dotnet/runtime/issues/111649
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8