Ⅰ. Kotlin/Native 简介

Kotlin/Native 是一项将 Kotlin 代码直接编译为本机二进制文件(无需 JVM)的技术。其核心优势在于:

  • 高效执行:基于 LLVM 后端,生成针对特定平台优化的机器码。
  • 无缝互操作:可以轻松调用 C/C++ 等系统原生库。
  • 跨平台支持:最初为 iOS 设计,现已扩展至 Windows, Linux, macOS, Android 等多个平台。

Ⅱ. Windows 平台本机库概览

在 Windows 系统中,代码库主要有以下几种形式,了解它们的区别对于开发至关重要。

文件类型 扩展名 功能 类比
动态链接库 .dll 在程序运行时按需加载,可被多个程序共享。 Linux .so
静态链接库 .lib 在编译链接阶段,其代码被完整复制到最终的可执行文件中。 Linux .a
导入库 .lib 同样是 .lib 文件,但它仅包含符号引用,用于告诉链接器去哪里找对应的 .dll -

💡 .lib 文件的“双重身份”

Windows 下的 .lib 文件存在歧义:

  1. **静态库 (Static Library)**:包含完整的二进制代码(.obj 文件的集合),由 cl.exe (MSVC) 或 ar (MinGW) 生成,文件体积较大。
  2. **导入库 (Import Library)**:仅包含一个指向 .dll 的符号列表和元数据,本身不含任何可执行代码。它在链接时使用,文件体积非常小 (通常只有几 KB)。

Ⅲ. 实战:创建并使用 Kotlin/Native 动态库

本节将完整演示如何使用 Kotlin/Native 创建一个 .dll 动态库,并再创建一个 Kotlin/Native 程序来调用它。

📦 环境准备

  • Gradle 项目: 建议使用官方提供的 KMP 模板项目 git clone https://github.com/Kotlin/kmp-native-wizard.git
  • Visual Studio Build Tools: 用于生成导入库 .lib 文件。它比完整的 Visual Studio 更轻量。
    • 下载地址 (选择 Tools for Visual Studio -> Build Tools for Visual Studio)。

第 1 阶段:创建 Kotlin/Native 动态库 (.dll)

步骤 1: 配置 Gradle 构建脚本

build.gradle.kts 文件中,声明我们的目标平台为 mingwX64,并配置生成一个动态库。

1
2
3
4
5
6
7
8
9
10
11
12
// build.gradle.kts
kotlin {
// 仅针对 Windows MinGW 64位平台
mingwX64("native") { // "native" 是一个自定义的目标名称
binaries {
// 配置生成一个名为 "mordecai" 的动态库 (mordecai.dll)
sharedLib {
baseName = "mordecai"
}
}
}
}

步骤 2: 编写要导出的 Kotlin 代码

创建一个 Kotlin 文件,并使用 @CName 注解来导出一个函数,使其能被 C/C++ 调用。

1
2
3
4
5
6
7
8
// src/nativeMain/kotlin/Mordecai.kt
import kotlin.experimental.ExperimentalNativeApi

// @CName 将此函数导出为 C 语言符号 "sayHelloFromKotlinNative"
@CName("sayHelloFromKotlinNative")
fun sayHello() {
println("Hello, this message is from Kotlin/Native!")
}

步骤 3: 编译生成动态库

执行 Gradle 任务来编译项目。

1
2
3
4
5
6
7
# 执行此命令
./gradlew linkNativeDebugShared # 或 linkNativeReleaseShared

# 编译成功后,产物会出现在以下目录:
# build/bin/native/debugShared/mordecai.dll
# build/bin/native/debugShared/mordecai.def
# build/bin/native/debugShared/mordecai_api.h

产物说明:

  • mordecai.dll: 核心的动态库文件。
  • mordecai.def: 模块定义文件,描述了 DLL 导出的函数。
  • mordecai_api.h: C/C++ 头文件,包含了导出函数的声明。

第 2 阶段:在 Kotlin/Native 程序中调用动态库

现在,我们将创建一个可执行文件 (.exe) 来调用刚刚生成的 .dll

步骤 4: 生成导入库 (.lib)

为了让链接器能够识别 .dll 中的函数,我们需要从 .def 文件生成一个导入库 .lib

  1. 打开 x64 Native Tools Command Prompt for VS 2022 (通过开始菜单搜索)。

  2. 进入 DLL 所在的目录。

    1
    cd path\to\your\project\build\bin\native\debugShared
  3. 执行 lib.exe 命令生成导入库。

    1
    2
    3
    4
    # /def: 指定定义文件
    # /out: 指定输出的导入库文件名
    # /machine:x64 必须指定目标平台为 x64,否则可能因架构不匹配而链接失败
    lib /def:mordecai.def /out:mordecai.lib /machine:x64

执行完毕后,当前目录下会生成 mordecai.lib 文件。

步骤 5: 组织 C 互操作文件

为了让 Kotlin/Native 的 C 互操作工具 (cinterop) 能够找到库文件和头文件,我们需要将它们组织到项目中。

  1. src/ 目录下创建以下结构:

    1
    2
    3
    4
    5
    6
    7
    8
    src/
    └── nativeInterop/
    └── cinterop/
    ├── include/ # 存放头文件
    │ └── mordecai_api.h
    └── libs/ # 存放库文件
    ├── mordecai.dll
    └── mordecai.lib
  2. 拷贝文件

    • 将步骤 3 生成的 mordecai_api.h 拷贝到 include/ 目录。
    • 将步骤 3 生成的 mordecai.dll 和步骤 4 生成的 mordecai.lib 拷贝到 libs/ 目录。

