tags:

views:

173

answers:

2

Hi:

I am building a program that uses a very simple plugin system. This is the code I'm using to load the possible plugins:

  public interface IPlugin
  {
    string Name { get; }
    string Description { get; }
    bool Execute(System.Windows.Forms.IWin32Window parent);
  }


  private void loadPlugins()
  {
    int idx = 0;
    string[] pluginFolders = getPluginFolders();
    Array.ForEach(pluginFolders, folder =>
    {
      string[] pluginFiles = getPluginFiles(folder);
      Array.ForEach(pluginFiles, file =>
      {
        try
        {
          System.Reflection.Assembly assembly = System.Reflection.Assembly.LoadFile(file);
          Array.ForEach(assembly.GetTypes(), type =>
          {
            if(type.GetInterface("PluginExecutor.IPlugin") != null)
            {
              IPlugin plugin = assembly.CreateInstance(type.ToString()) as IPlugin;
              if(plugin != null)
                lista.Add(new PluginItem(plugin.Name, plugin.Description, file, plugin));
            }
          });
        }
        catch(Exception) { }
      });
    });
  }

When the user selects a particular plugin from the list, I launch the plugin's Execute method. So far, so good! As you can see the plugins are loaded from a folder, and within the folder are several dll's that are needed but the plugin. My problem is that I can't get the plugin to 'see' the dlls, it just searches the launching applications startup folder, but not the folder where the plugin was loaded from.

I have tried several methods: 1. Changing the Current Directory to the plugins folder. 2. Using an inter-op call to SetDllDirectory 3. Adding an entry in the registry to point to a folder where I want it to look (see code below)

None of these methods work. What am I missing? As I load the dll plugin dynamically, it does not seem to obey any of the above mentioned methods. What else can I try?

Regards, MartinH.

//HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths
Microsoft.Win32.RegistryKey appPaths = Microsoft.Win32.Registry.LocalMachine.CreateSubKey(
  string.Format(
    @"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\{0}",
     System.IO.Path.GetFileName(Application.ExecutablePath)),
  Microsoft.Win32.RegistryKeyPermissionCheck.ReadWriteSubTree);
appPaths.SetValue(string.Empty, Application.ExecutablePath);
object path = appPaths.GetValue("Path");
if(path == null)
  appPaths.SetValue("Path", System.IO.Path.GetDirectoryName(pluginItem.FileName));
else
{
  string strPath = string.Format("{0};{1}", path, System.IO.Path.GetDirectoryName(pluginItem.FileName));
  appPaths.SetValue("Path", strPath);
}
appPaths.Flush();
+1  A: 

Use Assembly.LoadFrom not Assembly.LoadFile

tyranid
If I use this method my interface, which is present in the executing program is also present in the loaded assembly, and the program can't distinguish between them.How would I go about this?Regards,Martin.
MartinHT
Erm it shouldn't have the interface in both places, you should define it once in the core assembly and then each plugin assembly should reference the core version of that type. Then you can use something like `Type.IsAssignableFrom` to determine if the type in your list implements the interface you want.
tyranid
Yes, I do have the interface defined in it's own assembly, and both the executing program and the plugin reference it.For instance, I have prog.exe and plugin.dll (where IPlugin is defined) in the root folder. I then have a nested folder where I have myplugin.dll (the actual working plugin) and plugin.dll (referenced by myplugin.dll).When I use LoadFrom to load myplugin, it also loads the referenced plugin.dll, which is already present in my executing assembly.I hope I have defined my problem a little clearer.Regards,Martin.
MartinHT
Ah I see, well other than just removing all extra copies of plugin.dll there is probably not alot you can do :)
tyranid
+2  A: 

Whenever I dynamically load plugins like this, I usually create an app domain and load the assembly in the new app domain. When creating an app domain, you can specify the base directory. Dependent assemblies will be loaded from this base directory.

Tallek
Hmm, I was thinking about this, but how can I use discoverability in the new AppDomain? I need to dynamically load my assembly into the new AppDomain and than enumerate its type to see if it implements my interface (IPlugin).
MartinHT
You could reflect the assembly in the current app domain, get the list of classes implementing the IPlugin interface, then pass of the assembly location and class name to a new app domain. Within the new app domain you could load the assembly and create an instance of your IPlugin interface class.
Tallek
Yes, I like that idea. Simple, but effective I'll give it a try.Thanks very much.
MartinHT
Okay, that works perfectly and I have finished the program. I use reflection to get the information, and then create a second AppDomain to execute it.Thanks for your help, regards,Martin.
MartinHT