課題15: C言語連携

マンダトリー要件 (80点)

Part 1: Cヘッダーのインポート (20点)

以下の要件を満たすプログラムを作成してください:

ファイル名: part1_import.zig

const std = @import("std");
const c = @cImport({
    // TODO: 必要なヘッダーをインポート
});

// TODO: 以下の関数を実装してください

// 1. 標準出力にメッセージを出力 (C の printf を使用)
pub fn printMessage(message: []const u8) void {
    // ここに実装
}

// 2. 平方根を計算 (C の sqrt を使用)
pub fn calculateSqrt(x: f64) f64 {
    // ここに実装
}

// 3. 文字列の長さを取得 (C の strlen を使用)
pub fn getStringLength(s: []const u8) usize {
    // ここに実装
}

// 4. メモリを動的に割り当て、値をコピー (C の malloc/memcpy を使用)
pub fn duplicateData(data: []const u8) ?[*]u8 {
    // ここに実装
    // 注意: 呼び出し側で free が必要
}

pub fn main() void {
    printMessage("Hello from C!");

    const sqrt_result = calculateSqrt(16.0);
    std.debug.print("sqrt(16) = {d:.2}\n", .{sqrt_result});

    const test_str = "Hello, World!";
    const len = getStringLength(test_str);
    std.debug.print("Length: {}\n", .{len});

    const dup = duplicateData(test_str);
    if (dup) |ptr| {
        defer c.free(ptr);
        std.debug.print("Duplicated: {s}\n", .{ptr[0..len]});
    }
}

要件:

  • @cImport@cInclude を正しく使用
  • 各関数が正しく動作すること
  • メモリリークが発生しないこと

Part 2: POSIX APIの活用 (20点)

ファイル操作をPOSIX APIで実装してください:

ファイル名: part2_posix.zig

const std = @import("std");
const c = @cImport({
    // TODO: POSIXヘッダーをインポート
});

pub const FileError = error{
    OpenFailed,
    ReadFailed,
    WriteFailed,
    CloseFailed,
};

// TODO: 以下の関数を実装してください

// 1. ファイルを作成し、データを書き込む
pub fn writeFile(path: []const u8, data: []const u8) FileError!void {
    // ここに実装
    // open(), write(), close() を使用
}

// 2. ファイルからデータを読み込む
pub fn readFile(allocator: std.mem.Allocator, path: []const u8) FileError![]u8 {
    // ここに実装
    // open(), read(), close() を使用
}

// 3. ファイルの情報を取得
pub fn getFileSize(path: []const u8) FileError!usize {
    // ここに実装
    // stat() を使用
}

pub fn main() !void {
    const allocator = std.heap.page_allocator;

    // ファイルに書き込み
    const test_data = "Hello, POSIX!\nThis is a test file.\n";
    try writeFile("test_posix.txt", test_data);
    std.debug.print("File written successfully\n", .{});

    // ファイルサイズを取得
    const size = try getFileSize("test_posix.txt");
    std.debug.print("File size: {} bytes\n", .{size});

    // ファイルを読み込み
    const content = try readFile(allocator, "test_posix.txt");
    defer allocator.free(content);
    std.debug.print("File content:\n{s}\n", .{content});
}

要件:

  • POSIX APIを正しく使用
  • エラーハンドリングが適切であること
  • リソース(ファイルディスクリプタ)が正しく解放されること

Part 3: C互換関数のエクスポート (20点)

C言語から呼び出し可能な数学ライブラリを作成してください:

ファイル名: part3_export.zig

const std = @import("std");

// TODO: 以下のC互換関数を実装してください

// 1. 2つの整数の最大公約数を求める
export fn gcd(a: c_int, b: c_int) c_int {
    // ここに実装
}

// 2. 2つの整数の最小公倍数を求める
export fn lcm(a: c_int, b: c_int) c_int {
    // ここに実装
}

// 3. 階乗を計算
export fn factorial(n: c_int) c_longlong {
    // ここに実装
}

// 4. nの累乗を計算
export fn power(base: c_int, exp: c_int) c_longlong {
    // ここに実装
}

// 5. 配列の要素を合計
export fn sumArray(arr: [*]const c_int, len: c_int) c_int {
    // ここに実装
}

// テスト用のmain関数
pub fn main() void {
    std.debug.print("gcd(48, 18) = {}\n", .{gcd(48, 18)});
    std.debug.print("lcm(12, 18) = {}\n", .{lcm(12, 18)});
    std.debug.print("factorial(5) = {}\n", .{factorial(5)});
    std.debug.print("power(2, 10) = {}\n", .{power(2, 10)});

    const arr = [_]c_int{ 1, 2, 3, 4, 5 };
    std.debug.print("sum([1,2,3,4,5]) = {}\n", .{sumArray(&arr, arr.len)});
}

