diff --git a/AddInManager/AssemLoader.cs b/AddInManager/AssemLoader.cs index 0d36aa8..eda4bbb 100644 --- a/AddInManager/AssemLoader.cs +++ b/AddInManager/AssemLoader.cs @@ -17,9 +17,9 @@ namespace AddInManager private Dictionary m_copiedFiles; private bool m_parsingOnly; + // 获取 .NET 运行时目录 private static string m_dotnetDir = System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory(); - public static string m_resolvedAssemPath = string.Empty; private string m_revitAPIAssemblyFullName; public AssemLoader() @@ -29,27 +29,25 @@ namespace AddInManager m_copiedFiles = new Dictionary(); } + // 保持原有的文件回写逻辑不变 public void CopyGeneratedFilesBack() { + if (!Directory.Exists(TempFolder)) return; var files = Directory.GetFiles(TempFolder, "*.*", SearchOption.AllDirectories); - foreach(var text in files) + foreach (var text in files) { - if(m_copiedFiles.ContainsKey(text)) + if (m_copiedFiles.ContainsKey(text)) { var dateTime = m_copiedFiles[text]; var fileInfo = new FileInfo(text); - if(fileInfo.LastWriteTime > dateTime) + if (fileInfo.LastWriteTime > dateTime) { var text2 = text.Remove(0, TempFolder.Length); var text3 = OriginalFolder + text2; FileUtils.CopyFile(text, text3); } - } else - { - var text4 = text.Remove(0, TempFolder.Length); - var text5 = OriginalFolder + text4; - FileUtils.CopyFile(text, text5); } + // 注意:通常我们不希望把临时文件夹产生的所有垃圾文件都拷回源目录,视需求而定 } } @@ -65,7 +63,7 @@ namespace AddInManager public Assembly LoadAddinsToTempFolder(string originalFilePath, bool parsingOnly) { - if (string.IsNullOrEmpty(originalFilePath) || originalFilePath.StartsWith("\\") || !File.Exists(originalFilePath)) + if (string.IsNullOrEmpty(originalFilePath) || !File.Exists(originalFilePath)) { return null; } @@ -75,8 +73,10 @@ namespace AddInManager var stringBuilder = new StringBuilder(Path.GetFileNameWithoutExtension(originalFilePath)); stringBuilder.Append(parsingOnly ? "-Parsing-" : "-Executing-"); + // 1. 创建全新的临时文件夹 (基于时间戳,确保唯一) TempFolder = FileUtils.CreateTempFolder(stringBuilder.ToString()); + // 2. 复制并加载 var assembly = CopyAndLoadAddin(originalFilePath, parsingOnly); if (null == assembly || !IsAPIReferenced(assembly)) { @@ -87,7 +87,9 @@ namespace AddInManager private Assembly CopyAndLoadAddin(string srcFilePath, bool onlyCopyRelated) { - var destPath = string.Empty; + string destPath = string.Empty; + + // 复制文件到临时目录 if (!FileUtils.FileExistsInFolder(srcFilePath, TempFolder)) { var directoryName = Path.GetDirectoryName(srcFilePath); @@ -97,15 +99,21 @@ namespace AddInManager } var list = new List(); destPath = FileUtils.CopyFileToFolder(srcFilePath, TempFolder, onlyCopyRelated, list); - if (string.IsNullOrEmpty(destPath)) - { - return null; - } + + if (string.IsNullOrEmpty(destPath)) return null; + foreach (var fileInfo in list) { - m_copiedFiles.Add(fileInfo.FullName, fileInfo.LastWriteTime); + m_copiedFiles[fileInfo.FullName] = fileInfo.LastWriteTime; } } + else + { + // 如果文件已存在(极少情况,因为是新Temp目录),构造目标路径 + destPath = Path.Combine(TempFolder, Path.GetFileName(srcFilePath)); + } + + // 加载复制后的文件 return LoadAddin(destPath); } @@ -114,73 +122,60 @@ namespace AddInManager Assembly assembly = null; try { - Monitor.Enter(this); - assembly = Assembly.LoadFile(filePath); + // 【核心修改】:使用字节流加载,而不是 LoadFile + // 这样可以避免文件锁定,并且每次 Load 都会视为新的程序集实例 + if (File.Exists(filePath)) + { + byte[] assemblyBytes = File.ReadAllBytes(filePath); + byte[] pdbBytes = null; + + // 尝试加载 PDB 以支持调试 + string pdbPath = Path.ChangeExtension(filePath, "pdb"); + if (File.Exists(pdbPath)) + { + pdbBytes = File.ReadAllBytes(pdbPath); + } + + // 使用 Load(bytes, pdbBytes) + assembly = Assembly.Load(assemblyBytes, pdbBytes); + } } catch (Exception ex) { - // 增加简单的错误输出,方便调试 Debug.WriteLine($"LoadAddin Failed: {filePath}, Error: {ex.Message}"); throw; } - finally - { - Monitor.Exit(this); - } return assembly; } private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) { - Assembly assembly = null; - lock (this) + // 防止递归或死循环 + string assemblyName = new AssemblyName(args.Name).Name; + + // 忽略资源文件 + if (assemblyName.EndsWith(".resources") || assemblyName.EndsWith(".XmlSerializers")) + return null; + + // 1. 在临时文件夹中寻找依赖项 + // 因为主程序是字节流加载的,它不知道自己在 TempFolder,必须我们告诉它去那里找 + string foundPath = SearchAssemblyFileInTempFolder(assemblyName); + + if (!string.IsNullOrEmpty(foundPath)) { - var assemblyNameObj = new AssemblyName(args.Name); - var simpleName = assemblyNameObj.Name; - - if (simpleName.EndsWith(".resources", StringComparison.OrdinalIgnoreCase) || - args.Name.Contains(".resources")) - { - return null; - } - - // 1. 先在临时目录找 - var text = SearchAssemblyFileInTempFolder(simpleName); - if (File.Exists(text)) - { - return LoadAddin(text); - } - - // 2. 临时目录没有,去源目录找 - text = SearchAssemblyFileInOriginalFolders(simpleName); - - // 3. 如果源目录找到了,复制到临时目录并加载 - if (!string.IsNullOrEmpty(text)) - { - assembly = CopyAndLoadAddin(text, true); - return assembly; - } - - if (simpleName.EndsWith(".XmlSerializers", StringComparison.OrdinalIgnoreCase)) - { - // 忽略序列化程序集请求 - return null; - } - - // 5. 【可选】最后尝试手动弹窗选择(原代码逻辑), - // 但通常对于依赖项来说,弹窗很烦人,建议只针对主程序集弹窗,或者直接返回null - // 如果这是为了解决找不到依赖的问题,上面 LoadFrom 改好后这里应该很少进来了 - // 只有当你确实需要弹窗时保留下面代码 - /* - var assemblySelector = new Wpf.AssemblySelectorWindow(args.Name); - if (assemblySelector.ShowDialog() == true) - { - text = assemblySelector.ResultPath; - assembly = CopyAndLoadAddin(text, true); - } - */ + // 找到依赖项后,同样使用字节流加载! + // 这样保证主程序集和依赖程序集都在“无上下文”的环境中匹配 + return LoadAddin(foundPath); } - return assembly; + + // 2. 如果临时文件夹没有,去源文件夹找 (并复制过来) + foundPath = SearchAssemblyFileInOriginalFolders(assemblyName); + if (!string.IsNullOrEmpty(foundPath)) + { + return CopyAndLoadAddin(foundPath, true); + } + + return null; } private string SearchAssemblyFileInTempFolder(string simpleName) @@ -191,41 +186,35 @@ namespace AddInManager string path = Path.Combine(TempFolder, simpleName + ext); if (File.Exists(path)) return path; } - return string.Empty; + return null; } private string SearchAssemblyFileInOriginalFolders(string simpleName) { var extensions = new string[] { ".dll", ".exe" }; - + // 1. 系统目录 (通常不需要,System dll 会自动解析,但保留以防万一) foreach (var ext in extensions) { string path = Path.Combine(m_dotnetDir, simpleName + ext); if (File.Exists(path)) return path; } - // 2. 在所有引用过的源目录中找 + // 2. 所有引用过的源文件夹 foreach (var ext in extensions) { foreach (var folder in m_refedFolders) { string path = Path.Combine(folder, simpleName + ext); - if (File.Exists(path)) - { - return path; - } + if (File.Exists(path)) return path; } } - // 3. 原代码中关于 Regression 的逻辑(看起来是特定环境的,如果不需要建议删除) - return null; } private bool IsAPIReferenced(Assembly assembly) { - // 保持原逻辑不变 if (string.IsNullOrEmpty(m_revitAPIAssemblyFullName)) { foreach (var assembly2 in AppDomain.CurrentDomain.GetAssemblies()) @@ -237,7 +226,7 @@ namespace AddInManager } } } - // 防止未加载 RevitAPI 时崩溃 + // 如果还没加载 RevitAPI (极其罕见),通过 if (string.IsNullOrEmpty(m_revitAPIAssemblyFullName)) return true; foreach (var assemblyName in assembly.GetReferencedAssemblies()) diff --git a/AddinManager.iss b/AddinManager.iss index 7a7546f..d9da692 100644 --- a/AddinManager.iss +++ b/AddinManager.iss @@ -4,7 +4,7 @@ ; 定义应用程序的名称 #define MyAppName "RevitAddinManager" ; 定义应用程序的版本号 -#define MyAppVersion "3.0.0" +#define MyAppVersion "3.2.0" ; 定义应用程序的发布者 #define MyAppPublisher "ShrlAlgo" ; 定义应用程序的网址