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

I have a .NET application in which assemblies in separate AppDomains must share serialized objects that are passed by value.

Both assemblies reference the general assembly, which defines the base class for the server class, and also defines the base class for the entiy type, which will be passed between domains:

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

The server assembly defines the server class and the specific implementation of the entity type:

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

The client assembly creates the AppDomain , which will host the server assembly, and uses an instance of the server class to request a specific instance of the entity type:

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

Unfortunately, this approach fails with a SerializationException because the client assembly does not have direct knowledge of the returned specific type.

I read that .NET remoting supports unknown types when using binary serialization, but I'm not sure if this applies to my setup or how to configure it.

Alternatively, is there any other way to transfer an unknown specific type from the server to the client, given that the client only needs access to it through its well-known base class interface.

Thank you for your advice,

Tim

EDIT:

At the request of Hans, there is an exception message and a stack trace.

 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() 
+5
remoting appdomain
source share
3 answers

I asked a related question a while ago:

Could you say that remote access through the network depends on hard connectivity?

+1
source share

This fails because the CLR simply has no hope of finding the assembly; you put it in an unjustified place. It is trivial to solve this problem by adding a reference to the assembly and setting the Copy Local property for it to True so that server.dll is copied to your assembly directory. If you want to keep it where it is, you will have to implement AppDomain.AssemblyResolve to help the CLR find it.

+2
source share

I think I have a solution thanks to the current post, and this and its accepted answer: AppDomain.Load () error with FileNotFoundException

First, I think you should use an interface instead of the base class for your handler. The interface must be declared in the base class, and then you use it only.

Solution : create a specific type in the general assembly, which inherits from MarshalByRefObject and implements your server interface. This particular type is a proxy that can be serialized / deserialized between AppDomains because your main application knows its definition. You no longer need to inherit from MarshalByRefObject in your ServerBase class.

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

Note : for sending and receiving data, each type declared in IServer must be serializable (for example: with the [Serializable] attribute)

Then you can use the method found in the previous link “ Loader Class ”. Here is my modified Loader class that creates a specific type in the general assembly and returns a proxy for each plugin:

  /// <summary> /// Source: /questions/687073/appdomainload-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()); } } 

Then in the main application , I also use the code "dedpichto" and "James Thurley" to create the AppDomain, initialize and call the Loader class. Then I can use my proxy server, because it was my plugin, because .NET creates a "transparent proxy" because of MarshalByRefObject :

  /// <see cref="https://stackoverflow.com/questions/4185816/how-to-pass-an-unknown-type-between-two-net-appdomains"/> public class PlugInLoader { /// <summary> /// /questions/687073/appdomainload-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); } } } 

It is very difficult to find a working solution, but we can finally store instances of our specific proxy server types embedded in another AppDomain and use them as if they were available in the main application.

Hope this (huge answer) helps!

0
source share

All Articles