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);
}
}
Related
Please bear with me, I spent 30+ hours trying to get this work - but without success.
At the start of my program I load an Assembly (dll) in bytearray and delete it afterwards.
_myBytes = File.ReadAllBytes(#"D:\Projects\AppDomainTest\plugin.dll");
Later on in the program I create a new Appdomain, load the byte array and enumerate the types.
var domain = AppDomain.CreateDomain("plugintest", null, null, null, false);
domain.Load(_myBytes);
foreach (var ass in domain.GetAssemblies())
{
Console.WriteLine($"ass.FullName: {ass.FullName}");
Console.WriteLine(string.Join(Environment.NewLine, ass.GetTypes().ToList()));
}
The types get correctly listed:
ass.FullName: plugin, Version=1.0.0.0, Culture=neutral,PublicKeyToken=null
...
Plugins.Test
...
Now I want to create an instance of that type in the new AppDomain
domain.CreateInstance("plugin", "Plugins.Test");
This call results in System.IO.FileNotFoundException and I don't know why.
When I look in ProcessExplorer under .NET Assemblies -> Appdomain: plugintest I see that the assembly is loaded correctly in the new appdomain.
I suspect the exception to occur because the assembly is searched again on disk. But why does the program want to load it again?
How can I create an instance in a new appdomain with an assembly loaded from byte array?
The main problem here is thinking that you can instantiate a plugin while executing code in your primary appdomain.
What you need to do instead, is create a proxy type which is defined in an already loaded assembly, but instantiated in the new appdomain. You can not pass types across app domain boundaries without the type's assembly being loaded in both appdomains. For instance, if you want to enumerate the types and print to console as you do above, you should do so from code which is executing in the new app domain, not from code that is executing in the current app domain.
So, lets create our plugin proxy, this will exist in your primary assembly and will be responsible for executing all plugin related code:
// Mark as MarshalByRefObject allows method calls to be proxied across app-domain boundaries
public class PluginRunner : MarshalByRefObject
{
// make sure that we're loading the assembly into the correct app domain.
public void LoadAssembly(byte[] byteArr)
{
Assembly.Load(byteArr);
}
// be careful here, only types from currently loaded assemblies can be passed as parameters / return value.
// also, all parameters / return values from this object must be marked [Serializable]
public string CreateAndExecutePluginResult(string assemblyQualifiedTypeName)
{
var domain = AppDomain.CurrentDomain;
// we use this overload of GetType which allows us to pass in a custom AssemblyResolve function
// this allows us to get a Type reference without searching the disk for an assembly.
var pluginType = Type.GetType(
assemblyQualifiedTypeName,
(name) => domain.GetAssemblies().Where(a => a.FullName == name.FullName).FirstOrDefault(),
null,
true);
dynamic plugin = Activator.CreateInstance(pluginType);
// do whatever you want here with the instantiated plugin
string result = plugin.RunTest();
// remember, you can only return types which are already loaded in the primary app domain and can be serialized.
return result;
}
}
A few key points in the comments above I will reiterate here:
You must inherit from MarshalByRefObject, this means that the calls to this object can be proxied across app-domain boundaries using remoting.
When passing data to or from the proxy class, the data must be marked [Serializable] and also must be in a type which is in the currently loaded assembly. If you need your plugin to return some specific object to you, say PluginResultModel then you should define this class in a shared assembly which is loaded by both assemblies/appdomains.
Must pass an assembly qualified type name to CreateAndExecutePluginResult in its current state, but it would be possible to remove this requirement by iterating the assemblies and types yourself and removing the call to Type.GetType.
Next, you need to create the domain and run the proxy:
static void Main(string[] args)
{
var bytes = File.ReadAllBytes(#"...filepath...");
var domain = AppDomain.CreateDomain("plugintest", null, null, null, false);
var proxy = (PluginRunner)domain.CreateInstanceAndUnwrap(typeof(PluginRunner).Assembly.FullName, typeof(PluginRunner).FullName);
proxy.LoadAssembly(bytes);
proxy.CreateAndExecutePluginResult("TestPlugin.Class1, TestPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
}
Going to say this again because it's super important and I didn't understand this for a long time: when you're executing a method on this proxy class, such as proxy.LoadAssembly this is actually being serialized into a string and being passed to the new app domain to be executed. This is not a normal function call and you need to be very careful what you pass to/from these methods.
This call results in System.IO.FileNotFoundException and I don't know why. I suspect the exception to occur because the assembly is searched again on disk. But why does the program want to load it again?
The key here is understanding loader contexts, there's an excellent article on MSDN:
Think of loader contexts as logical buckets within an application domain that hold assemblies. Depending on how the assemblies were being loaded, they fall into one of three loader contexts.
Load context
LoadFrom context
Neither context
Loading from byte[] places the assembly in the Neither context.
As for the Neither context, assemblies in this context cannot be bound to, unless the application subscribes to the AssemblyResolve event. This context should generally be avoided.
In the code below we use the AssemblyResolve event to load the assembly in the Load context, enabling us to bind to it.
How can I create an instance in a new appdomain with an assembly loaded from byte array?
Note this is merely a proof of concept, exploring the nuts and bolts of loader contexts. The advised approach is to use a proxy as described by #caesay and further commented upon by Suzanne Cook in this article.
Here's an implementation that doesn't keep a reference to the instance (analogous to fire-and-forget).
First, our plugin:
Test.cs
namespace Plugins
{
public class Test
{
public Test()
{
Console.WriteLine($"Hello from {AppDomain.CurrentDomain.FriendlyName}.");
}
}
}
Next, in a new ConsoleApp, our plugin loader:
PluginLoader.cs
[Serializable]
class PluginLoader
{
private readonly byte[] _myBytes;
private readonly AppDomain _newDomain;
public PluginLoader(byte[] rawAssembly)
{
_myBytes = rawAssembly;
_newDomain = AppDomain.CreateDomain("New Domain");
_newDomain.AssemblyResolve += new ResolveEventHandler(MyResolver);
}
public void Test()
{
_newDomain.CreateInstance("plugin", "Plugins.Test");
}
private Assembly MyResolver(object sender, ResolveEventArgs args)
{
AppDomain domain = (AppDomain)sender;
Assembly asm = domain.Load(_myBytes);
return asm;
}
}
Program.cs
class Program
{
static void Main(string[] args)
{
byte[] rawAssembly = File.ReadAllBytes(#"D:\Projects\AppDomainTest\plugin.dll");
PluginLoader plugin = new PluginLoader(rawAssembly);
// Output:
// Hello from New Domain
plugin.Test();
// Output:
// Assembly: mscorlib
// Assembly: ConsoleApp
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
Console.WriteLine($"Assembly: {asm.GetName().Name}");
}
Console.ReadKey();
}
}
The output shows CreateInstance("plugin", "Plugins.Test") is successfully called from the default app domain, although it has no knowledge of the plugin assembly.
Have you tried providing the Assemblies full name, In your case
domain.CreateInstance("plugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "Plugins.Test");
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.)
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.
Ok, here is the deal:
I want to load a user defined Assembly into my AppDomain, but I only want to do so if the specified Assembly matches some requirements. In my case, it must have, among other requirements, an Assembly level Attribute we could call MandatoryAssemblyAttribute.
There are two paths I can go as far as I see:
Load the Assembly into my current AppDomain and check if the Attribute is present. Easy but inconvenient as I'm stuck with the loaded Assembly even if it doesnt have the MandatoryAssemblyAttribute. Not good.
I can create a new AppDomain and load the Assembly from there and check if my good old MandatoryAddemblyAttribute is present. If it is, dump the created AppDomain and go ahead and load the Assembly into my CurrentAppDomain, if not, just dump the new AppDomain, tell the user, and have him try again.
Easier said than done. Searching the web, I've found a couple of examples on how to go about this, including this previous question posted in SO:Loading DLLs into a separate AppDomain
The problem I see with this solution is that you actually have to know a type (full name) in the Assembly you want to load to begin with. That is not a solution I like. The main point is trying to plug in an arbitrary Assembly that matches some requirements and through attributes discover what types to use. There is no knowledge beforehand of what types the Assembly will have. Of course I could make it a requirement that any Assembly meant to be used this way should implement some dummy class in order to offer an "entry point" for CreateInstanceFromAndUnwrap. I'd rather not.
Also, if I go ahead and do something along this line:
using (var frm = new OpenFileDialog())
{
frm.DefaultExt = "dll";
frm.Title = "Select dll...";
frm.Filter = "Model files (*.dll)|*.dll";
answer = frm.ShowDialog(this);
if (answer == DialogResult.OK)
{
domain = AppDomain.CreateDomain("Model", new Evidence(AppDomain.CurrentDomain.Evidence));
try
{
domain.CreateInstanceFrom(frm.FileName, "DummyNamespace.DummyObject");
modelIsValid = true;
}
catch (TypeLoadException)
{
...
}
finally
{
if (domain != null)
AppDomain.Unload(domain);
}
}
}
This will work fine, but if then I go ahead and do the following:
foreach (var ass in domain.GetAssemblies()) //Do not fret, I would run this before unloading the AppDomain
Console.WriteLine(ass.FullName);
I get a FileNotFoundException. Why?
Another path I could take is this one: How load DLL in separate AppDomain But I'm not getting any luck either. I'm getting a FileNotFoundException whenever I choose some random .NET Assembly and besides it defeats the purpose as I need to know the Assembly's name (not file name) in order to load it up which doesn't match my requirements.
Is there another way to do this barring MEF (I am not targeting .NET 3.5)? Or am I stuck with creating some dummy object in order to load the Assembly through CreateInstanceFromAndUnwrap? And if so, why can't I iterate through the loaded assemblies without getting a FileNotFoundException? What am I doing wrong?
Many thanks for any advice.
The problem I see with this solution is that you actually have to know
a type (full name) in the Assembly
That is not quite accurate. What you do need to know is a type name is some assembly, not necessarily the assembly you are trying to examine. You should create a MarshalByRef class in your assembly and then use CreateInstanceAndUnwrap to create an instance of it from your own assembly (not the one you are trying to examine). That class would then do the load (since it lives in the new appdomain) and examine and return a boolean result to the original appdomain.
Here is some code to get you going. These classes go in your own assembly (not the one you are trying to examine):
This first class is used to create the examination AppDomain and to create an instance of your MarshalByRefObject class (see bottom):
using System;
using System.Security.Policy;
internal static class AttributeValidationUtility
{
internal static bool ValidateAssembly(string pathToAssembly)
{
AppDomain appDomain = null;
try
{
appDomain = AppDomain.CreateDomain("ExaminationAppDomain", new Evidence(AppDomain.CurrentDomain.Evidence));
AttributeValidationMbro attributeValidationMbro = appDomain.CreateInstanceAndUnwrap(
typeof(AttributeValidationMbro).Assembly.FullName,
typeof(AttributeValidationMbro).FullName) as AttributeValidationMbro;
return attributeValidationMbro.ValidateAssembly(pathToAssembly);
}
finally
{
if (appDomain != null)
{
AppDomain.Unload(appDomain);
}
}
}
}
This is the MarshalByRefObject that will actually live in the new AppDomain and will do the actual examination of the assembly:
using System;
using System.Reflection;
public class AttributeValidationMbro : MarshalByRefObject
{
public override object InitializeLifetimeService()
{
// infinite lifetime
return null;
}
public bool ValidateAssembly(string pathToAssembly)
{
Assembly assemblyToExamine = Assembly.LoadFrom(pathToAssembly);
bool hasAttribute = false;
// TODO: examine the assemblyToExamine to see if it has the attribute
return hasAttribute;
}
}
This can be easily done by using a managed assembly reader, such as Mono.Cecil. You'd check whether the assembly is decorated with an attribute, without loading the assembly in the AppDomain, and actually, without messing with AppDomains at all. For instance, with Cecil:
bool HasMandatoryAttribute (string fileName)
{
return AssemblyDefinition.ReadAssembly (fileName)
.CustomAttributes
.Any (attribute => attribute.AttributType.Name == "MandatoryAttribute");
}
That's basically what are doing most of he plugins systems, as creating an AppDomain and tearing it down is quite expensive for the runtime.
I'm having a problem with XML deserialization that is baffling me.
I'm building an application that supports local customization of various services that it uses. I've implemented an abstract ServiceLocator class whose methods return various objects. Each custom installation is responsible for implementing a subclass of this and providing implementations of those methods. The meat of this class looks like this:
public abstract class ServiceLocator
{
public static void Initialize(string customFeaturesPath)
{
Assembly a = Assembly.LoadFrom(customFeaturesPath);
Type t = a.GetExportedTypes()
.AsEnumerable()
.Where(x => x.IsSubclassOf(typeof (ServiceLocator)))
.First();
Default = (ServiceLocator)a.CreateInstance(t.FullName);
}
public static ServiceLocator Default { get; private set; }
public abstract DefaultValuesContainer CreateDefaultValuesContainer();
}
This works just fine: I get the path to the custom features assembly from the application configuration file, the program calls Initialize, and then the application can call the various methods on ServiceLocator.Default and they return the appropriate custom implementations of the services.
One of these services is a DefaultValuesContainer. This is a simple object that exposes properties whose values need to be persisted in a user settings file. The idea is that I can serialize this object into a single user setting of type string. It makes for a user setting file that you wouldn't want to edit manually, but I'm cool with that.
Here's a concrete implementation of ServiceLocator.CreateDefaultValuesContainer:
protected override DefaultValuesContainer CreateDefaultValuesContainer(string serializedXml)
{
DefaultValuesContainer c = new ClientDefaultValuesContainer();
if (string.IsNullOrEmpty(serializedXml))
{
return c;
}
XmlSerializer x = new XmlSerializer(c.GetType());
return (DefaultValuesContainer) x.Deserialize(new StringReader(serializedXml));
}
Now here's the thing.
I've built unit tests for this using NUnit. When I run the tests in the test fixture class that exercises the client custom features, they work. When I run the entire test suite, the last line of the above method throws this exception:
System.InvalidOperationException : There is an error in XML document (0, 0).
----> System.IO.FileLoadException : Could not load file or assembly 'ClientCustomFeatures, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. Invalid pointer (Exception from HRESULT: 0x80004003 (E_POINTER))
----> System.ArgumentNullException : Value cannot be null.
Parameter name: path1
I'm kind of baffled as to why. The SetUp method still runs, and ServiceLocator.Default still returns an object of type ClientServiceLocator, which means that it has loaded the ClientCustomFeatures assembly. Indeed, the very method that's throwing the exception is in the assembly that I'm being told can't be loaded.
What is the XmlSerializer trying to do here? Why is it trying to load an assembly that's already loaded? What on earth does "Invalid pointer" mean? And above all, how should I be debugging something like this?
If your custom assembly does not know where to load the assembly containing the ClientCustomFeatures class, this will happen. This occurs when you've deployed your custom assembly to a location that is not in the path of your main assembly and your main assembly is not in the gac. So if your custom asseblies are loaded from sub directories of your main assembly this should go away. However, if they are located in arbitrary places, you'll have a problem because they need to load your main assembly as they need access to the ClientCustomFeatures type.
I've had problems with the assembly loader (Fusion?) when one assembly loads another assembly which itself has (non-GAC) references. YourDLL.XmlSerializers.dll might be one such assembly. Try turning off Visual Studio's option to automatically generate an XML serialization assembly (Project options) - this will remove the additional assembly (and hence the dependency on it).
Fusion Log Viewer
To help diagnose assembly loading problems like these, take a look at the Fusion Log Viewer (AKA fuslogvw.exe).
Fusion == the .NET component that locates and loads assemblies.
Try to replace the line:
XmlSerializer x = new XmlSerializer(c.GetType());
with:
XmlSerializer x = new XmlSerializer(c.GetType(), new Type[] { typeof(DefaultValuesContainer), typeof(ClientDefaultValuesContainer) });