我有2个应用程序,program.exe和updater.exe,都是用Delphi5编写的。 程序在没有admin-rights(没有manifest)的情况下运行,updater有一个带有“requireAdministrator”的清单,因为他必须能够在Program-Folder中写入以更新program.exe。
问题是启动更新程序并让他等到程序关闭。 我在网上找到了不同的方式,但都没有用(大多数情况下,第一个应用程序启动第二个应用程序并等待第二个应用程序的结束,在我的情况下,第二个应用程序应该等待第一个应用程序的结束)。
更新者应该等待,这很容易 updater.exe
{$R manifest.res}
label.caption:='Wait for program.exe closing';
repeat
sleep(1000);
until File is not open
ProgramHandle := Read Handle from File
WaitForSingleObject(ProgramHandle,INFINITE);
label.caption:='program.exe CLOSED';
Do updates
方式1
使用CreateProcess启动更新程序:
的Program.exe
FillChar(siInfo, SizeOf(siInfo), 0);
siInfo.cb := SizeOf(siInfo);
saProcessAttributes.nLength := SizeOf(saProcessAttributes);
saProcessAttributes.lpSecurityDescriptor := nil;
saProcessAttributes.bInheritHandle := TRUE;
saThreadAttributes.nLength := SizeOf(saThreadAttributes);
saThreadAttributes.lpSecurityDescriptor := nil;
saThreadAttributes.bInheritHandle := True;
if CreateProcess(nil,
PChar('updater.exe'),
@saProcessAttributes,
@saThreadAttributes,
TRUE, NORMAL_PRIORITY_CLASS, nil,
PChar(ExtractFilePath(Application.ExeName)),
siInfo, piInfo) then
begin
DuplicateHandle(GetCurrentProcess, GetCurrentProcess,
piInfo.hProcess, @MyHandle,
PROCESS_QUERY_INFORMATION, TRUE,
DUPLICATE_SAME_ACCESS) then
Write MyHandle in a File
end;
Close program
不执行任何操作,仅在updater没有带requireAdministrator的清单时才有效。 如果我使用explizit admin-rights运行程序,它也可以。
方式2
使用ShellExecuteEx启动更新程序:
的Program.exe
FillChar(Info, SizeOf(Info), Chr(0));
Info.cbSize := SizeOf(Info);
Info.fMask := SEE_MASK_NOCLOSEPROCESS;
Info.lpVerb := PChar('runas');
Info.lpFile := PChar('update.exe');
Info.lpDirectory := nil;
Info.nShow := SW_RESTORE;
ShellExecuteEx(@Info);
MyHandle:=OpenProcess(PROCESS_ALL_ACCESS, False, GetCurrentProcessId())));
Write MyHandle in a File
Close program
不行,MyHandle每次运行此程序时都有不同的值(不重新启动程序),因此更新程序无法使用它。
所以我不知道如何启动updater.exe并在文件中写入program.exe的句柄。
我对编程的这些部分不太熟悉......有人对我的问题有所了解吗?
答案 0 :(得分:7)
您的代码无效,因为句柄表是每个进程,这意味着第二个进程可以具有指向另一个内核对象的相同句柄。下面是许多可能的解决方案之一:
创建过程2时,将过程1的PID作为参数传递:
procedure CreateUpdater;
var
Info: TShellExecuteInfo;
begin
FillChar(Info, SizeOf(TShellExecuteInfo), 0);
Info.cbSize := SizeOf(TShellExecuteInfo);
Info.fMask := SEE_MASK_NOCLOSEPROCESS;
Info.lpVerb := PChar('runas');
Info.lpFile := PChar('Update.exe');
Info.lpParameters := PChar(IntToStr(GetCurrentProcessId));
Info.lpDirectory := nil;
Info.nShow := SW_RESTORE;
ShellExecuteEx(@Info);
//NOTE: MISSING ERROR CHECKING!
end;
在Updater中,等待process1终止:
procedure WaitForAndClose;
var
PID: String;
AHandle: Cardinal;
Ret: longbool;
ExitNumber: DWORD;
begin
PID:= ParamStr(1);
if PID <> '' then
begin
AHandle:= OpenProcess(PROCESS_QUERY_INFORMATION, False, StrToInt(PID));
//NOTE: MISSING ERROR CHECKING!
try
repeat
Ret:= GetExitCodeProcess(AHandle, ExitNumber);
//NOTE: MISSING ERROR CHECKING!
Sleep(1000); //define a time to poolling
until (ExitNumber <> STILL_ACTIVE);
finally
CloseHandle(AHandle);
end;
//Terminate the process;
Application.Terminate;
end;
end;
您还可以使用WaitForSingleObject
来避免轮询:
WaitForSingleObject(AHandle, INFINITE);
//NOTE: MISSING ERROR CHECKING!
但您需要SYNCHRONIZE访问才能打开该过程:
AHandle:= OpenProcess(SYNCHRONIZE, False, StrToInt(PID));
//NOTE: MISSING ERROR CHECKING!
注意:此处没有错误检查。您应该阅读文档并正确检查错误。
注意2:我想请你注意你正在泄漏手柄的事实。
当您使用SEE_MASK_NOCLOSEPROCESS
时,调用者负责关闭calee的句柄。在你的情况下,我认为你根本不需要那个面具。我会将其删除。
答案 1 :(得分:1)
以下是如何使用事件实现此目的的基本示例:
program.exe
:
// manual-reset event, non-signaled
Event := CreateEvent(nil, True, False, 'MyUniqueName');
ExecuteUpdater; // via ShellExecuteEx with runas
// synchronize - wait for the event to be signaled
WaitForSingleObject(Event, INFINITE);
// WAIT_OBJECT_0 = The state of the specified object is signaled.
CloseHandle(Event);
updater.exe
:
Event := CreateEvent(nil, True, False, 'MyUniqueName');
if Event = 0 then RaiseLastWin32Error;
SetEvent(Event); // sets the event object to the signaled state
CloseHandle(Event);
您还应该向program.exe
添加清单(requestedExecutionLevel
应为level="asInvoker"
),以避免虚拟化。
答案 2 :(得分:0)
我在两个事件的不确定顺序中看到了主要问题:关闭程序并启动更新程序主代码。
解决这个问题的一种可能方法是使用事件 - https://msdn.microsoft.com/ru-ru/library/windows/desktop/ms686670(v=vs.85).aspx
程序创建事件(带有子项继承它的选项),然后启动更新程序(传递事件和程序的句柄&#39) ; s通过命令行作为整数处理它!),然后在事件上的WaitForSingleObject中冻结。
这确保程序在更新程序准备好监视之前不会退出,因此PID不会失效。
更新程序然后调用程序从命令行获取的PID上的OpenProcess,然后调用SignalAndWait同时敲击事件(从命令行获取)并冻结句柄(获得来自OpenProcess) - https://msdn.microsoft.com/ru-ru/library/windows/desktop/ms686293(v=vs.85).aspx
现在正在等待事件释放的程序终止。 该过程的终止正在发出信号,因此现在更新程序依次被释放并且可以开始执行主要工作。
C++, How to determine if a Windows Process is running?建议的另一种方法是查询程序 ProcessID的退出代码 - 据说程序仍在运行时会有一个具体的错误代码,你可以睡觉(100)然后再试一次。任何其他结果意味着程序已经完成。
启动更新程序后,程序立即退出,而不等待它开始监控。
这似乎很好的方法,除了我现在不保证PID值不会被重复使用。机会是无穷小的,但仍然不是零。
我个人可能会使用一个标志文件。 CreateFile API有一个非常有趣的标志 - 临时文件模式。这意味着,Windows会在进程结束后自动删除该文件。那么
同样,在程序终止且更新程序再次检查之间,某些其他进程创建具有完全相同名称的标记文件的可能性极小,但这在实践中几乎是不可能的