2007年11月09日

メンバ関数の呼び出し規約とマルチスレッド

使い方によっては強力な効果を発揮するマルチスレッドプログラミング。Windowsプログラミングやネットワークプログラミングでは重宝する。しかし一般的にVisual C++とPlatform SDKを用いてWindowsプログラミングをする場合、コールバック関数などの設計思想がC言語主体であるので、マルチスレッドプログラミングにてクラスを用いたアプリケーションを開発する場合に不都合な点が生じる。主にスレッドを作成する関数として、CreateThread、_beginthread,_beginthreadexなどが挙げられる。しかしMSDNによると
C のランタイムライブラリに記録されている関数を使うスレッドは、CreateThread 関数と ExitThread 関数ではなく、C のランタイム関数である beginthread 関数と endthread 関数を使うべきです。この方法に従わないと、ExitThread 関数を呼び出したときにわずかなメモリリークが発生します。
と、あるので通常は_beginthreadか_beginthreadexにてスレッド作成を行うのが好ましい。
本題だがスレッドを生成するためには引数として関数のアドレスを渡す必要がある。しかしMSDNの「_beginthread、_beginthreadex (CRT)」には次のように書かれている。
start_address
新規スレッドの実行を起動するルーチンの開始アドレス。_beginthread の呼び出し規約は __cdecl または __clrcall で、_beginthreadex の呼び出し規約は __stdcall または __clrcall です。
(ちなみに__cdeclや__stdcallの意味が分からない人は検索エンジンで「呼出規約」などのキーワードで調べれば分かるだろう。)
普通のクラスのメンバ関数の呼び出し規約は__thiscallであるので、引数としては適切ではない。なので引数として渡す関数を静的メンバ関数として宣言する。そうすることにより呼び出し規約は__thiscallから__cdeclへと変更される。しかし_beginthreadex関数の引数は__stdcallか__clrcallでないといけないので、関数の宣言としてWINAPIをさらに追加することにより、呼出規約が__stdcallへと変更される。しかし問題は他にもある。それは静的メンバ関数にしてしまうと、メンバの参照が静的なものに限られてしまい、効率が悪い。そこで呼出規約を__stdcallへと変更した関数をラッパ関数の役割として使い、reinterepret_castを用いて無理やり静的でないメンバ関数の呼び出しを行えばよい。以下にプログラムの例を示す。

class Hoge{
public:
  static unsigned int WINAPI Thread(void*);
  unsigned int _Thread(void);
};

unsigned int WINAPI Hoge::Thread(void *obj)
{
  return reinterpret_cast<Hoge*>(obj)->_Thread();
}

unsigned int Hoge::_Thread(void)
{
  //通常のスレッド用プログラミング
}

/*
そして、仮にクラスのインスタンスとしてHoge obj;としている場合、
_beginthreadexの第4引数として&objを与えればよい。仮に呼び出し元が
クラスのメンバ関数であればthisを与えればよい。
*/

非常にややこしいが、これでクラスのメンバ関数を引数としたスレッドの生成が可能となる。


×

この広告は180日以上新しい記事の投稿がないブログに表示されております。