簡體   English   中英

如何在兩個.NET AppDomains之間傳遞未知類型?

[英]How to pass an unknown type between two .NET AppDomains?

我有一個.NET應用程序,其中單獨的AppDomains中的程序集必須共享按值傳遞的序列化對象。

兩個程序集都引用一個共享程序集,該程序集定義服務器類的基類,還定義將在域之間傳遞的entiy類型的基類:

public abstract class ServerBase : MarshalByRefObject
{
    public abstract EntityBase GetEntity();
}

[Serializable]
public abstract class EntityBase
{
}

服務器程序集定義服務器類和實體類型的具體實現:

public class Server : ServerBase
{
    public override EntityBase GetEntity()
    {
        return new EntityItem();
    }
}

[Serializable]
public class EntityItem : EntityBase
{
}

客戶端程序集創建將托管服務器程序集的AppDomain ,並使用服務器類的實例來請求實體類型的具體實例:

class Program
{
    static void Main()
    {
        var domain = AppDomain.CreateDomain("Server");

        var server = (ServerBase)Activator.CreateInstanceFrom(
            domain,
            @"..\..\..\Server\bin\Debug\Server.dll",
            "Server.Server").Unwrap();

        var entity = server.GetEntity();
    }
}

不幸的是,這種方法因SerializationException而失敗,因為客戶端程序集並不直接了解要返回的具體類型。

我已經讀過使用二進制序列化時.NET Remoting支持未知類型,但我不確定這是否適用於我的設置或如何配置它。

或者,是否有任何其他方法可以將未知的具體類型從服務器傳遞到客戶端,因為客戶端只需要通過其已知的基類接口訪問它。

謝謝你的建議,

蒂姆

編輯:

根據Hans的要求,這里是異常消息和堆棧跟蹤。

SerializationException
Type is not resolved for member 'Server.EntityItem,Server, Version=1.0.0.0,Culture=neutral, PublicKeyToken=null'.

at Interop.ServerBase.GetEntity()
at Client.Program.Main() in C:\Users\Tim\Visual Studio .Net\Solutions\MEF Testbed\Client\Program.cs:line 12
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()

這失敗了,因為CLR只是沒有希望找到程序集,你把它放在一個不可靠的位置。 通過添加對程序集的引用並將其Copy Local屬性設置為True來簡單地解決此問題,以便將server.dll復制到您的構建目錄中。 如果你想保持它在那里,那么你將必須實現AppDomain.AssemblyResolve來幫助CLR找到它。

我回答了一個相關的問題:

你會說.Net remot依賴於緊耦合嗎?

我認為我有一個解決方案,感謝當前的帖子,這個及其接受的答案: AppDomain.Load()因FileNotFoundException而失敗

首先,我認為您應該使用接口代替基類作為您的處理程序。 接口應該在基類上聲明,然后你只使用它。

解決方案 :在共享程序集中創建一個具體類型,該類型繼承自MarshalByRefObject ,並實現您的服務器接口。 此具體類型是可以在AppDomains之間進行序列化/反序列化的代理 ,因為您的主應用程序知道其定義。 您不再需要從類ServerBase MarshalByRefObject繼承。

  // - MUST be serializable, and MUSNT'T use unknown types for main App
  [Serializable]
  public class Query 
  {
     ...
  }

  public interface IServerBase
   {  
       string Execute(Query q);
   }

  public abstract class ServerBase : IServerBase
  {
       public abstract string Execute(Query q);
  }

// Our CUSTOM PROXY: the concrete type which will be known from main App
[Serializable]
public class ServerBaseProxy : MarshalByRefObject, IServerBase
{
    private IServerBase _hostedServer;

    /// <summary>
    /// cstor with no parameters for deserialization
    /// </summary>
    public ServerBaseProxy ()
    {

    }

    /// <summary>
    /// Internal constructor to use when you write "new ServerBaseProxy"
    /// </summary>
    /// <param name="name"></param>
    public ServerBaseProxy(IServerBase hostedServer)
    {
        _hostedServer = hostedServer;
    }      

    public string Execute(Query q)
    {
        return(_hostedServer.Execute(q));
    }

}

注意 :為了發送和接收數據,在IServer中聲明的每個類型都必須是可序列化的(例如:使用[Serializable]屬性)

然后,您可以使用上一個鏈接“ Loader class ”中找到的方法。 這是我修改的Loader類,它在共享程序集中實例化具體類型,並為每個插件返回一個代理:

  /// <summary>
