Does anyone know if there's a way to hook into an "OnLoad" event to run some operations when an assembly loads?
Specifically, I am creating a plug-in for an application. The plug-in's DLL gets loaded and objects start being used, but the problem is I need to load another assembly dynamically before anything happens. This assembly can't be copied to the application's directory and must remain invisible to it.
You need to hook on to AssemblyLoad event.
Refer-
http://msdn.microsoft.com/en-us/library/system.appdomain.assemblyload.aspx
It is really sad that writing a Main() function in an Assembly DLL is never called by the .NET framework.
It seems that Microsoft forgot that.
But you can easily implement it on your own:
In the DLL assembly you add this code:
using System.Windows.Forms;
public class Program
{
public static void Main()
{
MessageBox.Show("Initializing");
}
}
Then in the Exe Assembly that loads this DLL you add this function:
using System.Reflection;
void InitializeAssembly(Assembly i_Assembly)
{
Type t_Class = i_Assembly.GetType("Program");
if (t_Class == null)
return; // class Program not implemented
MethodInfo i_Main = t_Class.GetMethod("Main");
if (i_Main == null)
return; // function Main() not implemented
try
{
i_Main.Invoke(null, null);
}
catch (Exception Ex)
{
throw new Exception("Program.Main() threw exception in\n"
+ i_Assembly.Location, Ex);
}
}
Obviously you should call this function at the very beginning before doing anything else with that Assembly.
C# does not provide a way to do that but the underlying IL code does via module initializers. You can use tools like Fody/ModuleInit to turn a specially named static C# class to run as a module initializer which will be run when your dll is loaded.
Related
I'm a bit confused here and haven't gotten much help from google. Here's what I'm trying to do:
public Boolean LoadModule(String moduleHandle)//name of module MUST match its .dll name. Name of AppDomain is the same as the Handle.
{
try
{
AppDomain moduleDomain = AppDomain.CreateDomain(moduleHandle);
String pathToDll = #"C:\IModules.dll"; //Full path to dll you want to load
Type moduleType = typeof(IModule);
IModule loadedModule = (IModule)moduleDomain.CreateInstanceFromAndUnwrap(pathToDll, moduleType.FullName);
ModuleList.Add(loadedModule, moduleDomain);
Broadcast("Module loaded: " + moduleHandle, ModuleManagerHandle);
return true;
}
catch (Exception e)
{
//console writeline the error? probably cant
OutputBox.AppendText(e.ToString() + Environment.NewLine);
return false;
}
}
I thought I finally had this figured out but when I try to instantiate the IModule (ConsoleModule, in this case), I get the following error:
System.MissingMethodException: Constructor on type 'IModules.IModule' not found.
I take this to mean that I need to have a constructor, as if this were a class object instantiating itself on this function call, but I cannot make an interface have a constructor.
I have seen other threads suggesting ways to solve this problem, but they use assembly instead of appdomain, which will mess up the ability to unload modules. I'm concerned that without the ability to unload modules the application will suffer memory bloating over time.
The end goal is to be able to write a module, leave the program running and load/unload the modules during runtime without any changes to the core program, and add functionality on the go.
Anyone know of a workaround or maybe a better way to deal with dynamic module loading and unloading?
This is fixed with .NET 5.0 AssemblyLoadContext:
var basePath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
AssemblyLoadContext moduleAssemblyLoadContext = new AssemblyLoadContext(moduleHandle, true);
Assembly moduleAssembly = moduleAssemblyLoadContext.LoadFromAssemblyPath($"{basePath}\\{moduleHandle}.dll");
Type[] types = moduleAssembly.GetTypes();
foreach (Type type in types)
{
// Does this class support the transport interface?
Type typeModule = type.GetInterface("IModule");
if (typeModule == null)
{
// Not supported.
continue;
}
// This class supports the interface. Instantiate it.
IModule loadedModule = moduleAssembly.CreateInstance(type.FullName) as IModule;
if (loadedModule != null)
{
loadedModule.LoadedModule(this);
ModuleList.Add(loadedModule, moduleAssemblyLoadContext);
Broadcast("Module loaded: " + moduleHandle, ModuleManagerHandle);
OutputTextBox.AppendText(moduleHandle + " was loaded." + Environment.NewLine);
// Successfully created the interface. We are done.
return true;
}
}
return false;
Can't find the source anymore but found it looking for a related problem (you can find it on MSDN anyways). This successfully loads and unloads assemblies into their context. User must set the isCollectible value to TRUE to enable full unloading.
Only issue I had is that .NET 5.0 is not compatible -with itself- yet and libraries loaded as .NET 5.0 into .NET 5.0 programs will give a BadImageFormatException when trying to load the assembly. To fix, set the LIBRARY to the next most recent target framework (in my case, .NET Core 3.1) and move the newly compiled dll to wherever it goes and the application should run using the new dll.
The error tells you that there is no default (empty) constructor found for the type IModule. Since IModule is an interface, the message seems to make some sense.
Resulotion: Instantiate a class that implements IModule. An interface can never be intantiated on its own.
To instantiate the class, just one line neds to be changed:
Type moduleType = typeof(ClassThatImplementIModule);
You can still cast the instance to IModule
the MSDN says it "Adds an assembly to the application's set of referenced assemblies.". but how? where effects happens exactly?is there any ienumerable/list of referenced assembly to access and add manually to them?
actually if we call this method after app start it throws
"This method can only be called during the application's pre-start
initialization phase. Use PreApplicationStartMethodAttribute to
declare a method that will be invoked in that phase."
ok. now i don't want to use it. i want to know what does do exactly and then i simulate in my custom function.
thanks
It's a good thing we have the source code:
public static void AddReferencedAssembly(Assembly assembly)
{
if (assembly == null)
{
throw new ArgumentNullException("assembly");
}
ThrowIfPreAppStartNotRunning();
s_dynamicallyAddedReferencedAssembly.Add(assembly);
}
s_dynamicallyAddedReferencedAssembly.Add is a List<Assembly> of dynamically added assemblies which will be taken into account during compilation.
There is another internal method called GetReferencedAssembiles which gets all the assemblies for the project, which iterates and adds all the dynamically added assemblies to the list of referenced assemblies:
internal static ICollection GetReferencedAssemblies(CompilationSection compConfig,
int removeIndex)
{
// shorted for brevity
foreach (Assembly assembly in s_dynamicallyAddedReferencedAssembly)
{
referencedAssemblies.Add(assembly);
}
}
I have a service which acts as a scripting engine for normalization of content. The service extends its scripting language by dynamically loading DLLs at startup, passing a context object interface to a Load() method in each DLL. The context object provides the DLL with methods for Binding string names to factory objects, commands/functors, script object wrappers, etc.
I am having a problem where when the Load() method of one of the DLLs is called it throws a MissingMethodException if it attempts to call one of the binding methods. The method is one which takes an interface which is declared in another DLL, and I suspect something is going on with the method signature when passing the context object through Type.GetMethod.Invoke().
It should be noted that the exception is only thrown when dynamically loading the DLL via Assembly.LoadFile(). Referencing the DLL in the main process and using reflection to get the class/method, then dynamically invoking it from there causes no exception to be thrown.
My code/project reference setup ...
Content.dll:
References nothing
public class ContentPackage { ... }
public interface IContentDataSource
{
ContentPackage Receive();
void Send(ContentPackage content);
}
public interface IContentDataSourceFactory
{
IContentDataSource CreateInstance(string param);
IEnumerable<IContentDataSource> CreateInstances(string param);
}
public class ContentXmlDataSource : IContentDataSource
{
ContentPackage IContentDataSource.Receive() { ... }
void IContentDataSource.Send(ContentPackage content) { ... }
public class Factory : IContentDataSourceFactory
{
IContentDataSource IContentDataSourceFactory.CreateInstance(string param) { return new ContentXmlDataSource(param); }
IEnumerable<IContentDataSource> IContentDataSourceFactory.CreateInstances(string param) { ... }
IContentDataSourceFactory.CreateInstance(
public static Factory Singleton { get { return s_Singleton; } }
private static Factory m_Singleton = new Factory();
}
}
ExtensionInterface.dll:
References Content.dll
public interface IXmlTagCommand() { ... }
public interface IExtensionBindingContext
{
void BindCommand(string name, IXmlTagCommand cmd);
void BindDataSourceFactory(string name, IContentDataSourceFactory factory);
}
Service.dll:
References ExtensionInterface.dll, Content.dll
public partial class TheService
{
public class ExtensionBindingContext : IExtensionBindingContext
{
void IExtensionBindingContext.BindCommand(string name, IXmlTagCommand cmd)
{ ... }
void BindDataSourceFactory(string name, IContentDataSourceFactory factory)
{ ... }
ExtensionBindingContext(string basePath)
{
var paths = Directory.GetFiles(basePath, "*.dll");
foreach (var path in paths)
{
Assembly assembly = Assembly.LoadFile(path);
Type t = assembly.GetType("ExtensionLibrary");
MethodInfo method = t.GetMethod("Load",
BindingFlags.Public | BindingFlags.Static);
method.Invoke(null, new object[] { this }); // Throws exception!
}
}
}
TheService()
{
(new ExtensionBindingContext()).LoadExtensions(".\DLLFolder\");
}
}
Extension.StandardContentFactories.dll: (Loaded dynamically)
References ExtensionInterface.dll, Content.dll
public class ExtensionLibrary
{
public void Load(IExtensionBindingContext context)
{
context.BindCommand("SomeCommand", XTagCommands.SomeCommand); // Fine: No exception.
context.BindDataSourceFactory("ContentXml", ContentXmlFactory.Singleton); // Causes whole function to throw exception if not commented out, preventing even the "BindCommand" call to not be reached.
}
}
The exception specifics:
Exception:
System.Reflection.TargetInvocationException, {"Exception has been thrown by the target of an invocation."}
Inner Exception:
{"Method not found: 'Void SomeNamespace.ContentService.IExtensionBindingContext.BindDataSourceFactory(System.String, SomeNamespace.Content.IContentDataSourceFactory)'."}
If I comment out the BindDataSourceFactory() line in StandardContentFactories.dll, the problem goes away. I am also successfully loading multiple other DLLs in this manner.
I've tried the following:
- Checking GAC folders for same named assemblies from project, none found
- Cleaning solution and rebuilding
- Verifying that all assemblies were using same .NET (4.0, not client profile)
- Passing the interface in a wrapper object instead, changing the method to take the wrapper: Field not found exception instead
- Passing the factory object as an "Object" (updating method parameters as well), then casting that object back into the interface that the factory object inherits: Invalid cast
Is there some kind of issue with passing objects behind interfaces to DLLs via dynamic assembly loading and invoking if that interface has a method that takes an interface which is defined in another referenced assembly? Are there possible work arounds to allow for this dynamic binding of factories from unknown DLLs if the interface issue is non-solvable?
EDIT:
After checking the modules as per mike z's suggestion, I see that their are two copies of each shared interface DLL being loaded (Content.dll and ExtensionInterface.dll); the first set of DLLs is being loaded from the service's host process bin\Debug directory; the second set of DLLs is being loaded from the bin\Debug directory of the first addon DLL that is loaded by Assembly.LoadFile().
The process uses an array of directory paths to search for addon DLLs in, and I was able to force the process to find/load the DLL that has the problem first, which caused the problem to go away. I'm guessing that all of the Assembly.LoadFile() loaded DLLs are using the set of shared interface DLLs that are loaded as a side effect of the first call to Assembly.LoadFile() (i.e. for the first addon DLL). When these DLLs load, maybe they are only including interface information for the IExtensionBindingContext.BindCommand() but not the IExtensionBindingContext.BindDataSourceFactory() and/or IContentDataSourceFactory since they are not used by that DLL.
Does anybody know if this assessment is correct? If so, is there a (standard or correct) way to force my Assembly.LoadFile() DLLs to correctly load ALL of the interface/method information from a shared library? (I'd rather not use contrived solutions like forcing the load order from a config file or creating a fake library that used all the features at the start.)
I have a Winforms application that relies on a 3rd party SDK. I've included the .NET reference in the application, but don't always need/use it. If I try and execute the program on a machine without the DLLs it will not open at all: it won't even enter Main.
Is it possible have a reference but instruct it to only load the DLLs when required?
(PDFSharp (another reference I use) appears to only load when a PdfSharp method is called, which makes me wonder if it's something I can control.)
Edit...I can't see any 3rd party reference in the Program class, but just in case here it is:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace MyProgram
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
try
{
Application.Run(new Main(args));
}
catch (Exception Ex)
{
MessageBox.Show(Ex.Message, "The program has quit :(", MessageBoxButtons.OK, MessageBoxIcon.Error);
string TracefileContents = "Please email this file to some#body.com\n\n" + "Exception:\n" + Ex.Message + "\n\nStack trace:\n" + Ex.StackTrace.ToString();
System.IO.File.WriteAllText("Issue Report " + DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss") + ".dat", TracefileContents);
}
}
}
}
.NET does that automatically, everything is loaded on demand by default.
Assemblies are only loaded when you start using some type from the assembly. Just having a reference to the assembly has no impact on the run-time behavior of the application
To be more accurate, CLR loads an assembly only when a it JIT-compiles a method that uses that type. This also includes using a type that derives from/implements one of the classes/interfaces of the assembly.
Even instantiating a class that have a field or property of a type from another assembly, does not enforce loading the assembly. Unless the field or property is being accessed in the class' constructor. For example when you set a field's value in its definition statment:
// `TypeFromAnotherAssembly` is loaded when the class is instantiated
class Test
{
private TypeFromAnotherAssembly myField = CreateTypeFromAnotherAssembly();
}
compiler emits initialization code in the class' constructor. Then, according to the rule above, when the constructor is JIT-compiled (the class is instantiated for the first time), the CLR loads the assembly. This also includes setting the field's value to null:
// `TypeFromAnotherAssembly` is loaded when the class is instantiated
class Test
{
private TypeFromAnotherAssembly myField = null;
}
This does not happen when you omit the initialization statement, although the result is exactly the same (the .NET runtime automatically initialized class fields to null or 0):
// `TypeFromAnotherAssembly` is NOT loaded when the class is instantiated
class Test
{
private TypeFromAnotherAssembly myField;
}
You should be careful about static fields' initialization, because accessing the class in any way causes the initialization to occur.
I'm making a program depending on some DLLs included in a third party program. I'm not allowed to distribute these DLLs myself. The third party program must be installed for my program to work.
How can i make a reference to these DLLs? I know the exact location of them through a registry key set by the program.
I have tried to add the files in Project->References and set CopyLocal to false but when i start i then get a FileNotFoundException "Could not load file or assembly".
I have tried to add an event to AppDomain.CurrentDomain.AssemblyResolve and load the files there but the problem is that i get the exception before my program even starts. Even if i put a breakpoint on the first line the exception will be thrown before the breakpoint is hit.
From C# 3.0 in a Nutshell, 3rd edition, by Joseph and Ben Albahari, p. 557-558:
Deploying Assemblies Outside the Base Folder
Sometimes you might choose to deploy assemblies to locations other than the application base directory [...] To make this work, you must assist the CLR in finding the assemblies outside the base folder. The easiest solution is to handle the AssemblyResolve event.
(We can ignore the fact that in your case, someone other than you is deploying the assemblies.)
Which you tried. But a very important clue follows somewhat later. Read the two code comments:
public static void Loader
{
static void Main(string[] args)
{
AppDomain.CurrentDomain.AssemblyResolve += FindAssem;
// We must switch to another class before attempting to use
// any of the types in C:\ExtraAssemblies:
Program.Go();
}
static Assembly FindAssem(object sender, ResolveEventArgs args)
{
string simpleName = new AssemblyName(args.Name).Name;
string path = #"C:\ExtraAssemblies\" + simpleName + ".dll";
if (!File.Exists(path)) return null;
return Assembly.LoadFrom(path);
}
}
public class Program
{
public static void Go()
{
// Now we can reference types defined in C:\ExtraAssemblies
}
}
As you see, the class where you resolve the external assemblies must not refer to any type from any of the external DLLs anywhere. If it did, code execution would stop way before your AssemblyResolve ever gets a chance to run.
Your app bombs because the JIT compiler is the first one that needs to load the assembly. You'll need to carefully avoid using types from the assembly in your Main() method. That's not hard to do, just write another Main method and give it an attribute that tells the jitter that it should never inline that method. Without the attribute it may still bomb in the Release build when the optimizer inlines the method. Like this:
using System;
using System.Runtime.CompilerServices;
class Program {
static void Main(string[] args) {
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
DelayedMain(args);
}
[MethodImpl(MethodImplOptions.NoInlining)]
static void DelayedMain(string[] args) {
// etc..
}
static System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) {
// etc...
}
}
Before you commit doing it this way, do contact the vendor and ask for recommendations. There has to be an easier way to exercise your license rights. The GAC would be a common choice, maybe you just need to run gacutil on your dev machine.