AppDomain.Load() завершается с ошибкой FileNotFoundException

Я пытаюсь загрузить свой плагин dll в отдельный AppDomain, но метод Load() завершается с ошибкой FileNotFoundException. Более того, похоже, что установка свойства PrivateBinPath для AppDomainSetup не имеет никакого эффекта, потому что в журнале я вижу "Initial PrivatePath = NULL". Все плагины имеют строгое имя. Обычно каждый плагин хранится в [Application startp path] \ postplugins \ [plugindir]. Если я помещаю подкаталоги плагинов в каталог [Application startp path], все работает. Я также попытался изменить свойство AppBase вручную, но оно не изменилось.
Вот код:

public void LoadPostPlugins(IPluginsHost host, string pluginsDir)
    {
        _Host = host;
        var privatePath = "";
        var paths = new List<string>();
        //build PrivateBinPath
        var dirs = new DirectoryInfo(pluginsDir).GetDirectories();
        foreach (var d in dirs)
        {
            privatePath += d.FullName;
            privatePath += ";";
        }
        if (privatePath.Length > 1) privatePath = privatePath.Substring(0, privatePath.Length - 1);
        //create new domain
        var appDomainSetup = new AppDomainSetup { PrivateBinPath = privatePath };
        Evidence evidence = AppDomain.CurrentDomain.Evidence;
        var sandbox = AppDomain.CreateDomain("sandbox_" + Guid.NewGuid(), evidence, appDomainSetup);
        try
        {
            foreach (var d in dirs)
            {
                var files = d.GetFiles("*.dll");
                foreach (var f in files)
                {
                    try
                    {
                        //try to load dll - here I get FileNotFoundException
                        var ass = sandbox.Load(AssemblyName.GetAssemblyName(f.FullName));
                        var f1 = f;
                        paths.AddRange(from type in ass.GetTypes()
                                       select type.GetInterface("PluginsCore.IPostPlugin")
                                       into iface
                                       where iface != null
                                       select f1.FullName);
                    }
                    catch (FileNotFoundException ex)
                    {
                        Debug.WriteLine(ex);
                    }
                }
            }
        }
        finally
        {
            AppDomain.Unload(sandbox);
        }
        foreach (var plugin in from p in paths
                               select Assembly.LoadFrom(p)
                                   into ass
                                   select
                                       ass.GetTypes().FirstOrDefault(t => t.GetInterface("PluginsCore.IPostPlugin") != null)
                                       into type
                                       where type != null
                                       select (IPostPlugin)Activator.CreateInstance(type))
        {
            plugin.Init(host);
            plugin.GotPostsPartial += plugin_GotPostsPartial;
            plugin.GotPostsFull += plugin_GotPostsFull;
            plugin.PostPerformed += plugin_PostPerformed;
            _PostPlugins.Add(plugin);
        }
    }

А вот и журнал:

'FBTest.vshost.exe' (Managed (v4.0.30319)): Loaded 'D:\VS2010Projects\PNotes - NET\pnfacebook\FBTest\bin\Debug\postplugins\pnfacebook\pnfacebook.dll', Symbols loaded.
A first chance exception of type 'System.IO.FileNotFoundException' occurred in FBTest.exe
System.IO.FileNotFoundException: Could not load file or assembly 'pnfacebook, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9e2a2192d22aadc7' or one of its dependencies. The system cannot find the file specified.
File name: 'pnfacebook, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9e2a2192d22aadc7'
   at System.Reflection.RuntimeAssembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
   at System.Reflection.RuntimeAssembly.nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
   at System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(AssemblyName assemblyRef, Evidence assemblySecurity, RuntimeAssembly reqAssembly, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
   at System.Reflection.RuntimeAssembly.InternalLoad(String assemblyString, Evidence assemblySecurity, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean forIntrospection)
   at System.Reflection.RuntimeAssembly.InternalLoad(String assemblyString, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection)
   at System.Reflection.Assembly.Load(String assemblyString)
   at System.UnitySerializationHolder.GetRealObject(StreamingContext context)

   at System.AppDomain.Load(AssemblyName assemblyRef)
   at PNotes.NET.PNPlugins.LoadPostPlugins(IPluginsHost host, String pluginsDir) in D:\VS2010Projects\PNotes - NET\pnfacebook\FBTest\PNPlugins.cs:line 71


=== Pre-bind state information ===
LOG: User = ANDREYHP\Andrey
LOG: DisplayName = pnfacebook, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9e2a2192d22aadc7
 (Fully-specified)
LOG: Appbase = file:///D:/VS2010Projects/PNotes - NET/pnfacebook/FBTest/bin/Debug/
LOG: Initial PrivatePath = NULL
Calling assembly : (Unknown).
===
LOG: This bind starts in default load context.
LOG: No application configuration file found.
LOG: Using host configuration file: 
LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework\v4.0.30319\config\machine.config.
LOG: Post-policy reference: pnfacebook, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9e2a2192d22aadc7
LOG: Attempting download of new URL file:///D:/VS2010Projects/PNotes - NET/pnfacebook/FBTest/bin/Debug/pnfacebook.DLL.
LOG: Attempting download of new URL file:///D:/VS2010Projects/PNotes - NET/pnfacebook/FBTest/bin/Debug/pnfacebook/pnfacebook.DLL.
LOG: Attempting download of new URL file:///D:/VS2010Projects/PNotes - NET/pnfacebook/FBTest/bin/Debug/pnfacebook.EXE.
LOG: Attempting download of new URL file:///D:/VS2010Projects/PNotes - NET/pnfacebook/FBTest/bin/Debug/pnfacebook/pnfacebook.EXE.

2 ответа

Решение

Когда вы таким образом загружаете сборку в AppDomain, для поиска сборки используется текущий PrivateBinPath AppDomain.

Для вашего примера, когда я добавил следующее в свой App.config, оно работало нормально:

<runtime>
  <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
    <probing privatePath="[PATH_TO_PLUGIN]"/>
  </assemblyBinding>
</runtime>

Это не очень полезно для вас, хотя.

Вместо этого я создал новую сборку, которая содержала интерфейсы IPostPlugin и IPluginsHost, а также класс с именем Loader, который выглядел следующим образом:

public class Loader : MarshalByRefObject
{
    public IPostPlugin[] LoadPlugins(string assemblyName)
    {
        var assemb = Assembly.Load(assemblyName);

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

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

        return instances;
    }
}

Я сохраняю эту новую сборку в корне приложения, и она не должна существовать в каталогах плагинов (она может быть использована, но не будет использоваться, так как сначала будет выполняться поиск в корне приложения).

Затем в основном AppDomain я сделал это вместо:

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

var plugins = loader.LoadPlugins(AssemblyName.GetAssemblyName(f.FullName).FullName);

foreach (var p in plugins)
{
    p.Init(this);
}

_PostPlugins.AddRange(plugins);

Итак, я создаю экземпляр известного типа Loader, а затем получаю его для создания экземпляров плагина из внутри плагина AppDomain. Таким образом, PrivateBinPaths используются так, как вы хотите.

Еще одна вещь, пути к приватным бинам могут быть относительными, а не добавлением d.FullName Вы могли бы добавить pluginsDir + Path.DirectorySeparatorChar + d.Name сохранить окончательный список путей коротким. Это только мое личное предпочтение, хотя! Надеюсь это поможет.

Большое спасибо ДедПикто и Джеймсу Терли; Мне удалось реализовать полное решение, я разместил в этом посте.

У меня была та же проблема, что и у Эмиля Бада: если вы попытаетесь вернуть из класса "Loader" интерфейс, представляющий конкретный класс, который неизвестен в текущем AppDomain, вы получите "Исключение сериализации".

Это потому, что конкретный тип пытается быть десериализованным. Решение: я смог вернуть из класса "Loader" конкретный тип "пользовательского прокси", и он работает. Смотрите ссылочный пост для деталей:

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

}

Этот прокси может быть возвращен и использован, как если бы это был настоящий конкретный тип!

Другие вопросы по тегам