步骤 6: 配置 C 互操作 (cinterop)

回到 build.gradle.kts,添加 cinterop 配置,告诉 Kotlin 如何与我们的 C 库进行交互。同时,添加一个 executable 配置来生成 .exe 文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// build.gradle.kts
kotlin {
mingwX64("native") {
// ... sharedLib 配置保留 ...

// 新增 executable 配置,用于生成 .exe 文件
binaries {
sharedLib { baseName = "mordecai" } // 可以保留
executable {
// entryPoint 的默认值就是 "main",可以省略
}
}

compilations.getByName("main") {
cinterops {
// 创建一个名为 "mordecai" 的 cinterop 配置
val mordecai by creating {
// 指定 .def 文件,cinterop 会据此生成 Kotlin 绑定
defFile(project.file("src/nativeInterop/cinterop/mordecai.def"))
// 添加头文件搜索路径
includeDirs("src/nativeInterop/cinterop/include")
}
}
}
}
}

同时,创建 src/nativeInterop/cinterop/mordecai.def 文件,用于指导 cinterop 工具。

1
2
3
4
5
6
7
8
9
10
11
12
# src/nativeInterop/cinterop/mordecai.def

# 生成的 Kotlin 代码将位于此包下
package = com.mordecai.kn.tools

# 需要解析的头文件
headers = mordecai_api.h

# 链接器选项
# -L: 指定库文件的搜索路径
# -l: 指定要链接的库名 (mordecai.lib -> -lmordecai)
linkerOpts.mingw_x64 = -Lsrc/nativeInterop/cinterop/libs -lmordecai

配置完成后,同步 Gradle 项目 (Sync Project with Gradle Files),cinterop 会自动运行并生成 Kotlin 绑定代码。

步骤 7: 编写主程序并运行

现在可以编写 main 函数来调用库中的 sayHelloFromKotlinNative 函数了。

1
2
3
4
5
6
7
8
// src/nativeMain/kotlin/Main.kt
import com.mordecai.kn.tools.sayHelloFromKotlinNative

fun main() {
println("Calling the function from our DLL...")
sayHelloFromKotlinNative()
println("Call finished.")
}

执行 Gradle 任务来运行程序。

1
./gradlew runDebugExecutableMingwX64

⚠️ 运行时错误?

第一次运行时,你可能会遇到一个错误,提示找不到 mordecai.dll。这是因为 .exe 在运行时需要在其所在目录或系统路径中找到它依赖的 .dll 文件。

解决方案:将 src/nativeInterop/cinterop/libs/mordecai.dll 文件手动复制到可执行文件的输出目录 build/bin/native/debugExecutable/ 下,然后再次运行命令即可成功。


Ⅳ. 简化流程:使用静态库

如果不想处理 .dll 运行时依赖的问题,可以选择静态链接。这会将库代码直接编译进最终的 .exe 文件中,使其成为一个独立的单文件程序,但会增加可执行文件的体积。

  1. 配置 Gradle 生成静态库
    build.gradle.ktsbinaries 块中,添加 staticLib 配置。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    kotlin {
    mingwX64("native") {
    binaries {
    // 生成静态库 mordecai.a (MinGW 默认) 或 mordecai.lib (MSVC)
    staticLib { baseName = "mordecai" }
    executable() // 保持可执行文件配置
    }
    // ... cinterop 配置保持不变 ...
    }
    }
  2. 编译生成静态库

    1
    ./gradlew linkNativeDebugStatic # 或 linkNativeReleaseStatic

    这会在 build/bin/native/debugStatic/ 目录下生成 libmordecai.a 文件。

  3. 组织并链接

    • libmordecai.amordecai_api.h 拷贝到 src/nativeInterop/cinterop/ 下对应的 libsinclude 目录。
    • mordecai.def 文件中的 linkerOpts 配置保持不变,链接器会自动识别并使用 .a 文件。
    • 同步 Gradle 项目。
  4. 直接运行

    1
    ./gradlew runDebugExecutableMingwX64

    由于所有代码都已链接进 .exe,这次无需再手动拷贝任何文件,程序可以直接运行。


Ⅴ. 总结

本文详细介绍了在 Windows 平台上使用 Kotlin/Native 创建和消费原生库的两种主要方式:

  • 动态链接:通过 .dll 和导入库 .lib 实现。优点是代码共享、模块化更新;缺点是需要处理运行时依赖。
  • 静态链接:通过静态库 .a.lib 实现。优点是部署简单、单文件分发;缺点是可执行文件体积较大。

掌握这两种技术,可以让你无缝地将 Kotlin/Native 集成到现有的 C/C++ 项目中,或者为其他语言提供高性能的 Kotlin 库。

参考源码与自动化插件

本文所有流程的实现代码,以及一个可以自动拷贝依赖文件的 Gradle 插件,都可以在以下仓库找到:
https://github.com/crowforkotlin/mordecai-kn-mingw-example