Как передать неизвестный тип между двумя доменами приложений .NET? - PullRequest
5 голосов
/ 15 ноября 2010

У меня есть приложение .NET, в котором сборки в отдельных доменах приложений должны совместно использовать сериализованные объекты, которые передаются по значению.

Обе сборки ссылаются на общую сборку, которая определяет базовый класс для класса сервера, а также определяет базовый класс для типа 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 поддерживает неизвестные типы при использовании двоичной сериализации, но я не уверен, применимо ли это к моей установке или как ее настроить.

В качестве альтернативы, есть ли другой способ передачи неизвестного конкретного типа от сервера к клиенту, учитывая, что клиенту нужен только доступ к нему через его известный интерфейс базового класса.

Спасибо за ваш совет,

Тим

EDIT:

По просьбе Ганса, вот сообщение об исключении и трассировка стека.

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

Ответы [ 3 ]

2 голосов
/ 15 ноября 2010

Это не получится, потому что у CLR просто нет надежды на то, что он сможет найти сборку, вы помещаете ее в недоступное для поиска место.Чтобы решить эту проблему, добавьте ссылку на сборку и установите для ее свойства Copy Local значение True, чтобы server.dll копировался в каталог сборки.Если вы хотите сохранить его там, где он есть, вам придется реализовать AppDomain.AssemblyResolve, чтобы помочь CLR найти его.

1 голос
/ 15 ноября 2010
0 голосов
/ 22 февраля 2018

Я думаю, что у меня есть решение благодаря текущему посту, и этот и его принятый ответ: Сбой AppDomain.Load () с FileNotFoundException

Во-первых, я думаю, что вы должны использовать интерфейс вместо базового класса, чтобы быть вашим обработчиком. Интерфейс должен быть объявлен в базовом классе, и тогда вы только используете его.

Решение : создайте конкретный тип в общей сборке, который наследуется от MarshalByRefObject и реализует интерфейс вашего сервера. Этот конкретный тип - прокси , который может быть сериализован / десериализован между доменами приложений, потому что ваше основное приложение знает его определение. Вам больше не нужно наследовать от MarshalByRefObject в вашем классе ServerBase.

  // - 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, который создает конкретный тип в общей сборке и возвращает прокси для каждого плагина:

  /// <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, создания экземпляра и вызова класса Loader. Затем я могу использовать мой Прокси, как это было моим плагином, потому что .NET создает «прозрачный прокси» из-за MarshalByRefObject:

   /// <see cref="/3837937/kak-peredat-neizvestnyi-tip-mezhdu-dvumya-domenami-prilozhenii-net"/>
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);
        }
  }
}

Действительно трудно найти работающее решение, но мы, наконец, можем сохранить экземпляры пользовательских прокси-серверов наших конкретных типов в другом домене приложений и использовать их так, как если бы они были доступны в основном приложении.

Надеюсь, это (огромный ответ) поможет!

...