/// Source: https://stackoverflow.com/questions/16367032/appdomain-load-fails-with-filenotfoundexception
/// </summary>
public class Loader : MarshalByRefObject
{

    /// <summary>
    /// Load plugins
    /// </summary>
    /// <param name="assemblyName"></param>
    /// <returns></returns>
    public IPlugin[] LoadPlugins(string assemblyPath)
    {
        List<PluginProxy> proxyList = new List<PluginProxy>(); // a proxy could be transfered outsite AppDomain, but not the plugin itself ! https://stackoverflow.com/questions/4185816/how-to-pass-an-unknown-type-between-two-net-appdomains

        var assemb = Assembly.LoadFrom(assemblyPath); // use Assembly.Load if you want to use an Assembly name and not a path

        var types = from type in assemb.GetTypes()
                    where typeof(IPlugin).IsAssignableFrom(type)
                    select type;

        var instances = types.Select(
            v => (IPlugin)Activator.CreateInstance(v)).ToArray();

        foreach (IPlugin instance in instances)
        {
            proxyList.Add(new PluginProxy(instance));
        }
        return (proxyList.ToArray());
    }

}

然后, 在主應用程序中 ,我還使用“dedpichto”和“James Thurley”的代碼來創建AppDomain,instanciate並調用Loader類。 然后我可以使用我的代理,因為它是我的插件,因為.NET由於MarshalByRefObject而創建了一個“透明代理”:

   /// <see cref="https://stackoverflow.com/questions/4185816/how-to-pass-an-unknown-type-between-two-net-appdomains"/>
public class PlugInLoader
{       

    /// <summary>
    /// https://stackoverflow.com/questions/16367032/appdomain-load-fails-with-filenotfoundexception
    /// </summary>
    public void LoadPlugins(string pluginsDir)
    {
        // List all directories where plugins could be
        var privatePath = "";
        var paths = new List<string>();
        List<DirectoryInfo> dirs = new DirectoryInfo(pluginsDir).GetDirectories().ToList();
        dirs.Add(new DirectoryInfo(pluginsDir));
        foreach (DirectoryInfo d in dirs)
            privatePath += d.FullName + ";";
        if (privatePath.Length > 1) privatePath = privatePath.Substring(0, privatePath.Length - 1);

        // Create AppDomain !
        AppDomainSetup appDomainSetup = AppDomain.CurrentDomain.SetupInformation;
        appDomainSetup.PrivateBinPath = privatePath; 

        Evidence evidence = AppDomain.CurrentDomain.Evidence;
        AppDomain sandbox = AppDomain.CreateDomain("sandbox_" + Guid.NewGuid(), evidence, appDomainSetup);

        try
        {
            // Create an instance of "Loader" class of the shared assembly, that is referenced in current main App
            sandbox.Load(typeof(Loader).Assembly.FullName);

            Loader loader = (Loader)Activator.CreateInstance(
                sandbox,
                typeof(Loader).Assembly.FullName,
                typeof(Loader).FullName,
                false,
                BindingFlags.Public | BindingFlags.Instance,
                null,
                null,
                null,
                null).Unwrap();

            // Invoke loader in shared assembly to instanciate concrete types. As long as concrete types are unknown from here, they CANNOT be received by Serialization, so we use the concrete Proxy type.

            foreach (var d in dirs)
            {
                var files = d.GetFiles("*.dll");
                foreach (var f in files)
                {
                    // This array does not contains concrete real types, but concrete types of "my custom Proxy" which implements IPlugin. And here, we are outside their AppDomain, so "my custom Proxy" is under the form of a .NET "transparent proxy" (we can see in debug mode) generated my MarshalByRefObject.
                    IPlugin[] plugins = loader.LoadPlugins(f.FullName);
                    foreach (IPlugin plugin in plugins)
                    {
                        // The custom proxy methods can be invoked ! 
                        string n = plugin.Name.ToString();
                        PluginResult result = plugin.Execute(new PluginParameters(), new PluginQuery() { Arguments = "", Command = "ENUMERATE", QueryType = PluginQueryTypeEnum.Enumerate_Capabilities });
                        Debug.WriteLine(n);
                    }                    
                }
            }
        }
        finally
        {
            AppDomain.Unload(sandbox);
        }
  }
}

找到一個可行的解決方案真的很難,但我們最終可以將我們具體類型的自定義代理實例保存在另一個AppDomain中,並使用它們就好像它們在主應用程序中可用一樣。

希望這(巨大的答案)有所幫助!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM