从非托管进程中卸载 .NET DLL
Posted
技术标签:
【中文标题】从非托管进程中卸载 .NET DLL【英文标题】:Unload a .NET DLL from an unmanaged process 【发布时间】:2015-04-07 21:54:52 【问题描述】:我正在扩展我的 Inno-Setup 脚本,其中包含在托管 DLL 中最好用 C# 实现的代码。我已经知道如何将托管 DLL 中的方法导出为用于非托管进程的函数。这可以通过 IL weaving 来完成,并且有一些工具可以自动执行此操作:
NetDllExport(我写的) UnmanagedExports所以在导出后,我可以在 Inno-Setup 安装程序中从 Pascal 脚本调用我的函数。但是还有一个问题:DLL 似乎无法再卸载了。使用 Inno-Setup 的 UnloadDLL(...)
无效,并且文件保持锁定状态,直到安装程序退出。因此,安装程序会等待 2 秒,然后无法从临时目录(或安装目录)中删除我的 DLL 文件。事实上,它确实会一直留在那里,直到有人清理驱动器。
我知道托管程序集不能再从 AppDomain 中卸载,除非整个 AppDomain 被关闭(进程退出)。但这对非托管主机进程意味着什么?
有没有更好的方法让 Inno-Setup 在加载和使用后卸载或删除我的 DLL 文件?
【问题讨论】:
顺便说一句,.NET 是 Windows 操作系统组件已有一段时间了,无论如何只支持最新版本。所以 .NET 应该已经存在,不应该消失,甚至可能被认为在 Windows 上无处不在。 【参考方案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 文件。事实上,它确实一直存在,直到有人清理驱动器。“ - 你的答案无法解决这个问题问题。以上是关于从非托管进程中卸载 .NET DLL的主要内容,如果未能解决你的问题,请参考以下文章
gcServer 设置未从非托管 exe 传递到托管 dll
从非托管 DLL 导入函数时,0x8007007F 是啥意思?