从非托管进程卸载.NET DLL

时间:2015-02-07 22:15:24

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

我正在使用我最好在托管DLL中的C#中实现的代码扩展我的Inno-Setup脚本。我已经知道如何从托管DLL导出方法作为在非托管进程中使用的函数。它可以通过IL编织来完成,并且有一些工具可以实现自动化:

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

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

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

6 个答案:

答案 0 :(得分:3)

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

一个简单的解决方案是创建一个特殊的批处理文件,该文件循环直到可以删除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;

答案 1 :(得分:1)

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

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

答案 2 :(得分:1)

如本代码项目文章中所述:https://www.codeproject.com/kb/threads/howtodeletecurrentprocess.aspx

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

char str[] = "00400000";
unsigned long ul;
struct myStruct s;

sscanf(str, "%lx", &ul);
s.address = (void*) (uintptr_t) ul;

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

答案 3 :(得分:0)

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

答案 4 :(得分:0)

这是我所做的,改编自马丁的精彩回答。注意“睡眠”,这对我有用。因为执行是在后台线程中调用的,这不是阻塞程序,并且为 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;    

答案 5 :(得分:-1)

通过AppDomain轻松完成所需操作。您可以卸载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;
      }
   }
}