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アプリが書けてしまうかもしれない。
それはまた気が向いたときに。