CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Page 1 of 2 12 LastLast
Results 1 to 15 of 16
  1. #1
    Join Date
    Dec 2007
    Location
    Brazil
    Posts
    37

    [RESOLVED] Linking at runtime?

    I am writing a physics simulator with a controler as main program and the physical systems as classes in dlls. To compile the main program I must have the systems in my reference list. Each time I add a new system, I must compile the main program anew.
    Now I would like to be able to add new systems without recompiling the main program each time. To do so, 2 problems must be solved:

    1. The new systems must be linked at runtime. Is that possible?

    2. When I compile the main program I still don't know the name of the classes, the additional dlls will implement. Let's say that the first point is solved. At runtime I look what dlls are in a certain directory and I can link them somehow. One of it has the name 'pendulum.dll' and from that I gess that it implements a class type with the same name. So I have a string with the value "pendulum". How do I transform this in a codeline like:
    'pendulum sys = new pendulum()'?

  2. #2
    Join Date
    Apr 2002
    Location
    Egypt
    Posts
    2,210

    Post Re: Linking at runtime?

    Hello hreba,
    this is a simple way to do it:
    • Define a common interface that all your simulators need to implement.
    • Put this interface in a separate project.
    • The main project has to rerference the interface project.
    • Create as many simulators as you wish, they all will reference the interface project (dll) and implement the defined interface
    • When you want to add simulators to the main project, just drop your simulator's dll and add a configuration key (or whatever) that the main application reads to know about new simulators (no recompilation required)
    • Instanciate simulators using Activator.CreateInstance overload that takes assembly(dll) name and type name.
    • On the returned Object handle call the Unwrap method to return an object.
    • Cast the returned object to the interface and use it.
    This may seem complex, but it's smart and will make your appliction organized and clear.

    Hope this helps.

  3. #3
    Join Date
    Mar 2004
    Location
    Prague, Czech Republic, EU
    Posts
    1,701

    Re: Linking at runtime?

    "Linking" at runtime can be done trought approach called loose coupling. Just create an standalone assembly with a common interface (abstract class can also used, but interface is purier), which defines the contract for physical system. Compile the simulator with reference to the assembly with the interface, and each physical system dll too.

    Then you can use reflection to iterate types inside the physical system dll and look for the one which implement the interface. If you find one, instantinate it (see Activator class) and call it trought the interface.

    In this approach, everything you need to no at compilet time is the interface.

    Do you see the point?
    • Make it run.
    • Make it right.
    • Make it fast.

    Don't hesitate to rate my post.

  4. #4
    Join Date
    Dec 2007
    Location
    Brazil
    Posts
    37

    Re: Linking at runtime?

    Your answers seem very complete, thanks a lot. It will take me some time (during those workdays) to get through it.

  5. #5
    Join Date
    Mar 2004
    Location
    Prague, Czech Republic, EU
    Posts
    1,701

    Re: Linking at runtime?

    I have created a sample for you. It is done very roughtly and there is many to improve, so take it just as inspiration, not as solution.
    Attached Files Attached Files
    • Make it run.
    • Make it right.
    • Make it fast.

    Don't hesitate to rate my post.

  6. #6
    Join Date
    Dec 2007
    Location
    Brazil
    Posts
    37

    Re: Linking at runtime?

    Thanks a lot. It is exactly what I need.

  7. #7
    Join Date
    Dec 2007
    Location
    Brazil
    Posts
    37

    Re: Linking at runtime?

    Hi boudino,

    your example code contains the line
    Code:
     ISimulator sim = (ISimulator)Activator.CreateInstance(t);
    where ISimulator is an interface. In my case, all actual classes are derived from an abstract class instead. I used that for typecasting, it compiled, but provoked a runtime error. After a short reflection (no pun) I got aware why this is obvious. In your posting you wrote that abstract classes can be used instead of interfaces. How?

  8. #8
    Join Date
    Apr 2002
    Location
    Egypt
    Posts
    2,210

    Re: Linking at runtime?

    What was the runtime error? Type cast exception?
    Hesham A. Amin
    My blog , Articles


    <a rel=https://twitter.com/HeshamAmin" border="0" /> @HeshamAmin

  9. #9
    Join Date
    Oct 2003
    Location
    .NET2.0 / VS2005 Developer
    Posts
    7,104

    Re: Linking at runtime?

    I have a a project, where all the work is done by plugins that descend from HorusPluginBase - an abstract class that contains SOME implementation common to all plugins, but the main work is done by an abstract method that the plugin must implement. Here is how I load a plugin, using a FileSystemWatcher to look for new plugins all the time rather than at application load (this is why there are great efforts to load files into memory first; it's a nasty hack that releases the write-lock on the file so a plugin can be re-loaded without going into the complexity of creating a separate AppDomain [that can be unloaded] and marshalling across the boundary):
    Code:
        private void LoadPlugins()
        {
          StringBuilder sb = new StringBuilder().AppendLine("Horus loaded the following plugins:");
    
          _knownPlugins.Clear(); //a list of plugins
          _ancilliaryAsm.Clear();
    
          if(_pluginWatcher == null) //the filesystemwatcher looking at the plugins dir
            return;
    
          Assembly asm = null;
    
          string[] dllPaths = Directory.GetFiles(_pluginWatcher.Path, "*.dll", SearchOption.TopDirectoryOnly); //dir list plugins
          
          foreach(string dllPath in dllPaths) //for each dll
          {
            FileInfo fi = new FileInfo(dllPath);
    
            try {
    #if DEBUG
              asm = Assembly.LoadFile(fi.FullName); //load it in debug, just normally; we wont be hotswapping plugins in debug mode
    #else
              asm = Assembly.Load(LoadFileAsByteArray(fi.FullName)); //load into memory first, allows hotswap
    #endif
              
    
              foreach(Type typ in asm.GetTypes()) { //loop throu assembly looking for any relevant assignable types
                if(!typ.IsAbstract && typeof(Horus.HorusPluginBase).IsAssignableFrom(typ)) {
                  HorusPluginBase tmPlug =
                    Activator.CreateInstance(typ, new object[] { _coreServices }) as HorusPluginBase;
    
                  _knownPlugins.Add(tmPlug); //add to list
                  sb.AppendFormat("LOAD: {0}\r\n", tmPlug.ToString());
                }
              }
            } catch(ReflectionTypeLoadException ex) {
              foreach(Exception le in ex.LoaderExceptions)
                sb.AppendFormat("FAIL: {0} ({1})\r\n", fi.FullName, le.Message);
            } catch(Exception ex) {
              sb.AppendFormat("FAIL: {0} {2} ({1})\r\n", fi.FullName, ex.Message, ex.GetType().ToString());
            }
    
       
    
          }
          sb.AppendLine("\r\n<end of plugins list>");
    
          _coreServices.SimpleEventLog(sb.ToString());
    
          if(_resEvtHnd == null){
            _resEvtHnd = new ResolveEventHandler(CurrentDomain_AssemblyResolve);
            AppDomain.CurrentDomain.AssemblyResolve += _resEvtHnd;
          }
    
          if(PluginsReloaded != null) //signal an event raise tha thte plugins were reloaded. debug windows listen to this
            PluginsReloaded.Invoke(_knownPlugins);
        }
         
         
         
        //i cannot for the life of me remember exactly why I wrote this - but i *THINK* it's because
        //my app bundles DLLs with it for e.g. CSV reading and the plugins can make use of them, but 
        //without this, they were constantly looking for the CSV reader DLLs to be alongside the 
        //plugin DLLs. This AssemblyResolve handler looks for the relevant assembly in the program
        //folder, not the plugins folder. You might not need such functionality
         
        private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
          string asmName;
          string asmVer;
          string asmNameVer;
    
          try {
            string[] asmBits = args.Name.Split(',');
            asmName = asmBits[0];
            if(asmBits.Length > 1) {
              asmVer = asmBits[1];
              asmBits = asmVer.Split('=');
              if(asmBits.Length > 1)
                asmVer = asmBits[1].Trim();
              else
                asmVer = "NoVersion";
            } else
              asmVer = "NoVersion";
            asmNameVer = asmName + asmVer;
          } catch(Exception ex) {
            _coreServices.MultiLog(
              "setupErrors",
              "resolveAsmArgs",
              string.Format("Attempting to resolve a reference to {0} failed: {1}",
                args.Name, ex.Message
              ),
              MultiLogType.Warn,
              MultiLogMethod.All
            );
            return null;
          }
    
          //have we loaded this before?
          if(_ancilliaryAsm.ContainsKey(asmNameVer))
            return _ancilliaryAsm[asmNameVer];
    
          //attempt to get the assembly from the plugins folder
          if(_pluginWatcher == null || _pluginWatcher.Path == null)
            return null;
    
          FileInfo fInf = null;
          try 
          { 
            List<FileInfo> toCheck = new List<FileInfo>();
    
            toCheck.AddRange(new DirectoryInfo(_pluginWatcher.Path).GetFiles(asmName + ".*"));
            toCheck.AddRange(new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.GetFiles(asmName + ".*"));
    
            foreach(FileInfo fi in toCheck){
              fInf = fi;
              string fileNameNoExt = System.IO.Path.GetFileNameWithoutExtension(fi.Name);
              
              FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(fi.FullName);
              if(fvi.ProductVersion == null || fvi.FileVersion == null) {
                continue;
              }
              string fileFileVer = fvi.FileVersion.Substring(0, fvi.FileVersion.LastIndexOf('.'));
              string fileProdVer = fvi.ProductVersion.Substring(0, fvi.ProductVersion.LastIndexOf('.'));
    
              
              if(asmName.Equals(fileNameNoExt) &&
                (asmVer.StartsWith(fileFileVer.Replace(".0", ".")) || asmVer.StartsWith(fileProdVer.Replace(".0", ".")))
              ) {
    #if DEBUG
                _ancilliaryAsm[asmNameVer] = Assembly.LoadFile(fi.FullName);
    #else
                _ancilliaryAsm[asmNameVer] = Assembly.Load(LoadFileAsByteArray(fi.FullName));
    #endif
                return _ancilliaryAsm[asmNameVer];
              } else {
    
    //report error
    
                  string.Format("While attempting to resolve a reference to {0}, the file " +
                    "{1} was ignored because neither FileVersion ({2}) nor ProductVersion ({3}) matched",
                    args.Name, fi.FullName, fvi.FileVersion, fvi.ProductVersion
    
              }
    
            }
          }
          catch (Exception ex)
          {
    //report error
          }
    
          return null;
          
        }
    
        static byte[] LoadFileAsByteArray(string filename)
        {
          byte[] buffer = null;
    //try 5 times with 1 second pause, to read the file - solves most File In Use problems
          for(int attempts = 0; attempts < 5; attempts++) {
            System.Threading.Thread.Sleep(attempts * 1000);
            try {
              FileStream fs = new FileStream(filename, FileMode.Open);
              buffer = new byte[(int)fs.Length];
              fs.Read(buffer, 0, buffer.Length);
              fs.Close();
    
              //exit the loop
              attempts = 6;
    
            } catch(IOException ioe) {
              //if the file is in use, catch and loop round to read again
              if(!ioe.Message.Contains("being used by another process"))
                throw ioe;
            }
          }
    
          return buffer;
        }
    "it's a fax from your dog, Mr Dansworth. It looks like your cat" - Gary Larson...DW1: Data Walkthroughs 1.1...DW2: Data Walkthroughs 2.0...DDS: The DataSet Designer Surface...ANO: ADO.NET2 Orientation...DAN: Deeper ADO.NET...DNU...PQ

  10. #10
    Join Date
    Oct 2003
    Location
    .NET2.0 / VS2005 Developer
    Posts
    7,104

    Re: Linking at runtime?

    note the loop i have over the assembly, to see if the type within is a subclass of my abstract class; my assembiles contain many types and they arent all subclasses of the base.. if i didnt have this check, it would go boom when i try to cast e.g. a DataTable into a HorusPluginBase

    I couldnt open boudino's project to se eif he makes a similar check - i'm stuck on vs 2005 for now
    "it's a fax from your dog, Mr Dansworth. It looks like your cat" - Gary Larson...DW1: Data Walkthroughs 1.1...DW2: Data Walkthroughs 2.0...DDS: The DataSet Designer Surface...ANO: ADO.NET2 Orientation...DAN: Deeper ADO.NET...DNU...PQ

  11. #11
    Join Date
    May 2007
    Posts
    1,546

    Re: Linking at runtime?

    What you want is a plugin framework, personally i'd recommend Mono/Addins (http://www.mono-project.com/Mono.Addins) and implement each of your simulators as an 'addin'.
    www.monotorrent.com For all your .NET bittorrent needs

    NOTE: My code snippets are just snippets. They demonstrate an idea which can be adapted by you to solve your problem. They are not 100% complete and fully functional solutions equipped with error handling.

  12. #12
    Join Date
    Mar 2002
    Location
    St. Petersburg, Florida, USA
    Posts
    12,125

    Re: Linking at runtime?

    Quote Originally Posted by hspc
    Hello hreba,
    this is a simple way to do it:....
    A good explaination. However this approach will prevent the dynamically loaded assemblies from EVER being unloaded. This may not be a concern for the OP, but is something to be aware of...
    TheCPUWizard is a registered trademark, all rights reserved. (If this post was helpful, please RATE it!)
    2008, 2009,2010
    In theory, there is no difference between theory and practice; in practice there is.

    * Join the fight, refuse to respond to posts that contain code outside of [code] ... [/code] tags. See here for instructions
    * How NOT to post a question here
    * Of course you read this carefully before you posted
    * Need homework help? Read this first

  13. #13
    Join Date
    Apr 2002
    Location
    Egypt
    Posts
    2,210

    Re: Linking at runtime?

    Quote Originally Posted by TheCPUWizard
    A good explaination. However this approach will prevent the dynamically loaded assemblies from EVER being unloaded. This may not be a concern for the OP, but is something to be aware of...
    Good point , but AFAIK, you can't unload the loaded assembly without creating new app domain, execute the code within it, Unload it. This can cause a small performance hit.
    Anyway, one should select the best method based on usage scenarios, and optimizations needed.
    Hesham A. Amin
    My blog , Articles


    <a rel=https://twitter.com/HeshamAmin" border="0" /> @HeshamAmin

  14. #14
    Join Date
    Dec 2007
    Location
    Brazil
    Posts
    37

    Re: Linking at runtime?

    hspc, the error message is
    "System.InvalidCastException: Cannot cast from source type to destination type."

    cjard, my code is similar (Base.PhySys is my abstract class):
    Code:
        Base.PhySys		sys;
        foreach (Type t in types)
          if (t.IsSubclassOf(typeof(Base.PhySys)))
          { sys = (Base.PhySys) Activator.CreateInstance(t);
            break;
          }
    I tried it with your if-clause, with "as" instead of (<type>), nothing helps, I get the Exception above.

    Mutant_Fruit, I'll have a look at these addins, I'm using mono anyway.

  15. #15
    Join Date
    Apr 2002
    Location
    Egypt
    Posts
    2,210

    Re: Linking at runtime?

    hreba, please read the exception details to know the source and destination types.
    Are you sure that you use the same assembly versions for both application and plugins ?
    Hesham A. Amin
    My blog , Articles


    <a rel=https://twitter.com/HeshamAmin" border="0" /> @HeshamAmin

Page 1 of 2 12 LastLast

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  





Click Here to Expand Forum to Full Width

Featured