はじめに
たまには小さな EXE を作りたい時がありますよね?「フォームは要らない、でもコンソールアプリケーションじゃないのがいい」みたいなの。
コード
こんな感じでしょうか?
program FormLess;
{.$DEFINE USE_COM}
uses
Winapi.Windows, Winapi.Messages{$IFDEF USE_COM}, Winapi.ActiveX{$ENDIF};
{$R *.res}
var
Msg: TMsg;
Handles: array [0..0] of THandle = (0);
// 起動時に一度だけ実行する処理
procedure Setup;
begin
// put your setup code here, to run once:
end; { Setup }
// 毎ループで実行する処理
function Loop: Boolean;
begin
// put your main code here, to run repeatedly:
Result := True; // False を返せば終了
end; { Loop }
begin
{$IFDEF USE_COM}
CoInitializeEx(nil, COINIT_APARTMENTTHREADED);
{$ENDIF}
try
Setup;
repeat
case MsgWaitForMultipleObjects(0, Handles, False, INFINITE, QS_ALLINPUT) of
WAIT_OBJECT_0:
while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do
begin
if Msg.Message = WM_QUIT then
Exit;
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
end;
until not Loop;
finally
{$IFDEF USE_COM}
CoUninitialize;
{$ENDIF}
end;
end.
構造的には Arduino っぽくしてあります。
COM を使う場合には USE_COM を有効にしてください。具体的には {.$DEFINE USE_COM} のドットを抜いて {$DEFINE USE_COM} にしてださい。
See also:
- MsgWaitForMultipleObjects (learn.microsoft.com)
- PeekMessage (learn.microsoft.com)
- TranslateMessage (learn.microsoft.com)
- DispatchMessage (learn.microsoft.com)
これと何が違うの?
多分、素朴な疑問として「コレ (単純ループ版)と何が違うの?」というのがあるかと思います。
program FormLess2;
{$R *.res}
// 起動時に一度だけ実行する処理
procedure Setup;
begin
// put your setup code here, to run once:
end; { Setup }
// 毎ループで実行する処理
function Loop: Boolean;
begin
// put your main code here, to run repeatedly:
Result := True; // False を返せば終了
end; { Loop }
begin
Setup;
repeat
until not Loop;
end.
違いは 2つのプログラムを同時に実行させると解ります。
前者はメッセージループがあるので、処理内容が空なら CPU を殆ど使いません。後者は単なる無限ループなので、メッセージを利用する機構がすべて動作せず、処理内容が空でも CPU を使ってしまいます。
フォームのあるアプリケーションでは Application.Run がメッセージループの本体で、メインフォームが閉じられるとメッセージループを抜ける (=アプリケーションが終了する) ようになっています。
...
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
See also:
では、これとは何が違うの?
毎ループで実行する処理の部分をスレッドにしてみました。
program FormLess3;
{.$DEFINE USE_COM}
uses
Winapi.Windows, Winapi.Messages{$IFDEF USE_COM}, Winapi.ActiveX{$ENDIF},
System.SysUtils, System.Classes;
{$R *.res}
type
TWorkerThread = class(TThread)
protected
procedure Execute; override;
end;
var
Msg: TMsg;
Handles: array [0..0] of THandle;
// 起動時に一度だけ実行する処理
procedure Setup;
begin
// put your setup code here, to run once:
end; { Setup }
// 毎ループで実行する処理
procedure TWorkerThread.Execute;
begin
try
while not Terminated do
begin
// put your main code here, to run repeatedly:
// Terminate() を呼べば終了
end;
except
Terminate;
end;
end; { Execute }
begin
{$IFDEF USE_COM}
CoInitializeEx(nil, COINIT_APARTMENTTHREADED);
{$ENDIF}
try
Setup;
var wt := TWorkerThread.Create(False);
Handles[0] := wt.Handle;
while True do
case MsgWaitForMultipleObjects(1, Handles, False, INFINITE, QS_ALLINPUT) of
WAIT_OBJECT_0:
begin
wt.Free;
Exit;
end;
WAIT_OBJECT_0 + 1:
while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do
begin
if Msg.Message = WM_QUIT then
Exit;
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
end;
finally
{$IFDEF USE_COM}
CoUninitialize;
{$ENDIF}
end;
end.
毎ループで実行する処理の部分をスレッド化しても無意味では?と思えるかもしれませんが、このやり方だとメッセージへの応答性が向上します。
- Windows の終了を阻害しない
- アプリケーションが
応答なしにならない
言うまでもなく、メインスレッドをメッセージループ専用に使っているからです。
See also:
おわりに
常駐プログラムなら、本当は普通に作って TTrayIcon 貼ってタスクトレイから設定できるようにするのがセオリーかとは思いますが、本当に何も要らなくて裏で延々と動作してくれてればいい (何かの機器の状態をポーリングするとか)、みたいなのがたまに必要になりますよね。
...ならん?
See also:

