By | 2017年8月12日

おりばーです。
今年に入ってC/C++で開発することが多く、C#ではdelegateで一発だったコールバック関数をC++で使うのも一苦労したのでメモ書きです。

コールバック関数とは

普段何気なく使っているコールバック関数ですが、定義というか他人に説明するときにどうやって説明するか悩んでしまったので調べました。

コールバック(英: Callback)とは、プログラミングにおいて、他のコードの引数として渡されるサブルーチンである。これにより、低レベルの抽象化層が高レベルの層で定義されたサブルーチン(または関数)を呼び出せるようになる。
コールバック – Wikipedia

コード(疑似C言語)で表すと

// お辞儀をする = サブルーチン
void bow(void)
{
    printf("Hello! How are you doing?");
}

// 挨拶 
void hello(void (bowFunc)(void)) 
{
    bowFunc();
}

int main()
{
   hello(bow);
   return 0;
}

のようmain() -> hello() -> bow() の順番に呼ばれる機構をコールバックと呼ぶらしいです。



C++でコールバック関数

ここから本題です。まどろっこしい説明はあとでいきなりコードです。

ソースコード

Invoker.h

class Invoker
{
private:
    int dummyData_;

pubic:
    Invoker();
    ~Invoker();

    // コールバック関数
    static int CallbackFunc(void* userData);
    // 呼び出し元
    void Execute();
}

Invoker.cpp

// コンストラクタ
Invoker::Invoker()
  : dummyData_(100)
{
}

// デストラクタ
Invoker::~Invoker()
{
}

// コールバック実態
int Invoker::CallbackFunc(void* userData))
{
    Invoker invokerObject = reinterpret_cast<Invoker*>(userData);
    // 100が返る
    return invokerObject->dummyData_;
}

// 実行関数
void Invoker::Execute()
{
    int result = 0;
    result = reinterpret_cast<Invoker*>(this)->CallbackFunc(this);
}

これでInvoker::Executeを呼び出すことでInvoker::CallbackFuncがコールバックされます。

仕組み

クラス内のコールバックでミソになるのはstaticで、staticなしだと

仮想関数のアドレスを取ろうとしました

といったエラーが(Visual Studioだと)吐き出されると思います。

これは、クラスがnewされるまでメモリ上に展開されず、呼び出し元の関数がどのポインタに参照すればよいかわからないため出力されるエラーです。すなわちstaticで静的関数にしてポインタの位置を固定してあげてreinterpret_castでアドレスの解釈を強制的に変更してあげることで動的取得したクラス関数にアクセスできるようになるわけです。

たまにやり方を忘れるのでメモ書きでした。