PharoからCで書いたプログラムを呼び出す

これはSmalltalk Advent Calendar 2018の記事です。

ごく稀にPharoから外部のプログラムを呼び出して使いたいことがあります。以前は外部プログラムを呼び出す仕組みが毎年のように変わっていたため、ちょっとご無沙汰していると置いてけぼりを食らうことが多々ありました。

最近では UFFI という仕組みで落ち着いた感がありますが、Pharo のことなのでまた変わってしまうのでしょう。

この記事では UFFI を使って Pharo から C で書いたプログラムを呼び出す方法を簡単に説明します。

Cプログラムの準備

まずは、C言語のプログラムを用意しておきましょう。普段 Linux を使っているので、Linux (64bit) を例に説明します。おそらく、mingw を使えば Windows でも同じようにいけるはずです。

int doublenumber(int n)
{
return n * 2;
}

上記のようなプログラムを用意します。ファイル名は uffitest.c とします。内容は整数を2倍する doublenumber 関数を定義しているだけです。

このプログラムをコンパイルして共有オブジェクトを作ります。

gcc -fPIC -shared -o uffitest.so uffitest.c

カレントディレクトリに uffitest.so というファイルができます。

UFFITestLibrary クラスの作成

Pharo を起動します。ここでは Linux 版のPharo6.1 のイメージを 64bit VM で起動したものとします。UFFILibrary クラスを継承した UFFITestLibrary クラスを作成します。

FFILibrary subclass: #UFFITestLibrary
instanceVariableNames: ''
classVariableNames: ''
package: 'UFFITest'

このクラスで外部のプログラムとの結びつきを定義します。そのためにメソッドを追加します。このメソッドはインスタンスメソッドとして登録します。

unixModuleName
^ 'uffitest.so'

このメソッドで先程作った uffitest.so ファイルのありかを示します。イメージと同じ場所にあるならパス指定は不要なようです。

他のOSで使う場合には別のメソッド名を用います。Mac OS Xならば macModuleName とし、Windows ならば win32ModuleName を使います。Windows は 32bit VM しか公開されていないので、先程の共有ライブラリも32btにする必要がありそうです。

外部プログラムを呼び出すメソッドの追加

doublenumber 関数を呼び出すメソッドを追加します。どんなクラスに書いてもいいのですが、ここではテストのため UFFITest クラスを作成してメソッドを登録します。

Object subclass: #UFFITest
instanceVariableNames: ''
classVariableNames: ''
package: 'UFFITest'

何の変哲もない Object クラスを継承したクラスです。このクラスのインスタンス側に doubleNumber: というメソッドを登録します。このメソッドは引数を1つ取り、その2倍の値を返します。

doubleNumber: anInteger
^ self ffiCall: #( int doublenumber (int anInteger)) module: UFFITestLibrary

このメソッドは #ffiCall:module: というメッセージを送るだけのものです。第一引数には C言語の関数プロトタイプ宣言のような形式で、呼び出す関数を指定します。また、第二引数には共有オブジェクトを定義したクラス名を指定します。

試してみる

以上で準備は完了しました。実際に使ってみましょう。Playground を開いて以下のように入力します。

UFFITest new doubleNumber: 10. 

Go を押せば結果(20)が得られます。

エラー?

External module not found というエラーメッセージが出ることがあります。これは、適切な共有オブジェクトファイルが見つからなかった時に現れます。Pharo のイメージファイルと同じディレクトリにファイルがあるか、32bit VMなのに64bitのオブジェクトファイルになっていないか、UFFITestLibrary クラスのメソッドが間違っていないかなど、確認してください。

おわりに

どうだったでしょうか?簡単にC言語の関数を呼び出せることがわかったと思います。C-API を持っているライブラリを活用する助けになれば幸いです。