【问题标题】:Unload a .NET DLL from an unmanaged process从非托管进程中卸载 .NET DLL
【发布时间】:2015-04-07 21:54:52
【问题描述】:

我正在扩展我的 Inno-Setup 脚本,其中包含在托管 DLL 中最好用 C# 实现的代码。我已经知道如何将托管 DLL 中的方法导出为用于非托管进程的函数。这可以通过 IL weaving 来完成,并且有一些工具可以自动执行此操作:

所以在导出后,我可以在 Inno-Setup 安装程序中从 Pascal 脚本调用我的函数。但是还有一个问题:DLL 似乎无法再卸载了。使用 Inno-Setup 的 UnloadDLL(...) 无效,并且文件保持锁定状态,直到安装程序退出。因此,安装程序会等待 2 秒,然后无法从临时目录(或安装目录)中删除我的 DLL 文件。事实上,它确实会一直留在那里,直到有人清理驱动器。

我知道托管程序集不能再从 AppDomain 中卸载,除非整个 AppDomain 被关闭(进程退出)。但这对非托管主机进程意味着什么?

有没有更好的方法让 Inno-Setup 在加载和使用后卸载或删除我的 DLL 文件?

【问题讨论】:

  • 顺便说一句,.NET 是 Windows 操作系统组件已有一段时间了,无论如何只支持最新版本。所以 .NET 应该已经存在,不应该消失,甚至可能被认为在 Windows 上无处不在。

标签: c# .net com inno-setup unmanaged


【解决方案1】:

正如其他答案中所建议的,您可以在安装结束后启动一个单独的进程,该进程将在安装过程完成后负责清理。

一个简单的解决方案是创建一个临时批处理文件,该文件循环直到可以删除 DLL 文件,然后还删除(现在为空的)临时文件夹及其本身。

procedure DeinitializeSetup();
var
  FilePath: string;
  BatchPath: string;
  S: TArrayOfString;
  ResultCode: Integer;
