8
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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を提供するための手動ラッピングテクニックを紹介する。

この記事が役に立ったら、いいね・ストックしてもらえると嬉しいです!

8
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?