Rustで書いたDLLをZigで読み込む
姉妹編
まあ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.toml
にcdylib
モードを追加します。
@@ -3,4 +3,7 @@
version = "0.1.0"
edition = "2021"
+[lib]
+crate-type = ["cdylib"]
+
[dependencies]
今回はそのまま自動生成されたrust-lib/src/lib.rs
を使うので、その中身を覗いてみましょう。簡単な足し算関数add()
がエクスポートされているのがわかります。
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が名前をいじらないようにします。
@@ -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
に以下のように書き換えます。
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
にコピーするコードを追加します。変更したものはこうなります。
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に公開しました!
Discussion