JVCLのTJvThreadはTThreadと違い、コンポーネントをフォームに配置して使用します。
TJvThreadのExecute()メソッドを呼ぶと、OnExecuteイベントに記述した処理が別スレッドで実行します。
サンプルプログラムは0から1000までの数を100ミリ秒ごとに表示するプログラムです。
(「Delphi Acid Floor」を参考にしました。)
Unit1.h
class TForm1 : public TForm
{
__published: // IDE で管理されるコンポーネント
TJvThread *JvThread1;
TLabel *Label1;
void __fastcall JvThread1Execute(TObject *Sender, Pointer Params);
void __fastcall FormShow(TObject *Sender);
private: // ユーザー宣言
int FCount;
void __fastcall CountUp();
public: // ユーザー宣言
__fastcall TForm1(TComponent* Owner);
};
Unit1.cpp
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
void __fastcall TForm1::FormShow(TObject *Sender)
{
//スレッドを実行する
JvThread1->Execute(NULL);
}
/**
* 別スレッドで実行する処理
*/
void __fastcall TForm1::JvThread1Execute(TObject *Sender, Pointer Params)
{
for (this->FCount = 0; this->FCount < 1000; this->FCount++)
{
JvThread1->Synchronize(this->CountUp);
Sleep(100);
}
}
void __fastcall TForm1::CountUp()
{
Label1->Caption = IntToStr(this->FCount);
}
興味深いのは、サンプルコードではTForm1::JvThread1Execute()は別のスレッドで処理が行われることです。
にもかかわらず、TForm1::JvThread1Execute()内のthisはForm1を指しています。
別スレッドで行われる処理がForm1のprivateなメンバ変数やメンバ関数にアクセスできるため、ソースコードを簡潔に記述できています。
TJvThreadはどのようにして実現しているのか、ソースコードを見てみましょう。
TJvThreadのExecute()が呼ばれると、TThreadのサブクラスであるTJvBaseThreadが作成されます。
function TJvThread.Execute(P: Pointer): TJvBaseThread;
var
BaseThread: TJvBaseThread;
begin
BaseThread := TJvBaseThread.Create(Self, FOnExecute, P);
TJvBaseThreadのコンストラクタで、TJvThreadのFOnExecuteメソッドとその引数が渡されています。
constructor TJvBaseThread.Create(Sender: TObject; Event: TJvNotifyParamsEvent; Params: Pointer);
begin
FExecuteEvent := Event;
FParams := Params;
スレッドを実行すると、FExecuteEvent(TJvThreadのFOnExecuteメソッド)が実行されます。
procedure TJvBaseThread.Execute;
begin
FExecuteEvent(Self, FParams);
TJvNotifyParamsEventは次のように定義されています。
TJvNotifyParamsEvent = procedure(Sender: TObject; Params: Pointer) of object;
つまりTJvNotifyParamsEventはメソッドポインタです。
メソッドポインタは、クラスインスタンスへの隠されたポインタを保持しています。
だから、JvThread1ExecuteのthisがForm1を指していました。
メソッドポインタを上手に活用すると、クラスの設計やソースコードが簡潔になりそうだと思いました。