2026/01/27(火) [n年前の日記]
#3 [lazarus] Lazarusでスクリーンセーバを作ろうとしてハマった
Windows11 x64 25H2 + Lazarus 4.4 でWindows用のスクリーンセーバを作ろうとしたけど、かなりハマった…。
Delphi と似たノリで作れるかなと試していたけれど、全画面表示モード、設定画面モードについては似た感じで作れたものの、プレビュー画面モードで躓いた。
Windowsから与えられたウインドウハンドルを、フォームの親ウインドウを指定するプロパティ、ParentWindow に代入すれば済むだろう、Delphi ならそれだけで上手く行ったし…。
しかし試してみたら、プレビュー画面モードで起動したプロセスがいつまで経っても終了してくれない。「スクリーンセーバーの変更」ウインドウでリストを切り替えるたびに、プロセスが次々に発生して、そのまま残り続ける…。大量のプロセスがずっと残ったままになる…。
AI君に対策を尋ねてみたけれど、どれもなかなか上手く行かなくて…。
A. 親ウインドウの存在をチェックする方法は、「スクリーンセーバの変更」ウインドウを閉じた時しか効かなかった。「スクリーンセーバーの変更」ウインドウが画面に表示されてる間は、大量のプロセスが残り続けてしまう。どうやら件のウインドウが表示されてる間、親ウインドウになるべきウインドウは、ずっと同じ状態で存在しているのだろう…。
B. WndProc()のオーバーライドも、「スクリーンセーバーの変更」ウインドウを閉じた時しか効かず…。
C. 持っている親ウインドウハンドルと、与えられたウインドウハンドルが違っているかチェックする方法は、フォームが表示された直後に終了してしまう。何故。
D. 自身が非表示になったら終了する方法を試したら、ようやくプロセスがその都度終了してくれた。この方法で大丈夫なのか分からんけど…。でも、プロセスが大量に残り続けるよりはいいだろう…。
Windowsの「スクリーンセーバーの変更」ウインドウは、一体何を子ウインドウに送ることで子ウインドウを消滅させているのだろうか…。
Delphi と似たノリで作れるかなと試していたけれど、全画面表示モード、設定画面モードについては似た感じで作れたものの、プレビュー画面モードで躓いた。
Windowsから与えられたウインドウハンドルを、フォームの親ウインドウを指定するプロパティ、ParentWindow に代入すれば済むだろう、Delphi ならそれだけで上手く行ったし…。
しかし試してみたら、プレビュー画面モードで起動したプロセスがいつまで経っても終了してくれない。「スクリーンセーバーの変更」ウインドウでリストを切り替えるたびに、プロセスが次々に発生して、そのまま残り続ける…。大量のプロセスがずっと残ったままになる…。
AI君に対策を尋ねてみたけれど、どれもなかなか上手く行かなくて…。
- A. TTimer を設置して一定時間毎に親ウインドウが存在するかどうかチェックして、存在しなかったら終了。
- B. WndProc(var Message: TMessage) を override して、Windowsから送られてくるメッセージをチェックしてみる。WM_CLOSE, WM_DESTROY, WM_NCDESTROY が届いたら終了。
- C. 自身のフォームが持っている親ウインドウのハンドルと、与えられたウインドウハンドルが違っていたら終了。
- D. 自分が非表示にされていたら終了。
A. 親ウインドウの存在をチェックする方法は、「スクリーンセーバの変更」ウインドウを閉じた時しか効かなかった。「スクリーンセーバーの変更」ウインドウが画面に表示されてる間は、大量のプロセスが残り続けてしまう。どうやら件のウインドウが表示されてる間、親ウインドウになるべきウインドウは、ずっと同じ状態で存在しているのだろう…。
B. WndProc()のオーバーライドも、「スクリーンセーバーの変更」ウインドウを閉じた時しか効かず…。
C. 持っている親ウインドウハンドルと、与えられたウインドウハンドルが違っているかチェックする方法は、フォームが表示された直後に終了してしまう。何故。
D. 自身が非表示になったら終了する方法を試したら、ようやくプロセスがその都度終了してくれた。この方法で大丈夫なのか分からんけど…。でも、プロセスが大量に残り続けるよりはいいだろう…。
Windowsの「スクリーンセーバーの変更」ウインドウは、一体何を子ウインドウに送ることで子ウインドウを消滅させているのだろうか…。
◎ ソース :
一応、プレビュー画面モード部分のソースを貼っておく。
_previewformunit.pas
_sslazarus1.lpr
_previewformunit.pas
unit PreviewFormUnit;
{$mode ObjFPC}{$H+}
interface
uses
Classes, SysUtils, Forms, Controls, Graphics, Dialogs, LCLType,
StdCtrls, LCLIntf, ExtCtrls,
Windows;
type
{ TPreviewForm }
TPreviewForm = class(TForm)
Label1: TLabel;
Timer1: TTimer;
procedure FormCreate(Sender: TObject);
procedure FormKeyDown(Sender: TObject; var Key: word; Shift: TShiftState);
procedure Timer1Timer(Sender: TObject);
private
FParentHWND: HWND;
protected
procedure WndProc(var Message: TMessage); override;
public
procedure EmbedIntoParent(pHWND: HWND);
end;
var
PreviewForm: TPreviewForm;
implementation
{$R *.lfm}
{ TPreviewForm }
procedure TPreviewForm.FormCreate(Sender: TObject);
begin
//FParentHWND := 0;
BorderStyle := bsNone;
Left := 0;
Top := 0;
Width := 152;
Height := 112;
Label1.Left := (ClientWidth - Label1.Width) div 2;
Label1.Top := (ClientHeight - Label1.Height) div 2;
Label1.Font.Color := clBlue;
end;
// 親ウインドウを指定
procedure TPreviewForm.EmbedIntoParent(pHWND: HWND);
var
r: TRect;
begin
FParentHWND := pHWND;
if pHWND <> 0 then
begin
ParentWindow := pHWND;
Windows.SetParent(self.Handle, pHWND);
SetWindowLong(Self.Handle, GWL_STYLE, GetWindowLong(self.Handle, GWL_STYLE) or
WS_CHILD);
Windows.GetClientRect(pHWND, r);
MoveWindow(Self.Handle, 0, 0, r.Right - r.Left, r.Bottom - r.Top, True);
Visible := True;
end;
end;
procedure TPreviewForm.Timer1Timer(Sender: TObject);
begin
// 一定時間毎に自身が消えるべきかチェックする
//Windows.Beep(440, 100);
if (FParentHWND <> 0) and (not Windows.IsWindow(FParentHWND)) then
begin
// 親ウインドウが存在していないので終了
//Windows.Beep(1000, 100);
Application.Terminate;
end
else if not Windows.IsWindowVisible(self.Handle) then
begin
// 自分が非表示にされているなら終了
//Windows.Beep(1000, 1000);
Application.Terminate;
end;
end;
procedure TPreviewForm.WndProc(var Message: TMessage);
begin
//Windowsから閉じろとメッセージが来ているなら終了
case Message.Msg of
WM_CLOSE, WM_DESTROY, WM_NCDESTROY:
begin
//Windows.Beep(2000, 300);
Application.Terminate;
end;
end;
inherited WndProc(Message);
end;
procedure TPreviewForm.FormKeyDown(Sender: TObject; var Key: word; Shift: TShiftState);
begin
if FParentHWND = 0 then
begin
// 開発時用。ESCキーで終了
if Key = VK_ESCAPE then
Application.Terminate;
end;
end;
//initialization
// RegisterClass(TPreviewForm);
end.
_sslazarus1.lpr
program sslazarus1;
{$mode objfpc}{$H+}
uses
{$IFDEF UNIX}
cthreads,
{$ENDIF}
{$IFDEF HASAMIGA}
athreads,
{$ENDIF}
Interfaces, // this includes the LCL widgetset
Forms,
LCLIntf,
LCLType,
Windows,
SysUtils,
FullScrFormUnit,
ConfigFormUnit,
PreviewFormUnit { you can add units after this };
{$R *.res}
var
arg: string;
phwnd: HWND;
hMutex: THandle;
const
MUTEX_NAME: string = 'ScreenSaverLazarus1Mutex8686';
begin
RequireDerivedFormResource := True;
Application.Scaled := True;
{$PUSH}
{$WARN 5044 OFF}
//Application.MainFormOnTaskbar := True;
Application.MainFormOnTaskbar := False;
{$POP}
Application.Initialize;
if ParamCount >= 1 then
arg := LowerCase(Copy(ParamStr(1), 1, 2))
else
arg := '';
if arg = '/s' then
begin
// Fullscreen mode
hMutex := CreateMutex(nil, False, PChar(MUTEX_NAME));
if (hMutex = 0) or (GetLastError = ERROR_ALREADY_EXISTS) then
begin
if hMutex <> 0 then
CloseHandle(hMutex);
Exit;
end;
try
Application.CreateForm(TFullScreenForm, FullScreenForm);
Application.Run;
finally
if hMutex <> 0 then
CloseHandle(hMutex);
end;
end
else if arg = '/c' then
begin
// Config mode
Application.CreateForm(TConfigForm, ConfigForm);
Application.Run;
Exit;
end
else if arg = '/p' then
begin
// Preview mode
if ParamCount >= 2 then
phwnd := HWND(StrToInt64Def(ParamStr(2), 0))
else
phwnd := 0;
Application.CreateForm(TPreviewForm, PreviewForm);
if phwnd <> 0 then
PreviewForm.EmbedIntoParent(phwnd);
Application.Run;
end
else
begin
// Config mode
Application.CreateForm(TConfigForm, ConfigForm);
Application.Run;
end;
end.
◎ 余談。プロセスの確認 :
プロセスが残り続けているかどうかは、System Explorer 7.1.0.5359 を使ってチェックした。
_System Explorer Portable | PortableApps.com
右上のフィルタ入力欄(?)に文字列を打ち込めば、その文字列を含んだ名前のプロセスがリストアップされる。
_System Explorer Portable | PortableApps.com
右上のフィルタ入力欄(?)に文字列を打ち込めば、その文字列を含んだ名前のプロセスがリストアップされる。
[ ツッコむ ]
以上です。