C言語との相互運用

NagiScript から C 関数を呼び出す方法と、C から NagiScript を呼び出す方法を学びます。

現行仕様(v0.2.0)

C 標準関数は extern "C" fn で宣言して利用できます。これは現行コンパイラーで動作する機能です。ただし *const u8 / *mut u8 のような const/mut の区別は現行では不安定なため、ここでは *u8 を使います。


C 関数の呼び出し

extern "C" fn 宣言

filename.ngs
// C 関数の宣言(現行の正しい形式は extern "C" fn)
extern "C" fn strlen(s: *u8) -> i32
extern "C" fn putchar(c: i32) -> i32

使用例

filename.ngs
extern "C" fn strlen(s: *u8) -> i32

fn main() {
    // C の文字列は null 終端。*u8 と長さを組み合わせて扱う
    val msg = "Hello, C!"
    println(msg)
}

C 標準ライブラリ

math.h

filename.ngs
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

filename.ngs
// 現行では 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 構造体との連携

構造体の定義

filename.ngs
// 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 を呼び出す

エクスポート関数

filename.ngs
// 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
// header.h
extern int add(int a, int b);
extern int double_(int x);

ポインタ操作

アドレス取得

filename.ngs
fn main() {
    var x = 42
    val ptr = &x as *i32  // アドレスを取得

    unsafe {
        val value = *ptr  // 値を参照
        println(value)  // 42
    }
}

ポインタ演算

filename.ngs
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
}

動的ライブラリのロード

現行仕様(v0.2.0)

dlopen / dlsym 等を extern "C" fn で宣言すれば利用できます。空ポインタは *u8(または *i32)で扱います。

filename.ngs
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 型は組み込みでは存在しないため、この例では使っていません)。

filename.ngs
// 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")
}

注意事項

  1. 型の整合性: C と NagiScript の型サイズが一致することを確認
  2. メモリ管理: C で確保したメモリは C で解放する
  3. 文字列の変換: C の文字列は null 終端、NagiScript は長さ付き
  4. スレッド安全性: C 関数がスレッド安全かどうかを確認
  5. const/mut: *const u8 / *mut u8 は現行では不安定なので *u8 を使う