Main Loop Hooks/ja

From Lazarus wiki
Jump to navigationJump to search

English (en) français (fr) 日本語 (ja) slovenčina (sk) 中文(中国大陆) (zh_CN)

問題について

プログラムの中で何らかのイヴェント(あるいはソケットやパイプなど)を待たなければならなくなったとします。しかし、これをメイン (GUI) スレッドの中で行い、しかも GUI ブロックせずまたマルチスレッドにもしたくないとしたらどうしたらいいでしょうか。解決策は、新たな「ハンドル」を追加し、それをメインイヴェントループで監視することです。

解決法の詳細

ユニット LCLIntf にこれを果たすための三つの関数が追加されています。それらは:

AddEventHandler(AHandle: THandle; AFlags: dword; 
     AEventHandler: TWaitHandleEvent; AData: PtrInt): PEventHandler;
RemoveEventHandler(var AHandler: PEventHandler);
SetEventHandlerFlags(AHandler: PEventHandler; NewFlags: dword);

TWaitHandleEvent 型は、ユニット InterfaceBase の中でこう宣言されています:

TWaitHandleEvent = procedure(AData: PtrInt; AFlags: dword) of object;

AddEventHandler は、メインイヴェントループで監視できるようにハンドルを追加します。ハンドルが「シグナル」されると、AEventHandler として渡された手続きが呼び出されます。このとき、イヴェントハンドラが登録される時に渡された AData と、OS 依存の AFlags が渡されます。AddEventHandler に渡したフラグは SetEventHandlerFlags によって変更することができます。AddEventHandlerが返すハンドラ(ポインタ)は保存しておき、RemoveEventHandler によってイヴェントの監視を終了する際に同関数に渡さなければなりません。フラグが用意されている主な理由は、様々な種類のイヴェントを切り替えながら監視するための手段としてです(読む、書く、エラー)。

RemoveEventHandler は、指定のハンドルの監視を停止します。引数として AHandler をとり、これには AddEventHandler が返したポインタを渡します。RemoveEventHandler は制御を返す前に AHandler を nil に設定します。

SetEventHandlerFlags は、アクティヴなイヴェントハンドラへのフラグを変更します。現在の所、Windows では無効です(下記)が、他のプラットフォームでは利用されており、監視すべきイヴェントの種類を ON/OFF するための手段を提供しています。

Windows

現在の実装では AddEventHandler の引数 AFlags は無視され、TWaitHandleEvent の AFlags の値は 0 になります。

Win32 は次の種類のハンドルをサポートします。基本的に、MsgWaitForMultipleObjects がサポートするものです:

  • 変更通知 (ファイルとディレクトリの)
  • コンソール入力
  • イヴェント: SetEvent WinAPI 関数でシグナルされる
  • ミューテックス: 所有されなくなるとシグナルされる
  • プロセス: 終了するとシグナルされる
  • セマフォ: カウントが 0 を超えるとシグナルされる
  • スレッド: 終了するとシグナルされる
  • タイマ: 指定時間経過後シグナルされる

Gtk/Unix

AddEventHandler に渡される AFlags は、ハンドルがシグナルされるべき条件を指定します。指定可能な値は glib 中の GIOCondition 型を参照のこと。

コールバック関数(訳注: イヴェントハンドラ)側では、AFlags は上記の GIOCondition 型の中で満たされた条件を持ちます。

GTK/UNIX は次の種類のハンドルをサポートします:

  • ファイル
  • ソケット
  • パイプ

Win32 はソケットとパイプをサポートしないことに注意してください(少なくとも、MSDN の中で「推奨されない」利用法です)。提案: WSAEventSelect を使ってソケット関係のイヴェントを生成し、イヴェントハンドラに渡します。

Qt4

Qt4 インターフェースは、内部的に Gtk や Win32 と異なっていますが、可能な限り GTK と同様に見えるように LCL 内でエミュレートされています。関数に渡すフラグもまた共通化されています (GIO_IN、GIO_OUT、GIO_ERR)。ユーザから見た事実上唯一の相違点は、同一のソケットにおいて、複数の種類のイヴェントが「同時に」生起しうることです。この場合、フラグを OR した一回の呼出しが行われるのではなく、複数回の呼出しが起こります。これは Qt4 の限界であり、READ や WRITE に先んじて EXCEPTION イヴェントが呼ばれてしまうのは避け難いと思われます。Qt4 インターフェースは Lazarus v0.9.29 で加えられました。

フラグ

いまのところ、引数 AFlags はウィジェットセット依存です。上記のように、Windows ではこの引数は全く用いられていませんが、Gtk や Qt などほかのプラットフォームでは、監視すべきイヴェントの種類を指定するために用いられています。

パイプとプロセス終了

クロスプラットフォームなソリューションのために、パイプ(Win32 のサポートが弱い)とプロセス(Gtk/Unix のサポートが弱い)について、追加の関数が用意されています:

TChildExitReason = (cerExit, cerSignal);
TPipeReason = (prDataAvailable, prBroken, prCanWrite);
TPipeReasons = set of TPipeReason;
 
TChildExitEvent = procedure(AData: PtrInt; AReason: TChildExitReason; AInfo: dword) of object;
TPipeEvent = procedure(AData: PtrInt; AReasons: TPipeReasons) of object;

function AddProcessEventHandler(AHandle: THandle; 
  AEventHandler: TChildExitEvent; AData: PtrInt): PProcessEventHandler;
procedure RemoveProcessEventHandler(var AHandler: PProcessEventHandler);
 
function AddPipeEventHandler(AHandle: THandle; 
  AEventHandler: TPipeEvent; AData: PtrInt): PPipeEventHandler;
procedure RemovePipeEventHandler(var AHandler: PPipeEventHandler);

プロセスが終了すると、指定のイヴェントハンドラが呼ばれます。AReason が cerExit ならば、AInfo は終了コードを持っています。AReason が cerSignal なら、AInfo は終了シグナルです(UNIX に限る)。Gtk/Unix では、AHandle として監視対象の PID を用いてください。内部的に、SIGCHLD シグナルを捕捉するためのシグナルハンドラが導入されます。Win32 では、AddEventHandler がプロセス終了を監視するのに用いられます。

パイプからの入力を監視する場合は、処理を行うためのイヴェントハンドラを AEventHandler に、ファイル識別子 (UNIX) あるいはパイプの read-end ハンドルを AHandler にして、AddPipeEventHandler に渡してください。Gtk/Unix では、AddEventHandler が呼び出され、ファイル識別子でのトラヒックを監視します。Win32 では、100 ミリ秒毎にメッセージループがタイムアウトして、監視対象のパイプを全て検査し、読み出せるデータがないか調べます。データがあれば、イヴェントハンドラが呼ばれます。

関連項目