📖

Rustで書いたDLLをZigで読み込む

2024/03/30に公開

姉妹編
https://zenn.dev/mkpoli/articles/591756f1af6ca8

まあRustからZigのDLLを読み込む文章を書いたので、その逆もやってみましょうか。

はじめに

本稿では、ZigのアプリにRustで生成されたDLL(ダイナミック・リンク・ライブラリ)を読み込む方法についてお話します。基本的にはWindows中心ですが、Linuxなどでも似たような手順でできるかと思われます。

TL;DR

読み込みはC ABIを介して、std.DynLibでやります。

インストール

TL;DR

Windows Terminalを入れて)Powershellを開いて

# Scoopをインストール
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
Invoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression

# Rustをインストール
scoop install rustup
rustup update

# Zigをインストール
scoop bucket add versions
scoop install versions/zig-dev

再起動します。

開発環境

VSCodeを入れます。rust-lang.rust-analyzer及びziglang.vscode-zigという拡張機能をインストールします(Ctrl+Shift+P、Install Extention)。出てきたZigの確認窓でPATHにあるものを設定、ZLSはインストールします。

プロジェクト設定

mkdir rust-zig-interop
cd rust-zig-interop
code .

開いたVSCodeのターミナルを開いて

mkdir rust-lib
cd rust-lib
cargo init
cd ..
mkdir zig-exe
cd zig-exe
zig init
cd ..

すると、以下のようなフォルダー構造になります

.
├── rust-lib
│   ├── Cargo.toml
│   └── src
│       └── lib.rs
└── zig-exe
    ├── build.zig
    ├── build.zig.zon
    └── src
        ├── main.zig
        └── root.zig

ライブラリの開発(Rust側)

rust-lib/Cargo.tomlcdylibモードを追加します。

rust-lib/Cargo.toml
@@ -3,4 +3,7 @@
 version = "0.1.0"
 edition = "2021"

+[lib]
+crate-type = ["cdylib"]
+
 [dependencies]

今回はそのまま自動生成されたrust-lib/src/lib.rsを使うので、その中身を覗いてみましょう。簡単な足し算関数add()がエクスポートされているのがわかります。

rust-lib/src/lib.rs
pub fn add(left: usize, right: usize) -> usize {
    left + right
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        let result = add(2, 2);
        assert_eq!(result, 4);
    }
}

一番上のadd()のすぐ上に、#[no_mangle]を追加して、Rustが名前をいじらないようにします。

rust-lib/src/lib.rs
@@ -1,3 +1,4 @@
+#[no_mangle]
 pub fn add(left: usize, right: usize) -> usize {
     left + right
 }

そして、以下のコマンドを実行すると、コンパイルされたRustのDLLなどのファイルが生成されます。

cd rust-lib
cargo build
cd ..
├── debug
│   ├── build
│   ├── deps
│   │   ├── rust_lib.d
│   │   ├── rust_lib.dll
│   │   ├── rust_lib.dll.exp
│   │   ├── rust_lib.dll.lib
│   │   └── rust_lib.pdb
│   ├── examples
│   ├── incremental
│   │   └── ...
│   ├── rust_lib.d
│   ├── rust_lib.dll
│   ├── rust_lib.dll.exp
│   ├── rust_lib.dll.lib
│   └── rust_lib.pdb

ライブラリの取り込み(Zig側)

まず、zig:zig-exe/src/main.zigに以下のように書き換えます。

zig-exe/src/main.zig
const std = @import("std");

pub fn main() !void {
    var dll = try std.DynLib.open("rust_lib.dll");
    const add = dll.lookup(*fn (i32, i32) i32, "add").?;

    std.debug.print("rust_lib.dll: add({}, {}) = {}\n", .{
        1,
        2,
        add(1, 2),
    });
}

zig-lib/build.zigを開きます。LIBを生成するため部分やコメントなどを消し、RustのコンパイルやZigのzig-out/binにコピーするコードを追加します。変更したものはこうなります。

zig-lib/build.zig
const std = @import("std");
const fs = std.fs;

const RUST_DIR = "../rust-lib";
const RUST_RELEASE_DIR = RUST_DIR ++ "/target/release";
const DLL_NAME = "rust_lib.dll";

const RUST_DLL_RELEASE_PATH = RUST_RELEASE_DIR ++ "/" ++ DLL_NAME;
const ZIG_BIN_OUT_DIR = "zig-out/bin";

pub fn build(b: *std.Build) !void {
    _ = b.run(&[_][]const u8{ "cargo", "build", "--manifest-path", RUST_DIR ++ "/Cargo.toml", "--release" });
    const cwd = fs.cwd();
    std.debug.print("Copying {s} to {s}\n", .{ RUST_DLL_RELEASE_PATH, ZIG_BIN_OUT_DIR });
    try fs.Dir.copyFile(cwd, RUST_DLL_RELEASE_PATH, cwd, ZIG_BIN_OUT_DIR ++ "/" ++ DLL_NAME, .{});
    std.debug.print("Copied rust-lib.dll to {s}\n", .{ZIG_BIN_OUT_DIR});

    const target = b.standardTargetOptions(.{});

    const optimize = b.standardOptimizeOption(.{});

    const exe = b.addExecutable(.{
        .name = "zig-exe",
        .root_source_file = .{ .path = "src/main.zig" },
        .target = target,
        .optimize = optimize,
    });

    b.installArtifact(exe);
    const run_cmd = b.addRunArtifact(exe);
    run_cmd.step.dependOn(b.getInstallStep());
    if (b.args) |args| {
        run_cmd.addArgs(args);
    }

    const run_step = b.step("run", "Run the app");
    run_step.dependOn(&run_cmd.step);
}

ターミナルで以下のコマンドを実行します。

cd zig-exe
zig build run

するとrust_lib.dll: add(1, 2) = 3の表示が出てくるはずです。

まとめ

以上のように、Rustで書いたDLLライブラリをZigで取り込んでみましたが、いかがだったでしょうか?

作動するコードをGithubに公開しました!
https://github.com/mkpoli/zig-rust-interop

Discussion