Kotlin/CLR 让Kotlin走进.NET世界

让 Kotlin 走进 .NET 的世界

项目介绍

这是一个 Kotlin/CLR 后端编译器, 通过复用官方编译器实现基本前端编译, 使用 C# Assembly API 实现程序集解析, 用于提供符号以支持 Kotlin 调用 C#, 后端编译至 C# 源码, 再由用户手动通过 Roslyn 编译完成完整的编译流程

项目链接

基本功能

  • 由于项目刚刚起步, 很多语法还未支持, 标准库也未完善

基本的标准库调用

kotlin 复制代码
fun main() {
    println("Hello World!")
    Console.WriteLine("Hello .NET!")
}

类型映射

kotlin.Any <-> System.Object

kotlin.Int <-> System.Int32

kotlin.Byte <-> System.SByte

...

面向对象

kotlin 复制代码
open class Shape

class Rectangle(val height: Double, val length: Double): Shape() {
    val perimeter = (height + length) * 2
}
  • 生成的 C#
c# 复制代码
public class Shape : global::System.Object
{
    public Shape() : base()
    {
    }
}
public sealed class Rectangle : global::Shape
{
    public Rectangle(global::System.Double height, global::System.Double length) : base()
    {
        this.height = height;
        this.length = length;
        this.perimeter = ((this.height) + (this.length)) * (2);
    }
    public global::System.Double height { get; }
    public global::System.Double length { get; }
    public global::System.Double perimeter { get; }
}

KMP

尽管目前并不能作为 Gradle 插件使用, 也没有 IDE 支持, 但你依然可以与现有 KMP 项目一同使用, 且支持 KMP

在 Arguments 处提供属性 commonSources 即可将 commonMain 视为 common 进行编译:

kotlin 复制代码
CLRCompilerArguments().apply {
    freeArgs += "src/commonMain/kotlin"
    freeArgs += "src/clrMain/kotlin"
    commonSources = arrayOf("src/commonMain/kotlin")
    ...
}

原理

  1. 使用官方 kotlin-compiler-embeddable, 复用官方编译器组件进行 Configuration, Frontend, Fir2Ir 编译
  2. 编写 Kotlin/CLR 后端编译器将 Kotlin IR 降级并生成 C# 源码
  3. 使用 C# 编写的 AssemblyResolver 解析 .dll 文件, 并将其传递给 Kotlin/CLR 编译器
  4. 使用 C# 编写的标准库和 AssemblyResolver 为 Kotlin/CLR 提供标准库
  5. 通过 ClrSymbolProvider 提供 C# 的符号解析, 使得 Kotlin/CLR 可以使用 C# 的类型和方法

诞生历程

我几年前经常玩一款游戏 蔚蓝(Celeste), 这是一款由 .NET C# 和 XNA/FNA 开发的 2D 横板游戏, 我在游玩一段时间后接触到了 Mod, 希望了解 Mod 开发, 在当时我就希望能使用 Kotlin 开发一个 Mod, 我尝试了 IKVM 但 IKVM 作为 Mod 貌似无法启动, 在当时便想到通过 Kotlin/Native 实现, 但由于不熟悉便就此放弃

去年我一个朋友拉我入坑了另一款游戏 星露谷物语(Stardew Valley), 这也是一款由 .NET C# 和 XNA/FNA 开发的 2D 俯视角游戏, 她当时在开发一个 Mod, 恰巧她们团队的 Coder 跑路了, 我就被拉来做她的 Coder

随后我写了一段时间的 C#, 但作为一个写了一年的 Kotliner, 实在是不能习惯 C#, 此时我又想起了 Kotlin/Native, 并做了一期视频和两条动态介绍(分享一下如何在C#中使用Kotlin/Native开发, C#使用Kotlin - 结构体传递, C#使用Kotlin - 对象传递优化)

但也遇到了一些问题: 遇到异常直接 Fatal Error且没有任何可用信息, 不管是 Kotlin 还是 C# 侧指针都用的太多了, 维护成本很大, 且开发体验极差

随后我想到了做一个编译器, 在了解了一点编译原理知识后开始了首次尝试, 使用 C# 和 ANTLR 做了一个简易的 Kotlin 解释器, 但项目架构简陋, 后面的开发会变得很难, 且无法直接支持和 C# 进行互调用, 又因为不习惯 C#, 便放弃了这个项目

再往后, 我尝试用 Kotlin 编写一个编译器, 也就是这个项目, 但由于对编译原理知识的缺乏, 对于语义分析等没有什么了解, 也很难再继续写下去, 于是又放弃了

再往后, 就是这篇文章的主角 Kotlin/CLR 后端编译器, 通过复用 Kotlin 官方编译器进行前端编译和 Fir2Ir, 而我只需要实现 CLR 后端, 以及符号提供等就行, 开发难度变得很低

.NET 有什么好处?

得益于 .NET 原生支持值类型和指针, 在内存严格情况下能有更低的内存占用

Kotlin 一直是类型擦除 + 具象化泛型, 在使用上总归差点感觉, 而 .NET 真泛型就能很好解决这一痛点

