gdcc - D を C から触るブリッジというかなんというか

C から D を呼びたい場合、が仮にあるとすれば、まあ常識的には C 向けインターフェイスを書くわけです。ただその際インターフェイスのコードによるほんの少しの速度低下とコード増大と、それなりに発生する手間というオーバヘッドがあります。手間を削減する方法としては SWIG なんてのがあってこれはすばらしいものです。ですがネイティブ言語どうしなんだからマングリングと呼出し規則さえなんとかすれば直接呼べるはずなのになあ…というもどかしさが少しあったりします。

説明飽きたので実物を。

d_test.d:

class C {
    this(int i0) { i = i0; }
    int p() {
        return i;
    }
    int i;
}

void d_test() {
    C c = new C(3);
    int i = c.p();
    printf("%d in D\n", i);
    delete c;
    return;
}

extern(C) void c_test();

int main() {
    c_test();
    d_test();
    return 0;
}

まあ普通のコード。ここで定義されてない c_test はどんなものかというと…

c_test.c:

void* _d_newclass(void*);
void _d_delclass(void**);

void* d_test_C__ctor(void*, int);
int d_test_C_p(void*);

extern struct {} d_test_C;

void c_test() {
    void* cc;
    void* c;
    int i;

    cc = _d_newclass(&d_test_C);
    c = d_test_C__ctor(cc, 3);
    i = d_test_C_p(c);
    printf("%d in C\n", i);
    _d_delclass(&c);
}

c_test は d_test とだいたい等価なことをしています。 d_test_C はクラス C の TypeInfo で、 d_test_C__ctor はコンストラクタ、 d_test_C_p はメソッドメソッドを呼ぶ時は第一引数がオブジェクトにする、 new する時は _d_newclass を先に呼んでおく、 delete は _d_delclass を使う、です。

もちろんこんなもん普通にコンパイルしてもリンク通りません。それを解決するのが謎の物体 gdcc でして、こいつでコンパイルした D ファイルは C から呼び出しやすいように demangling されます。

今回は技術的に可能かどうかを知りたかっただけなので実現はとてもいい加減で、かつ未実装だらけです。

実現法。まず普通に gdc -c でコンパイル。できたオブジェクトを nm に喰わせてそのファイルで定義されたシンボルを連想配列に保存。次に gdc -S でアセンブラを吐く。で、その中のシンボルでさきの nm の時に保存されてるものは dfilt.d を少し書きかえたもの(gdcc.d に含まれています) で demangle して別なアセンブラファイルとして出力。で、 gcc -c でそれをアセンブル

問題点。オーバーロード、ポリモーフィリズム、テンプレート、 D のオブジェクトがリンクできなくなる(phobos も含めて全てを gdcc でコンパイルしちまって nm のチェックを外せば良い、はず)。そもそも外部プロセスに依存しすぎ。などなど、実用は全く想定していません。

要するにアセンブラの段階でシンボルを書き変える、っていうのがなんとなく魅力的に感じられただけです。常識的に考えるとコンパイラをいじるんでしょうね。もしくはリンカ。このへんは必要な知識が多いのでたいへんだから簡単にやってみようと。後は全てダイナミックリンクするようにして dlopen を上書きというのも面白げ。

なにかあれば下記メールアドレスへ。
shinichiro.hamaji _at_ gmail.com
shinichiro.h