我正在使用 CreateProcess 啟動一個可執行檔案,如果它沒有在 3 秒內終止(測驗),我將向它發送一個 WM_CLOSE
代碼,該代碼基于源中的 SO URL。
問題:
- SendWMCloseEnumFunc 做它的事情并發送 WM_CLOSE
- 程式不回應WM_CLOSE(2秒內)
- 我隨后用 TerminateProcess 殺死它(引發了指示器“(2)”的例外)
就好像我將 WM_CLOSE 發送到錯誤的行程,但我在這里沒有看到我的錯誤?
function SendWMCloseEnumFunc(hHwnd:HWND; dwData:LPARAM): Boolean;
var vID:NativeInt;
begin
GetWindowThreadProcessID(hHwnd, @vID);
if vID = dwData then
begin
PostMessage(hHwnd, WM_CLOSE, 0, 0); // Tell window to close gracefully
Result := False; // Can stop enumerating
end
else
Result := TRUE; // Keep enumerating
end;
procedure ExecAndWait(const ACmdLine: String);
// https://stackoverflow.com/questions/30003135/optimal-try-finally-placement-for-createprocess-waitforsingleobject-close
var
pi: TProcessInformation;
si: TStartupInfo;
lResult: DWord;
begin
FillChar(si, SizeOf(si), 0);
si.cb := SizeOf(si);
si.dwFlags := STARTF_USESHOWWINDOW;
si.wShowWindow := SW_NORMAL; // @@ Of FALSE?
if not CreateProcess(nil, // Application blank, then:
PChar(ACmdLine), // Full commandline
nil, // ProcessAttributes
nil, // ThreadAttributes
False, // InheritHandles
CREATE_NEW_PROCESS_GROUP NORMAL_PRIORITY_CLASS, // CreationFlags
nil, // Environment
nil, // Directory; current if blank
si, // StartupInfo
pi) then // ProcessInformation
RaiseLastOSError;
try
lResult := WaitForSingleObject(pi.hProcess, 3000); // @@Test 3 sec. Wij nemen 10 minuten = 10*60*1000
if lResult = WAIT_TIMEOUT then
begin
// https://stackoverflow.com/questions/9428456/how-to-terminate-a-process-created-by-createprocess
// https://stackoverflow.com/questions/268208/delphi-gracefully-closing-created-process-in-service-using-tprocess-create
// Try it nicely:
EnumWindows(@SendWMCloseEnumFunc, pi.dwProcessId);
if WaitForSingleObject(pi.hProcess, 2000) <> WAIT_OBJECT_0 then
begin
// Force termination:
if TerminateProcess(pi.hProcess,lResult) then
raise Exception.Create('Verwerking afgebroken (2)')
else
raise Exception.Create('Verwerking afgebroken - process niet gestopt (' IntToStr(lResult) ')');
end
else
raise Exception.Create('Verwerking afgebroken (1)');
end
else
begin
GetExitCodeProcess(pi.hProcess,lResult);
if lResult <> 0 then
raise Exception.Create('Het externe proces is gestopt met exit code ' IntToStr(lResult));
end;
finally
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
end;
end;
被呼叫的程式有一個 WindowProc 來監視傳入的 WM_CLOSE 并且似乎沒有觸發:
procedure TFrmExternalProgram.CommonWindowProc(var Message: TMessage);
begin
if Message.Msg = WM_CLOSE then
begin
Memo1.Lines.Add('WM_CLOSE');
Sleep(500);
end;
SaveProc(Message); // Call the original handler for the other form
end;
procedure TFrmExternalProgram.FormCreate(Sender: TObject);
begin
SaveProc := WindowProc;
WindowProc := CommonWindowProc;
end;
procedure TFrmExternalProgram.FormDestroy(Sender: TObject);
begin
WindowProc := SaveProc;
end;
procedure TFrmExternalProgram.FormShow(Sender: TObject);
var i,pc: integer;
begin
Memo1.Lines.Clear;
pc := ParamCount;
if pc = 0 then
Memo1.Lines.Add('- No arguments-')
else
begin
Memo1.Lines.Add('Called with ' IntToStr(pc) ' parameters:');
Memo1.Lines.Add('');
for i := 1 to pc do
Memo1.Lines.Add(ParamStr(i));
end;
end;
但是,如果我從 comamnd 行啟動這個“外部程式”并從任務管理器中終止它,我也看不到“WM_CLOSE”備忘錄行(當我在 FormCloseQuery 中有此除錯訊息時也看不到)。
What am I overlooking?
This is a 32-bit app under Windows 10.
uj5u.com熱心網友回復:
因為TerminateProcess沒有發送任何訊息。它只是簡單地終止了這個程序。
WM_CLOSE 僅當您通過單擊關閉按鈕或從另一個程式手動將其發送到程式的主視窗時才有效。
uj5u.com熱心網友回復:
HWND默認情況下,Delphi VCL 應用程式最初至少有 2秒,即TApplication視窗和MainForm視窗。您僅將WM_CLOSE訊息發送給HWND您找到的第一個。你是假設這HWND是你的TFrmExternalProgram視窗,但它MIGHT是TApplication視窗,而不是。HWND在發送WM_CLOSE給它之前,您沒有驗證的類/標題。僅檢查其行程 ID 是不夠的,除非您發送給您找到的每個 行程HWND。
此外,您的回呼與EnumWindows()預期的簽名不匹配。它需要使用Windows.BOOL(4 個位元組)而不是System.Boolean(1 個位元組)作為其回傳值。并且需要用stdcall呼叫約定來宣告,而不是Delphi默認的register呼叫約定。
試試這個:
function SendWMCloseEnumFunc(hHwnd:HWND; dwData:LPARAM): BOOL; stdcall;
var
ProcessID: DWORD;
WndClassName: array[0..23] of Char;
begin
GetWindowThreadProcessID(hHwnd, @ProcessID);
if ProcessID = dwData then
begin
GetClassName(hHwnd, WndClassName, Length(WndClassName));
if StrComp(WndClassName, 'TFrmExternalProgram') = 0 then
begin
PostMessage(hHwnd, WM_CLOSE, 0, 0); // Tell window to close gracefully
Result := False; // Can stop enumerating
Exit;
end;
end;
Result := True; // Keep enumerating
end;
順便說一句,您應該考慮使用EnumThreadWindows()而不是EnumWindows(). EnumWindows()列舉所有行程的所有頂級視窗,而僅EnumThreadWindows()列舉指定執行緒的頂級視窗。由于您已經知道CreateProcess()創建的主執行緒的 ID ,您可以使用它EnumThreadWindows()來減少需要查看的視窗數量。
function SendWMCloseEnumFunc(hHwnd:HWND; dwData:LPARAM): BOOL; stdcall;
var
WndClassName: array[0..23] of Char;
begin
GetClassName(hHwnd, WndClassName, Length(WndClassName));
if StrComp(WndClassName, 'TFrmExternalProgram') = 0 then
begin
PostMessage(hHwnd, WM_CLOSE, 0, 0); // Tell window to close gracefully
Result := False; // Can stop enumerating
end else
Result := True; // Keep enumerating
end;
procedure ExecAndWait(const ACmdLine: String);
var
pi: TProcessInformation;
...
begin
...
EnumThreadWindows(pi.dwThreadId, @SendWMCloseEnumFunc, 0);
...
end;
如果您不關心觸發 Form 的OnClose(Query)事件,您也可以只將WM_QUIT訊息發布到執行緒的訊息佇列,那么您根本不必列舉其視窗。
procedure ExecAndWait(const ACmdLine: String);
var
pi: TProcessInformation;
...
begin
...
PostThreadMessage(pi.dwThreadId, WM_QUIT, 0, 0);
...
end;
但是,如果你確實 post WM_CLOSE,那么至少考慮覆寫 Form 的虛WndProc()方法而不是子類化它的WindowProc屬性。
protected
procedure WndProc(var Message: TMessage); override;
...
procedure TFrmExternalProgram.WndProc(var Message: TMessage);
begin
if Message.Msg = WM_CLOSE then
Memo1.Lines.Add('WM_CLOSE');
inherited; // Call the original handler
end;
或者,只使用OnClose(Query)已經回應WM_CLOSE訊息的表單事件。
procedure TFrmExternalProgram.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
Memo1.Lines.Add('OnCloseQuery');
end;
procedure TFrmExternalProgram.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Memo1.Lines.Add('OnClose');
end;
轉載請註明出處,本文鏈接:https://www.uj5u.com/gongcheng/315595.html
標籤:delphi createprocess delphi-10.4-sydney