目前游戏开发(Unity, Godot, Unreal Engine w/ UnrealSharp) 多是 C# 脚本, 支持 .NET 或许能让 Kotlin 在游戏领域有一席之地

在桌面开发上, Kotlin 现有方案是 Compose Desktop(Awt), 在性能, 内存和特性支持上表现较差, 且不够成熟, GTK Kotlin Binding 学习资料较少, 但 C# 这块有非常多的框架(WinForm, WPF, UWP, MAUI, Xamarin, Avalonia, Uno Platform...), 如果支持 .NET 后在桌面领域 Kotlin 也能实现无缝迁移

我做这个项目有什么好处?

我写代码一直是出于兴趣, 我想做这个项目, 这就是我的动力

其次, 在 KMP 非官方目标方向上, 该项目或许可以成为一个参考, 激发更多 Kotlin 开发者尝试自己心中的目标, 为 Kotlin 生态发展出一份力

遇到的问题

其他 KMP 目标都是直接编译到产物, 而 CLR 目前却选择编译到半成品(C# 源码), 在开工之前就想过直接编译到产物, 但由于 Roslyn 只能使用 .NET 系语言使用, 而官方编译器使用 Kotlin/JVM 编写, 很难迁移至 Kotlin/CLR, 而跨进程传递 IR 工作量过大, 而编译到 IL 难度也十分大, 综合考虑后编译到 C# 是目前最快见效, 性价比最高的方案


Kotlin 语言一直采用类型擦除, 而 .NET 是真泛型, 这在互操作体验上会造成一定的割裂, 但好在 Kotlin 有 reified 具象化泛型, 可以通过不 inline 的 reified 实现真泛型


协程 C# 有相应的 await + async, 但具体的 CoroutineScope, Continuation 等可能是一个问题, 这在目前还没有想到如何解决(其实是我不懂 Kotlin 协程和 C# 的异步导致的)


C# 有 Struct 和指针, 这在 Kotlin/JVM 上是没有对应的, 但在了解到 Kotlin/Native 后, 我得到了答案:

Multi-Field Value Class 可以作为 Struct 的实现方案, Pointer<T> 则可以作为指针的实现方案, 对于 unsafe 使用如下:

kotlin 复制代码
@OptIn(Unsafe::class)
fun unsafeFun(): Pointer<Int> {
    val variable = 10
    return variable.pointer
}

fun main() {
    unsafe {
        val ptr = unsafeFun()
        println("address: $ptr, value: ${ptr.value}")
    }
}

KMP 依赖也是一个问题, 目前想到的解决方法是 Kotlin/CLR 插件自动检测仓库有没有 CLR 目标的清单及产物, 没有时会报错, 但开发者也可通过手动引入 CLR 目标的扩展依赖来补全 KMP 依赖

kotlin 复制代码
implementation("cn.yurin.ktor:ktor-client-core-clr:3.1.3" completion "io.ktor:ktor-client-core:3.1.3")

展望未来

目前而言, 由于编译器不完善无法编译 Kotlin 标准库, 标准库依然是使用 C# 编写, 在编译器完善后将迁移至 Kotlin 标准库


在目前设想中, 未来会改为直接编译至 IL/DLL, 也会通过 Gradle 插件作为官方 KMP 插件扩展插件使用, 且支持 KMP, 且能直接在 gradle build file 内引入 nuget 依赖

kotlin 复制代码
implementation(nuget("Newtownsoft.Json", "13.0.3"))

由于 C# 及其生态链并不被 IDEA 支持, Kotlin 及其生态链也不被 Rider/Visual Studio 支持, 因此考虑到开发体验, 还需要推出 IDEA 的 C#, msbuild, nuget 等插件, 并在此基础上推出 Kotlin/CLR 插件实现最佳开发体验


在 Kotlin/CLR 基本完善后, 尝试将一些主流库移植到 CLR, 如 Ktor, Exposed, Compose Multiplatform...

相关推荐
モンキー・D・小菜鸡儿4 小时前
Android 中 StateFlow 的使用
android·kotlin
我又来搬代码了5 小时前
【Android】【Compose】Compose知识点复习(一)
android·前端·kotlin·android studio
hnlgzb10 小时前
好像kotlin class和kotlin file都可以是activity?
android·开发语言·kotlin
zhangphil10 小时前
Kotlin超时withTimeout超时与ensureActive()取消协程任务执行
kotlin
hnlgzb18 小时前
安卓app开发,如何快速上手kotlin和compose的开发?
android·开发语言·kotlin
alexhilton19 小时前
Jetpack Compose 2025年12月版本新增功能
android·kotlin·android jetpack
lin62534221 天前
Android九宫格,1张图到9张图适配;图片自定义UI
android·ui·kotlin
zhangphil1 天前
Kotlin协程buffer缓冲池,调度任务执行
kotlin
モンキー・D・小菜鸡儿2 天前
Android Jetpack Compose 基础控件介绍
android·kotlin·android jetpack·compose
侠***I2 天前
基于OOA-TCN-BiGRU-Attention的鱼鹰算法优化多变量时间序列预测
kotlin