Cヘッダーファイルも作成: mathlib.h

#ifndef MATHLIB_H
#define MATHLIB_H

#ifdef __cplusplus
extern "C" {
#endif

int gcd(int a, int b);
int lcm(int a, int b);
long long factorial(int n);
long long power(int base, int exp);
int sumArray(const int* arr, int len);

#ifdef __cplusplus
}
#endif

#endif // MATHLIB_H

要件:

  • すべての関数が export キーワードを使用
  • C互換の型を使用
  • 対応するヘッダーファイルを作成

Part 4: Cライブラリのラッピング (20点)

システムの時刻関連APIをラップするライブラリを作成してください:

ファイル名: part4_wrapper.zig

const std = @import("std");
const c = @cImport({
    // TODO: 時刻関連のヘッダーをインポート
});

pub const TimeError = error{
    GetTimeFailed,
    FormatFailed,
};

pub const DateTime = struct {
    year: u32,
    month: u32,
    day: u32,
    hour: u32,
    minute: u32,
    second: u32,

    // TODO: 以下のメソッドを実装してください

    // 1. 現在の日時を取得
    pub fn now() TimeError!DateTime {
        // ここに実装
        // time(), localtime() を使用
    }

    // 2. Unix時刻から変換
    pub fn fromTimestamp(timestamp: i64) TimeError!DateTime {
        // ここに実装
    }

    // 3. Unix時刻に変換
    pub fn toTimestamp(self: DateTime) i64 {
        // ここに実装
        // mktime() を使用
    }

    // 4. フォーマットした文字列を取得
    pub fn format(
        self: DateTime,
        allocator: std.mem.Allocator,
    ) std.mem.Allocator.Error![]u8 {
        // ここに実装
        // 形式: "YYYY-MM-DD HH:MM:SS"
    }
};

pub fn main() !void {
    const allocator = std.heap.page_allocator;

    // 現在時刻を取得
    const now = try DateTime.now();
    const formatted = try now.format(allocator);
    defer allocator.free(formatted);

    std.debug.print("Current time: {s}\n", .{formatted});

    // Unix時刻との相互変換
    const timestamp = now.toTimestamp();
    std.debug.print("Unix timestamp: {}\n", .{timestamp});

    const from_ts = try DateTime.fromTimestamp(timestamp);
    const formatted2 = try from_ts.format(allocator);
    defer allocator.free(formatted2);

    std.debug.print("From timestamp: {s}\n", .{formatted2});
}

要件:

  • CのAPIをZigの型システムでラップ
  • エラーハンドリングを適切に実装
  • メモリ管理が正しく行われること

ボーナス課題 (20点)

Bonus 1: SQLiteラッパー (10点)

SQLiteデータベースのラッパーライブラリを作成してください:

ファイル名: bonus1_sqlite.zig

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

pub const SqliteError = error{
    OpenFailed,
    PrepareFailed,
    BindFailed,
    StepFailed,
    ExecuteFailed,
};

pub const Database = struct {
    db: ?*c.sqlite3,

    pub fn open(path: []const u8) SqliteError!Database {
        // TODO: 実装
    }

    pub fn close(self: *Database) void {
        // TODO: 実装
    }

    pub fn execute(self: Database, sql: []const u8) SqliteError!void {
        // TODO: 実装
    }
};

pub const Statement = struct {
    stmt: ?*c.sqlite3_stmt,

    pub fn prepare(db: Database, sql: []const u8) SqliteError!Statement {
        // TODO: 実装
    }

    pub fn bindInt(self: Statement, index: c_int, value: i32) SqliteError!void {
        // TODO: 実装
    }

    pub fn bindText(self: Statement, index: c_int, text: []const u8) SqliteError!void {
        // TODO: 実装
    }

    pub fn step(self: Statement) SqliteError!bool {
        // TODO: 実装
    }

    pub fn getInt(self: Statement, index: c_int) i32 {
        // TODO: 実装
    }

    pub fn getText(self: Statement, allocator: std.mem.Allocator, index: c_int) ![]u8 {
        // TODO: 実装
    }

    pub fn finalize(self: *Statement) void {
        // TODO: 実装
    }
};

pub fn main() !void {
    const allocator = std.heap.page_allocator;

    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,
        \\    age INTEGER
        \\);
    );

    // データ挿入
    var stmt = try Statement.prepare(db, "INSERT INTO users (name, age) VALUES (?, ?)");
    defer stmt.finalize();

    try stmt.bindText(1, "Alice");
    try stmt.bindInt(2, 30);
    _ = try stmt.step();

    std.debug.print("Database operations completed\n", .{});
}

