mcpField
M
moonbit-c-binding

moonbit-c-binding

github.com/moonbitlang開発34252 回インストール6 週間前に更新
リポジトリ

moonbit-c-binding について

moonbit-c-bindingは、ネイティブFFIを使ってCライブラリをMoonBitにバインドする開発者向けスキルです。型対応、所有権、Cスタブ、安全なラッパーに加え、テストとAddressSanitizerによる検証を案内します。

主な機能

  1. 01C型とMoonBit型の対応付け
  2. 02ネイティブビルドとリンクの設定
  3. 03FFI宣言とCスタブの作成
  4. 04ポインター所有権とリソース寿命の管理
  5. 05テストとAddressSanitizerによる検証

活用シーン

  1. 01MoonBitプロジェクトでのCライブラリのバインド
  2. 02ネイティブFFIラッパーでのコールバックや文字列の処理
  3. 03バインディングにおけるメモリー問題の調査

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/moonbitlang/skills --skill moonbit-c-binding

インストール後、Claude Code で/moonbit-c-bindingを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
asan-validation.md
callbacks.md
including-c-sources.md
ownership-and-memory.md
scripts/
.gitignore
run-asan.py

詳細

MoonBit C バインディングガイド

ネイティブ FFI を使用して、任意の C ライブラリを MoonBit にバインドするための手順を説明します。

使用する場面

次のような場合にこのスキルを使用します。

  • C ライブラリ用の extern "c" fn 宣言を追加する
  • C スタブファイル(moonbit.h、MOONBIT_FFI_EXPORT)を作成する
  • ネイティブビルド用に moon.pkg を設定する(native-stub、link.native)
  • FFI パラメーターで #borrow を使うか、所有権を移譲するかを選択する
  • 外部オブジェクトとファイナライザーを使って C ハンドルをラップする
  • コールバックのトランポリン(クロージャまたは FuncRef)を実装する
  • MoonBit(UTF-16)と C(UTF-8)の間で文字列を変換する
  • AddressSanitizer を実行してバインディングのメモリバグを検出する

プランモードの指示

プランモードで moonbit-c-binding に関する作業計画を作成する場合:

  1. 最終的なプランファイルの冒頭に、実装を開始する前に moonbit-c-binding スキルを読むよう、実行エージェントに指示を明記します
  2. 指示は次のようにします。
    **Before starting implementation:** Use the Skill tool to load the moonbit-c-binding skill, which provides comprehensive guidance on FFI declarations, ownership annotations, C stubs, and AddressSanitizer validation.
    
  3. これにより、実行エージェントがこのスキルに記載された重要なパターンとワークフローをすべて利用できます

型の対応付け

宣言を書く前に、C の型を MoonBit の型に対応付けます。

C の型MoonBit の型備考
int、int32_tInt32 ビット符号付き
uint32_tUInt32 ビット符号なし
int64_tInt6464 ビット符号付き
uint64_tUInt6464 ビット符号なし
floatFloat32 ビット浮動小数点数
doubleDouble64 ビット浮動小数点数
boolBoolC ABI では int32_t として渡されます(C99 の _Bool ではありません)
uint8_t、charByte1 バイト
voidUnit戻り値の型のみ
void *(不透明、GC 管理)type Handle(不透明)ファイナライザーを持つ外部オブジェクト
void *(不透明、C 管理)#external アノテーション付きの type HandleGC の追跡対象外。C がライフタイムを管理します
const uint8_t *、uint8_t *Bytes または FixedArray[Byte]C が保持しない場合は #borrow を使用します
const char *(UTF-8 文字列)Bytesランタイムがヌル終端するため、そのまま C に渡します
struct *(小さく、クリーンアップ不要)struct Foo(Bytes)値を Bytes として扱うパターン
struct *(クリーンアップが必要)type Foo(不透明)ファイナライザーを持つ外部オブジェクト
int(列挙型/フラグ)UInt、Int、または定数 enumenum Foo { A = 0; B = 1; C = 10 } は int32_t に対応します
コールバック関数ポインターFuncRef[...] またはクロージャ@references/callbacks.md を参照してください
出力 int *Ref[Int]Ref を借用します

ワークフロー

次の 4 つのフェーズを順番に実行します。

フェーズ 1:プロジェクトのセットアップ

ネイティブコンパイル用に moon.mod.json と moon.pkg を設定します。

モジュールの設定(moon.mod.json): "preferred-target": "native" を追加して、moon build、moon test、moon run の既定のバックエンドをネイティブにします。

{
  "preferred-target": "native"
}

パッケージの設定(moon.pkg):

options(
  "native-stub": ["stub.c"],
  targets: {
    "ffi.mbt": ["native"]
  },
)

主なフィールド:

