较大的 .exe 文件无法自行删除,而较小的 .exe 文件可以使用相同的代码 (Delphi)

Larger .exe file cannot delete itself, while smaller one can with the same code (Delphi)

我想在 Delphi 中编写一个程序,它会删除自身并复制另一个版本而不是它,另一个文件夹中的另一个同名 .exe 文件。我在一个测试项目上尝试了这段代码并且它有效。但是当我为我的大项目复制它时,它无法自行删除。 大型项目的大小是 68 413 KB(如果它与问题有任何关系..)

有什么建议吗?

AppName:= ExtractFileName(ParamStr(0));
myBatFile:= 'copy.bat';
AssignFile(myText, myBatFile);
Rewrite(myText);
Writeln(myText, 'del ' + AppName);
Writeln(myText, 'copy whateverPath\myProgram.exe whateverPath\destination\myProgram.exe');
Writeln(myText, 'del ' + myBatFile);
CloseFile(myText);
ShellExecute(Application.Handle,'open','copy.bat',nil,nil,SW_ShowNormal);
Halt;

当您调用 ShellExecute 时,会创建一个新的命令解释器进程,进程 B。然后 ShellExecute returns 和您的进程,进程 A,继续执行。

您现在拥有所谓的比赛。这两个进程在竞争。进程 B 正在尝试删除用于创建进程 A 的可执行文件。因此进程 A 需要在进程 B 完成之前终止才能成功。但是您没有采取任何措施来实现这一目标。

正确的实现方式如下:

  1. 有第二个执行复制和删除的可执行文件。这将是进程 B。
  2. 让进程 A 创建新进程 B,将文件名作为命令行参数传递。并且还为进程 A 传递了一个重复的可继承进程句柄。
  3. 一旦进程 A 完成了第 2 步,它就会终止。
  4. 进程 B 现在等待步骤 2 中提供的进程句柄。当该句柄发出信号时,进程 B 知道进程 A 已终止并且可以删除可执行文件。

您可以使用 http://www.catch22.net/tuts/self-deleting-executables 中的自删除批处理文件方法在退出后删除您的可执行文件,然后复制新的可执行文件并删除批处理文件。

procedure UpdateMeAfterClose(const NewFilename: string);
var
  batch: TStrings;
  appName, newApp, batchName: string;
begin
  batch := TStringList.Create;
  try
    //** Get batch filename in temp
    batchName := TPath.GetTempPath;
    if GetTempFileName(PChar(batchName), 'bat', 0, PChar(batchName)) = 0 then
      RaiseLastOSError;
    batchName := ChangeFileExt(batchName, '.bat');
    newApp := AnsiQuotedStr(NewFilename, '"');
    appName := AnsiQuotedStr(Application.ExeName, '"');

    batch.Add(':Repeat');
    batch.Add('del ' + appName); // try to delete current exe
    batch.Add('if exist ' + appName + ' goto Repeat');
    batch.Add('copy ' + newApp + ' ' + appName); // copy new exe
    batch.Add(appName); // start new app
    batch.Add('del "' + batchName + '"'); // delete this batch file
    batch.SaveToFile(batchName);
  finally
    batch.Free;
  end;
  ShellExecute(0, 'open', PChar(batchName), nil, nil, SW_HIDE);
end;

您无法删除 运行 进程的文件。但是你可以重命名它! 所以你可以:

  1. 将新的可执行文件下载到 *.tmp 文件中
  2. 重命名 *.exe 为 *.exe.tmp
  3. 将下载的文件从 *.tmp 重命名为 *.exe
  4. 重新启动应用程序
  5. 删除*.exe.tmp