C言語との相互運用
NagiScript から C 関数を呼び出す方法と、C から NagiScript を呼び出す方法を学びます。
C 標準関数は
extern "C" fn
で宣言して利用できます。これは現行コンパイラーで動作する機能です。ただし
*const u8 / *mut u8 のような
const/mut の区別は現行では不安定なため、ここでは
*u8 を使います。
C 関数の呼び出し
extern "C" fn 宣言
// C 関数の宣言(現行の正しい形式は extern "C" fn)
extern "C" fn strlen(s: *u8) -> i32
extern "C" fn putchar(c: i32) -> i32
使用例
extern "C" fn strlen(s: *u8) -> i32
fn main() {
// C の文字列は null 終端。*u8 と長さを組み合わせて扱う
val msg = "Hello, C!"
println(msg)
}
C 標準ライブラリ
math.h
extern "C" fn sqrt(x: f64) -> f64
extern "C" fn pow(base: f64, exp: f64) -> f64
extern "C" fn sin(x: f64) -> f64
extern "C" fn cos(x: f64) -> f64
fn main() {
val result = sqrt(16.0) // 4.0
val power = pow(2.0, 10.0) // 1024.0
println(result)
}
stdio.h
// 現行では FILE 型は *u8 で扱う(構造体定義は実装依存)
extern "C" fn fopen(path: *u8, mode: *u8) -> *u8
extern "C" fn fclose(stream: *u8) -> i32
extern "C" fn fgets(buffer: *u8, size: i32, stream: *u8) -> *u8
fn main() {
// ロードマップ: 文字列(*u8)+ 長さでの高水準な読み取りは未検証
// ここでは単純に関数宣言のみ示す
}
C 構造体との連携
構造体の定義
// C 側の構造体
// struct Point { double x; double y; };
// NagiScript 側
struct Point {
x: f64
y: f64
}
extern "C" fn c_distance(p1: *Point, p2: *Point) -> f64
fn main() {
val p1 = Point { x: 0.0, y: 0.0 }
val p2 = Point { x: 3.0, y: 4.0 }
val dist = c_distance(&p1 as *Point, &p2 as *Point) // 5.0
println(dist)
}
C から NagiScript を呼び出す
エクスポート関数
// NagiScript 関数を C/Wasm から呼び出せるようにエクスポート(export "C" fn 形式が正)
export "C" fn add(a: i32, b: i32) -> i32 {
return a + b
}
export "C" fn double(x: i32) -> i32 {
return x * 2
}
C 側の宣言
// header.h
extern int add(int a, int b);
extern int double_(int x);
ポインタ操作
アドレス取得
fn main() {
var x = 42
val ptr = &x as *i32 // アドレスを取得
unsafe {
val value = *ptr // 値を参照
println(value) // 42
}
}
ポインタ演算
fn sum_array(arr: *i32, len: i32) -> i32 {
var total = 0
var i = 0
unsafe {
while i < len {
total += *arr
i = i + 1
}
}
return total
}
fn main() {
val numbers = [10, 20, 30, 40, 50]
val sum = sum_array(&numbers[0] as *i32, 5) // 150
}
動的ライブラリのロード
dlopen / dlsym 等を
extern "C" fn
で宣言すれば利用できます。空ポインタは
*u8(または *i32)で扱います。
extern "C" fn dlopen(path: *u8, flags: i32) -> *u8
extern "C" fn dlsym(handle: *u8, symbol: *u8) -> *u8
extern "C" fn dlclose(handle: *u8) -> i32
fn load_library(path: *u8) -> *u8 {
return dlopen(path, 1) // RTLD_LAZY
}
fn get_function(handle: *u8, name: *u8) -> *u8 {
return dlsym(handle, name)
}
実践的な例: C ライブラリのラッパー(ロードマップ)
元の例は関数型フィールド / Result 型 / 文字列連結 /
io.println
を利用しており、現行ではそのまま動きません。ここでは現行構文で書ける簡略版を示します(Result
型は組み込みでは存在しないため、この例では使っていません)。
// libcurl のラッパー(簡略版・現行構文)
extern "C" fn curl_easy_init() -> *u8
extern "C" fn curl_easy_cleanup(curl: *u8)
struct Curl {
handle: *u8
}
impl Curl {
fn init(self: Curl) {
curl_easy_init()
}
fn cleanup(self: Curl) {
curl_easy_cleanup(self.handle)
}
}
fn main() {
val curl = Curl { handle: curl_easy_init() }
curl.cleanup()
println("done")
}
注意事項
- 型の整合性: C と NagiScript の型サイズが一致することを確認
- メモリ管理: C で確保したメモリは C で解放する
- 文字列の変換: C の文字列は null 終端、NagiScript は長さ付き
- スレッド安全性: C 関数がスレッド安全かどうかを確認
-
const/mut:
*const u8/*mut u8は現行では不安定なので*u8を使う
