Ruby/DLとコールバック
Windowsアプリを作ろうと思うと、ウィンドウプロシージャへのポインタをウィンドウクラスに登録する必要がある。
ウィンドウプロシージャは関数であり、ウィンドウクラスに登録するのは関数のアドレスだ。
そうすることにより、ウィンドウへのメッセージがあった場合にWindowsがウィンドウプロシージャ
を呼び出してくれる。
コールバックという仕組みは、登録時に関数のアドレスを渡して、実行時にそこにジャンプするように実装される。
Rubyで純粋なWindowsアプリを作る場合、ウィンドウプロシージャのコールバックが問題になる。
なぜなら、RubyのメソッドはRubyインタプリタが管理しているものであり、CPUの実行コードとして存在いるわけではないからだ。
コールバックされるメソッドはアドレスだけでは表現できない。
しかしコールバックの仕組みで使うのはアドレスだけだ。
ここまで見るとRubyでウィンドウプロシージャを書くことはできなさそうに思える。
今日、Ruby1.8のDL(1.9.1のDLは別物)のマニュアルを眺めていたら、なんだかコールバック関数を定義して、メソッドを関連付けることができるよーなことが書いてあった。
Cでは実行時に関数を追加することなど普通はできないことで、ライブラリ側で巧妙な細工がされているのだろうと想像できる。
ものはためしということで、ちょっと実験してみた。
CでDLLを作る。
#include "stdafx.h" BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { return TRUE; } __declspec(dllexport) int cal(int (*temp)(int)) { return (temp)(100); }
Rubyでこんなコードを書く。
require 'dl/import' module LIBC extend DL::Importable dlload "testdll.dll" extern "int cal(int*)" # DLは関数ポインタの記法を認識してくれないのでintポインタにした def my_cal(data) data + 51 end CAL = callback "int my_cal(int)" end p LIBC.cal(LIBC::CAL)
結果はこうだ。
151
どうやらちゃんとコールバックできるみたいだ。
頑張ればRubyだけでWindowsアプリが書けてしまうかもしれない。
それはまた気が向いたときに。