乱语:
你打发无聊时间的方法,决定了你的成色。
今天是庚子年一月一日,就是俗话说的大年初一。全国被新型冠状病毒引发的传染病所笼罩,当看到那些冲向灾区的逆行者,除了泪目之外,还从心底发出一定要做点儿什么的急切心愿。
我们这些做程序的,自嘲了称呼声码农,往大了说那也是工程师,留下点儿有用于世间的东西吧。于是写下了这篇文章。
正文:
问题背景:
现在搞Revit二次开发的人越来越多,一些外界人才的涌入,终于冲击到了那些非程序专业人搞二次开发的一些顽固想法。市面上,Revit二次开发产品UI也慢慢的由传统的Winform向Wpf过度。现在似乎已经没有人在问那种我用Winform挺好的,为什么要换成Wpf的守旧问题。但是还是要强答一波,因为Revit的UI,起码菜单是Wpf开发的,所以二次开发最好就用Wpf。其他的优劣对比,一概懒得说了。
但使用Wpf开发的类库有一个问题。您大概也是因为这个问题才检索到我这篇文章的。我们使用Wpf进行二次开发,一般要把项目编译成dll。随着我们对界面显示或者代码结构的要求越来越高。我们可能会在,我们开发的.xaml窗体中,引用到第三方的dll。如果这个第三方的dll,只是在xaml中被使用了,当我们通过Revit的命令按钮去执行时,可能会发生找不到相关dll的异常,但它的的确确时在插件dll的目录下。
问题剖析:
首先明确这不是Revit的问题,Wpf进行dll开发时,确实会出现这种“bug”。如果是自己开发的exe可能没问题,但是dll中xmal解析确实可能存在问题的。至于原因是什么,我们就不去深究了。
解决问题:
刚开始的时候,我们发现这个问题,用了很粗暴的方法,就是把加载出异常说找不到的dll记录下来,然后在插件程序安装,或者插件程序启动的时候,把这些dll 拷贝到Revit安装目录下。
这种方法能解决问题,但是不够优雅。现在有了更好的思路。
思路很简单,在Revit启动插件的时候,主动的自己去加载那些不能自动解析的dll。
写了一个小的加载类,供大家参考
/// <summary>
/// 程序集加载器
/// </summary>
public class AssemblyLoader
{
/// <summary>
/// 加载程序集找不到引用时搜索路径
/// </summary>
/// <param name="searchPaths"></param>
public AssemblyLoader(List<string> searchPaths)
{
SerachPaths = new ReadOnlyCollection<string>(searchPaths);
}
public AssemblyLoader()
{
var paths = new List<string>();
paths.Add(AppDomain.CurrentDomain.BaseDirectory);
paths.Add(Path.GetDirectoryName( this.GetType().Assembly.Location));
paths = paths.Distinct().ToList();
SerachPaths = new ReadOnlyCollection<string>(paths);
}
public ReadOnlyCollection<string> SerachPaths { get; private set; }
#region 公开方法
/// <summary>
/// 加载dlls,paths为绝对路径,或者是在相对于当前dll位置的相对路径
/// </summary>
/// <param name="paths"></param>
public void LoadDlls(List<string> paths)
{
try
{
AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve;
foreach (string path in paths)
{
if (File.Exists(path))
{
try
{
Assembly.LoadFrom(path);
}
catch (Exception ex)
{
Console.Write(ex);
}
}
}
}
catch (Exception ex)
{
Console.Write(ex.StackTrace.ToString());
}
finally
{
AppDomain.CurrentDomain.AssemblyResolve -= OnAssemblyResolve;
}
}
private Assembly OnAssemblyResolve(object sender, ResolveEventArgs args)
{
var assemblyName = new AssemblyName(args.Name);
foreach (var item in SerachPaths)
{
var file = string.Format("{0}.dll", Path.Combine(item, assemblyName.Name));
if (File.Exists(file))
{
try
{
return Assembly.LoadFrom(file);
}
catch (Exception ex)
{
}
}
}
return args.RequestingAssembly;
}
#endregion
#region 静态方法
private const string PATH = "PATH";
/// <summary>
/// 增加路径环境变量
/// </summary>
/// <param name="input"></param>
public static void AddEnvironmentPath(params string[] input)
{
var path = new[] { Environment.GetEnvironmentVariable(PATH) ?? string.Empty };
//加在最前面,
var newPath = string.Join(Path.PathSeparator.ToString(), input.Concat(path));
Environment.SetEnvironmentVariable(PATH, newPath);
}
#endregion
}
然后对这个类进行使用的话,是放在自己实现 IExternalApplication接口的public virtual Result OnStartup(UIControlledApplication application)方法里进行调用。
AssemblyLoader loader = new AssemblyLoader();
var dlls = new List<string>();
dlls.Add("xxx.dll");
loader.LoadDlls(dlls);