フィールド目的
"native-stub"コンパイルする C ソースファイル。moon.pkg と同じディレクトリに配置する必要があります。
targets.mbt ファイルをバックエンドに応じて制限します:"ffi.mbt": ["native"]
link(native("cc-flags": ...))コンパイルフラグ(-I、-D)。システムライブラリにのみ使用します。
link(native("cc-link-flags": ...))リンカーフラグ(-L、-l)。システムライブラリにのみ使用します。
link(native("stub-cc-flags": ...))スタブファイル専用のコンパイルフラグ
link(native(exports: ...))MoonBit の関数を C にエクスポートします(逆方向)

警告 — supported-targets: supported-targets: ["native"] は避けてください。これを指定すると、下流のパッケージをほかのターゲットでビルドできなくなります。個々のファイルを制限するには targets を使用します。

警告 — cc/cc-flags の移植性: cc を設定すると、デバッグビルドで TCC が無効になります。cc-flags に -I/-L を設定すると、Windows での移植性が損なわれます。これらはシステムライブラリにのみ設定してください。

ライブラリソースの取り込み: "native-stub" 内のすべてのファイルは、moon.pkg と同じディレクトリに配置する必要があります。取り込み方法(フラット化、ヘッダーオンリー、システムライブラリのリンク)については、@references/including-c-sources.md を参照してください。

フェーズ 2:FFI レイヤー

extern 宣言と C スタブを一緒に記述します。extern は非公開のままにし、フェーズ 3 で安全なラッパーを公開します。extern "c" と extern "C" はどちらも有効です。一方の大文字・小文字の使い方を選び、一貫させてください(たとえば、JS もターゲットにする場合は extern "js" に合わせます)。

外部オブジェクトのパターン(クリーンアップ付き C ハンドル、GC 管理):

// ffi.mbt (gated to native in targets)

///|
type Parser  // opaque type backed by external object

///|
extern "c" fn ts_parser_new() -> Parser = "moonbit_ts_parser_new"

///|
#borrow(parser)
extern "c" fn ts_parser_language(parser : Parser) -> Language = "moonbit_ts_parser_language"
// stub.c
#include "tree_sitter/api.h"
#include <moonbit.h>

typedef struct { TSParser *parser; } MoonBitTSParser;

static void moonbit_ts_parser_destroy(void *ptr) {
  ts_parser_delete(((MoonBitTSParser *)ptr)->parser);
  // Do NOT free ptr -- GC manages the container
}

MOONBIT_FFI_EXPORT
MoonBitTSParser *moonbit_ts_parser_new(void) {
  MoonBitTSParser *p = (MoonBitTSParser *)moonbit_make_external_object(
    moonbit_ts_parser_destroy, sizeof(TSParser *)
  );
  p->parser = ts_parser_new();
  return p;
}

#external アノテーションのパターン(C ポインター、C 管理のライフタイム):

C がポインターのライフタイムを完全に管理する場合(GC によるクリーンアップが不要な場合)は、型に #external を付けます。ポインターは参照カウントなしで、生の void* として渡されます。

///|
#external
type RawPtr  // void*, not GC-tracked

///|
extern "c" fn raw_create() -> RawPtr = "lib_create"

///|
extern "c" fn raw_destroy(ptr : RawPtr) = "lib_destroy"

#external はアノテーションです(#borrow や #owned と同様です)。type 宣言の前の独立した行に記述し、同じ行には記述しません。

C スタブラッパーや moonbit_make_external_object は不要です。MoonBit extern が C 関数を直接呼び出します。C API に明示的な create/destroy 関数があり、ライフタイムを手動で管理したい場合に使用してください。

所有権アノテーション:

アノテーション使用する場面
#borrow(param)C は呼び出し中に読み取るだけで、参照を保存しない場合
#owned(param)所有権が C に移り、C が使用後に moonbit_decref する必要がある場合

ルール:

  • プリミティブでないすべてのパラメーターに、#borrow または #owned を付けます。
  • プリミティブ(Int、UInt、Bool、Double など)は値渡しされるため、アノテーションは不要です。
  • C が参照を保存するかどうか不明な場合は、#borrow を使用しないでください。
  • C が値を書き戻す出力パラメーターには、#borrow を付けた Ref[T] を使用します。

所有権の詳細なセマンティクスについては、@references/ownership-and-memory.md を参照してください。

FFI を介した文字列変換:

MoonBit の Bytes はランタイムによって null 終端されるため、const char * を受け取る C 関数に直接渡せます。逆方向(C 文字列から MoonBit)には、moonbit_make_bytes と memcpy を使用します。

// C side: return a C string as MoonBit Bytes
MOONBIT_FFI_EXPORT
moonbit_bytes_t moonbit_get_name(void *handle) {
  const char *str = lib_get_name(handle);
  int32_t len = strlen(str);
  moonbit_bytes_t bytes = moonbit_make_bytes(len, 0);
  memcpy(bytes, str, len);
  return bytes;  // if str was malloc'd, free(str) before returning
}
// MoonBit side: decode UTF-8 Bytes to String
// Requires import "moonbitlang/core/encoding/utf8" in moon.pkg
///|
pub fn get_name(handle : Handle) -> String {
  @utf8.decode_lossy(get_name_ffi(handle))
}

