I am trying to embed a third party library into my DLL. The DLL has been referenced and set not to copy local as well as added to the resources. I then do the following to load the DLL at during runtime.
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{
string resourceName = new AssemblyName(args.Name).Name + ".dll";
string resource = Array.Find(Assembly.GetExecutingAssembly().GetManifestResourceNames(), element => element.EndsWith(resourceName));
Assembly assembly;
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resource))
{
Byte[] assemblyData = new Byte[stream.Length];
stream.Read(assemblyData, 0, assemblyData.Length);
assembly = Assembly.Load(assemblyData);
stream.Close();
}
return assembly;
};
The assembly is returned and I can step further into the code but when instantiating a new object from the DLL's constructor I receive a TypeInitilizationException with an inner message saying "Assembly has been tampered".
Is there anyway around this or is my method of loading the DLL wrong?
EDIT
instance = new iConfServerDotNet();
also tried
System.Reflection.ConstructorInfo constructorInfo = typeof(iConfServerDotNet).GetConstructor(new Type[] { });
if (constructorInfo.DeclaringType.Name == "iConfServerDotNet")
{
object o = constructorInfo.Invoke(new Object[] { }) as UserControl;
}
EDIT 2
The new code to get the type of class... and call it's contstructor. This still causes the same exception.
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{
string resourceName = new AssemblyName(args.Name).Name + ".dll";
string resource = Array.Find(Assembly.GetExecutingAssembly().GetManifestResourceNames(), element => element.EndsWith(resourceName));
Assembly assembly;
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resource))
{
Byte[] assemblyData = new Byte[stream.Length];
stream.Read(assemblyData, 0, assemblyData.Length);
assembly = Assembly.Load(assemblyData);
stream.Close();
}
Type type = assembly.GetModule(resourceName).GetType("iConfServer.NET.iConfServerDotNet");
//object instance = type.GetConstructors()[0].Invoke(null);
object instance = Activator.CreateInstance(type);
return assembly;
};
Exception occurs at Activator.CreatInstance(type);
Assuming these statements where correct (note that these where posted by the original posted in the comments underneath the question):
"The type initializer for .. threw an exception." at iConfServer.NET.iConfServerDotNet..ctor()"
The assembly has appropriate information when debugging
The problem probably lies in your way of invoking the constructor. I've created a simple testing library to show you how you can do this properly:
First, the testing library:
namespace TestLibrary
{
public class Main
{
public string GetString()
{
return "Hey, I'm working well!";
}
}
}
I've compiled this to an dynamic link library (.DLL) and added it as reference to an console application project (also, just for testing).
As a note, I've changed the Build Action property of the resource (you can open the property window by right clicking on the resource in the Solution Explorer and clicking Properties) to Embedded Resource.
This is your code (I just changed the first part as I didn't need it, feel free to change it back) to load the library into our running application:
string resourceName = "TestLibrary.dll";
string resource = Array.Find(Assembly.GetExecutingAssembly().GetManifestResourceNames(), element => element.EndsWith(resourceName));
Assembly assembly;
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resource))
{
Byte[] assemblyData = new Byte[stream.Length];
stream.Read(assemblyData, 0, assemblyData.Length);
assembly = Assembly.Load(assemblyData);
stream.Close();
}
Now, to create a new instance of the class we need to find the type of the class first:
Type _typeOfClass = assembly.GetModule("TestLibrary.dll").GetType("TestLibrary.Main");
_typeOfClass will now contain the Type that leads us to The Main class inside the library.
Next we are going to create a new instance of the class and we are going to invoke the GetString method to see if it actually worked:
// Create an instance of the class invoking the (only) constructor.
object _instance = _typeOfClass.GetConstructors()[0].Invoke(null);
// Call the method on the instance we just instantiated
object result = _typeOfClass.GetMethod("GetString").Invoke(_instance, null);
Console.WriteLine(result);
The Console will now show: Hey, I'm Working well!.
If you have multiple constructors and you just want to get the default constructor (no parameters) you can use object _instance = Activator.CreateInstance(_typeOfClass); too.
Related
I'm writing a visual studio extension and I'm completely confused about how and where and when it's loading assemblies. What I have is this:
My Visual Studio extension project (let's call it MyExtension) references several assemblies including an assembly called Foo.dll.
MyExtension contains a class called FooManager that will be instantiated in response to a menu item being clicked.
When FooManager is instantiated it is passed the output path of a project in the current solution and it creates an AppDomain which should load that assembly, something like this:
public FooManager(string assemblyPath)
{
// The actual ApplicationBase of the current domain will be the one of VS and
// not of my plugin
// We need our new AppDomain to be able to find our assemblies
// without this even the CreateInstanceAndUnwrap will fail
var p = System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
var cachePath = Path.Combine(p, "Cache");
var pluginPath = Path.Combine(p, "Test");
if (Directory.Exists(cachePath))
{
Directory.Delete(cachePath, true);
}
if (Directory.Exists(pluginPath))
{
Directory.Delete(pluginPath, true);
}
Directory.CreateDirectory(cachePath);
Directory.CreateDirectory(pluginPath);
var newPath = Path.Combine(pluginPath, Path.GetFileName(assemblyPath));
File.Copy(assemblyPath, newPath, true);
var setup = new AppDomainSetup()
{
ApplicationBase = p,
ShadowCopyFiles = "true",
ShadowCopyDirectories = pluginPath,
CachePath = cachePath
};
domain = AppDomain.CreateDomain("MyTest_AppDomain", AppDomain.CurrentDomain.Evidence, setup);
// FooCollection is defined in MyExtension. but has several references
// to things defined in Foo.dll - it used MEF to load the assembly
// referenced by pluginPath
collection = domain.CreateInstanceAndUnwrap(
typeof(FooCollection).Assembly.FullName,
typeof(FooCollection).FullName,
false,
BindingFlags.Default,
null,
new object[] { pluginPath }, null, null) as FooCollection;
}
Now one property of FooManager looks like this (FooInfo is defined in
Foo.dll):
public IEnumerable<FooInfo> Spiders
{
get
{
return collection.Foos.Select(s => s.Metadata);
}
}
But when I try to access that I get a System.ArgumentException with the message Object type cannot be converted to target type. which I know happens if two copies of the same assembly are loaded from different locations, and I think, ultimately, that's what's happening here, but I can't figure out how to get it to load from the same place.
So after struggling with this a lot (and the above is only my latest attempt), I thought maybe I could serialize to byte[] and then deserialize again as a way to avoid the problem with types, so I tried something like this:
var msgBytes = collection.SerializeFooInfo();
var msg = FooInfo.DeserializeMessage(msgBytes);
Where my serialize and deserialize just use a BinaryFormatter (the classes are marked as Serializable). The serialization seems to work, but on deserialization, when I get to here:
public static List<FooInfo> DeserializeMessage(byte[] source)
{
using (var stream = new MemoryStream(source))
{
BinaryFormatter formatter = new BinaryFormatter();
var msg = formatter.Deserialize(stream);
return msg as List<FooInfo>;
}
}
msg comes back as null. If I try to run it in the debugger using the immediate window, I see that Deserialize threw a FileNotFoundException with the message:
Cannot load assembly 'C:\Users\matt.burland\AppData\Local\Microsoft\VisualStudio\14.0Exp\ProjectAssemblies\qesxy6ms01\Foo.dll'
But I don't understand where that path came from. It's not where my extension has been installed, which is C:\Users\matt.burland\AppData\Local\Microsoft\VisualStudio\14.0Exp\Extensions\MyCompany\FooTools\1.0 and was set as the ApplicationBase for my AppDomain and contains the file foo.dll. So why is it trying to load from the other mystery location? The other location seems to be created dynamically and contains only the foo.dll assembly.
I've do something very similar to this in a windows service (and using a lot of the same classes) and it works just fine, so this seems to be something particular to the way Visual Studio extensions. Can anybody shine a light here?
So I thought this might help: http://geekswithblogs.net/onlyutkarsh/archive/2013/06/02/loading-custom-assemblies-in-visual-studio-extensions-again.aspx
But if I try to attach an AssemblyResolve handler in package class as suggested it doesn't get called for anything interesting (which isn't surprising, it's not the domain I'm try to load from), but if I try to attach to the new domain I create then if I try something like this:
domain.AssemblyResolve += OnAssemblyResolve;
Then it fails because my FooManager isn't marked as serializable. So I created a proxy just for binding the AssemblyResolve, but the AssemblyResolve never fires. So I tried not setting the ApplicationBase when creating my domain, thinking that would force it to have to try to resolve, but then I can't create my proxy class in the created domain because it doesn't know where to load the assembly from!.
So my problem revolves around saving memory.
In essence I need to load an assembly in to a separate app domain other than the main/current domain, check for types within that assembly, and then unload the new domain when done.
Currently my solution is as follows:
AppDomain NewDomain = AppDomain.CreateDomain("newdomain");
foreach(string path in dllPaths) //string list of dll paths
{
byte[] dllBytes = File.ReadAllBytes(dll);
NewDomain.Load(dllBytes); //offending line
}
DoStuffWithNewDomain();
AppDomain.Unload(NewDomain);
The NewDomain.Load line seems to load the assembly into the new domain but also into the current domain of my program.
I used this link as a reference - http://www.csharp411.com/how-to-load-a-net-assembly-into-a-separate-appdomain-so-you-can-unload-it/
Many thanks :)
As mentioned already, loading the assembly from bytes can only happen for the current app domain.
Here is one way to context-switch to make your target app domain the current one, using the DoCallBack method on the app domain (http://msdn.microsoft.com/en-us/library/system.appdomain.docallback%28v=vs.110%29.aspx).
After the assemblies are loaded you can then use the same method to inspect the assemblies and types of the new app domain, and create instances as necessary, before unloading it.
class Program
{
static void Main(string[] args)
{
AppDomain newDomain = AppDomain.CreateDomain("NewDomain");
List<string> dllPaths = new List<string>() { #"c:\dev\taglib-sharp.dll" };
foreach (string dll in dllPaths)
{
AppDomainAsmLoader asmLoad = new AppDomainAsmLoader(File.ReadAllBytes(dll));
newDomain.DoCallBack(new CrossAppDomainDelegate(asmLoad.LoadAsm));
}
newDomain.DoCallBack(new CrossAppDomainDelegate(DoWorkWithAppDomain));
AppDomain.Unload(newDomain);
Console.ReadKey();
}
public static void DoWorkWithAppDomain()
{
Assembly[] asms = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly asm in asms)
{
Type[] types = asm.GetTypes();
foreach (Type type in types)
{
Console.WriteLine("Found the type: {0}", type.FullName);
}
}
}
[Serializable]
public class AppDomainAsmLoader
{
private byte[] AsmData;
public AppDomainAsmLoader(byte[] data)
{
AsmData = data;
}
public void LoadAsm()
{
Assembly asm = Assembly.Load(AsmData);
}
}
}
As Erik suggested, you need to use CreateInstanceAndWrap. Here's an example I did in VB a while back...
Private Function GetCoupler() As IBatchCoupler
Dim CouplerProxy As IBatchCoupler = Nothing
Try
Dim DomainSetupInfo As AppDomainSetup = New AppDomainSetup()
DomainSetupInfo.ConfigurationFile = Path.Combine(mFilePath, "web.config")
DomainSetupInfo.ApplicationBase = Path.Combine(mFilePath, "bin")
DomainSetupInfo.ShadowCopyFiles = "true"
Dim domain As AppDomain = AppDomain.CreateDomain("CoolDomain", AppDomain.CurrentDomain.Evidence, DomainSetupInfo)
'Create remote object in new appDomain via the coupler interface
'to avoid loading the design library into the calling application's primary appDomain
CouplerProxy = domain.CreateInstanceFromAndUnwrap(Path.Combine(DomainSetupInfo.ApplicationBase, "Design.dll"), "BigApplication.Design.BatchCoupler")
Catch ex As Exception
ThisLogger.Error(ex)
End Try
Return CouplerProxy
End Function
I have a command handler which basically works like this:
ControlList.Handlers[CommandType.MyCommandComesHere].Handle(data);
Handlers is a Dictionary<CommandType, ICommandHandler> and CommandType is a enum.
Handle by its turn would lead it to this:
using System;
using log4net;
namespace My_Application
{
public class MyCommand : ICommandHandler
{
private static readonly ILog Logger = LogManager.GetLogger(typeof(MyCommand));
public void Handle(Events data)
{
Console.WriteLine("I can load cs files on the fly yay!!");
}
}
}
My question is how can I make so my application would compile and let me use that cs file while its running?
Any simple example of this would be greatly appreciated but not required as long as I can get some pointers as to what I need to look for as I am not even sure what do I need to make this happen.
To put it simple I am currently trying to understand how could I load a cs file into my application that is already compiled and is currently running.
Using CodeDOM, you need to first create a compiler provider. (You might want to set GenerateExecutable to false and GenerateInMemory to true for your purposes.)
var csc = new CSharpCodeProvider();
var parameters = new CompilerParameters(new[] { "mscorlib.dll", "System.Core.dll" }, "foo.exe", true);
parameters.GenerateExecutable = false;
parameters.GenerateInMemory = true;
Then, you can compile the assembly using CompileAssemblyFromSource and get the CompilerResults returned from it. From this returned object, get a reference to the generated assembly, using its CompiledAssembly property.
var results = csc.CompileAssemblyFromSource(parameters, "contents of the .cs file");
var assembly = results.CompiledAssembly;
Then you can use reflection to create instances from that assembly and call methods on them.
var instance = assembly.CreateInstance("MyCommand");
// etc...
Alternatively, if you're only interested in short code snippets, it might be worth it to use Roslyn instead. You need to create a ScriptEngine first.
var engine = new ScriptEngine();
Then you can just Execute strings on it - or Execute<T> if you're confident that the expression in the string returns a type assignable to T.
var myObject = engine.Execute("1+1");
var myInt = engine.Execute<int>("1+1");
It's definitely more immediate, so it's worth looking into if it serves your purpose.
I have looked for different ways to achieve this and found cs script library lightweight and usable. Here is code snippet how I use it. It runs cs code within app domain so it presumes, that the cs script being compiled comes form trusted source.
using CSScriptLibrary;
using csscript;
using System.CodeDom.Compiler;
using System.Reflection;
//Method example - variable script contains cs code
//This is used to compile cs to DLL and save DLL to a defined location
public Assembly GetAssembly(string script, string assemblyFileName)
{
Assembly assembly;
CSScript.CacheEnabled = true;
try
{
bool debugBuild = false;
#if DEBUG
debugBuild = true;
#endif
if (assemblyFileName == null)
assembly = CSScript.LoadCode(script, null);
else
assembly = CSScript.LoadCode(script, assemblyFileName, debugBuild, null);
return assembly;
}
catch (CompilerException e)
{
//Handle compiler exceptions
}
}
/// <summary>
/// Runs the code either form script text or precompiled DLL
/// </summary>
public void Run(string script)
{
try
{
string tmpPath = GetPathToDLLs(); //Path, where you store precompiled DLLs
string assemblyFileName;
Assembly assembly = null;
if (Directory.Exists(tmpPath))
{
assemblyFileName = Path.Combine(tmpPath, GetExamScriptFileName(exam));
if (File.Exists(assemblyFileName))
{
try
{
assembly = Assembly.LoadFrom(assemblyFileName); //Načtení bez kompilace
}
catch (Exception exAssemblyLoad)
{
Tools.LogError(exAssemblyLoad.Message);
assembly = null;
}
}
}
else
assemblyFileName = null;
//If assembly not found, compile it form script string
if (assembly ==null)
assembly = GetAssembly(script, assemblyFileName);
AsmHelper asmHelper = new AsmHelper(assembly);
//This is how I use the compiled assembly - it depends on your actual code
ICalculateScript calcScript = (ICalculateScript)asmHelper.CreateObject(GetExamScriptClassName(exam));
cex = calcScript.Calculate(this, exam);
Debug.Print("***** Calculated {0} ****", exam.ZV.ZkouskaVzorkuID);
}
catch (Exception e)
{
//handle exceptions
}
}
My application should be scriptable by the users in C#, but the user's script should run in a restricted AppDomain to prevent scripts accidentally causing damage, but I can't really get it to work, and since my understanding of AppDomains is sadly limited, I can't really tell why.
The solution I am currently trying is based on this answer https://stackoverflow.com/a/5998886/276070.
This is a model of my situation (everything except Script.cs residing in a strongly named assembly). Please excuse the wall of code, I could not condense the problem any further.
class Program
{
static void Main(string[] args)
{
// Compile the script
CodeDomProvider codeProvider = CodeDomProvider.CreateProvider("CSharp");
CompilerParameters parameters = new CompilerParameters()
{
GenerateExecutable = false,
OutputAssembly = System.IO.Path.GetTempFileName() + ".dll",
};
parameters.ReferencedAssemblies.Add(Assembly.GetEntryAssembly().Location);
CompilerResults results = codeProvider.CompileAssemblyFromFile(parameters, "Script.cs");
// ... here error checks happen ....//
var sandbox = Sandbox.Create();
var script = (IExecutable)sandbox.CreateInstance(results.PathToAssembly, "Script");
if(script != null)
script.Execute();
}
}
public interface IExecutable
{
void Execute();
}
The Sandbox class:
public class Sandbox : MarshalByRefObject
{
const string BaseDirectory = "Untrusted";
const string DomainName = "Sandbox";
public static Sandbox Create()
{
var setup = new AppDomainSetup()
{
ApplicationBase = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, BaseDirectory),
ApplicationName = DomainName,
DisallowBindingRedirects = true,
DisallowCodeDownload = true,
DisallowPublisherPolicy = true
};
var permissions = new PermissionSet(PermissionState.None);
permissions.AddPermission(new ReflectionPermission(ReflectionPermissionFlag.RestrictedMemberAccess));
permissions.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));
var domain = AppDomain.CreateDomain(DomainName, null, setup, permissions,
typeof(Sandbox).Assembly.Evidence.GetHostEvidence<StrongName>());
return (Sandbox)Activator.CreateInstanceFrom(domain, typeof(Sandbox).Assembly.ManifestModule.FullyQualifiedName, typeof(Sandbox).FullName).Unwrap();
}
public object CreateInstance(string assemblyPath, string typeName)
{
new FileIOPermission(FileIOPermissionAccess.Read | FileIOPermissionAccess.PathDiscovery, assemblyPath).Assert();
var assembly = Assembly.LoadFile(assemblyPath);
CodeAccessPermission.RevertAssert();
Type type = assembly.GetType(typeName); // ****** I get null here
if (type == null)
return null;
return Activator.CreateInstance(type);
}
}
The loaded Script:
using System;
public class Script : IExecutable
{
public void Execute()
{
Console.WriteLine("Boo");
}
}
In CreateInstance of SandBox, I always get null at the marked line. I tried various forms of giving the name, including reading the type name (or fuly qualified name) from results.CompiledAssembly using reflection.
What am I doing wrong here?
The first thing that i'll check is if there are compilation errors (i had several headache caused by this issues)
The second idea is about the resolution of assemblies. I always add as a security check an event handler for AppDomain.CurrentDomain.AssemblyResolve, where i seek on my known path for the missing Assemblies. When the not found assembly is the one i just compiled i add a static reference to it and return it.
What I usually do is this:
Create the new Assembly on file system with the compiler
Load its content with the File.ReadAllBytes
Load the dll with the Assembly.Load in the AppDomain in which i will be using the object
Add the AppDomain.CurrentDomain.AssemblyResolve event
Just in case (since i use this a lot) i created a small library to accomply this kind of things
The code and documentation are here: Kendar Expression Builder
While the nuget package is here: Nuget Sharp Template
I am trying to create a plug-in type archetecure for my project. I would like the ability to load an assembly, get a type that is derived from an abstract base class in my project, instantiate it and load that derived type into the main processing object.
My problem right now is that when I instantiate the object from the reflected assembly, it is always null. I feel that the problem may lie in the fact that the referenced assembly has 3rd party dlls that it is using. Here is the code: the only exception that gets hit is the final one.
static void Main(string[] args)
{
string engineFilePath = ConfigurationManager.AppSettings["EngineFilesDirectory"]
+ "\\" + ConfigurationManager.AppSettings["EngineDllFileName"];
Assembly asm = Assembly.LoadFile(engineFilePath);
Type engineType = asm.GetType(ConfigurationManager.AppSettings["EngineType"]);
if (!engineType.IsSubclassOf(typeof(EngineConcrete)))
{
throw new ArgumentException("Engine is not derived from base implimentation.");
}
object engineInstance = asm.CreateInstance(engineType.Namespace + "." + engineType);
if (engineInstance == null)
{
//always thrown at this point
throw new Exception(string.Format("Engine object is null."));
}
return;
}
If I change the instantiation line to Activator.CreateInstance(engineType), I receive an error saying that one of the 3rd party dll's being referenced by the reflected assembly can't be found, though they are in the same directory as the .dll being reflected.
There is a public constructor for the type that is being reflected as well. It has no parameters and inherits from the EngineConcrete class.
I think null is what you get when the type can't be found; the namespace name plus the type name may not be sufficient (strong naming issues). What happens if you put the 3rd-party dlls in the directory of the executing application, not the directory of the plugin?
This is the bug: engineType.Namespace + "." + engineType — this expression evaluates into "The.Namespace.The.Namespace.Type" instead of "The.Namespace.Type". Either use engineType.FullName or the Activator.CreateInstance class and use the engineType directly.
Update: Note that MSDN says “… or null if typeName is not found.” about Assembly.CreateInstance's return value.
The issue is that the CLR is not finding the third-party assemblies because they are not in a folder that is being probed.
You could add an event handler for the AppDomain.CurrentDomain.AssemblyResolve event to handle those cases. Below is an example implementation that worked for me where assemblies are being dynamically loaded from a "Plugins" folder that is contained within the bin folder:
class Program
{
static string _PluginDirectory;
static string PluginDirectory
{
get
{
if (_PluginDirectory == null)
{
_PluginDirectory = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), #"Plugins");
}
return _PluginDirectory;
}
}
static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
AssemblyName assemblyName = new AssemblyName(args.Name);
return Assembly.LoadFile(System.IO.Path.Combine(PluginDirectory, assemblyName.Name + ".dll"));
}
static void Main(string[] args)
{
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
var asm = Assembly.LoadFile(System.IO.Path.Combine(PluginDirectory, #"Plugin.dll"));
var transmogrifier = asm.CreateInstance("Plugin.ConcreteTransmogrifier") as Common.Transmogrifier;
if (transmogrifier != null)
{
Console.WriteLine(transmogrifier.Transmogrify("Wowzers!"));
}
}
}