空想犬猫記

※当日記では、犬も猫も空想も扱っておりません。(旧・エト記)

RubyCocoa = ObjC-Ruby Bridge

RubyCocoa というフレームワークがある。一般的には「Cocoa アプリケーションを Ruby で記述するためのフレームワーク」として認識されていて,御本家にも

RubyCocoaは、オブジェクト指向スクリプト言語Rubyでの Cocoaプログラミングを可能とする、 Mac OS Xフレームワークです。

RubyCocoaを使って、CocoaアプリケーションをRubyで書いたり、Rubyスクリプトで Cocoaオブジェクトを生成して機能を利用することができます。 Cocoaアプリケーションでは、RubyObjective-Cのソースが混在するCocoa アプリケーションを作ることも可能です。

次のようなときにRubyCocoaを使えます:

  • irbで対話的にCocoaオブジェクトの性質を探求
  • Cocoaアプリケーションのブロトタイピング・開発
  • RubyObjective-C双方の長所を活かしたCocoaアプリケーション
  • RubyスクリプトにMac OS X風のユーザインターフェースをかぶせる

と,紹介されている。しかしよく調べてみると,これは単なる Cocoa APIRuby wrapper なのではなく,汎用的な Objective-CRuby wrapper として使えることが分かった。

平たく言うと,RubyCocoa は,たんなる Cocoa.framework や Foundation.framework に Ruby の皮をかぶせただけでなく,MacOSXObjective-C 向けに提供されたあらゆる APIRuby インターフェースを簡単に作成することができるライブラリなのだ。RubyCocoa は,その名前から連想されるのよりも,遥かに大きな可能性を持ったライブラリなのである*1

実際にこれがどれだけパワフルなことなのか,AquaTerm を例にとって実証してみた。

AquaTerm というのは,MacOSXレンダリングエンジンを使って,ベクターグラフィックスを簡単に作成することが出来る描画ソフト。御本家では plotting frontend とか graphics terminal と言っている。AquaTerm を起動すると,描画サーバとして待機した状態になり,その状態で AquaTerm が提供する API を(別プロセスで)実行すると,AquaTerm のウィンドウに絵が描かれるという仕組み。AquaTerm はそれ自身では何もせずに,API を通して描画コマンドが送られてくるのをひたすら待っているのである。
例えば,最近の gnuplot はこのターミナルに対応しており,ダイレクトに Aqua のエンジンを使ってグラフが描画できるらしい。このように外部プロセスから AquaTerm に描画命令を送信する接合部分のプログラムを adapter という。御本家のページによると,既に PerlPython 向けの adapter は用意されているが,Ruby から AquaTerm を操るための adapter は存在しない。今回はこれを作ってみる。
AquaTerm の Objective-C API は AquaTerm.framework で提供されているので,要は,AquaTerm.framework の Ruby wrapper(Ruby 拡張ライブラリ)を自作することになる。以下の説明は,RubyCocoa と AquaTerm が正しくインストールされているものとして書いている。

AquaTerm.framework が提供するクラスの 1 つである AQTAdapter を Ruby で利用可能にするための拡張ライブラリのコードは,以下の 3 行:aqtadapter.m

#import 
#import 
void Init_aqtadapter() {}

で,このファイルを以下のコマンドでビルドする。

% gcc -o aqtadapter.bundle -bundle -framework Foundation -framework AquaTerm aqtadapter.m

以上で完成。とてもあっけない。次に実際にこれを使ってみる。本家のサイトにある Objective-C のサンプルコード:

#import 
#import 

int main(void)
{    
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    AQTAdapter *adapter = [[AQTAdapter alloc] init];
    [adapter openPlotWithIndex:1];
    [adapter setPlotSize:NSMakeSize(600,400)];
    [adapter addLabel:@"HelloAquaTerm!" atPoint:NSMakePoint(300, 200) angle:0.0 align:1];
    [adapter renderPlot];
    [adapter release];
    [pool release];
    return 0;
}

を Ruby で再現すると以下のようになる。Ruby の GC がメモリ管理を請け負ってくれるので,オリジナルよりもさらにシンプルになる。注意点としては,Objective-C で使用される文字列は全て NSString になっているという辺りかな。

#!/usr/bin/env ruby
require 'osx/cocoa'
require 'aqtadapter'

OSX::ns_import :AQTAdapter
adapter = OSX::AQTAdapter.alloc.init
adapter.openPlotWithIndex(1)
adapter.setPlotSize(OSX::NSSize.new(600, 400))
adapter.addLabel(OSX::NSString.stringWithCString("HelloRubyAquaTerm!"), :atPoint, OSX::NSPoint.new(300, 200), :angle, 0.0, :align, 1)
adapter.renderPlot

結果はこんな感じ。

http://static.flickr.com/95/248862516_11040f4a96_o.png

要は,RubyCocoa を使うと,OSX::ns_import で「Objective-C のクラスを何でも Ruby にインポートできる」というのが味噌なんだけど,同じ手法で,世の中の Objective-C のために書かれたライブラリを,ことごとく Ruby で利用できるようになるはず。これは結構,素晴らしいことなんじゃないかと思います。

*1:追記:てゆうか,私が Cocoa とは何なのかをよく理解できていなかったようだ。MacOSX の Objective-C 環境 = Cocoa 環境ってことなのかな?とにかく,NSObject の派生クラスのラッパーを自動的に生成できるという点に感動したのです…。