Foreign Function Interface をいくつか見てまわる

動機

VMC++ に移植開始したのだけど、以前 M さんから指摘のあった C/C++ を呼び出すインターフェースもしくはその逆、つまりForeign Function Interface について考えるなら今しかないので考えよう。


同年代のスーパープログラマ M さんに「どうやって勉強したら良いだろうか?」と問うたら即答。

まずは、CommonLispのdefun-c-callableとchunkあたりを調べて
次にJavaのJNIとC#のP/Invoke周り(特にcustomのmarshalerとか)を調べて
後はrubyとかgaucheの共有ライブラリあたりしっときゃいいんじゃね?

だそうです。
ひぇ。まだまだ彼には追いつける気がしない。

Common Lisp の defun-c-callable

defun-c-callable は obsolete で defun-foreign-callable に変わってた。

何のためのものか?

Lisp 以外から呼び出すことができる手続きを作るマクロ。

(ff:defun-foreign-callable c-callable-mouseclick (button x y)
  (%mouse-clicked button x y))

なのだけどこれ以上の資料、特に実装周りの資料が Google 先生では見つからない。
残念ながら Common Lisp は断念。

Java の JNI

何のためのものか?

Java のネイティブインターフェースは2つ。(Java Native Interface 仕様の目次

  • Java からネイティブメソッド(C/C++)を呼ぶ
  • C/C++のアプリに JVM を組み込む

後者に興味はないので前者に焦点をあてよう。

Java からネイティブメソッド(C/C++)を呼ぶ


Java側は native をつける。後述の動的ライブラリをロードする。

// 初期化コードで動的にライブラリをロード
System.loadLibrary("hogeImpl");

// native をつける
public native void hoge();


javah でC用ヘッダファイルを生成。C側はお約束のヘッダを include して実装する。
これをコンパイルして動的ライブラリにすれば OK 。

JNIEXPORT void JNICALL Java_Hoge_hoge
  (JNIEnv *, jobject)

第一引数 JNIEnv* の指す先に JNI インターフェースのポインタテーブルがある。
第二引数は、オブジェクトの参照、static の場合は Java クラスの参照。
他の引数は j から始まる構造体かプリミティブ型で、String なら jString。

(*env)->xxxx

Java 組み込みの手続きを呼び出す。(C/C++2種類のインターフェースがあるっぽい。C++だと多少アクセスが楽)


Java とネイティブの引数のやりとりは整数、文字などのプリミティブ型は、Java とネイティブメソッド間でコピーされる。
任意の Java オブジェクトは参照渡しとなる。
VM はネイティブコードに渡されたすべてのオブジェクトがガベージコレクタによって解放されないよう、これらのオブジェクトを追跡しなければなりません」と注意書きが。

仕組みを想像し考察

まず主要な Java オブジェクトやプリミティブ型がネイティブメソッドから呼び出すことの出きるような設計になってなければいけなそう。
C++ のクラスよりは構造体の方がCから呼び出すことを考えると楽だと思う。
もし処理系内のオブジェクトを C++ のクラスで表現したいなら、インターフェースの前後で変換をする必要がありそう。
その後は loadLibrary なりなんなりで名前呼び出しすれば良し。

Microsoft .NET Framework の P/Invoke

DLL の関数を呼ぶことに主眼を置いているみたい。Cのコードから .Net Framework にアクセスはできないのかな?
それ以外は Java と同じ。

[DllImport("coredll.dll", SetLastError=true)]
private static extern bool SHGetSpecialFolderPath(
   int hwndOwner, 
   string lpszPath,
   ceFolders nFolder,
   bool fCreate);

上のように宣言して呼び出すだけ。
基本的な型はマーシャリングされるが、構造体などはマーシャリングのコードを自分で書かないといけない。
仕組みの Java のものとそれほど変わらないと想像。

Gauche

インタプリタや中間コード実装の場合、それを実現するには 実行時ライブラリが外部関数宣言を解釈しなければならない。 難しくはないがオーバヘッドは大きくなる。 そのため、ターゲット言語とは別ファイルに宣言を記述しておき、 そちらをネイティブコードコンパイルした上で実行時ライブラリに 結合する、という方法がとられることもある。Gaucheは現在この方針。

(define-cproc %sort (seq)
  (body <top>
        (cond
         ((SCM_VECTORP seq)
          (let* ((r (Scm_VectorCopy (SCM_VECTOR seq) 0 -1 SCM_UNDEFINED)))
            (Scm_SortArray (SCM_VECTOR_ELEMENTS r) (SCM_VECTOR_SIZE r) '#f)
            (result r)))
         ((>= (Scm_Length seq) 0)
          (result (Scm_SortList seq '#f)))
         (else
          (SCM_TYPE_ERROR seq "proper list or vector")
          (result SCM_UNDEFINED)))))

Java.Net Framework が動的だったのに対し、現在の Gauche は静的なのか。なるほど。

Gauche の c-wrapper

id:koguroさんによる c-wrapper 。これは先進的らしい。

(use c-wrapper)

(c-load "stdio.h" :libs "-lc")

(printf "Hello, world\n")

仕組みを予想してみよう

  1. loadLibrary する
  2. まず stdio.h ヘッダをパースしていわゆる native 宣言を自動生成する(つまり関数や引数の型、戻り値の型などを検出する)
  3. 必要な手続きなポインタを得る
  4. libffi で call

という感じに違いない。
libffi のドキュメントを読んでも最初は libffi の何がありがたいのかさっぱり分からなかったが浅はかだった
動的に呼べることがすごいんだ。そりゃそうだよなあ。Cには apply ないし。

まとめ

まとめると以下のものが必要。

  • 処理系のターゲットとする言語でネイティブコードを利用することを指示する仕組み
  • 処理系とネイティブメソッドの間でやりとりの際にコピーや変換が適切に行われる仕組み(マーシャリング)
  • 動的ライブラリのロードの仕組み
  • C/C++ から処理系のオブジェクトにアクセスできるようにするのであればそれを意識したデータ構造


もし間違いや足りない点があればご指摘いただけると助かります_(__)_

追記

あ、Ruby 調べるの忘れた。。
きっと同じような仕組みだろう(だといいな)

追記2

id:koguro さんに2点ご指摘いただきました。ありがとうございます。

  • c-wrapper の仕組み予想の 1 と 2 順序が逆
  • libffi はクロージャを生成できるのが便利