2025-09-04 09:53:20 +08:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
|
using System.IO;
|
|
|
|
|
|
using System.Reflection;
|
|
|
|
|
|
using System.Text;
|
|
|
|
|
|
using System.Threading;
|
|
|
|
|
|
|
|
|
|
|
|
namespace AddInManager
|
|
|
|
|
|
{
|
|
|
|
|
|
public class AssemLoader
|
|
|
|
|
|
{
|
|
|
|
|
|
public string OriginalFolder { get; set; }
|
|
|
|
|
|
public string TempFolder { get; set; }
|
|
|
|
|
|
|
2026-01-02 11:40:39 +08:00
|
|
|
|
private List<string> m_refedFolders;
|
|
|
|
|
|
private Dictionary<string, DateTime> m_copiedFiles;
|
|
|
|
|
|
private bool m_parsingOnly;
|
|
|
|
|
|
|
2026-01-23 18:07:21 +08:00
|
|
|
|
// 获取 .NET 运行时目录
|
2026-01-02 11:40:39 +08:00
|
|
|
|
private static string m_dotnetDir = System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory();
|
|
|
|
|
|
|
|
|
|
|
|
private string m_revitAPIAssemblyFullName;
|
|
|
|
|
|
|
2025-09-04 09:53:20 +08:00
|
|
|
|
public AssemLoader()
|
|
|
|
|
|
{
|
|
|
|
|
|
TempFolder = string.Empty;
|
|
|
|
|
|
m_refedFolders = new List<string>();
|
|
|
|
|
|
m_copiedFiles = new Dictionary<string, DateTime>();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-23 18:07:21 +08:00
|
|
|
|
// 保持原有的文件回写逻辑不变
|
2025-09-04 09:53:20 +08:00
|
|
|
|
public void CopyGeneratedFilesBack()
|
|
|
|
|
|
{
|
2026-01-23 18:07:21 +08:00
|
|
|
|
if (!Directory.Exists(TempFolder)) return;
|
2025-09-04 09:53:20 +08:00
|
|
|
|
var files = Directory.GetFiles(TempFolder, "*.*", SearchOption.AllDirectories);
|
2026-01-23 18:07:21 +08:00
|
|
|
|
foreach (var text in files)
|
2025-09-04 09:53:20 +08:00
|
|
|
|
{
|
2026-01-23 18:07:21 +08:00
|
|
|
|
if (m_copiedFiles.ContainsKey(text))
|
2025-09-04 09:53:20 +08:00
|
|
|
|
{
|
|
|
|
|
|
var dateTime = m_copiedFiles[text];
|
|
|
|
|
|
var fileInfo = new FileInfo(text);
|
2026-01-23 18:07:21 +08:00
|
|
|
|
if (fileInfo.LastWriteTime > dateTime)
|
2025-09-04 09:53:20 +08:00
|
|
|
|
{
|
|
|
|
|
|
var text2 = text.Remove(0, TempFolder.Length);
|
|
|
|
|
|
var text3 = OriginalFolder + text2;
|
|
|
|
|
|
FileUtils.CopyFile(text, text3);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-23 18:07:21 +08:00
|
|
|
|
// 注意:通常我们不希望把临时文件夹产生的所有垃圾文件都拷回源目录,视需求而定
|
2025-09-04 09:53:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void HookAssemblyResolve()
|
|
|
|
|
|
{
|
|
|
|
|
|
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void UnhookAssemblyResolve()
|
|
|
|
|
|
{
|
|
|
|
|
|
AppDomain.CurrentDomain.AssemblyResolve -= CurrentDomain_AssemblyResolve;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public Assembly LoadAddinsToTempFolder(string originalFilePath, bool parsingOnly)
|
|
|
|
|
|
{
|
2026-01-23 18:07:21 +08:00
|
|
|
|
if (string.IsNullOrEmpty(originalFilePath) || !File.Exists(originalFilePath))
|
2025-09-04 09:53:20 +08:00
|
|
|
|
{
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
m_parsingOnly = parsingOnly;
|
|
|
|
|
|
OriginalFolder = Path.GetDirectoryName(originalFilePath);
|
2026-01-02 11:40:39 +08:00
|
|
|
|
|
2025-09-04 09:53:20 +08:00
|
|
|
|
var stringBuilder = new StringBuilder(Path.GetFileNameWithoutExtension(originalFilePath));
|
2026-01-02 11:40:39 +08:00
|
|
|
|
stringBuilder.Append(parsingOnly ? "-Parsing-" : "-Executing-");
|
|
|
|
|
|
|
2026-01-23 18:07:21 +08:00
|
|
|
|
// 1. 创建全新的临时文件夹 (基于时间戳,确保唯一)
|
2025-09-04 09:53:20 +08:00
|
|
|
|
TempFolder = FileUtils.CreateTempFolder(stringBuilder.ToString());
|
2026-01-02 11:40:39 +08:00
|
|
|
|
|
2026-01-23 18:07:21 +08:00
|
|
|
|
// 2. 复制并加载
|
2025-09-04 09:53:20 +08:00
|
|
|
|
var assembly = CopyAndLoadAddin(originalFilePath, parsingOnly);
|
|
|
|
|
|
if (null == assembly || !IsAPIReferenced(assembly))
|
|
|
|
|
|
{
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
return assembly;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private Assembly CopyAndLoadAddin(string srcFilePath, bool onlyCopyRelated)
|
|
|
|
|
|
{
|
2026-01-23 18:07:21 +08:00
|
|
|
|
string destPath = string.Empty;
|
|
|
|
|
|
|
|
|
|
|
|
// 复制文件到临时目录
|
2025-09-04 09:53:20 +08:00
|
|
|
|
if (!FileUtils.FileExistsInFolder(srcFilePath, TempFolder))
|
|
|
|
|
|
{
|
|
|
|
|
|
var directoryName = Path.GetDirectoryName(srcFilePath);
|
|
|
|
|
|
if (!m_refedFolders.Contains(directoryName))
|
|
|
|
|
|
{
|
|
|
|
|
|
m_refedFolders.Add(directoryName);
|
|
|
|
|
|
}
|
|
|
|
|
|
var list = new List<FileInfo>();
|
2026-01-02 11:40:39 +08:00
|
|
|
|
destPath = FileUtils.CopyFileToFolder(srcFilePath, TempFolder, onlyCopyRelated, list);
|
2026-01-23 18:07:21 +08:00
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrEmpty(destPath)) return null;
|
|
|
|
|
|
|
2025-09-04 09:53:20 +08:00
|
|
|
|
foreach (var fileInfo in list)
|
|
|
|
|
|
{
|
2026-01-23 18:07:21 +08:00
|
|
|
|
m_copiedFiles[fileInfo.FullName] = fileInfo.LastWriteTime;
|
2025-09-04 09:53:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-23 18:07:21 +08:00
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// 如果文件已存在(极少情况,因为是新Temp目录),构造目标路径
|
|
|
|
|
|
destPath = Path.Combine(TempFolder, Path.GetFileName(srcFilePath));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 加载复制后的文件
|
2026-01-02 11:40:39 +08:00
|
|
|
|
return LoadAddin(destPath);
|
2025-09-04 09:53:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private Assembly LoadAddin(string filePath)
|
|
|
|
|
|
{
|
|
|
|
|
|
Assembly assembly = null;
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2026-01-23 18:07:21 +08:00
|
|
|
|
// 【核心修改】:使用字节流加载,而不是 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);
|
|
|
|
|
|
}
|
2026-01-02 11:40:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.WriteLine($"LoadAddin Failed: {filePath}, Error: {ex.Message}");
|
|
|
|
|
|
throw;
|
2025-09-04 09:53:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
return assembly;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
|
|
|
|
|
|
{
|
2026-01-23 18:07:21 +08:00
|
|
|
|
// 防止递归或死循环
|
|
|
|
|
|
string assemblyName = new AssemblyName(args.Name).Name;
|
2026-01-02 11:40:39 +08:00
|
|
|
|
|
2026-01-23 18:07:21 +08:00
|
|
|
|
// 忽略资源文件
|
|
|
|
|
|
if (assemblyName.EndsWith(".resources") || assemblyName.EndsWith(".XmlSerializers"))
|
|
|
|
|
|
return null;
|
2026-01-02 11:40:39 +08:00
|
|
|
|
|
2026-01-23 18:07:21 +08:00
|
|
|
|
// 1. 在临时文件夹中寻找依赖项
|
|
|
|
|
|
// 因为主程序是字节流加载的,它不知道自己在 TempFolder,必须我们告诉它去那里找
|
|
|
|
|
|
string foundPath = SearchAssemblyFileInTempFolder(assemblyName);
|
2026-01-02 11:40:39 +08:00
|
|
|
|
|
2026-01-23 18:07:21 +08:00
|
|
|
|
if (!string.IsNullOrEmpty(foundPath))
|
|
|
|
|
|
{
|
|
|
|
|
|
// 找到依赖项后,同样使用字节流加载!
|
|
|
|
|
|
// 这样保证主程序集和依赖程序集都在“无上下文”的环境中匹配
|
|
|
|
|
|
return LoadAddin(foundPath);
|
|
|
|
|
|
}
|
2026-01-02 11:40:39 +08:00
|
|
|
|
|
2026-01-23 18:07:21 +08:00
|
|
|
|
// 2. 如果临时文件夹没有,去源文件夹找 (并复制过来)
|
|
|
|
|
|
foundPath = SearchAssemblyFileInOriginalFolders(assemblyName);
|
|
|
|
|
|
if (!string.IsNullOrEmpty(foundPath))
|
|
|
|
|
|
{
|
|
|
|
|
|
return CopyAndLoadAddin(foundPath, true);
|
2025-09-04 09:53:20 +08:00
|
|
|
|
}
|
2026-01-23 18:07:21 +08:00
|
|
|
|
|
|
|
|
|
|
return null;
|
2025-09-04 09:53:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-02 11:40:39 +08:00
|
|
|
|
private string SearchAssemblyFileInTempFolder(string simpleName)
|
2025-09-04 09:53:20 +08:00
|
|
|
|
{
|
2026-01-02 11:40:39 +08:00
|
|
|
|
var extensions = new string[] { ".dll", ".exe" };
|
|
|
|
|
|
foreach (var ext in extensions)
|
2025-09-04 09:53:20 +08:00
|
|
|
|
{
|
2026-01-02 11:40:39 +08:00
|
|
|
|
string path = Path.Combine(TempFolder, simpleName + ext);
|
|
|
|
|
|
if (File.Exists(path)) return path;
|
2025-09-04 09:53:20 +08:00
|
|
|
|
}
|
2026-01-23 18:07:21 +08:00
|
|
|
|
return null;
|
2025-09-04 09:53:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-02 11:40:39 +08:00
|
|
|
|
private string SearchAssemblyFileInOriginalFolders(string simpleName)
|
2025-09-04 09:53:20 +08:00
|
|
|
|
{
|
2026-01-02 11:40:39 +08:00
|
|
|
|
var extensions = new string[] { ".dll", ".exe" };
|
|
|
|
|
|
|
2026-01-23 18:07:21 +08:00
|
|
|
|
// 1. 系统目录 (通常不需要,System dll 会自动解析,但保留以防万一)
|
2026-01-02 11:40:39 +08:00
|
|
|
|
foreach (var ext in extensions)
|
2025-09-04 09:53:20 +08:00
|
|
|
|
{
|
2026-01-02 11:40:39 +08:00
|
|
|
|
string path = Path.Combine(m_dotnetDir, simpleName + ext);
|
|
|
|
|
|
if (File.Exists(path)) return path;
|
2025-09-04 09:53:20 +08:00
|
|
|
|
}
|
2026-01-02 11:40:39 +08:00
|
|
|
|
|
2026-01-23 18:07:21 +08:00
|
|
|
|
// 2. 所有引用过的源文件夹
|
2026-01-02 11:40:39 +08:00
|
|
|
|
foreach (var ext in extensions)
|
2025-09-04 09:53:20 +08:00
|
|
|
|
{
|
2026-01-02 11:40:39 +08:00
|
|
|
|
foreach (var folder in m_refedFolders)
|
2025-09-04 09:53:20 +08:00
|
|
|
|
{
|
2026-01-02 11:40:39 +08:00
|
|
|
|
string path = Path.Combine(folder, simpleName + ext);
|
2026-01-23 18:07:21 +08:00
|
|
|
|
if (File.Exists(path)) return path;
|
2025-09-04 09:53:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-02 11:40:39 +08:00
|
|
|
|
|
2025-09-04 09:53:20 +08:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private bool IsAPIReferenced(Assembly assembly)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrEmpty(m_revitAPIAssemblyFullName))
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var assembly2 in AppDomain.CurrentDomain.GetAssemblies())
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.Compare(assembly2.GetName().Name, "RevitAPI", true) == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_revitAPIAssemblyFullName = assembly2.GetName().Name;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-23 18:07:21 +08:00
|
|
|
|
// 如果还没加载 RevitAPI (极其罕见),通过
|
2026-01-02 11:40:39 +08:00
|
|
|
|
if (string.IsNullOrEmpty(m_revitAPIAssemblyFullName)) return true;
|
|
|
|
|
|
|
2025-09-04 09:53:20 +08:00
|
|
|
|
foreach (var assemblyName in assembly.GetReferencedAssemblies())
|
|
|
|
|
|
{
|
|
|
|
|
|
if (m_revitAPIAssemblyFullName == assemblyName.Name)
|
|
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-02 11:40:39 +08:00
|
|
|
|
}
|