コミックマーケット105に申し込みました
作成日:
お知らせ
お久しぶりです。
当サークル『神和電子』は、コミックマーケット105(2024年の冬コミ)に申し込みました。
サークルカットにも書いてあるのですが、新刊のテーマはGObjectを利用した異言語間のライブラリ呼び出し(Foreign function interface)です。C言語だけでなくRustで書いたライブラリを別の言語(RubyなりPerlなり)から呼び出す方法の説明を書ければと思っています。
サクカの子は 前回の本 の詠ちゃんです。頑張って描きました。今回の本も前回のGStreamerと関連するテーマなのもあって、詠ちゃんが主人公として続投予定です。関連はしているものの、特に前回の本が前提知識ではありません。中学生くらいのときからずっと萌え萌え技術書が大好きなので、今回もそういう道を追っていきたいですね。
GObjectって何? 何が嬉しいの?
ここからは、何故GObjectの本を書きたいかというポエムをしたためていきたいと思います。
まず、GObject は、C言語上でオブジェクト指向の仕組みを実現するためのライブラリです。
そういうと、「別にオブジェクト指向の言語はいっぱいあるんだから、わざわざ古いC言語で無理矢理オブジェクト指向をやらなくてもいいんじゃない? C++もあるし」という反応がありそうですが、話はそう単純ではありません。GObjectは、別の言語から呼び出されるライブラリをオブジェクト指向でつくれるという点で、ほぼ唯一無二の強みを持っています。
それはどういうことか。
まず、上で「ライブラリ」と書きましたが、ここで扱いたいのは主に動的リンクライブラリのことです。Windowsでは *.dll
、Linuxでは *.so
、macOSでは *.dylib
という名前のやつです(以下の文章ではWindowsの拡張子に代表させます)。
一般に *.exe
のような実行ファイルは、ソースコードをコンパイルしてできた複数のオブジェクト(ここでいうオブジェクトはオブジェクト指向とは関係ありません)をリンクしてひとつにまとめることでつくります。
最も分かりやすいのは、必要な全部のオブジェクトをリンクして一つの *.exe
だけで動くようにすることですが、様々な事情で一部を別のファイルに切り出しておきたい場合もあります。様々な事情というのは、いろいろな実行ファイルで一つのオブジェクト(ライブラリ)を共有したいとか、プラグインのように後で追加したいとか、その部分はクローズドソースでバイナリとして提供したいとか、その部分は別の言語で書きたいとかです。
そういうときに使われるのが「動的リンク」、つまり実行時に必要なオブジェクトを探してリンクするという技術です。つまり、*.exe
ひとつだけではなく、必要な*.dll
も用意しないとプログラムが動かないようなプログラムの配布方法です。
さて、上で挙げた動的リンクの採用理由のうち、注目したいのは *.exe
と *.dll
を別の言語で書けるということです。C言語で書いた関数を *.dll
に詰めて、Go言語で書いた *.exe
から呼び出すといったことができます。こういうのを FFI (Foreign function interface) といいます。
(少し脱線しますが、)プログラミング言語の未来はどうなるか | κeenのHappy Hacκing Blog という記事を読んだとき、
一応触れておくと、私の考え方の根底には「プログラムはアプリケーションを書くための巨大なDSLライブラリと、そのDSLを使った小さなプログラムからなる」の思想があります。
という記述に私は結構な感銘を受けました。その思想のわかりやすい例が「C言語で書かれた高速なライブラリを書きやすいRubyから呼ぶ」といったものになるわけで、つまりFFIはこの意味でも重要なわけです。「C#で大統一」も夢はあるのですが、現実的には異言語のライブラリをいいかんじに呼び出す仕組みが整備された方がみんな幸せになるのではないかと思っています。「プラットフォームではなくプロトコルを」という標語は、自然言語の通信だけでなくプログラミング言語の通信にもいえるかもしれません。
閑話休題。動的リンクの技術を使うと異言語間でライブラリを呼び出せるわけですが、ここに一つ問題があります。異言語から呼び出すためには、当然ながらそのライブラリがどのような形式で作られているかを知る必要があります。しかし、普通に作ると言語ごとに機能がバラバラなので、ライブラリの形式もバラバラになってしまいます。
では統一規格なんてないのかというとそうではなく、多くの言語は「DLLをC言語の形式でつくる」機能と、「C言語の形式のDLLを呼び出す」機能を持っています。そのため、DLLのインターフェースはC言語の形式に合わせるのが多くの場合の最適解になっています。ちょうど今週公開された Rust から Go で書かれた関数を呼ぶ - estie inside blog という記事が、分かりやすい事例です(この例は静的リンクしていますが1)。
なぜC言語なのかといえば、一つは歴史が長いこともあるでしょうが、C言語のABI (Application Binary Interface) がとても安定していることも大きい理由だと思います。ABIとはなんぞやといい始めるとまた長くなるのですが、要するにC言語の形式で作られたDLLは可搬性や互換性が高く、一度作るといろんな環境でずっと使うことができます。
C言語以外、例えばC++の形式でDLLを作るとそうではなく、DLLを作ったときのコンパイラのバージョンとexeを作るときのコンパイラのバージョンが違うと動かないといったことが起きます(大学生の頃C++とMFCで書かれたGUIアプリを保守するアルバイトをしていたのですが、そのときこれに苦しめられました……)。なので、C++で書かれたDLLをC++から使うとしても、DLLのインターフェース部分はC言語の形式でつくる(extern "C"
する)方がよいことさえあるかもしれません2。
じゃあ「C言語のインターフェースでみんな書けばいいからそれで解決だね」といえるかというと、そうでもないでしょう。皆さんは、配列を渡すときにデータへのポインタと長さの整数を別変数で渡したり、複数の返値がほしいときに参照渡しでデータを受け取ったりしたいですか、という話です。「ライブラリの機能を呼び出せる」ことと「ライブラリの機能を使いやすい」ことは別なのです。
この問題に対する回答の一つは、C言語のインターフェースを使わないという選択です。さっきは「多くの場合の最適解」ではないと書きましたが、潤沢なリソースを捻出できる状況では、C++やRustでDLLを書いて、別の言語から呼び出せるようにするライブラリを書くという手が選べます。代表的な例が、PythonとC++の橋渡しをする pybind11 や、PythonとRustの橋渡しをする PyO3 です。こういったライブラリは、言語固有のオブジェクトシステムにラッピングすることでいい感じに呼び出せるようにするものですが、言語同士のバージョンごとの差異に追従する必要があるためメンテナンスコストが高めで、何より「呼び出す側の言語の数」掛ける「呼び出される側の言語の数」の乗算でライブラリが必要になります。そのため、今挙げた例のように需要が高い言語の組であればよいものの、「RacketからGoを呼びたい」といった事例では望みが薄くなります。
別の回答が、他ならぬGObjectです。つまり、C言語のインターフェースを保ちつつ、そこでオブジェクトを渡せるようにするという方法です。このことで、呼び出される側の言語は「C言語のインターフェースでDLLをつくり、GObjectを返すようにする仕組み(ライブラリ)を用意する」、呼び出す側では「C言語のDLLを呼ぶ仕組みと、GObjectを扱うライブラリを用意する」ことで、異言語間のやりとりが可能になります。つまり、乗算ではなく加算の数で済むのです。この特性は、比較的マイナーな言語であっても恩恵を受けやすいという利点になります。
GObjectは、実装としては「オブジェクトの定義として、どのような構造体や関数をどのような名前で用意するか」という規約の集合体と、それに従ってつくられた便利なクラスの集合体です。端的に言えば、言語間共通のオブジェクト指向呼び出しプロトコルと、その参考実装です。
このようなアプローチを行っているライブラリは、私の知る限り今ではGObjectがほぼ唯一だと思います。「今では」というのは、過去にはMicrosoftが COM (Component Object Model) という、GObjectと同様の技術を開発して広く使っていたからです。なのですが、COMが複雑で使いにくかったこともあり、MicrosoftはCOMを使うのを辞めて .NET に寄せていきました3。Microsoftは気合いでWindowsのAPI(C言語のDLL)を .NET でラップしたので、WindowsのアプリをC#やF#で書くならオブジェクト指向でいい感じに書けますが、他の言語からWindowsの機能を呼ぶにはC言語のDLLをC言語のインターフェースで呼ばないといけない、という状況になっているわけです(最近はRustバインディングに力を入れているようですが)。
上記が、主なGObjectの嬉しさです。なかなか夢もあるおもしろ技術で、広く使われた方が嬉しいものなのにあまり知られていないので、是非同人誌で書きたいと思いました。
更にこの上に、GObject Introspectionというものもあります。GObjectがバイナリ本体を言語間で呼び出せるものであるのに対し、GObject Introspectionはメタデータ(C言語のヘッダファイル相当)を言語間で共有するような仕組みです。ここまで来ると呼び出し側は関数の宣言を書くことすら不要になります。もしあなたがUbuntuを使っているなら、/usr/lib/x86_64-linux-gnu/girepository-1.0
にいろいろなライブラリのtypelibがインストールされていると思うので、是非それを PyGObject あたりで呼び出してみてください。なかなか感動的だと思います。GObject IntrospectionはC言語ヘッダファイルからの変換しかできないようなのですが、cargo-c あたりを使ってRustのライブラリでも使えないか調査中です(成功したら本に書きます)。
さて、ここまではGObjectの素晴らしさについて書いてきましたが、対抗技術についても触れておきます。そもそもDLLを使わないFFIがあるからです。
一つは、ネットワークを通じたFFI、すなわちRPC(遠隔手続き呼び出し)です。昨今はマイクロサービスといって、諸機能を別のプロセスに切り出し、そのプロセス間をネットワーク経由で結ぶRPCの技術が流行しています。最も有力なのがgRPCでしょう。RPC技術が広く使われてエコシステムも成熟している以上、ローカルでのFFIもプロセス間通信にしてしまうというのが有力な選択肢になるはずです。今調べたらASP.NET Core にgRPCでのプロセス間通信機能が入っているらしくて驚きました(gRPC を使用したプロセス間通信 | Microsoft Learn)。
もう一つ有力なのが、WebAssemblyです。WebAssemblyはWebと名乗っていますが、今やブラウザを離れて「CPUやOSに依存せずに高速に動くバイナリ」、有り体に言えば次世代Java(というよりJVM)を目指して進んでいっています。WebAssemblyはいろいろな言語からコンパイルできるバイナリなので、当然FFIも視野に入ります。それが今年1月に安定版になったばかりの WebAssembly Component Model で、インターフェースを定義するWITを見ると、既に配列、クラス、インターフェース、オプション型といった便利そうな型が並んでいます。もし世界のほとんどのアプリケーションが伝統的なバイナリではなくWebAssemblyで動くような時代が来たら、DLLすら使わないのでGObjectの出る幕はなくなるかもしれません。
と、今を時めくイケイケ技術が対抗馬にあるわけですが、RPCではデータ転送のオーバーヘッドがあるためデータ科学やマルチメディア処理といった分野では後塵を拝するはずですし、WebAssemblyが全てを支配する未来はまだしばらくは来ないでしょう。そんなわけで、GObjectを今知っておくのは悪くないんじゃないかなと思います。副次的に「オブジェクト指向とは何か」を実装の意味で理解することができるという魅力もありますし4。
-
「静的リンクでもFFIできてるじゃん!」というツッコミが入りそうですが、まあRustやGoのようなコンパイル言語ではなくRubyやPythonといったインタプリタ言語では静的リンクが使えないと思うので許してください……。 ↩︎
-
Visual Studio のバージョン間の C++ バイナリ互換性 | Microsoft Learn によると、Visual Studio 2015 以降ではコンパイラのバージョンが変わってもC++のABIの互換性が保たれるようになったらしいです。なので、今後はWindowsではC++でDLLを書いても問題ないかもしれません。 ↩︎
-
とはいっても今でもCOMに依存しているアプリケーションはたくさんあります。最も一般に馴染みがあるだろうものはMS Officeにある「オブジェクトの挿入」機能です。Word文書の中に一太郎文書を埋め込んだりできるおもしろ機能なのですが、これは Object Linking and Embedding といってCOMベースの機能です(正確にはOLEの一部が切り出されてCOMになったらしい)。私は久しく使っていませんでしたが、どうもCADソフトなどでは今も需要があるようです。でも今からCOMを勉強したいとは正直思えないので、ここでは候補から外させてください(以前DirectShowを使おうとして痛い目を見ました……)。 ↩︎
-
理念的な意味でのオブジェクト指向については、『ちょうぜつソフトウェア設計入門』などで学ぶことができます(素晴らしい本で感動しました)。一方で、実装の意味でのオブジェクト指向については、(Perl界隈の人を除けば)意識することはそうありますまい。 ↩︎