动态加载DLL后释放DLL与PDB文件的问题求助
这问题我之前帮朋友排查过类似的,核心就是你的Windows主应用进程锁住了obj目录下的PDB文件——哪怕你已经释放了bin目录里的DLL和PDB,编译Coded UI项目时,编译器需要写入obj的PDB,还是会因为文件被占用报错。给你几个实用的解决方案,按优先级排序:
1. 仅加载DLL字节,避免触发符号文件加载
你已经试过用Assembly.Load加载字节数组,但可能之前的操作还是让CLR尝试加载了PDB符号。记得只加载DLL的字节流,不要附带加载PDB文件,这样CLR不会去查找任何符号文件,自然不会锁定obj目录的PDB。
示例代码:
string dllPath = @"C:\YourProject\RecordAndPlayback\bin\Debug\RecordAndPlayback.dll"; byte[] dllBytes = File.ReadAllBytes(dllPath); // 只加载DLL字节,不加载符号 Assembly codedUIAssembly = Assembly.Load(dllBytes); // 调用测试方法...
这样主应用完全不会接触到obj目录的PDB,编译时自然不会冲突。
2. 修改Coded UI项目的PDB输出路径
默认情况下,Coded UI项目会在obj\Debug目录生成中间PDB,同时在bin\Debug生成输出PDB。你可以把中间PDB的路径改到和输出目录一致,或者直接跳过中间PDB的生成:
- 右键Coded UI项目 → 属性
- 切换到生成标签页,点击右下角的高级按钮
- 在弹出的窗口里,把调试信息改成
pdb-only,然后把PDB文件路径设为bin\Debug\RecordAndPlayback.pdb(和输出DLL同目录) - 保存设置后重新编译
这样编译时只会在bin目录生成PDB,obj目录不再产生PDB文件,也就不存在被锁定的问题了。
3. 启用AppDomain的影子复制(Shadow Copy)
如果你需要保留调试符号的加载,那可以用AppDomain的影子复制功能——CLR会把要加载的DLL/PDB复制到临时目录,然后加载副本,原文件就会被释放,不会被锁定。之前你试过AppDomain加载卸载,可能没配置影子复制:
// 配置AppDomain的影子复制 var setup = new AppDomainSetup { ShadowCopyFiles = "true", // 启用影子复制 CachePath = Path.Combine(Path.GetTempPath(), "CodedUICache"), // 缓存副本的临时目录 ApplicationBase = Path.GetDirectoryName(@"C:\YourProject\RecordAndPlayback\bin\Debug") // DLL所在目录 }; // 创建独立的AppDomain var codedUIDomain = AppDomain.CreateDomain("CodedUITestDomain", null, setup); // 在新域中加载并执行测试方法 var testInstance = codedUIDomain.CreateInstanceAndUnwrap( "RecordAndPlayback", "RecordAndPlayback.TestClass" ) as ITestInterface; // 记得定义一个共享的接口来调用方法 testInstance.RunTest(); // 卸载AppDomain,释放所有资源 AppDomain.Unload(codedUIDomain);
这个方案的好处是能保留调试功能,同时完全释放原文件,编译时不会冲突。
4. 兜底方案:强制释放文件句柄(不推荐)
如果上面的方案都不行,可以用Windows API强制关闭占用obj/PDB的句柄,但这个方法比较hack,可能导致主应用不稳定,仅作为应急手段。你需要用P/Invoke调用NtDuplicateObject和NtClose来查找并关闭对应句柄,具体代码可以参考进程句柄操作的示例,但一定要谨慎使用。
内容的提问来源于stack exchange,提问作者Islam El-gendy




