CreateInstanceAndUnwrap 在另一个域中?

Posted

技术标签:

【中文标题】CreateInstanceAndUnwrap 在另一个域中?【英文标题】:CreateInstanceAndUnwrap in Another Domain? 【发布时间】:2014-07-15 14:13:26 【问题描述】:

由于某种原因,我目前在使用 CreateInstanceAndUnwrap 时遇到问题(之前工作过)。

我的流程是这样的:

我动态生成一些代码并通过 MEF 从子目录加载 DLL。然后,这些应用程序从这些 DLL 加载不同的部分(按需)。我必须更新我的代码,现在包含一个 AppDomainSetup,其中包含调用程序集的路径。

我正确地创建了新的 AppDomain——没有问题。当我尝试运行此代码时:

object runtime = domain.CreateInstanceAndUnwrap(
                typeof(CrossDomainApplication).Assembly.FullName,
                typeof(CrossDomainApplication).FullName);

我有很多问题 - 运行时(上面的变量)不再可以转换为 CrossDomainApplication 或 ICrossDomainApplication。

实际对象如下:

public class CrossDomainApplication : MarshalByRefObject, ICrossDomainApplication

界面如下:

public interface ICrossDomainApplication

    void Run(CrossDomainApplicationParameters parameters);

参数如下:

[Serializable]
public class CrossDomainApplicationParameters : MarshalByRefObject

    public object FactoryType  get; set; 
    public Type ApplicationType  get; set; 
    public string ModuleName  get; set; 
    public object[] Parameters  get; set; 

运行时的本机类型似乎是 MarshalByRefObject ——它不喜欢转换为其他任何东西。

对可能出现的问题有什么想法吗?

编辑:这是我运行时遇到的错误,如下所示:

            ICrossDomainApplication runtime = (ICrossDomainApplication)domain.CreateInstanceAndUnwrap(
                     typeof(CrossDomainApplication).Assembly.FullName,
                     typeof(CrossDomainApplication).FullName);

            //Exception before reaching here
            runtime.Run(parameters);

System.InvalidCastException:无法将透明代理转换为类型“Infrastructure.ICrossDomainApplication”。

这是我创建域时的样子:

        AppDomain domain = AppDomain.CreateDomain(
                   Guid.NewGuid().ToString(),
                   null,
                   new AppDomainSetup()
                   
                       ApplicationBase = GetPath(),
                       ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile,
                       ApplicationName = AppDomain.CurrentDomain.SetupInformation.ApplicationName,
                       LoaderOptimization = LoaderOptimization.MultiDomainHost
                   );               

GetPath() 看起来像这样:

    private string GetPath()
    
        Uri path = new Uri(Assembly.GetCallingAssembly().CodeBase);

        if (path.IsFile)
        
            path = new Uri(path, Path.GetDirectoryName(path.AbsolutePath));
        

        return path.LocalPath.Replace("%20", " ");
    

【问题讨论】:

可能就像 CLR 找不到程序集一样简单。不要跳过记录您得到的确切异常。 我似乎没有遇到任何异常——除了我上面的理想情况,我只是将它转换为所需的类型。我还附加了处理程序来记录 AppDomain.Current 以及新域上的事件回调。除非我尝试快速转换,否则没有任何异常或回调。 这是一个非常常见的插件问题。 ICrossDomainApplication 有 两个 定义。它们来自不同的程序集。要么是因为您复制了一个程序集,要么是因为您包含了两次源代码。 CLR 将它们视为独立且不兼容的类型。您必须确保在两个 appdomains 中加载具有唯一 ICrossDomainApplication 的完全相同的程序集。 我尝试通过使用 CreateInstanceFromAndUnwrap 从新的 AppDomain 解决它 - 仍然没有运气并且仍然存在问题。您是否有任何资源/链接/文章可能有助于使这一点更加明显或为我们提供如何“修复”它的示例? 【参考方案1】:

出于帮助其他穷人的愿望,我终于在尝试了很多其他组合后想通了。

我不得不改变一些东西...首先:

            AppDomain domain = AppDomain.CreateDomain(
                    Guid.NewGuid().ToString(),
                    AppDomain.CurrentDomain.Evidence,
                    new AppDomainSetup()
                    
                        ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase,
                        ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile,
                        LoaderOptimization = LoaderOptimization.MultiDomainHost,
                        PrivateBinPath = GetPrivateBin(AppDomain.CurrentDomain.SetupInformation.ApplicationBase)
                    );

PrivateBinPath 是这里的真正技巧,它使其他一切(最终)开始工作。 PrivateBinPath(阅读文档)绝对需要是相对路径。如果您有一个名为“程序集”的子文件夹,那么 PrivateBinPath 应该是“程序集”。如果在它前面加上 \ 或绝对路径,它将不起作用。

通过这样做,它导致 AssemblyResolve 事件最终触发。我通过以下方式做到了这一点(在创建新的子 AppDomain 之前):

            AppDomain.CurrentDomain.AssemblyResolve += ResolveAssembly;