要件:

  • データベースの開閉
  • SQL実行
  • プリペアドステートメント
  • パラメータバインディング
  • 結果の取得

Bonus 2: HTTPクライアント (10点)

libcurlを使用したHTTPクライアントを作成してください:

ファイル名: bonus2_http.zig

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

pub const HttpError = error{
    InitFailed,
    SetOptionFailed,
    PerformFailed,
};

const WriteCallback = fn (
    ptr: [*]u8,
    size: usize,
    nmemb: usize,
    userdata: ?*anyopaque,
) callconv(.C) usize;

fn writeCallback(
    ptr: [*]u8,
    size: usize,
    nmemb: usize,
    userdata: ?*anyopaque,
) callconv(.C) usize {
    const real_size = size * nmemb;
    const buffer = @as(*std.ArrayList(u8), @ptrCast(@alignCast(userdata)));

    buffer.appendSlice(ptr[0..real_size]) catch return 0;
    return real_size;
}

pub const HttpClient = struct {
    handle: ?*c.CURL,

    pub fn init() HttpError!HttpClient {
        // TODO: 実装
    }

    pub fn deinit(self: *HttpClient) void {
        // TODO: 実装
    }

    pub fn get(
        self: HttpClient,
        allocator: std.mem.Allocator,
        url: []const u8,
    ) HttpError![]u8 {
        // TODO: 実装
        // コールバックを使用してレスポンスを取得
    }

    pub fn post(
        self: HttpClient,
        allocator: std.mem.Allocator,
        url: []const u8,
        data: []const u8,
    ) HttpError![]u8 {
        // TODO: 実装
    }
};

pub fn main() !void {
    const allocator = std.heap.page_allocator;

    var client = try HttpClient.init();
    defer client.deinit();

    // GETリクエスト
    const response = try client.get(allocator, "https://httpbin.org/get");
    defer allocator.free(response);

    std.debug.print("Response:\n{s}\n", .{response});
}

要件:

  • GETリクエスト
  • POSTリクエスト
  • レスポンスボディの取得
  • エラーハンドリング
  • 評価基準

    項目 配点
    Part 1: Cヘッダーのインポート 20点
    Part 2: POSIX APIの活用 20点
    Part 3: C互換関数のエクスポート 20点
    Part 4: Cライブラリのラッピング 20点
    **マンダトリー合計** **80点**
    Bonus 1: SQLiteラッパー 10点
    Bonus 2: HTTPクライアント 10点
    **ボーナス合計** **20点**

    合格基準

  • マンダトリー: 64点以上で合格(80%)
  • ボーナス: 追加評価
  • 提出方法

  • すべてのコードを exercise15/ ディレクトリに配置
  • 以下のファイル構成にすること:
  • exercise15/
    ├── part1_import.zig
    ├── part2_posix.zig
    ├── part3_export.zig
    ├── mathlib.h
    ├── part4_wrapper.zig
    ├── bonus1_sqlite.zig       # ボーナス
    ├── bonus2_http.zig         # ボーナス
    └── build.zig               # ビルド設定
    

  • テスト実行:
  • # Part 1
    zig build-exe part1_import.zig -lc
    ./part1_import
    
    # Part 2
    zig build-exe part2_posix.zig -lc
    ./part2_posix
    
    # Part 3
    zig build-exe part3_export.zig -lc
    ./part3_export
    
    # Part 4
    zig build-exe part4_wrapper.zig -lc
    ./part4_wrapper
    
    # Bonus 1 (SQLite必要)
    zig build-exe bonus1_sqlite.zig -lc -lsqlite3
    ./bonus1_sqlite
    
    # Bonus 2 (libcurl必要)
    zig build-exe bonus2_http.zig -lc -lcurl
    ./bonus2_http
    

    ヒント

  • @cImport の使い方:
const c = @cImport({
    @cInclude("stdio.h");
    @cInclude("stdlib.h");
});

  • null終端文字列の作成:
const path_z = try std.heap.page_allocator.dupeZ(u8, path);
defer std.heap.page_allocator.free(path_z);

  • C言語のポインタ:
const c_ptr: [*c]u8 = c.malloc(size);
// [*c] は C互換のポインタ型

  • エラーコードのチェック:
if (result < 0) {
    return error.Failed;
}

  • 構造体の初期化:
var tv: c.struct_timeval = undefined;
_ = c.gettimeofday(&tv, null);

参考資料

  • Zig C Interop: https://ziglang.org/documentation/master/#C
  • SQLite C API: https://www.sqlite.org/capi3ref.html
  • libcurl: https://curl.se/libcurl/c/
  • POSIX: https://pubs.opengroup.org/onlinepubs/9699919799/