AppDomain.Load() fails with FileNotFoundException

孤街醉人 提交于 2019-12-02 22:52:25
James Thurley

when you load an assembly into the AppDomain in that way, it is the current AppDomain's PrivateBinPath that is used to find the assembly.

For your example, when I added the following to my App.config it ran fine:

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

This is not very useful to you though.

What I did instead was to create a new assembly that contained the IPostPlugin and IPluginsHost interfaces, and also a class called Loader that looked like this:

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

I keep that new assembly in the application root, and it doesn't need to exist in the plugin directories (it can but won't be used as the application root will be searched first).

Then in the main AppDomain I did this instead:

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

So I create an instance of the known Loader type, and then get that to create the plugin instances from within the plug-in AppDomain. That way the PrivateBinPaths are used as you want them to be.

One other thing, the private bin paths can be relative so rather than adding d.FullName you could add pluginsDir + Path.DirectorySeparatorChar + d.Name to keep the final path list short. That's just my personal preference though! Hope this helps.

Thanks a lot to DedPicto and James Thurley ; I was able to implement a complete solution, I posted in this post.

I had the same problem as Emil Badh : if you try to return from "Loader" class an interface that represents a concrete class that is unknown in current AppDomain, you get a "Serialization Exception".

It is because the concrete type tries to be deserialized. The solution: I was able to return from "Loader" class a concrete type of a "custom proxy" and it works. See referenced post for details :

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

}

This proxy could be returned and use as if it was the real concrete type !

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!