3

在我正在处理的一个项目中,我创建了一个AppDomain并告诉它加载当前正在执行的程序集。然后我调用Activator.CreateInstanceFrom来创建一个内部类(类型MyType)的编组实例(你可以看到它也使用相同的装配位置):

        ObjectHandle handle = Activator.CreateInstanceFrom(
            Factory.operatingDomain,
            Assembly.GetExecutingAssembly().Location,
            typeof(MyType).FullName,
            false,
            BindingFlags.NonPublic | BindingFlags.Instance,
            null,
            new object[] { config },
            null,
            null);

当我对这个程序集进行单元测试时,一切正常,但是在 ASP.NET 调试环境中使用它时,它会抛出一个MissingMethodException. 我花时间创建了我自己的扩展类,Binder看看会发生什么。唯一的区别似乎是在调试环境中,传入的参数 ( config) 从位于常规目录的程序集中ParameterInfo获取其类型,而对象从位于 ASP.NET 临时文件夹中的程序集获取其类型。

即使我通过了typeof(config).Assembly.Location,我也会得到相同的结果。所以它是框架内部的东西。

除了保留一个利基使用的内部可访问的自定义绑定器之外,我能做些什么来纠正这个问题,当参数类型匹配时返回 true FullName

更新

我尝试传递从应用程序域加载的程序集中获取的程序集的位置,但仍然没有骰子。我的自定义活页夹也不起作用——它需要我实现 Binder.ChangeType 我发现我什至无法从一种类型转换为另一种类型(尽管如前所述,它们之间的唯一区别是位置大会的。)

更新 2

我尝试了以下代码以确保应用程序域从正确的位置加载正确的程序集:

        if (binPath != String.Empty)
        {
            Factory.operatingDomain.SetData("assemblyLocation", Assembly.GetExecutingAssembly().Location);

            Factory.operatingDomain.DoCallBack(() =>
            {
                String location = AppDomain.CurrentDomain.GetData("assemblyLocation").ToString();
                String filename = System.IO.Path.GetFileName(location);
                List<String> paths = AppDomain.CurrentDomain.RelativeSearchPath.Split(';').ToList();

                foreach (String path in paths.ToArray())
                {
                    paths.Remove(path);
                    paths.AddRange(System.IO.Directory.GetFiles(path, filename));
                }

                Assembly.LoadFrom(paths[0]);
            });
        }

这是正确的!它加载了正确的程序集!但在这里,稍晚一点:

String location = Factory.operatingDomain
                         .GetAssemblies()
                         .Single(a => a.FullName == Assembly.GetExecutingAssembly().FullName)
                         .Location;

这将返回 .NET 临时文件夹路径。那是不对的。我会认为这是框架中的一个错误!

4

2 回答 2

1

你的应用是什么?ASP.NET 应用程序?如果是,您应该知道名为程序集影子副本的野兽(请参阅http://msdn.microsoft.com/en-us/library/ms404279.aspx)。这个东西在 ASP.NET 中默认使用。简而言之,当您使用位于 bin 文件夹中的程序集以及 ASP.NET 应用程序程序集时,它会被复制到 ASP.NET 临时文件夹并从那里加载。

为确保这是导致您遇到的行为的原因,请尝试将定义“config”参数类型的程序集放入某个单独的文件夹中,并通过捕获 AppDomain_AssemblyResolve 事件来加载程序集,或者在使用之前手动加载它。这样,您将忽略程序集 shadowcopy 行为。

于 2013-07-31T17:42:28.503 回答
1

当您向应用程序域询问其程序集位置之一时,.NET 框架似乎无法正确解析程序集位置。为了解决这个问题,我不得不自己探测程序集:

        // This code I was already using:
        String binPath = String.Empty;

        if (!String.IsNullOrEmpty(AppDomain.CurrentDomain.RelativeSearchPath))
        {
            String[] paths = AppDomain.CurrentDomain.RelativeSearchPath.Split(';');

            for (var i = 0; i < paths.Length; i++)
            {
                paths[i].Remove(0, AppDomain.CurrentDomain.BaseDirectory.Length);
            }

            binPath = String.Join(";", paths);
        }

        Factory.operatingDomain = AppDomain.CreateDomain("my_remote_domain", null,
            new AppDomainSetup
            {
                ApplicationBase = AppDomain.CurrentDomain.BaseDirectory,
                // Sometimes, like in a web app, your bin folder is not the same
                // as the base dir.
                PrivateBinPath = binPath
            });

        // The new code that solved my problem begins here:
        if (binPath != String.Empty)
        {
            Factory.operatingDomain.SetData("assemblyLocation", Assembly.GetExecutingAssembly().Location);

            Factory.operatingDomain.DoCallBack(() =>
            {
                String location = AppDomain.CurrentDomain.GetData("assemblyLocation").ToString();
                String filename = System.IO.Path.GetFileName(location);
                List<String> paths = AppDomain.CurrentDomain.RelativeSearchPath.Split(';').ToList();

                foreach (String path in paths.ToArray())
                {
                    paths.Remove(path);
                    paths.AddRange(System.IO.Directory.GetFiles(path, filename));
                }

                Assembly.LoadFrom(paths[0]);

                AppDomain.CurrentDomain.SetData("assemblyLocation", paths[0]);
            });

            Factory.realAssemblyLocation = Factory.operatingDomain.GetData("assemblyLocation").ToString();
        }
        else
        {
            Factory.operatingDomain.Load(Assembly.GetExecutingAssembly().FullName);
        }

然后当我尝试创建远程对象的实例时,我会这样做:

        String location = Factory.realAssemblyLocation ?? Assembly.GetExecutingAssembly().Location;

        ObjectHandle handle = Activator.CreateInstanceFrom(
            Factory.operatingDomain,
            location,
            typeof(MyType).FullName,
            false,
            BindingFlags.NonPublic | BindingFlags.Instance,
            null,
            new object[] { config },
            null,
            null);

2014 年 4 月 16 日更新

事实证明,我真正需要做的就是将新 AppDomain 的 ApplicationBase 设置为包含当前正在执行的程序集的目录。简单得多:)

于 2013-07-31T17:34:10.433 回答