8

Program.exe と updater.exe の 2 つのアプリがあり、どちらも Delphi5 で記述されています。プログラムは管理者権限なしで (およびマニフェストなしで) 実行されます。アップデーターには「requireAdministrator」のマニフェストがあります。

問題は、アップデーターを起動して、プログラムが閉じられるまで彼を待たせることです。ウェブでさまざまな方法を見つけましたが、どれも機能しません (ほとんどの場合、最初のアプリは 2 番目のアプリを開始し、2 番目のアプリの終了を待ちます。私の場合、2 番目のアプリは 1 番目のアプリの終了を待つ必要があります)。

アップデータは待つ必要があります。それは簡単
です 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

何もせず、requireAdministrator を含むマニフェストがアップデーターにない場合にのみ機能します。Explizit 管理者権限でプログラムを実行すると、それも機能します。

方法 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 のハンドルをファイルに書き込む方法がわかりません。

私はプログラミングのこれらの部分にあまり詳しくありません...誰かが私の問題についてアイデアを持っていますか?

4

3 に答える 3

7

ハンドル テーブルがプロセスごとであるため、コードが機能していません。つまり、2 番目のプロセスが別のカーネル オブジェクトを指す同じハンドルを持つ可能性があります。以下に、考えられる多くの解決策の 1 つを示します。

プロセス 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のハンドルを閉じる責任があります。あなたの場合、そのマスクはまったく必要ないと思います。私はそれを削除します。

于 2015-06-10T15:51:18.330 に答える
0

主な問題は、プログラムの終了とアップデータのメイン コードの開始という 2 つのイベントの不確定な順序で見られます。

それを修正する1つの可能な方法は、イベントを使用することです - https://msdn.microsoft.com/ru-ru/library/windows/desktop/ms686670(v=vs.85).aspx

プログラムはイベントを作成し (子がそれを継承するオプション付き)、アップデーターを起動し(イベントとプログラムのプロセスの両方のハンドルをコマンドライン経由で整数として渡します!)、イベントの WaitForSingleObject でフリーズします。 .

これにより、アップデーターが監視する準備が整う前にプログラムが終了しないことが保証されるため、PID が無効になることはありません。

その後、アップデーターは、コマンド ラインから取得したプログラムの PID で OpenProcess を呼び出し、SignalAndWait を呼び出して、イベントをノックし (コマンド ラインから取得)、ハンドルでフリーズします (OpenProcess から取得) - https://msdn.microsoft.com/ ru-ru/library/windows/desktop/ms686293(v=vs.85).aspx

イベントの待機から解放されたプログラムは終了します。プロセスの終了はそれを通知しているため、今度はアップデーターが順番にリリースされ、メインの作業を開始できます。


C++ で提案された別のアプローチ、Windows プロセスが実行されているかどうかを判断する方法は? プログラムProcessIDの終了コードを照会しています-プログラムがまだ実行されている間、特定のエラーコードがあり、スリープ(100)してから再試行できると言われています。その他の結果は、プログラムがすでに終了していることを意味します。

プログラムは、アップデーターの起動後、監視の開始を待たずにすぐに終了します。

これは、PID 値が再利用されないという保証がないことを除けば、良いアプローチのように思えます。可能性は無限大ですが、それでもゼロではありません。


個人的には、おそらくフラグファイルを使用します。CreateFile API には、一時ファイル モードという非常に興味深いフラグがあります。つまり、プロセスが終了した後、Windows はファイルを自動的に削除します。それで

  1. このプログラムは、Windows API を使用して新しい GUID (または Crypto API を使用して新しいランダム値) を作成します。
  2. プログラムは一時ファイル フォルダーに、GUID またはランダム値に基づいた名前の一時モード ファイルを作成します。偶然そのようなファイルが既に存在する場合は、新しい GUID またはランダム値を取得するだけです。
  3. プログラムはアップデーターを起動し、コマンドライン経由でファイル名を渡します
  4. プログラムは、アップデーターの起動後、監視の開始を待たずにすぐに終了します。
  5. アップデーターは、ファイルが存在するかどうかを確認し続けます (試行の合間に一時停止します)。ファイルが存在しない場合 -プログラムが終了し、Windows がファイルを自動削除したことを意味します。

繰り返しになりますが、プログラムが終了してアップデーターが再度チェックする間に、他のプロセスがまったく同じ名前のフラグ ファイルを作成する可能性はごくわずかですが、実際にはほとんど不可能です。

于 2015-06-10T15:50:44.770 に答える