関数ポインタの制限

まず、デリゲートを使うとどんなことが可能であるかを説明するために、次にサンプル コードを示します。

#using <mscorlib.dll>
using namespace System;

__delegate void TestCallback( void );

__gc class ClassX
{
public:
    void FunctX( void )
    {
        Console::WriteLine( S"ClassX::FunctX" );
    }
};

__gc class ClassY
{
public:
    void FunctY( void )
    {
        Console::WriteLine( S"ClassY::FunctY" );
    }
};

int main( void )
{
    ClassX *pX = new ClassX();
    ClassY *pY = new ClassY();
    
    TestCallback *cb = new TestCallback( pX, &ClassX::FunctX );
    cb += new TestCallback( pY, &ClassY::FunctY );

    cb->Invoke();

    return 0;
}


/* 実行結果
ClassX::FunctX
ClassY::FunctY

*/

__delegate というキーワードを使用することで、MulticastDelegate というクラスから派生したマルチキャスト デリゲート クラスが定義されます。ここでは TestCallback という、戻り値と引数のない関数を保持するクラスです。cb->Invoke で pX->FunctX と pY->FunctY が順番に呼び出されることから、一つのデリゲートには、不特定のクラスのインスタンスとそのメソッドのアドレスを保持するメンバがあると予想されます。

さて、これと似たようなことを純粋な C++ で行なおうとすれば、およそ次のようなコードになるでしょう。

typedef void ( *CPPCallback )( void );

int main( void )
{
    ClassX *pX = new ClassX();
    ClassY *pY = new ClassY();
    
    CPPCallback cppcb[ 2 ] = { &ClassX::FunctX, &ClassY::FunctY };

    for ( int i = 0; i < 2; i++ ) 
        ( cppcb[ i ] )();

    delete pY;
    delete pX;

    return 0;
}

ところがこれをコンパイルしようとしても、「変換できない」という理由で失敗します。具体的には、&ClassX::FunctX は void ( __thiscall ClassX::* )( void ) 型であり、void ( __cdecl * )( void ) には変換できないというわけです。同様の理由で ClassY::FunctY についても変換できません。仮にできたとしても、この関数は非静的関数ですから、呼び出しにはインスタンスが必要になります。つまり呼び出すときに、スタックかレジスタを使って this ポインタを関数に渡す必要がありますが、このコードではそうなっておらず、ただのグローバル関数か静的関数として呼び出されることになってしまいます。

結局のところ、デリゲートの実装は C++ の言語仕様を逸脱していて、C++ だけでは実現できない行為なのです。そこで、私はアセンブリ言語(及びマシン語)の力を借りて、仕様の壁を打破することにしました。これはセキュリティやコンパチビリティを全く無視した技法ですので、これから紹介していくコードを参考にしてプログラムを作るようなことはしないでください。研究室での危ない実験と思って、参考に留める程度にしてください。実験には既に成功しておりますので、二、三回に分けて報告したいと思います。