begin
  FilePath := ExpandConstant('{tmp}\MyAssembly.dll');
  if not FileExists(FilePath) then
  begin
    Log(Format('File %s does not exist', [FilePath]));
  end
    else
  begin
    BatchPath :=
      ExpandConstant('{%TEMP}\') +
      'delete_' + ExtractFileName(ExpandConstant('{tmp}')) + '.bat';
    SetArrayLength(S, 7);
    S[0] := ':loop';
    S[1] := 'del "' + FilePath + '"';
    S[2] := 'if not exist "' + FilePath + '" goto end';
    S[3] := 'goto loop';
    S[4] := ':end';
    S[5] := 'rd "' + ExpandConstant('{tmp}') + '"';
    S[6] := 'del "' + BatchPath + '"';
    if not SaveStringsToFile(BatchPath, S, False) then
    begin
      Log(Format('Error creating batch file %s to delete %s', [BatchPath, FilePath]));
    end
      else
    if not Exec(BatchPath, '', '', SW_HIDE, ewNoWait, ResultCode) then
    begin
      Log(Format('Error executing batch file %s to delete %s', [BatchPath, FilePath]));
    end
      else
    begin
      Log(Format('Executed batch file %s to delete %s', [BatchPath, FilePath]));
    end;
  end;
end;

【讨论】:

    【解决方案2】:

    您可以添加一个批处理脚本(以运行 cmd -c 的形式)在安装结束时执行,等待文件可删除并将其删除。 (只需确保将 inno 选项设置为不等待 cmd 进程完成)

    您还可以让已安装的程序在首次执行时检测并删除它。

    【讨论】:

      【解决方案3】:

      正如本代码项目文章中所建议的那样:https://www.codeproject.com/kb/threads/howtodeletecurrentprocess.aspx

      使用如下所示的参数调用 cmd。

       Process.Start("cmd.exe", "/C ping 1.1.1.1 -n 1 -w 3000 > Nul & Del " +  Application.ExecutablePath);
      

      但基本上正如@Sean 建议的那样,请确保您不要等待 cmd.exe 在脚本中退出。

      【讨论】:

      • 为了保证可靠性,您必须等待安装程序完成。等待固定时间并不可靠。
      【解决方案4】:

      虽然不能完全回答您的问题,但您不能将 DLL 标记为在下次重新启动计算机时删除吗?

      【讨论】:

      • 如果我没记错的话,你应该把它作为评论添加到问题本身
      • 虽然这是一种可能的解决方法,但它并不理想,没有回答问题,也没有解释首先卸载 DLL 失败的原因。
      【解决方案5】:

      这就是我所做的,改编自 Martin 的出色回答。注意“睡眠”,这对我有用。因为执行是在后台线程中调用的,所以它不是阻塞器,并且为 InnoSetup 留出了足够的时间来释放资源。 之后,我就可以清理临时文件夹了。

      // Gets invoked at the end of the installation
      procedure DeinitializeSetup();
      var
        BatchPath: String;
        S:         TArrayOfString;
        FilesPath: TStringList;
        ResultCode, I, ErrorCode: Integer;
      
      begin
        I := 0
        FilesPath := TStringList.Create;
      
        FilesPath.Add(ExpandConstant('{tmp}\DLL1.dll'));
        FilesPath.Add(ExpandConstant('{tmp}\DLL2.dll'));
        FilesPath.Add(ExpandConstant('{tmp}\DLLX.dll'));
      
        while I < FilesPath.Count do
        begin
          if not FileExists(FilesPath[I]) then
          begin
            Log(Format('File %s does not exist', [FilesPath[I]]));
          end
          else
          begin
            UnloadDLL(FilesPath[I]);
            if Exec('powershell.exe',
              FmtMessage('-NoExit -ExecutionPolicy Bypass -Command "Start-Sleep -Second 5; Remove-Item -Recurse -Force -Path %1"', [FilesPath[I]]),
              '', SW_HIDE, ewNoWait, ErrorCode) then
            begin
              Log(Format('Temporary file %s successfully deleted', [ExpandConstant(FilesPath[I])]));
            end
            else
            begin
              Log(Format('Error while deleting temporary file: %s', [ErrorCode]));
            end;
          inc(I);
          end;
        end;
      
        Exec('powershell.exe',
          FmtMessage('-NoExit -ExecutionPolicy Bypass -Command "Start-Sleep -Second 5; Remove-Item -Recurse -Force -Path %1"', [ExpandConstant('{tmp}')]),
          '', SW_HIDE, ewNoWait, ErrorCode);
        Log(Format('Temporary folder %s successfully deleted', [ExpandConstant('{tmp}')]));
      end;    
      

      【讨论】:

        【解决方案6】:

        做你想做的事的简单方法是通过 AppDomain。您可以卸载 AppDomain,而不是最初的。所以解决方案是创建一个新的 AppDomain,在其中加载托管 DLL,然后卸载 AppDomain。

                AppDomain ad = AppDomain.CreateDomain("Isolate DLL");
                Assembly a = ad.Load(new AssemblyName("MyManagedDll"));
                object d = a.CreateInstance("MyManagedDll.MyManagedClass");
                Type t = d.GetType();
                double result = (double)t.InvokeMember("Calculate", BindingFlags.InvokeMethod, null, d, new object[] { 1.0, 2.0 });
                AppDomain.Unload(ad);
        

        这是 DLL 代码的样子...

        namespace MyManagedDll
        {
           public class MyManagedClass
           {
              public double Calculate(double a, double b)
              {
                return a + b;
              }
           }
        }
        

        【讨论】:

        • 这与 OP 无关。 OP 希望在 .NET 中实现对现有非托管应用程序的扩展。 OP 无法创建新的 AppDomain,因为他/她不控制 DLL 加载过程。
        • 确实是相关的。他可以访问托管 DLL。该托管 DLL 只是创建一个应用程序域并加载另一个托管 DLL,然后将其卸载。所以我的回答中缺少的是需要 2 个托管 DLL。我只是认为这很明显。
        • 是的,这很明显。但是 OP 的问题是:“DLL 似乎无法再卸载了。使用 Inno-Setup 的 UnloadDLL(...) 没有任何效果,并且文件保持锁定状态,直到安装程序退出。因此,安装程序等待 2 秒,然后无法从临时目录(或安装目录)中删除我的 DLL 文件。事实上,它确实一直存在,直到有人清理驱动器。“ - 你的答案无法解决这个问题问题。
        猜你喜欢
        • 1970-01-01
        • 2023-02-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-11-28
        • 2012-05-25
        • 2018-02-10
        相关资源
        最近更新 更多