让.Net程序可以在自定义目录查找托管dll程序集的几种方法

目前(.Net4.5.1)能用的有3种:

//#define HACK_UPDATECONTEXTPROPERTY  //RECOMMENDED
//#define HACK_SETLEGACYCASPOLICYENABLED

using System.Collections.Generic;
using System.IO;
using System.Reflection;

namespace System.Helpers
{
    public static partial class AppDomainHelper
    {
        public static string AssemblyExtension { get; set; }
        static AppDomainHelper() { AssemblyExtension = "dll"; }

        public static void SetPrivateBinPath(params string[] dirNames)
        {
            var appDomain = AppDomain.CurrentDomain;
            var dlls = new Dictionary<string, List<string>>();
            foreach (var dn in dirNames)
            {
                foreach (var f in Directory.GetFiles(appDomain.BaseDirectory + dn, "*." + AssemblyExtension))
                {
                    var an = AssemblyName.GetAssemblyName(f).FullName;
                    if (!dlls.ContainsKey(an)) dlls.Add(an, new List<string>());
                    dlls[an].Add(f);
                }
            }
            appDomain.AssemblyResolve += (_, resolveEventArgs) =>
                dlls.ContainsKey(resolveEventArgs.Name)
                    ? appDomain.Load(File.ReadAllBytes(dlls[resolveEventArgs.Name][0]))
                    : null
                ;
        }
    }
    public static partial class AppDomainHelper
    {
        #region HACK_UPDATECONTEXTPROPERTY
#if HACK_UPDATECONTEXTPROPERTY
        public static void SetPrivateBinPathHack(params string[] dirNames)
        {
            const string privateBinPathKeyName = "PrivateBinPathKey";
            const string methodName_UpdateContextProperty = "UpdateContextProperty";
            const string methodName_GetFusionContext = "GetFusionContext";

            var privateBinDirectories = string.Join(";", dirNames);
            var curApp = AppDomain.CurrentDomain;
            var appType = typeof (AppDomain);
            var appDomainSetupType = typeof (AppDomainSetup);
            var privateBinPathKey = appDomainSetupType
                .GetProperty(privateBinPathKeyName, BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.GetProperty)
                .GetValue(null)
                .ToString();
            curApp.SetData(privateBinPathKey, privateBinDirectories);
            appDomainSetupType
                .GetMethod(methodName_UpdateContextProperty, BindingFlags.NonPublic | BindingFlags.Static)
                .Invoke(null, new[]
                {
                    appType
                        .GetMethod(methodName_GetFusionContext, BindingFlags.NonPublic | BindingFlags.Instance)
                        .Invoke(curApp, null),
                    privateBinPathKey,
                    privateBinDirectories
                });
        }
#endif
        #endregion
        #region HACK_SETLEGACYCASPOLICYENABLED
#if HACK_SETLEGACYCASPOLICYENABLED
        public static void SetPrivateBinPathHack2(params string[] dirNames)
        {
            var appDomain = AppDomain.CurrentDomain;
            var dlls = new Dictionary<string, List<string>>();
            foreach (var dn in dirNames)
            {
                foreach (var f in Directory.GetFiles(appDomain.BaseDirectory + dn, "*." + AssemblyExtension))
                {
                    var an = AssemblyName.GetAssemblyName(f).FullName;
                    if (!dlls.ContainsKey(an)) dlls.Add(an, new List<string>());
                    dlls[an].Add(f);
                }
            }
            typeof (AppDomain)
                .GetMethod("SetLegacyCasPolicyEnabled", BindingFlags.NonPublic | BindingFlags.Instance)
                .Invoke(appDomain, null);
            appDomain.AssemblyResolve += (_, resolveEventArgs) =>
                dlls.ContainsKey(resolveEventArgs.Name)
                    ? Assembly.LoadFrom(dlls[resolveEventArgs.Name][0])
                    : null
                ;
        }
#endif
        #endregion
    }
}
  1. SetPrivateBinPath - 这个算是比较“正统”的方式。程序集通过 AssemblyResolve 事件将程序集文件读入内存后加载,但是磁盘上的dll并不会被锁定,可以被移动修改删除等等,这个特点可以用来做单文件版之类的东东。

  2. HACK_UPDATECONTEXTPROPERTY - 第一种Hack。来源于 AppDomain.AppendPrivatePath 方法的框架源码,其实就是利用反射把这个方法做的事做了一遍。该方法已经被M$放弃,因为这个方法会在程序集加载后改变程序集的行为(其实就是改变查找后续加载的托管dll的位置)。目前(.Net4.5.1)还是可以用的,但是已经被标记为“已过时”了,后续版本不知道什么时候就会取消了。

  3. HACK_SETLEGACYCASPOLICYENABLED - 第二种Hack。在 .Net2.0 的时候是可以直接用 Assembly.LoadFrom 系列加载程序集的,但是某个版本之后(好像就是2.0之后),对程序集信任的安全策略进行了改变,这个方法就只能加载完全信任的程序集了,而这个旧的策略就叫做 Legacy CAS Policy,2.0之后就默认被关掉了。这个Hack就是利用反射打开这个策略,然后 Assembly.LoadFrom 系列就又可以用了。同时也采用了“正统”的自行解析程序集并加载的事件来完成加载,这个就是要锁磁盘文件的了。

M$ 对 AppDomain.AppendPrivatePath 的替代推荐是涉及到 AppDomainSetup 的一系列东西,很麻烦,必须在 AppDomain 加载前设置好参数,但是当前程序已经在运行了所以这种方法对自定义查找托管dll路径的目的无效。

通常来说,不推荐采用 Hack 的方法,毕竟是非正规的途径,万一哪天 M$ 改了内部的实现就抓瞎了。

第一种方法可以手动加个文件锁,这样在“外观”上就跟框架的表现一样了。



注意:这些方法只适用于托管dll程序集,对 DllImport 特性引入的非托管 dll 不起作用。

你可能感兴趣的:(C#,AppDomain,PrivateBinPath,自定义dll路径)