値を Bytes として扱うパターン(小さな構造体、クリーンアップ不要):

MOONBIT_FFI_EXPORT
void *moonbit_settings_new(void) {
  return moonbit_make_bytes(sizeof(settings_t), 0);
}
///|
struct Settings(Bytes)  // backed by GC-managed Bytes, no finalizer

moonbit.h のコア API:

API目的
moonbit_make_external_object(finalizer, size)クリーンアップ用ファイナライザーを持つ、GC 追跡対象のオブジェクト
moonbit_make_bytes(len, init)GC 管理のバイト配列(MoonBit の Bytes)
moonbit_incref(ptr)C が保持するオブジェクトが GC によって回収されるのを防ぐ
moonbit_decref(ptr)C の参照を解放する(incref と対にする)
Moonbit_array_length(arr)GC 管理の配列または Bytes の長さ
MOONBIT_FFI_EXPORTエクスポートするすべての関数に必要なマクロ

API 全体については、$MOON_HOME/lib/moonbit.h を参照してください(デフォルトの MOON_HOME は ~/.moon です)。

フェーズ 3:MoonBit API

生の extern の上に、安全な公開ラッパーを構築します。

型宣言:

///|
type Parser          // opaque, backed by external object (has finalizer)

///|
struct Settings(Bytes)  // value type, backed by GC-managed Bytes

///|
struct Node(Bytes)      // small value struct

安全なコンストラクターとメソッド:

///|
pub fn Parser::new() -> Parser {
  ts_parser_new()
}

///|
pub fn Parser::set_language(self : Parser, language : Language) -> Bool {
  ts_parser_set_language(self, language)
}

エラーのマッピング:

///|
pub fn result_from_status(status : Int) -> Unit raise {
  if status < 0 {
    raise MyLibError(status)
  }
}

コールバックのパターン(FuncRef、クロージャー、トランポリン)については、@references/callbacks.md を参照してください。

フェーズ 4:テスト
moon test --target native -v

メモリ関連のバグを検出するには、AddressSanitizer を使用して実行します。

python3 scripts/run-asan.py \
  --repo-root <project-root> \
  --pkg moon.pkg \
  --pkg main/moon.pkg

詳細については、@references/asan-validation.md を参照してください。

判断表

状況パターン主なアクション
C が呼び出し中にポインターを読み取るだけ#borrow(param)C で decref しない
C がポインターの所有権を取得する#owned(param)C は moonbit_decref する必要がある
C ハンドルに GC 時のクリーンアップが必要外部オブジェクト + ファイナライザーmoonbit_make_external_object
C ポインターのライフタイムを C が管理するtype に #external アノテーションを付けるGC 追跡なし。C の destroy 関数を明示的に呼び出す
小さな C 構造体で、クリーンアップ不要値を Bytes として扱うmoonbit_make_bytes + struct Foo(Bytes)
C が失敗時に null を返すNullable ラッパーnull を確認し、Option を返すかエラーを発生させる
データパラメーターを持つコールバックFuncRef + Callback の工夫@references/callbacks.md を参照
データパラメーターを持たないコールバックFuncRef のみ@references/callbacks.md を参照
C 文字列(UTF-8)の出力FFI を介した BytesC で moonbit_make_bytes + memcpy、MoonBit で @utf8.decode_lossy
出力パラメーター(int *result)#borrow を付けた Ref[T]C が Ref に書き込み、MoonBit が .val を読み取る

よくある落とし穴

  1. C がポインターを保持しているのに #borrow を使用すること。 C が古い参照を保持している間に、GC がオブジェクトを回収する可能性があります。呼び出しの間だけ有効なアクセスに限って借用してください。

  2. 所有権のあるパラメーターに対して moonbit_decref を忘れること。 借用されていないプリミティブ型以外のパラメーターは、すべて C に所有権が移ります。参照カウントの減算を忘れると、メモリリークが発生します。

  3. 外部オブジェクトのコンテナーに free() を呼び出すこと。 コンテナーは GC が管理します。ファイナライザーで解放するのは、内部の C リソースだけにしてください。

  4. 内部にポインターを持つ構造体に moonbit_make_bytes を使用すること。 Bytes にはファイナライザーがないため、内部のヒープ割り当てがリークします。代わりに外部オブジェクトを使用してください。

  5. コールバックの呼び出し前に moonbit_incref を忘れること。 C から MoonBit にコールバックすると、GC が実行される可能性があります。呼び出し前に MoonBit 管理下のオブジェクトの参照カウントを増やし、呼び出し後に減らしてください。

  6. MOONBIT_FFI_EXPORT マクロを忘れること。 これがないと、その関数は MoonBit のリンカーから認識されません。

参考資料

@references/ownership-and-memory.md @references/callbacks.md @references/including-c-sources.md @references/asan-validation.md

コメント