第15章: C言語連携
学習目標
この章を終えると、以下ができるようになります:
@cImportを使用してCヘッダーファイルを読み込む- C言語の関数をZigから呼び出す
- Zigの関数をC言語から呼び出せるようにする
- C言語のデータ構造とZigの間でデータを受け渡す
- 既存のCライブラリをラップするZigライブラリを作成する
- 既存資産の活用: 50年以上蓄積されたCライブラリを利用
- 段階的な移行: 既存のCプロジェクトを徐々にZigに移行
- システムAPI: OS APIやハードウェアドライバへのアクセス
- パフォーマンス: C言語と同等の性能を維持
ZigとCの相互運用性
なぜC言語連携が重要か
Zigの設計目標の1つは、C言語との完全な相互運用性です。これにより以下が可能になります:
[既存Cコードベース]
|
| ← Zigから呼び出し
|
[Zig Wrapper]
|
| ← 安全なZig API
|
[Zig Application]
C言語連携の基本原則
Zigは以下の原則でC言語と連携します:
@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言語の相互運用について学びました:
次の章では、非同期処理と並行処理について学びます。