fslasht (3370) の日記

2004 年 05 月 02 日
午前 09:58

[lua] C++メンバ関数をコールバック関数とする場合の妄想的改善案

STATUS: 故障中

 C++のクラスのメンバ関数をコールバック関数とする場合、そのメンバ関数をstatic関数にして、コールバック時にthisポインタを渡してもらいます。

【参考URL】
コールバック関数をクラスのメンバとするには?
MFC でコールバック関数を使う

 さて、lua言語を組み込んで独自機能を追加する場合、関数ポインタを設定してコールバック的に呼び出してもらうわけですが、この場合もthisポインタを返してもらう必要があります。
 でもluaスクリプト内で、thisポインタを扱わせるのは危なっかしいです。値を間違ったり省略したら即落ちますし、スクリプト中でいちいちthisポインタ渡すのがめんどくさい。
 コールバック関数内で、thisポインタの有効性をチェックしたり、ポインタの変わりにIDか何かを使ってもいいのですが、パフォーマンスが心配です。

● 改善案

 thisポインタをセットしてからコールバック関数を呼び出す(マシン語)コードを生成して、そのコードへのポインタをコールバック関数として設定すれば、よいのではないかと考えました。
 以下の、CallbackHelperクラスでは、上記の処理を実装したものです(ただし、C++もどきなテケトーコードです)。
 使用例では、lua_register関数の第3引数として、メンバ関数MyFuncをコールバック関数として登録しています。
 コールバック関数が呼ばれる際は、thisポインタもちゃんと渡されるため、static関数にする必要はありません。便利~。
 登録するコールバック関数の数だけCallbackHelperのインスタンスが必要になりますが、1個10byteそこらなので問題ないでしょう。

【コールバックヘルパクラス】(概念的なソース)

class CallbackHelper {
        BYTE byteCode[10];
        void* Create( void *pThis , void* pfCallback ) {
                // コールバック関数にthisポインタを渡すコードを生成する

                // thisポインタを設定(ecxレジスタと仮定 コンパイラ依存)
                byteCode[0] = 0xb9; // mov ecx,pThis
                byteCode[1 .. 4] = pThis;

                // 本来のコールバック関数へジャンプ
                byteCode[5] = 0x0f; // jp pfCallback
                byteCode[6 .. 9] = pfCallback;

                return byteCode;
        }
};

【使用例】

class MyClass {
        CallbackHelper ch;
        lua_Statech *m_pLua;

        MyClass() {
                m_pLua = lua_open();
                lua_baselibopen(m_pLua);
                lua_register( m_pLua, "MyFunc",ch.Create(this,MyFunc) ); // ココでコールバック関数を登録
                // 以下略
        }

        int MyFunc( lua_State *L ); // コールバック関数
};

● 改善案の問題点

・データ領域でコードを実行するので、WinXPの新しいSPでは動かないかも
・ヘルパクラスで生成するコードがコンパイラ依存
・そもそもこれ動くのか?
・luaの場合は、適当なグローバル変数にでもthisポインタを突っ込んでおいてコールバック関数(static)内で勝手取得して使えば良いということに気づいてしまった…
・lua_State自体にthisポインタを指定できたら便利だな(実は出来たりして)

この議論は賞味期限が切れたので、アーカイブ化されています。 新たにコメントを付けることはできません。

アレゲはアレゲ以上のなにものでもなさげ -- アレゲ研究家

処理中...