ZigでCラッパーを作るシリーズ
| Part1 translate-c | Part2 手動ラップ | Part3 SQLite |
|---|---|---|
| 👈 Now | - | - |
はじめに
「Zigって新しい言語だから、ライブラリ少なくない?」
そう思ってる人、多いんじゃないかな。
でも実は、ZigはCとのシームレスな相互運用性を持ってる。つまり、既存のCライブラリがそのまま使えるってこと。
Zigはtranslate-cという機能で、CのヘッダファイルをZigコードに自動変換できる。これ、ものすごく便利なんだよね。
Zigの@cImport
最も簡単な方法は@cImportを使うこと。
// main.zig
const std = @import("std");
const c = @cImport({
@cInclude("stdio.h");
@cInclude("stdlib.h");
});
pub fn main() void {
_ = c.printf("Hello from C!\n");
// malloc/freeも使える
const ptr = c.malloc(100);
if (ptr != null) {
c.free(ptr);
}
}
ビルド:
$ zig build-exe main.zig -lc
$ ./main
Hello from C!
-lcフラグでlibcをリンクする。
translate-cコマンド
zig translate-cでCヘッダをZigに変換できる。
$ zig translate-c /usr/include/stdio.h > stdio.zig
変換されたコードを見てみよう:
// 生成されたコードの一部
pub extern fn printf(__format: [*c]const u8, ...) c_int;
pub extern fn scanf(__format: [*c]const u8, ...) c_int;
pub extern fn fopen(__filename: [*c]const u8, __modes: [*c]const u8) ?*FILE;
pub extern fn fclose(__stream: ?*FILE) c_int;
Cの型とZigの型
┌─────────────────────────────────────────────────────────────────┐
│ C to Zig Type Mapping │
├─────────────────────────────────────────────────────────────────┤
│ │
│ C Type │ Zig Type │
│ ────────────────────┼─────────────────────────────── │
│ int │ c_int │
│ unsigned int │ c_uint │
│ long │ c_long │
│ long long │ c_longlong │
│ size_t │ usize │
│ char │ c_char (u8) │
│ char* │ [*c]u8 │
│ const char* │ [*c]const u8 │
│ void* │ ?*anyopaque │
│ int* │ [*c]c_int │
│ struct Foo │ struct Foo (extern struct) │
│ │
└─────────────────────────────────────────────────────────────────┘
実践:mathライブラリ
// math_wrapper.zig
const std = @import("std");
const c = @cImport({
@cInclude("math.h");
});
pub fn main() void {
// 三角関数
const angle: f64 = std.math.pi / 4.0;
std.debug.print("sin(π/4) = {d:.6}\n", .{c.sin(angle)});
std.debug.print("cos(π/4) = {d:.6}\n", .{c.cos(angle)});
std.debug.print("tan(π/4) = {d:.6}\n", .{c.tan(angle)});
// 指数・対数
std.debug.print("exp(1) = {d:.6}\n", .{c.exp(1.0)});
std.debug.print("log(e) = {d:.6}\n", .{c.log(std.math.e)});
// 平方根・べき乗
std.debug.print("sqrt(2) = {d:.6}\n", .{c.sqrt(2.0)});
std.debug.print("pow(2, 10) = {d:.0}\n", .{c.pow(2.0, 10.0)});
// 切り上げ・切り捨て
std.debug.print("ceil(3.2) = {d:.0}\n", .{c.ceil(3.2)});
std.debug.print("floor(3.8) = {d:.0}\n", .{c.floor(3.8)});
}
実行結果:
sin(π/4) = 0.707107
cos(π/4) = 0.707107
tan(π/4) = 1.000000
exp(1) = 2.718282
log(e) = 1.000000
sqrt(2) = 1.414214
pow(2, 10) = 1024
ceil(3.2) = 4
floor(3.8) = 3
実践:timeライブラリ
// time_wrapper.zig
const std = @import("std");
const c = @cImport({
@cInclude("time.h");
});
pub fn main() void {
// 現在時刻を取得
var now: c.time_t = undefined;
_ = c.time(&now);
std.debug.print("Unix timestamp: {}\n", .{now});
// ローカル時間に変換
const local = c.localtime(&now);
if (local) |tm| {
std.debug.print("Year: {}\n", .{tm.tm_year + 1900});
std.debug.print("Month: {}\n", .{tm.tm_mon + 1});
std.debug.print("Day: {}\n", .{tm.tm_mday});
std.debug.print("Hour: {}\n", .{tm.tm_hour});
std.debug.print("Minute: {}\n", .{tm.tm_min});
std.debug.print("Second: {}\n", .{tm.tm_sec});
}
// フォーマット出力
var buffer: [64]u8 = undefined;
const len = c.strftime(&buffer, buffer.len, "%Y-%m-%d %H:%M:%S", local);
if (len > 0) {
std.debug.print("Formatted: {s}\n", .{buffer[0..len]});
}
}
実行結果:
Unix timestamp: 1703001234
Year: 2024
Month: 12
Day: 19
Hour: 15
Minute: 33
Second: 54
Formatted: 2024-12-19 15:33:54
文字列の扱い
CとZigで文字列の扱いが異なるので注意。
// string_interop.zig
const std = @import("std");
const c = @cImport({
@cInclude("string.h");
@cInclude("stdio.h");
});
pub fn main() void {
// Zig文字列からC文字列へ
const zig_str = "Hello from Zig!";
// Zigの文字列リテラルはnull終端されている
const c_str: [*c]const u8 = zig_str.ptr;
_ = c.printf("C printf: %s\n", c_str);
// C文字列の長さ
const len = c.strlen(c_str);
std.debug.print("Length (strlen): {}\n", .{len});
// 文字列比較
const str1 = "apple";
const str2 = "banana";
const cmp = c.strcmp(str1.ptr, str2.ptr);
if (cmp < 0) {
std.debug.print("'apple' < 'banana'\n", .{});
}
// 文字列コピー(バッファに)
var buffer: [64]u8 = undefined;
_ = c.strcpy(&buffer, "Copied string");
const copied_len = c.strlen(&buffer);
std.debug.print("Copied: {s}\n", .{buffer[0..copied_len]});
// 文字列連結
_ = c.strcat(&buffer, " + appended");
const final_len = c.strlen(&buffer);
std.debug.print("Concatenated: {s}\n", .{buffer[0..final_len]});
}
null終端の罠
// null_terminator.zig
const std = @import("std");
const c = @cImport({
@cInclude("string.h");
});
pub fn main() void {
// スライスはnull終端されていない可能性がある
const data = [_]u8{ 'H', 'e', 'l', 'l', 'o' };
const slice: []const u8 = &data;
// これは危険!sliceはnull終端されていない
// _ = c.strlen(slice.ptr); // 未定義動作
// 安全な方法:明示的にnull終端を追加
var buffer: [6]u8 = undefined;
@memcpy(buffer[0..5], slice);
buffer[5] = 0; // null終端
const len = c.strlen(&buffer);
std.debug.print("Safe length: {}\n", .{len});
// または std.cstr を使う
// allocator使用時は allocator.dupeZ() が便利
}
// null終端文字列を作成するヘルパー
fn toCString(allocator: std.mem.Allocator, str: []const u8) ![:0]u8 {
return try allocator.dupeZ(u8, str);
}
build.zigでCライブラリをリンク
// build.zig
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "my_app",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// libcをリンク
exe.linkLibC();
// 追加のCライブラリをリンク
exe.linkSystemLibrary("m"); // math library
exe.linkSystemLibrary("pthread"); // pthread library
// Cのインクルードパスを追加
exe.addIncludePath(b.path("include"));
// Cソースファイルを追加
exe.addCSourceFile(.{
.file = b.path("src/helper.c"),
.flags = &.{ "-Wall", "-Wextra" },
});
b.installArtifact(exe);
}
CソースとZigの混合
// helper.c
#include <stdio.h>
void c_hello(const char* name) {
printf("Hello, %s! (from C)\n", name);
}
int c_add(int a, int b) {
return a + b;
}
// main.zig
const std = @import("std");
// Cの関数を宣言
extern fn c_hello(name: [*c]const u8) void;
extern fn c_add(a: c_int, b: c_int) c_int;
pub fn main() void {
c_hello("Zig");
const result = c_add(10, 20);
std.debug.print("10 + 20 = {}\n", .{result});
}
実行結果:
Hello, Zig! (from C)
10 + 20 = 30
まとめ
今回学んだこと:
| 機能 | 説明 |
|---|---|
@cImport |
Cヘッダを直接インポート |
translate-c |
CコードをZigに変換 |
| 型マッピング |
c_int、[*c]u8など |
-lc |
libcをリンク |
| null終端 | C文字列の扱いに注意 |
次回は、より綺麗なZig APIを提供するための手動ラッピングテクニックを紹介する。
この記事が役に立ったら、いいね・ストックしてもらえると嬉しいです!