Click here to Skip to main content
15,881,248 members
Please Sign up or sign in to vote.
4.50/5 (2 votes)
See more:
I'm trying to create a small plugin framework, and have been going around and around the AppDomain code with not much luck.

The most important thing for me is that I want to have no conflicts between different versions of the same class (for example a plugin may want to use its own Common.dll v 1.0 library whereas I may have 2.0), similar to other issues.

So I wrote the small app below.

C#
static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            Console.WriteLine(Utility.GetVersion());

            var ads = new AppDomainSetup();
            string appBase = @"../../ExtensionLib/ExtensionLib/bin/Debug";
            ads.ApplicationBase = appBase;
            //ads.PrivateBinPath = appBase;
            //ads.ConfigurationFile = "ExtensionLib.dll.config";
            //ads.PrivateBinPathProbe = ".";
            var ad = AppDomain.CreateDomain("Extension App", null, ads);
//          var ah = new AssemblyHelper();
//            ad.AssemblyResolve += ah.MyResolveEventHandler;
//            ad.AssemblyLoad += ah.AssemblyLoadedHandler;

            //MessageBox.Show(ad.BaseDirectory);
            Assembly a = ad.Load("ExtensionLib");
            ILib l = (ILib) a.CreateInstance("LibImpl");
            l.DoSomething();

            Console.WriteLine(Utility.GetVersion());
        }
    }


Common.dll contains the Utility class, but we have different versions of that class. Both the plugin and the host application use the same ILib (defined on the same dll).

Host Application Setup
c:\....\123
   Common.dll (v 2.0)
   IntegInterface.dll (where ILib is defined, v1.0)
   LoadCommon.exe (the host)


Plugin Application Setup
XML
c:\....\456
   Common.dll (v 1.0)
   IntegInterface.dll (where ILib is defined, v1.0)
   ExtensionLib.dll (the plugin)


When the app runs, I get an exception at the moment of ad.Load()

System.IO.FileNotFoundException was unhandled
  Message="Could not load file or assembly 'ExtensionLib' or one of its dependencies. The system cannot find the file specified."
  Source="mscorlib"
  FileName="ExtensionLib"
  FusionLog="Assembly manager loaded from:  C:\\Windows\\Microsoft.NET\\Framework\\v2.0.50727\\mscorwks.dll
  Running under executable  C:\\Users\\Roberto\\Documents\\Visual Studio 2008\\Projects\\LoadCommon\\LoadCommon\\bin\\Debug\\LoadCommon.vshost.exe
  --- A detailed error log follows. \n
  === Pre-bind state information ===
  LOG: User = Roberto-PC\\Roberto
  LOG: DisplayName = ExtensionLib\n (Partial)
  LOG: Appbase = file:///C:/Users/Roberto/Documents/Visual Studio 2008/Projects/LoadCommon/LoadCommon/ExtensionLib/ExtensionLib/bin/Debug
  LOG: Initial PrivatePath = NULL
  Calling assembly : (Unknown).
  ===
  LOG: This bind starts in default load context.
  LOG: Using application configuration file: C:\\Users\\Roberto\\Documents\\Visual Studio 2008\\Projects\\LoadCommon\\LoadCommon\\ExtensionLib\\ExtensionLib\\bin\\Debug\\ExtensionLib.dll.config
  LOG: Using machine configuration file from C:\\Windows\\Microsoft.NET\\Framework\\v2.0.50727\\config\\machine.config.
  LOG: Policy not being applied to reference at this time (private, custom, partial, or location-based assembly bind).
  "
  StackTrace:
       at System.Reflection.Assembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, Assembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection)
       at System.Reflection.Assembly.nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, Assembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection)
       at System.Reflection.Assembly.InternalLoad(AssemblyName assemblyRef, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection)
       at System.Reflection.Assembly.InternalLoad(String assemblyString, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection)
       at System.AppDomain.Load(String assemblyString)
       at System.AppDomain.Load(String assemblyString)
       at LoadCommon.Program.Main() in C:\Users\Roberto\Documents\Visual Studio 2008\Projects\LoadCommon\LoadCommon\Program.cs:line 36
       at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
       at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()
  InnerException: 


I also tried to setup an the assemblyresolve delegate to see if I could help, no luck however, I ended up with Serialization errors even though I did tag AssemblyHelper as serializable.

At some point I will also have the IntegInterface have different versions, and am sure I will end up with a whole slew of issues.

Can someone shed some light here? Very much appreciated.

Thanks
Posted

This is the usual confusion about loading assembly. Do you want to load Assembly by the file name of its main executable module?

Use System.Reflection.Assembly.LoadFrom(string fileName). See all other System.Reflection.Assembly.Load... methods and help pages on their differences. This call should be done in the newly created Application Domain context, not in your default (initial) Application Domain of your process. If my assumption about your plug-in assembly was wrong, please free to correct me, I'll give you a bit different approach.

C#
System.AppDomain ad = AppDomain.CreateDomain("Extension App", null, ads);
ad.DoCallBack(() => {
   //...
   System.Reflection.Assembly.LoadFrom(myPluginFileName);
   //...
});


If your plug-in Assembly has an entry point (EXE file) you can use System.AppDomain.ExecuteAssembly. I doubt you design you plug-ins this way (you mentioned ILib interface; if this is the interface supported by your plug-in — congratulations! This is the best approach. See be reference below on using a special assembly attribute to select a class implementing plug-in interface &mdas; very good for robustness and performance).
So, even though I don't think your design is not bases on EXE entry point, let me show this just in case:

C#
System.AppDomain ad = AppDomain.CreateDomain("Extension App", null, ads);
ad.ExecuteAssembly(myExeFileName);


This is great that you understand importance of loading plug-ins dynamically in the separate Application Domain, but this is only needed if you want to unload them later. In you need to load all plug-ins only once, work in one domain, your default one, do not create an extra Application Domain.

You can real all my Answers about plug-in Assemblies and using Application Domain, a variety of designs:
Create WPF Application that uses Reloadable Plugins...[^]
See also:
code generating using CodeDom[^].
Important example of use with CodeDOM compiler for use in .NET calculator:
Create WPF Application that uses Reloadable Plugins...[^].

—SA
 
Share this answer
 
v3
Comments
Manfred Rudolf Bihy 13-Apr-11 18:17pm    
Nice! My 5+
Cheers!
Sergey Alexandrovich Kryukov 13-Apr-11 18:18pm    
Thank you, Manfred. Just updated with important detail.
--SA
Try to load the assembly with the version, culture and PublicKeyToken

MIDL
Assembly.Load
            ("SampleAssembly, Version=2.0.0000.0, Culture=neutral, PublicKeyToken=12312320f8da049e3");
 
Share this answer
 
Comments
Sergey Alexandrovich Kryukov 13-Apr-11 18:02pm    
That would work, but I think not in the OP's case. If this is a plug-in Assembly, LoadFrom should be used.
Please see my Answer.
--SA
luisnike19 13-Apr-11 18:29pm    
Thanks for your comment SA.
All I needed to do was make a call, so although SAK's solution makes sense (thanks!), I decided to just use
ILib l = (ILib)ad.CreateInstanceAndUnwrap("ExtensionLib", "ExtensionLib.LibImpl");

which does the trick just fine. Furthermore, I can change the interface on the different domains and they are still somehow compatible. The transparent proxy does some funky dancing i'm sure.

Thanks all!
 
Share this answer
 

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900