第15章: C言語連携

学習目標

この章を終えると、以下ができるようになります:

  • @cImport を使用してCヘッダーファイルを読み込む
  • C言語の関数をZigから呼び出す
  • Zigの関数をC言語から呼び出せるようにする
  • C言語のデータ構造とZigの間でデータを受け渡す
  • 既存のCライブラリをラップするZigライブラリを作成する
  • ZigとCの相互運用性

    なぜC言語連携が重要か

    Zigの設計目標の1つは、C言語との完全な相互運用性です。これにより以下が可能になります:

  • 既存資産の活用: 50年以上蓄積されたCライブラリを利用
  • 段階的な移行: 既存のCプロジェクトを徐々にZigに移行
  • システムAPI: OS APIやハードウェアドライバへのアクセス
  • パフォーマンス: C言語と同等の性能を維持
  •   [既存Cコードベース]
             |
             | ← Zigから呼び出し
             |
        [Zig Wrapper]
             |
             | ← 安全なZig API
             |
       [Zig Application]
    

    C言語連携の基本原則

    Zigは以下の原則でC言語と連携します:

  • ABI互換性: CのABI(Application Binary Interface)に準拠
  • 型の対応: Zigの型がCの型に自然にマッピング
  • ゼロコスト: C言語呼び出しにオーバーヘッドなし
  • 安全性の層: CのunsafeなAPIをZigの安全なAPIでラップ

@cImportによるヘッダー読み込み

基本的な使い方

@cImport は、Cヘッダーファイルを読み込み、Zigの型システムに統合します。

const std = @import("std");
const c = @cImport({
    @cInclude("stdio.h");
});

pub fn main() void {
    _ = c.printf("Hello from C!\n");
}

動作の仕組み:

  • Zigのビルドシステムがclangを呼び出す
  • Cヘッダーを解析して型情報を抽出
  • Zigの型として利用可能にする
  • 複数のヘッダーファイル

    const c = @cImport({
        @cInclude("stdio.h");
        @cInclude("stdlib.h");
        @cInclude("string.h");
        @cInclude("math.h");
    });
    
    pub fn example() void {
        // stdio.h
        _ = c.printf("Using printf\n");
    
        // stdlib.h
        const ptr = c.malloc(1024);
        defer c.free(ptr);
    
        // math.h
        const result = c.sqrt(16.0);
        _ = c.printf("sqrt(16) = %f\n", result);
    }
    

    マクロの定義

    Cのマクロを定義してからヘッダーを読み込むことができます:

    const c = @cImport({
        @cDefine("_GNU_SOURCE", "1");
        @cDefine("DEBUG", "");
        @cInclude("features.h");
        @cInclude("unistd.h");
    });
    
    pub fn main() void {
        // GNU拡張機能が有効化された状態でAPIを使用
        const pid = c.getpid();
        std.debug.print("Process ID: {}\n", .{pid});
    }
    

    インクルードパスの指定

    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 = "myapp",
            .root_source_file = .{ .path = "src/main.zig" },
            .target = target,
            .optimize = optimize,
        });
    
        // インクルードパスを追加
        exe.addIncludePath(.{ .path = "/usr/local/include" });
        exe.addIncludePath(.{ .path = "external/mylib/include" });
    
        // ライブラリをリンク
        exe.linkSystemLibrary("mylib");
        exe.linkLibC();
    
        b.installArtifact(exe);
    }
    

    C言語関数の呼び出し

    標準ライブラリ関数

    const std = @import("std");
    const c = @cImport({
        @cInclude("stdio.h");
        @cInclude("string.h");
    });
    
    pub fn stringOperations() void {
        // 文字列操作
        const src = "Hello, World!";
        var dest: [100]u8 = undefined;
    
        _ = c.strcpy(&dest, src.ptr);
        const len = c.strlen(&dest);
    
        std.debug.print("Copied string: {s}\n", .{dest[0..len]});
        std.debug.print("Length: {}\n", .{len});
    }
    
    pub fn fileOperations() void {
        // ファイルI/O
        const file = c.fopen("test.txt", "w");
        if (file == null) {
            std.debug.print("Failed to open file\n", .{});
            return;
        }
        defer _ = c.fclose(file);
    
        _ = c.fprintf(file, "Line 1\n");
        _ = c.fprintf(file, "Line 2\n");
    }
    

    数学関数

    const c = @cImport({
        @cInclude("math.h");
    });
    
    pub fn mathOperations() void {
        const x = 2.0;
        const y = 3.0;
    
        const power = c.pow(x, y);              // 2^3 = 8
        const sine = c.sin(c.M_PI / 2.0);       // sin(90°) = 1
        const log = c.log10(100.0);             // log10(100) = 2
        const ceil = c.ceil(3.14);              // ceil(3.14) = 4
    
        std.debug.print("pow(2, 3) = {d:.2}\n", .{power});
        std.debug.print("sin(π/2) = {d:.2}\n", .{sine});
        std.debug.print("log10(100) = {d:.2}\n", .{log});
        std.debug.print("ceil(3.14) = {d:.2}\n", .{ceil});
    }
    

    POSIX API

    const c = @cImport({
        @cInclude("unistd.h");
        @cInclude("sys/types.h");
        @cInclude("sys/stat.h");
        @cInclude("fcntl.h");
    });
    
    pub fn posixExample() !void {
        // ファイルディスクリプタを使用したI/O
        const fd = c.open("test.txt", c.O_RDWR | c.O_CREAT, 0o644);
        if (fd < 0) {
            return error.OpenFailed;
        }
        defer _ = c.close(fd);
    
        const data = "Hello, POSIX!\n";
        const written = c.write(fd, data.ptr, data.len);
    
        if (written < 0) {
            return error.WriteFailed;
        }
    
        std.debug.print("Wrote {} bytes\n", .{written});
    }
    

    C言語の型とZigの型

    基本型の対応

    const c = @cImport({
        @cInclude("stdint.h");
    });
    
    pub fn typeCorrespondence() void {
        // C言語の型 -> Zigの型
        var c_int: c_int = 42;              // i32 (プラットフォーム依存)
        var c_uint: c_uint = 42;            // u32 (プラットフォーム依存)
        var c_long: c_long = 42;            // i64 or i32
        var c_ulong: c_ulong = 42;          // u64 or u32
        var c_longlong: c_longlong = 42;    // i64
        var c_ulonglong: c_ulonglong = 42;  // u64
    
        // 固定幅整数型
        var int8: c.int8_t = 42;            // i8
        var uint8: c.uint8_t = 42;          // u8
        var int32: c.int32_t = 42;          // i32
        var uint64: c.uint64_t = 42;        // u64
    
        // 浮動小数点
        var c_float: f32 = 3.14;            // float
        var c_double: f64 = 3.14;           // double
    
        _ = c_int;
        _ = c_uint;
        _ = c_long;
        _ = c_ulong;
        _ = c_longlong;
        _ = c_ulonglong;
        _ = int8;
        _ = uint8;
        _ = int32;
        _ = uint64;
        _ = c_float;
        _ = c_double;
    }
    

    ポインタの扱い

    const c = @cImport({
        @cInclude("stdlib.h");
        @cInclude("string.h");
    });
    
    pub fn pointerExample() void {
        // C言語のポインタ
        const c_ptr: [*c]u8 = c.malloc(100);
        if (c_ptr == null) {
            std.debug.print("Allocation failed\n", .{});
            return;
        }
        defer c.free(c_ptr);
    
        // Zigのスライスに変換
        const slice: []u8 = c_ptr[0..100];
    
        // メモリ操作
        _ = c.memset(c_ptr, 0, 100);
    
        std.debug.print("Allocated and zeroed 100 bytes\n", .{});
    }
    

    構造体

    const c = @cImport({
        @cInclude("time.h");
    });
    
    pub fn structExample() void {
        var tv: c.struct_timeval = undefined;
    
        // 構造体のメンバーアクセス
        _ = c.gettimeofday(&tv, null);
    
        std.debug.print("Seconds: {}\n", .{tv.tv_sec});
        std.debug.print("Microseconds: {}\n", .{tv.tv_usec});
    }
    
    // カスタムC構造体のインポート
    const MyStruct = extern struct {
        x: c_int,
        y: c_int,
        name: [32]u8,
    };
    

    関数ポインタ

    const c = @cImport({
        @cInclude("stdlib.h");
    });
    
    fn compareInts(a: ?*const anyopaque, b: ?*const anyopaque) callconv(.C) c_int {
        const x = @as(*const c_int, @ptrCast(@alignCast(a))).*;
        const y = @as(*const c_int, @ptrCast(@alignCast(b))).*;
    
        if (x < y) return -1;
        if (x > y) return 1;
        return 0;
    }
    
    pub fn callbackExample() void {
        var arr = [_]c_int{ 5, 2, 8, 1, 9 };
    
        // qsort にコールバックを渡す
        c.qsort(
            &arr,
            arr.len,
            @sizeOf(c_int),
            compareInts,
        );
    
        std.debug.print("Sorted: ", .{});
        for (arr) |val| {
            std.debug.print("{} ", .{val});
        }
        std.debug.print("\n", .{});
    }
    

    ZigからC言語へエクスポート

    C互換な関数の定義

    // C言語から呼び出し可能な関数
    export fn zigAdd(a: c_int, b: c_int) c_int {
        return a + b;
    }
    
    export fn zigMultiply(a: c_int, b: c_int) c_int {
        return a * b;
    }
    
    export fn zigGetString() [*:0]const u8 {
        return "Hello from Zig!";
    }
    
    // エラーハンドリング付き
    export fn zigDivide(a: c_int, b: c_int, result: *c_int) c_int {
        if (b == 0) {
            return -1; // エラーコード
        }
        result.* = @divTrunc(a, b);
        return 0; // 成功
    }
    

    対応するCヘッダー:

    // ziglib.h
    #ifndef ZIGLIB_H
    #define ZIGLIB_H
    
    #ifdef __cplusplus
    extern "C" {
    #endif
    
    int zigAdd(int a, int b);
    int zigMultiply(int a, int b);
    const char* zigGetString(void);
    int zigDivide(int a, int b, int* result);
    
    #ifdef __cplusplus
    }
    #endif
    
    #endif // ZIGLIB_H
    

    Cから使用:

    // main.c
    #include <stdio.h>
    #include "ziglib.h"
    
    int main() {
        int sum = zigAdd(10, 20);
        printf("10 + 20 = %d\n", sum);
    
        int result;
        if (zigDivide(100, 5, &result) == 0) {
            printf("100 / 5 = %d\n", result);
        }
    
        const char* msg = zigGetString();
        printf("%s\n", msg);
    
        return 0;
    }
    

    C互換な構造体

    // C言語と互換性のある構造体
    pub const Point = extern struct {
        x: c_int,
        y: c_int,
    };
    
    pub const Rectangle = extern struct {
        top_left: Point,
        width: c_int,
        height: c_int,
    };
    
    export fn createPoint(x: c_int, y: c_int) Point {
        return Point{ .x = x, .y = y };
    }
    
    export fn calculateArea(rect: *const Rectangle) c_int {
        return rect.width * rect.height;
    }
    

    Cライブラリのラッピング

    シンプルなラッパー例

    SQLiteをラップする例:

    const std = @import("std");
    const c = @cImport({
        @cInclude("sqlite3.h");
    });
    
    pub const Database = struct {
        db: ?*c.sqlite3,
    
        pub fn open(path: []const u8) !Database {
            var db: ?*c.sqlite3 = null;
            const path_z = try std.mem.Allocator.dupeZ(
                std.heap.page_allocator,
                u8,
                path
            );
            defer std.heap.page_allocator.free(path_z);
    
            const result = c.sqlite3_open(path_z.ptr, &db);
            if (result != c.SQLITE_OK) {
                return error.OpenFailed;
            }
    
            return Database{ .db = db };
        }
    
        pub fn close(self: *Database) void {
            if (self.db) |db| {
                _ = c.sqlite3_close(db);
                self.db = null;
            }
        }
    
        pub fn execute(self: Database, sql: []const u8) !void {
            const sql_z = try std.mem.Allocator.dupeZ(
                std.heap.page_allocator,
                u8,
                sql
            );
            defer std.heap.page_allocator.free(sql_z);
    
            var err_msg: ?[*:0]u8 = null;
            const result = c.sqlite3_exec(
                self.db,
                sql_z.ptr,
                null,
                null,
                &err_msg,
            );
    
            if (result != c.SQLITE_OK) {
                if (err_msg) |msg| {
                    defer c.sqlite3_free(msg);
                    std.debug.print("SQL error: {s}\n", .{msg});
                }
                return error.ExecutionFailed;
            }
        }
    };
    
    pub fn example() !void {
        var db = try Database.open("test.db");
        defer db.close();
    
        try db.execute(
            \\CREATE TABLE IF NOT EXISTS users (
            \\    id INTEGER PRIMARY KEY,
            \\    name TEXT NOT NULL
            \\);
        );
    
        try db.execute(
            \\INSERT INTO users (name) VALUES ('Alice');
        );
    
        std.debug.print("Database operations completed\n", .{});
    }
    

    エラーハンドリングの改善

    pub const SqliteError = error{
        OpenFailed,
        ExecutionFailed,
        PrepareStatementFailed,
        BindFailed,
        StepFailed,
    };
    
    pub const Statement = struct {
        stmt: ?*c.sqlite3_stmt,
    
        pub fn prepare(db: Database, sql: []const u8) !Statement {
            const sql_z = try std.mem.Allocator.dupeZ(
                std.heap.page_allocator,
                u8,
                sql
            );
            defer std.heap.page_allocator.free(sql_z);
    
            var stmt: ?*c.sqlite3_stmt = null;
            const result = c.sqlite3_prepare_v2(
                db.db,
                sql_z.ptr,
                @intCast(sql.len),
                &stmt,
                null,
            );
    
            if (result != c.SQLITE_OK) {
                return SqliteError.PrepareStatementFailed;
            }
    
            return Statement{ .stmt = stmt };
        }
    
        pub fn bindInt(self: Statement, index: c_int, value: i32) !void {
            const result = c.sqlite3_bind_int(self.stmt, index, value);
            if (result != c.SQLITE_OK) {
                return SqliteError.BindFailed;
            }
        }
    
        pub fn step(self: Statement) !bool {
            const result = c.sqlite3_step(self.stmt);
            if (result == c.SQLITE_ROW) {
                return true;
            } else if (result == c.SQLITE_DONE) {
                return false;
            } else {
                return SqliteError.StepFailed;
            }
        }
    
        pub fn finalize(self: *Statement) void {
            if (self.stmt) |stmt| {
                _ = c.sqlite3_finalize(stmt);
                self.stmt = null;
            }
        }
    };
    

    ビルド設定

    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 = "myapp",
            .root_source_file = .{ .path = "src/main.zig" },
            .target = target,
            .optimize = optimize,
        });
    
        // libc をリンク
        exe.linkLibC();
    
        // システムライブラリをリンク
        exe.linkSystemLibrary("sqlite3");
        exe.linkSystemLibrary("curl");
        exe.linkSystemLibrary("ssl");
    
        // カスタムライブラリのパス
        exe.addLibraryPath(.{ .path = "/usr/local/lib" });
        exe.addIncludePath(.{ .path = "/usr/local/include" });
    
        // 静的ライブラリをリンク
        exe.addObjectFile(.{ .path = "external/mylib.a" });
    
        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);
    }
    

    実践例: libcurlラッパー

    const std = @import("std");
    const c = @cImport({
        @cInclude("curl/curl.h");
    });
    
    pub const Curl = struct {
        handle: ?*c.CURL,
    
        pub fn init() !Curl {
            const handle = c.curl_easy_init();
            if (handle == null) {
                return error.InitFailed;
            }
            return Curl{ .handle = handle };
        }
    
        pub fn deinit(self: *Curl) void {
            if (self.handle) |handle| {
                c.curl_easy_cleanup(handle);
                self.handle = null;
            }
        }
    
        pub fn setUrl(self: Curl, url: []const u8) !void {
            const url_z = try std.heap.page_allocator.dupeZ(u8, url);
            defer std.heap.page_allocator.free(url_z);
    
            const result = c.curl_easy_setopt(
                self.handle,
                c.CURLOPT_URL,
                url_z.ptr,
            );
    
            if (result != c.CURLE_OK) {
                return error.SetOptionFailed;
            }
        }
    
        pub fn perform(self: Curl) !void {
            const result = c.curl_easy_perform(self.handle);
            if (result != c.CURLE_OK) {
                return error.PerformFailed;
            }
        }
    };
    
    pub fn httpGetExample() !void {
        var curl = try Curl.init();
        defer curl.deinit();
    
        try curl.setUrl("https://example.com");
        try curl.perform();
    
        std.debug.print("HTTP GET completed\n", .{});
    }
    

    まとめ

    この章では、ZigとC言語の相互運用について学びました:

  • @cImport: Cヘッダーの読み込みと型の統合
  • C関数呼び出し: 標準ライブラリ、POSIX API、外部ライブラリ
  • 型の対応: C言語の型とZigの型のマッピング
  • エクスポート: ZigのコードをC言語から呼び出す
  • ラッピング: CライブラリをZigの安全なAPIでラップ
  • 次の章では、非同期処理と並行処理について学びます。

    参考文献

  • Zig Language Reference - C Interop: https://ziglang.org/documentation/master/#C
  • Zig Build System: https://ziglang.org/learn/build-system/