Search Unity

Add a DLL loader?

Discussion in 'Scripting' started by Cliftonm, Dec 24, 2015.

  1. Cliftonm

    Cliftonm

    Joined:
    Nov 15, 2015
    Posts:
    36
    I know that you can reference specific .dll files in Unity, but I'm wondering how I can create a plugin loader for ANY plugin, similar to how Kerbal Space Program works. My goal is to make our game have just about endless possibilities for add ons.
     
  2. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    It is pretty easy.

    1. You have to have a contract for all the plugins in a form of an interface or some abstract class. For example
    Code (CSharp):
    1. public interface IPlugin
    2. {
    3.     string Name { get; }
    4.     void Initialize();
    5. }
    Each plugin must contain a public class that inherits from it. For example
    Code (CSharp):
    1. public class PluginA : IPlugin
    2. {
    3.     public string Name { get { return "Plugin A"; } }
    4.     public void Initialize() { }
    5. }
    6.  
    7. public class PluginB : IPlugin
    8. {
    9.     public string Name { get { return "Plugin B"; } }
    10.     public void Initialize() { }
    11. }
    2. The only thing left is to load the plugins:
    Code (CSharp):
    1.     private List<IPlugin> LoadPlugins(string directory)
    2.     {
    3.         var plugins = new List<IPlugin>();
    4.  
    5.         var files = Directory.GetFiles(directory, "*.dll", SearchOption.AllDirectories);
    6.         foreach (var file in files)
    7.         {
    8.             // load the plugin assembly into memory
    9.             var assembly = Assembly.LoadFrom(file);
    10.  
    11.             // scan through all its public types
    12.             foreach (var exportedType in assembly.GetExportedTypes())
    13.             {
    14.                 // filter those that inherit from IPlugin
    15.                 if (typeof(IPlugin).IsAssignableFrom(exportedType))
    16.                 {
    17.                     // instantiate those IPlugin implementations
    18.                     var plugin = (IPlugin)Activator.CreateInstance(exportedType);
    19.                     plugins.Add(plugin);
    20.                 }
    21.             }
    22.         }
    23.  
    24.         return plugins;
    25.     }
    26.  
    3. That's it. Now you can start to use the loaded plugins. For example
    Code (CSharp):
    1.         var plugins = LoadPlugins("./Plugins");
    2.         foreach (var plugin in plugins)
    3.         {
    4.             Debug.Log(plugin.Name);
    5.             plugin.Initialize();
    6.         }
    7.  
     
    Last edited: Dec 24, 2015
  3. kru

    kru

    Joined:
    Jan 19, 2013
    Posts:
    452
    This is a great answer. I hope it bubbles to the top of Google searches quickly.
     
  4. Cliftonm

    Cliftonm

    Joined:
    Nov 15, 2015
    Posts:
    36
    Thank you VERY much. I agree that it is a GREAT answer. Again, thank you!
     
  5. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    Well, there's an error in LoadPlugins method. There must be Assembly.LoadFrom instead of Assembly.Load. I've updated the example.

    The real code must definitely have some error handling. There are several ways how LoadPlugins could crash.

    1. Assembly.LoadFrom may throw an exception if the dll is not a managed dll, or is a managed dll but compiled for a different version of CLR.

    2. Activator.CreateInstance may fail to create an instance of the type if the type lacks a parameterless constructor.

    3. A plugin may have classes that implement IPlugin interface, but are abstract. You'll have to ignore them
    Code (CSharp):
    1. // filter those that inherit from IPlugin and not abstract
    2. if (exportedType.IsAbstract == false && typeof(IPlugin).IsAssignableFrom(exportedType))
    3. {
    4. }
    4. All the calls to plugin methods and properties should happen only inside try/catch blocks since you have no idea of how well they are written and what they do.
     
  6. Cliftonm

    Cliftonm

    Joined:
    Nov 15, 2015
    Posts:
    36
    This is probably a stupid question, but where do I add this part:
    Code (CSharp):
    1.     private List<IPlugin> LoadPlugins(string directory)
    2.     {
    3.         var plugins = new List<IPlugin>();
    4.  
    5.         var files = Directory.GetFiles(directory, "*.dll", SearchOption.AllDirectories);
    6.         foreach (var file in files)
    7.         {
    8.             // load the plugin assembly into memory
    9.             var assembly = Assembly.LoadFrom(file);
    10.  
    11.             // scan through all its public types
    12.             foreach (var exportedType in assembly.GetExportedTypes())
    13.             {
    14.                 // filter those that inherit from IPlugin
    15.                 if (typeof(IPlugin).IsAssignableFrom(exportedType))
    16.                 {
    17.                     // instantiate those IPlugin implementations
    18.                     var plugin = (IPlugin)Activator.CreateInstance(exportedType);
    19.                     plugins.Add(plugin);
    20.                 }
    21.             }
    22.         }
    23.  
    24.         return plugins;
    25.     }
    26.  
     
  7. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    Wherever you want, just before the point when you actually start to use the plugins. KSP does it right at the startup (when you see the loading screen and the progress bar at the bottom).

    I guess I'll make an example project. I did plugin support a couple of times in the past, but haven't done it in Unity yet. There shouldn't be any difference though.
     
  8. Cliftonm

    Cliftonm

    Joined:
    Nov 15, 2015
    Posts:
    36
    Should I put it in a void, or somewhere else? It throws errors when just in the class, and it gives a couple when in void Start()
     
  9. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,532
    Great post alexzzzz!


    Some added things you may want to do.

    1) the plugin will need a reference to the library that contains IPlugin. This could be done by referencing the 'Assembly***.dll' file in the 'Managed' folder of the built project. Or you could have a separate project that has the plugin framework built in VisualStudio/MonoDevelop.

    2) This will only work on Windows/Linux/OSX. Things like the iPhone, and the webgl player, don't use mono in the end and instead compile AOT. This dynamic loading of mono/.net dll's won't work.

    3) You could create a hook into the main game as an object that gets passed into the plugin so it has a reference to get a hold of your game specifics more easily:

    Code (csharp):
    1.  
    2. public class Game
    3. {
    4.  
    5.     //include methods and properties relavent to your game
    6.     //examples:
    7.     public Player GetPlayer(int index) {}
    8.    
    9. }
    10.  
    11. public interface IPlugin
    12. {
    13.  
    14.     string Name { get; }
    15.     void Initialize(Game game);
    16.  
    17. }
    18.  
     
  10. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,532
    It should only be called once, and it should be called at a point in your game where it makes sense. Such as when the game first starts. Usually my games start on an empty scene that just loads the basic guts of the game and then loads the next scene. This will have a 'GameStartup' script that only ever exists once in this empty scene.

    What are the errors you get?

    The code alexzzzz isn't simple cut/paste example that you can toss into any ol' script.
     
  11. Cliftonm

    Cliftonm

    Joined:
    Nov 15, 2015
    Posts:
    36
    Unexpected symbol 'private' when it is inside a void. Everything imaginable when it's just inside the class. (Mainly unexpected symbol) Outside the class, it gives me unexpected symbol 'list' though I don't think it should be outside the class.;)
     
  12. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    If you are asking about a complete source file to start with, that can be compiled with no errors, here it is
    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using System.IO;
    4. using System.Reflection;
    5.  
    6. public interface IPlugin
    7. {
    8.     string Name { get; }
    9.     void Initialize();
    10. }
    11.  
    12. public static class PluginManager
    13. {
    14.     public static List<IPlugin> Plugins { get; private set; }
    15.  
    16.     public static void LoadPlugins()
    17.     {
    18.         var pluginsDirectory = Path.Combine(Directory.GetCurrentDirectory(), "Plugins");
    19.         Plugins = LoadPlugins(pluginsDirectory);
    20.     }
    21.  
    22.     private static List<IPlugin> LoadPlugins(string directory)
    23.     {
    24.         var plugins = new List<IPlugin>();
    25.         var files = Directory.GetFiles(directory, "*.dll", SearchOption.AllDirectories);
    26.  
    27.         foreach (var file in files)
    28.         {
    29.             // load the plugin assembly into memory
    30.             var assembly = Assembly.LoadFrom(file);
    31.  
    32.             // scan through all its public types
    33.             foreach (var exportedType in assembly.GetExportedTypes())
    34.             {
    35.                 // filter those that inherit from IPlugin
    36.                 if (typeof(IPlugin).IsAssignableFrom(exportedType))
    37.                 {
    38.                     // instantiate those IPlugin implementations
    39.                     var plugin = (IPlugin)Activator.CreateInstance(exportedType);
    40.                     plugins.Add(plugin);
    41.                 }
    42.             }
    43.         }
    44.  
    45.         return plugins;
    46.     }
    47. }
    Call PluginManager.LoadPlugins() and it will load the available plugins and put them in PluginManager.Plugins list.
     
    Kiwasi, lordofduct and Cliftonm like this.
  13. Cliftonm

    Cliftonm

    Joined:
    Nov 15, 2015
    Posts:
    36
    Thank you!
     
  14. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,532
    Sounds like you just copy pasted all of that code right into a file.

    Not going to work quite exactly.

    Post what you did here.


    edit:
    already answered...
     
    Kiwasi likes this.
  15. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    You probably want to tackle the beginner scripting tutorials before attempting this. While dynamically loading code is not rocket science, it's also not something to attempt when you are still puzzling over methods and access modifiers.
     
  16. Cliftonm

    Cliftonm

    Joined:
    Nov 15, 2015
    Posts:
    36
    I already do all this stuff, but I've never actually loaded files. I know, I do come off as sounding stupid in this.