スクリプト言語の拡張機能の作り方とGObject Introspectionの紹介 - 2013-12-09 - ククログ

ククログ

株式会社クリアコード > ククログ > スクリプト言語の拡張機能の作り方とGObject Introspectionの紹介

スクリプト言語の拡張機能の作り方とGObject Introspectionの紹介

GNOME Advent Calendar 2013 9日目の記事です。

多くのスクリプト言語では、スクリプトからCで書かれたライブラリーを使うための機能を提供しています。ここでは便宜的に「拡張機能」と呼びます。「拡張機能」はスクリプト言語毎に違う名前がついています。例えば、Rubyでは「拡張ライブラリ」と呼んでいて、Pythonでは「拡張モジュール」と呼んでいます1

Cで書かれたライブラリーを使えるとできることが一気に増えます。既存の機能を利用できるからです。

例えば、NroongaGroongaというCで書かれた全文検索エンジンライブラリーを使う機能を200行くらいのC++で実装しています。これでNode.jsから全文検索機能を使うことができるようになります。いくらJavaScriptの方がCよりも簡潔に記述できるといっても、200行でGroongaと同じ機能・同じ性能の全文検索エンジンを実現することはできません。

このように、Cで書かれたライブラリーをスクリプト言語から使えるようにする機能は便利な機能です。今回は、その機能をより簡単に使うためのソフトウェアGObject Introspectionを紹介します。あわせて、拡張機能の書き方もいろいろ紹介します。

拡張機能の書き方

GObject Introspectionの紹介をする前に、拡張機能の書き方について説明します。これは、どうしてGObject Introspectionが必要とされているのかということを説明するために必要となるからです。

拡張機能の仕事はスクリプト言語とCで書かれたライブラリーの隙間を埋めることです。例えば、スクリプト言語での「文字列」とCの「文字列」は違う表現になっています。拡張機能はそれらの相互変換をします。隙間を埋めるという役割が「糊」の役割と似ていることから、拡張機能を実現するためのコードをグルー(糊の英語)コードと呼んだりします。

それでは、グルーコードの書き方を紹介します。

手書き

最初の書き方は手動で書いていく書き方です。

グルーコードはスクリプト言語の値とCでの値を相互変換する処理がほとんどなので、定型的な記述が多くなります。そのため、慣れてしまえば簡単に書くことができます。最も基本的な書き方です。

ただし、簡単というのは「技術的に」簡単なだけであって、「時間的に」は簡単ではありません。スクリプト言語に提供したいCのAPIが多いと、「技術的に」簡単でも、手間のかかる作業です。

自動生成

そんな手間のかかるけど定型的な作業を軽減するための書き方が、グルーコードを自動生成する方法です。

Cのヘッダーファイルから自動生成したり、手動で自動生成するための情報(グルーコードを書くよりは少ない記述で済む)を作成し、それを使って自動生成したりします。

この方法を使っているのが、PerlのXSや、Gaucheのgauche-packageと.stubの組み合わせなどです。言語に依存しないSWIGというソフトウェアもあります。

この方法では、一度C/C++のソースコードを生成し、それをコンパイルすると使えるようになります。そのため、この方法で作られた拡張機能を使うためにはコンパイラーが必要になります。

動的呼び出し

コンパイラーが必要になるとインストールの敷居がグッとあがります。そのため、コンパイラーがなくても動くようにしたくなります。また、コンパイラーから脱却するならグルーコードをスクリプト言語自身で書きたくなります。

そこで、Foreign_function_interfaceです。FFIを使えば、事前にコンパイルしなくても、実行時にCで書かれたライブラリーの関数を呼び出すことができます。

FFIの実装はlibffiが有名ですが、RubyのDL2のように自前でlibffiと同じような機能を実装しているライブラリーもあります。

libffi(Cで書かれたライブラリー)を使うための拡張機能を標準で提供しているスクリプト言語も増えています。Pythonはctypesというモジュールを提供していますし、RubyはFiddleを提供しています。Mozillaが搭載しているJavaScriptではjs-ctypes3を使えます。

FFIを使うことにより、コンパイラーは必要なくなり、スクリプト言語でグルーコードを書けるようになります。しかし、libffiでは関数のシグネチャーの情報を取得する機能は提供していないので、スクリプト言語側から返り値の情報を与えなければ適切に動きません。グルーコードを作る手間はだいぶ減りましたが、使いたいAPIが多い場合はまだ手間がかかります。

メタ情報を動的に取得

Cで書かれたライブラリーがどのようなシグネチャーの関数を提供しているかを実行時に知ることができれば、その情報を使って動的に自動でグルーコードを動的に生成できます。ほぼ全自動です。

この方法を実現しているのがGObject Introspectionです。GObject IntrospectionはGTK+4が使っているオブジェクトシステムの仕組みを使って動的にメタ情報を取得する機能を提供しています。また、libffiを使って動的に関数を呼び出す機能も提供しています。これらの機能を使うことによりほぼ全自動で動的にグルーコード相当のことをする機能を実現できます。

GTK+と同じくクロスプラットフォーム対応のGUIツールキットであるQtも同様の機能を提供しています。それはSMOKEと呼ばれています。

これらの仕組みを使えば、Cで書かれたライブラリーのバージョンが変わり、APIが増えても、グルーコード用の処理をなにも変更せずに対応できます。拡張機能のメンテナンスがほぼ0で常に最新の機能を使えるということです。

まとめ

Cで書かれたライブラリーの機能をスクリプト言語から使うための仕組みの実現方法を4つ紹介しました。

  • 手動でグルーコード(C/C++)を書いてビルドする
  • 自動でグルーコード(C/C++)を生成してビルドする
  • FFIを使ってスクリプト言語から動的に関数を呼び出す(関数のシグネチャーは手動で設定)
  • Cで書かれたライブラリーのメタ情報を取得し、それを基にFFIで動的に関数を呼び出す(ほぼ全自動)

ここで紹介した方法はすべて今でも使われているものです。後になるほどより便利になっているので先に紹介した方法はすでに過去のものと感じたかもしれませんが、そんなことはありません。

例えば、手動でグルーコードを書くことは面倒でイヤだと思うかもしれませんが、FFIのオーバーヘッドがない分、高速に動作するというメリットがあります。また、自動生成するAPIに比べてより使いやすいAPIを提供しやすく5なります。適材適所です。

拡張機能を実現する仕組みを紹介したうちの最後の仕組み「ほぼ全自動で動的に拡張機能を使う仕組み」を実現したライブラリーとしてGObject Introspectionがあることを紹介しました。

GObject Introspectionの使い方は別の機会に紹介します。

  1. Cで書かれたライブラリーを呼び出さない「拡張ライブラリ」や「拡張モジュール」もあります。その場合は、それら自身が「Cで書かれたライブラリー」と考えることもできます。しかし、ここではあまり厳密に考えなくてもよいです。

  2. Ruby 2.0からはdeprecatedになっています。

  3. 名前からわかる通り、Pythonのctypesを参考にしています。

  4. C言語で実装されているクロスプラットフォーム対応のGUIツールキット。ようやくGNOMEっぽい単語がでてきました。

  5. もちろん、開発者がどれだけがんばるか次第です。手動で作ったからよいAPIになるわけではありません。