2010-11-15 76 views
5

我有一個.NET應用程序,其中獨立AppDomain中的程序集必須共享按值傳遞的序列化對象。如何在兩個.NET AppDomains之間傳遞未知類型?

兩個組件引用共享組件定義的基類的服務器類,並且還定義了用於將域之間傳遞的entiy類型的基類:

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

[Serializable] 
public abstract class EntityBase 
{ 
} 

服務器組件限定服務器類和實體類型的具體implemetation:

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(); 
    } 
} 

Unfortnately,這種方法失敗了SerializationException,因爲客戶端組件具有被返回的具體類型沒有直接的知識。

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

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

謝謝你的建議,

編輯:

按照要求由漢斯,這裏是異常消息和堆棧跟蹤。

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() 

回答

2

這樣做會失敗,因爲CLR只是沒有希望能夠找到程序集,所以您將它放在一個不可顯示的位置。通過添加對程序集的引用並將其「複製本地」屬性設置爲「True」,以便將server.dll複製到構建目錄中,從而簡化解決此問題。如果你想保持它的位置,那麼你必須實現AppDomain.AssemblyResolve來幫助CLR找到它。

+0

感謝漢斯。您的建議非常合理,但我需要確保它不會引入額外的問題。我所描述的是沙箱方案的一部分,所以我不希望CLR將未知程序集加載到主AppDomain(它具有更廣泛的權限),如果它可能會危及安全。你有看法嗎?再次感謝。 – 2010-11-15 15:47:13

+0

使用一個接口,在其自己的程序集中聲明並由兩者引用。 – 2010-11-15 16:12:21

+0

好吧,我改變了EntityBase類作爲一個接口,它和以前一樣,駐留在共享程序集中,但是異常仍然被拋出(可能是因爲你已經聲明 - 傳遞的對象是未知的客戶)。 – 2010-11-15 16:39:11

0

我想我有一個解決方案由於現在的職務,而這一次和接受的答案:AppDomain.Load() fails with FileNotFoundException

第一件事,我認爲你應該使用一個接口來代替基類來作爲你的處理程序。接口應該在基類中聲明,然後你只使用它。

解決方案:在共享程序集中創建一個具體類型,它繼承自MarshalByRefObject,並實現您的服務器接口。這個具體類型是代理,可以在AppDomain之間進行序列化/反序列化,因爲您的主應用程序知道它的定義。你不再需要在你的課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」。 這是我的改性裝載機類實例化在共享組件具體類型,並返回一個代理用於每個插件:

/// <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中實例化並使用它們,就好像它們在主應用程序中可用一樣。

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

相關問題