ResolveAssembly 方法如下所示:

    private Assembly ResolveAssembly(object sender, ResolveEventArgs args)
    
        var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();

        foreach (var assembly in loadedAssemblies)
        
            if (assembly.FullName == args.Name)
            
                return assembly;
            
        

        return null;
    

使用 Linq 可以更轻松地编写该方法,但我保持这种方式是为了尝试让我自己更清楚地了解正在解析和加载的内容以进行调试。

并且,始终确保断开事件的连接(如果您一次加载 AppDomain):

            AppDomain.CurrentDomain.AssemblyResolve -= ResolveAssembly;

这解决了一切。感谢所有帮助过的人!

【讨论】:

什么是 GetPrivateBin 方法,它有什么作用? 终于成功了!谢谢!这么多不眠之夜【参考方案2】:

这不是答案,只是示例代码分享

嗯,会不会是别的?此示例与您所描述的不完全是 1:1 有效。

#region

using System;
using System.Reflection;
using System.Threading;

#endregion

internal class Program

    #region Methods

    private static void Main(string[] args)
    
        // Get and display the friendly name of the default AppDomain. 
        string callingDomainName = Thread.GetDomain().FriendlyName;
        Console.WriteLine(callingDomainName);

        // Get and display the full name of the EXE assembly. 
        string exeAssembly = Assembly.GetEntryAssembly().FullName;
        Console.WriteLine(exeAssembly);

        // Construct and initialize settings for a second AppDomain.
        var ads = new AppDomainSetup
                  
                      ApplicationBase = Environment.CurrentDirectory,
                      DisallowBindingRedirects = false,
                      DisallowCodeDownload = true,
                      ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile
                  ;

        // Create the second AppDomain.
        AppDomain ad2 = AppDomain.CreateDomain("AD #2", null, ads);

        // Create an instance of MarshalbyRefType in the second AppDomain.  
        // A proxy to the object is returned.
        var mbrt = (MarshalByRefType)ad2.CreateInstanceAndUnwrap(exeAssembly, typeof(MarshalByRefType).FullName);

        // Call a method on the object via the proxy, passing the  
        // default AppDomain's friendly name in as a parameter.
        mbrt.SomeMethod(new MarshalByRefParameterModuleName =  callingDomainName);

        // Unload the second AppDomain. This deletes its object and  
        // invalidates the proxy object.
        AppDomain.Unload(ad2);
        try
        
            // Call the method again. Note that this time it fails  
            // because the second AppDomain was unloaded.
            mbrt.SomeMethod(new MarshalByRefParameter  ModuleName = callingDomainName );
            Console.WriteLine("Successful call.");
        
        catch (AppDomainUnloadedException)
        
            Console.WriteLine("Failed call; this is expected.");
        
    


    #endregion


public interface IMarshalByRefTypeInterface

    void SomeMethod(MarshalByRefParameter parameter);


public class MarshalByRefType : MarshalByRefObject, IMarshalByRefTypeInterface

    //  Call this method via a proxy. 

    #region Public Methods and Operators

    public void SomeMethod(MarshalByRefParameter parameter)
    
        // Get this AppDomain's settings and display some of them.
        AppDomainSetup ads = AppDomain.CurrentDomain.SetupInformation;
        Console.WriteLine("AppName=0, AppBase=1, ConfigFile=2", ads.ApplicationName, ads.ApplicationBase, ads.ConfigurationFile);

        // Display the name of the calling AppDomain and the name  
        // of the second domain. 
        // NOTE: The application's thread has transitioned between  
        // AppDomains.
        Console.WriteLine("Calling from '0' to '1'.", parameter.ModuleName, Thread.GetDomain().FriendlyName);
    

    #endregion


[Serializable]
public class MarshalByRefParameter : MarshalByRefObject

    public string ModuleName  get; set; 

但是,我只是在抓取入口程序集,而您有一个动态编译的程序集。什么错误以及你实际得到了什么?

【讨论】:

请参见上文(进行编辑)。我并没有真正得到任何错误——我只是无法将它转换为我需要的类型——在你的示例中,这将是 IMarshalByRefTypeInterface。当“运行时”只是对象类型(没有强制转换)时,我记录了 runtime.GetType() 并且它总是想返回 MarshalByRefObject...

以上是关于CreateInstanceAndUnwrap 在另一个域中?的主要内容,如果未能解决你的问题,请参考以下文章

AppDomain.CreateInstanceAndUnwrap 在网站中不起作用

CreateInstanceAndUnwrap 和域

AppDomain CreateInstanceAndUnwrap:类型未标记为可序列化

使用 AppDomain.CreateInstanceAndUnwrap 创建类型 T 的实例,然后提前绑定到类型 T 的方法

ASP.NET:CreateDomain(...).CreateInstanceAndUnwrap(...) 或 Assembly.LoadFrom(...).GetExportedTypes() 抛

如果库的名称已更改,AppDomain.CreateInstanceAndUnwrap 将失败