c# using Assembly.Load to load multiply assembly from memory - c#

I have 2 assembly should be loaded from memory, I use the following code to implement it but it can't work. Help me, Thank you!
Assembly.Load(File.ReadAllBytes("b.dll"));
var assembly = Assembly.Load(File.ReadAllBytes("a.dll"));//a.dll referenced b.dll
var type = assembly.GetTypes().First(p => p.FullName == "Namespace1.Type1");
type.GetMethod("StaticMethod1", BindingFlags.Static | BindingFlags.Public).Invoke(null, new object[] { });//it throw an exception, can't load file or assembly b.dll

Using Assembly.Load to load an assembly from a byte array, loads the assembly but does not register it under its name in the Application Domain. In order to provide your own assembly from a byte array when the runtime is looking to load a specific assembly with a specific name, you implement the AppDomain.AssemblyResolve event.
See this question and the answer for example code.

Related

Mixed assembly won't load from ILMerge-ed result

I have a simple program that references The open-source AlphaVSS library. It contains 3 dlls:
AlphaFS.dll (IL)
AlphaVSS.Common.dll (IL)
AlphaVSS.x64.dll (Mixed)
Now I want to merge my main.exe with AlphaFS.dll and AlphaVSS.Common.dll, leave only AlphaVSS.x64.dll on the disk.
So I added an AssemblyResolve event to load AlphaVSS.x64.dll from system32:
AppDomain.CurrentDomain.AssemblyResolve += (sender, args2) =>
{
Console.WriteLine("Loading " + args2.Name);
string name = args2.Name.Split(',')[0];
string path = "c:\\windows\\system32\\" + name + ".dll";
if (args2.Name.StartsWith("AlphaVSS"))
{
if (File.Exists(path))
{
return Assembly.LoadFrom(path);
}
}
return null;
};
And I merged the rest successfully:
%ILMerge% /v4 /ndebug /out:"$(TargetDir)all-in-one.exe" "$(TargetDir)$(TargetFileName)" "$(TargetDir)AlphaVSS.Common.dll" "$(TargetDir)AlphaFS.dll"
Then I copied only all-in-one.exe and AlphaVSS.x64.dll to another computer, and launched all-in-one.exe. The first DLL to load is AlphaVSS.x64.dll and it's loaded. Then it tried to load AlphaVSS.Common.dll and it's not found.
From ILSpy, The AlphaVSS.Common.dll is already merged into the all-in-one.exe and loaded. But the mixed assembly AlphaVSS.x64.dll is still referencing AlphaVSS.Common.dll, so it tried to load it again.
Does anyone know how to resolve this?
Based on your answers in comments, when you load AlphaVSS.x64.dll from the drive, it tries to load AlphaVSS.Common.dll (since it references it).
But you already catching it with your custom resolver. And since AlphaVSS.Common.dll contains AlphaVSS this condition will be true if (args2.Name.StartsWith("AlphaVSS")) and your resolver'll try to load AlphaVSS.Common.dll from the drive.
What you need to do is modify your resolver to load only AlphaVSS.x64.dll from the drive and load AlphaVSS.Common.dll from contents of current assembly.
In case of AlphaVSS.Common.dll try doing something like this:
Assembly assembly = AppDomain.CurrentDomain.GetAssemblies()
.Where(a => a.GetName().Name == "AlphaVSS.Common")
.Single();

Load with Roslyn compiled DLL at runtime multiple times

Currently I'm compiling and loading an assembly at runtime. The assembly contains always the same namespaces and classes. When I'm doing this
multiple times in the same application instance, does always the newest assembly will be used, when creating new instances of the classes which are in the assembly? Or is this not guaranteed?
You're creating exactly what you're trying to create. This may or may not be what you want to create, though.
Most likely, your assemblies don't have a specific name, but rather, a randomized unique name - in that case, the types are entirely different and only accidentally similar as far as .NET is concerned. Types from two different compilations are entirely unrelated, and are not compatible. This may or may not be a problem when you're accessing them through an interface defined outside of the dynamic assembly, depending on how exactly you're using the types.
If you add an assembly name, the situation gets a bit more complicated. You can't load the same assembly twice (the old one is not replaced by the new one), so you need to change the version. However, two versions of the same assembly cannot be loaded in the same application domain (except when doing "fun" with AssemblyResolve etc. - but that's quite tricky to get right). The second assembly would simply fail to load.
In the end, the Type you're trying to instantiate is the one you do instantiate (barring the use of binding redirects, which are bonus fun :P). If some piece of your code holds on to a Type from a previous compilation, that's what it's going to create.
If your question is if I load an assembly in AppDomain
Assembly a1=Assembly.Load(Array of Assembly);
And then change code with roslyn like class name and create new assembly of your project and load it again
Assembly a2 =Assembly.Load(Array of Assembly);
Now is a2 is loaded in CurrentDomain ?
My answer is no .a1 is now in CurrentDomain.
You can test it .
So for work with new assembly you have to use below solution.
You need to load this assembly in another AppDomain and every time you can Unload this AppDomain and create it again and load assembly again
First create a class that CurrentDomain will load instance of that to another AppDomain this object of class must load your assembly and it's dependencies to second AppDomain .
// you can create this class in another project and
// make assembly .because you need a copy of it in
//folder that you set for ApplicationBase of second AppDomain
public class AssemblyLoader : MarshallByRefObject
{
AssemblyLoader()
{
AppDomain.CurrentAppDomain.AssemblyResolve += LoaddependencyOfAssembly;
}
public void LoaddependencyOfAssembly(object sender,)
{
//load depdency of your assembly here
// if you has replaced those dependencies to folder that you set for ApplicationBase of second AppDomain doesn't need do anything here
}
public Assembly asm {get;set;}
public void LoadAssembly(MemoryStream ms)
{
asm= Assembly.Load(ms.ToArray());
}
}
in where you want to load assembly
AppDomainSetup s=new AppDomainSetup(){ApplicationBase="anotherFolderFromYourAppBinFoldr};
AppDomain ad= AppDomain.CreateDomain("name",AppDomain.CurrentDomain.Evidence,s);
Type t = typeof( AssemblyLoader);
AssemblyLoader al = ( AssemblyLoader) ad.CreateInstanceAndUnwrap(t.Assembly.FullName,t.FullName);
// load assembly here by Stream or fileName
al.LoadAssembly(ms );
// now assembly loaded on ad
// you can do your work with this assembly by al
// for example create a method in AssemblyLoader to
// get il of methods with name of them
// Below IL is in CurrentDomain
//when compiler goes to GetIlAsByteArray you are in second AppDomain
byte[] IL = al.GetILAsByteArray("NameOfMethod");
//And unload second AppDomain
AppDomain.Unload(ad);

How to unloaded a loaded Assembly

I'm trying to load an assembly, use Reflection to get all the class' inside that .dll, and then delete the .dll. However I am getting access denied exception when trying to delete the .dll. This is not due to access rights as I can delete the .dll if I do not load it first.
I've looked on MSDN, and apparently there is no way to "unload", but I'm hoping that there might be another way.
Assembly assembly;
assembly = Assembly.LoadFrom(filepath);
Type[] listOfAllClassInDll = assembly.GetTypes();
List<string> listOfAllClassNamesInDll = new List<string>();
foreach (Type classInDll in listOfAllClassInDll)
{
listOfAllClassNamesInDll.Add(classInDll.Name);
}
File.Delete(filepath);
Actually you can't do it straightforward..
There is why: http://blogs.msdn.com/b/jasonz/archive/2004/05/31/145105.aspx
But you can dynamically load your assembly in another AppDomain. Then when you don't need your assembly - you have to unload you AppDomain with loaded assembly.
Example there: https://bookie.io/bmark/readable/9503538d6bab80
Instead of using LoadFrom/LoadFile you can use Load with File.ReadAllBytes. Here you do not use assembly file directly but read it's content and use the read data. So, you are free to rename/delete the file.
Your code will then look like:
Assembly assembly;
assembly = Assembly.Load(File.ReadAllBytes(filepath));
Type[] listOfAllClassInDll = assembly.GetTypes();
List<string> listOfAllClassNamesInDll = new List<string>();
foreach (Type classInDll in listOfAllClassInDll)
{
listOfAllClassNamesInDll.Add(classInDll.Name);
}
File.Delete(filepath);
Hope this helps :)

How to reproduce InvalidCastException when binding to an Assembly in the LoadFrom Context

In Suzanne Cook's .NET CLR Notes she talks about the dangers of the "LoadFrom" context. Specifically,
If a Load context assembly tries to load this assembly by display name, it will fail to be found by default (e.g., when mscorlib.dll deserializes this assembly)
Worse, an assembly with the same identity but at a different path could be found on the probing path, causing an InvalidCastException, MissingMethodException, or unexpected method behavior later on.
How do you reproduce this behavior with deserialization, but without explicitly loading two different versions of the assembly?
I've created a Console application, A.exe, which indirectly loads (via `Assembly.LoadFrom) and calls (via reflection) code from a class library, B.dll.
A.exe does not (necessarily) have a reference to B.dll, but B.dll should exist in the same directory as A.exe
A copy of of B.dll should be placed into another directory (here I've used the subdirectory called LoadFrom), this is the location we will use Assembly.LoadFrom on.
A.exe
class Program
{
static void Main(string[] args)
{
// I have a post build step that copies the B.dll to this sub directory.
// but the B.dll also lives in the main directory alongside the exe:
// mkdir LoadFrom
// copy B.dll LoadFrom
//
var loadFromAssembly = Assembly.LoadFrom(#".\LoadFrom\B.dll");
var mySerializableType = loadFromAssembly.GetType("B.MySerializable");
object mySerializableObject = Activator.CreateInstance(mySerializableType);
var copyMeBySerializationMethodInfo = mySerializableType.GetMethod("CopyMeBySerialization");
try
{
copyMeBySerializationMethodInfo.Invoke(mySerializableObject, null);
}
catch (TargetInvocationException tie)
{
Console.WriteLine(tie.InnerException.ToString());
}
Console.ReadKey();
}
}
B.dll
namespace B
{
[Serializable]
public class MySerializable
{
public MySerializable CopyMeBySerialization()
{
return DeepClone(this);
}
private static T DeepClone<T>(T obj)
{
using (var ms = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(ms, obj);
ms.Position = 0;
return (T)formatter.Deserialize(ms);
}
}
}
}
Output
System.InvalidCastException:
[A]B.MySerializable cannot be cast to
[B]B.MySerializable.
Type A originates from 'B, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
in the context 'Default' at location 'c:\Dev\bin\Debug\B.dll'.
Type B originates from 'B, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
in the context 'LoadFrom' at location 'c:\Dev\bin\Debug\LoadFrom\B.dll'.
at B.MySerializable.DeepClone[T](T obj)
at B.MySerializable.CopyMeBySerialization()
Here's what is happening:
When the call to formatter.Deserialize(ms) is made, it uses the information stored in the MemoryStream to determine what type of object it needs to create (and which assembly it needs in order to create that object).
It finds that it needs B.dll and attempts to Load it (from the default "Load" context).
The currently loaded B.dll is not found (because it was loaded in the "LoadFrom" context).
Thus, an attempt is made to find B.dll in the usual locations--it is found in the ApplicationBase directory and is loaded.
All types in this B.dll are considered different types that those from the other B.dll. Thus the cast in the expression (T)formatter.Deserialize(ms) fails.
Additional notes:
If the B.dll had not existed somewhere where A.exe could find it using Assembly.Load, then instead of an InvalidCastException, there would be a SerializationException with the message Unable to find assembly 'B, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
The same problem occurs even with signed assemblies, but what is more alarming with signed assemblies is that it can load a different version of the signed assembly. That is, if B.dll in the "LoadFrom" context is 1.0.0.0, but the B.dll found in the main directory is 2.0.0.0, the serialization code will still load the wrong version B.dll to do deserialization.
The DeepClone code I've shown seems to be one of the more popular ways to do a deep clone on an object. See: Deep cloning objects in C#.
So, from any code that was loaded into the "LoadFrom" context, you cannot use deserialization successfully (without jumping through additional hoops to allow the assembly to successfully load in the default "Load" context).

InvalidCastException for two Objects of the same type

I have this weird problem that I cannot handle myself. A class in the model of my mvp-project designed as singleton causes an InvalidCastException.
The source of error is found in this code line where the deserialised object is assigned to the instance variable of the class: engineObject = (ENGINE)xSerializer.Deserialize(str);. It occurs whenever I try to add one of my UserControls to a Form or to a different UC. All of my UCs have a special presenter that accesses the above mentioned instance variable of the singleton class.
This is what I get when trying to add a UC somewhere:
'System.TypeInitializationException: The type initializer for 'MVP.Model.EngineData' threw an exception. ---->
System.InvalidCastException: [A]Engine cannot be cast to [B]Engine. Type A originates from 'MVP.Model, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' in the context 'LoadNeither'
at location '[...]\AppData\Roaming\Microsoft\VisualStudio\9.0\ProjectAssemblies\uankw1hh01\MVP.Model.dll'.
Type B originates from 'MVP.Model, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' in the context 'LoadNeither'
at location '[...]\AppData\Roaming\Microsoft\VisualStudio\9.0\ProjectAssemblies\u_hge2de01\MVP.Model.dll'
...
So I somehow have two assemblies and they are not accessed from my project folder, but from a VS temp folder? I googled a lot and only found this: IronPython Exception: [A]Person cannot be cast to [B]Person. There is a solution offered, but it concerns IronPhyton and I don't know where to use it within my project.
Types are per-assembly; if you have "the same" assembly loaded twice, then types in each "copy" of the assembly are not considered to be the same type.
These issues normally crop up when the two assemblies are in the Load and LoadFrom contexts. See
Difference between LoadFile and LoadFrom with .NET Assemblies?
and the link to suzcook's blog for details on that issue.
Also, consider using the fusion log viewer to help diagnose the problem.
http://msdn.microsoft.com/en-us/library/e74a18c4%28VS.71%29.aspx
Judging by the context in which the assembly is getting loaded (the context is "LoadNeither"), some developers may be doing something like loading an assembly that has been internally packaged as a resource with your application. If you do this, you will be using the AppDomain.CurrentDomain.AssemblyResolve event handler, so that your application can specify where .NET should get any particular assembly that it needs.
My answer will not explain the machinations of how to do that - but I'm mentioning it because this process led directly to the same exact error encountered by the original poster. As Eric Lippert mentions, types are per-assembly. So if you load an individual assembly more than once, the same defined class will appear as different classes - even though visual inspection shows that they appear to be the same.
We've seen instances in which .NET will call the ResolveEventHandler more than once for the same DLL. I'm not sure why .NET sometimes does this (it happened on some machines, but not all machines). But to resolve the problem, we needed keep a global list of handles to loaded assemblies, so that if .NET wanted to load the assembly again, we returned a handle to the same Assembly that was originally loaded, instead of loading another copy into memory.
I have included the code that caused the issue for us, and notes about how to handle it properly.
public void AppStartup (object sender, StartupEventArgs e)
{
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
}
public System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
string dllName = args.Name.Contains(',') ? args.Name.Substring(0, args.Name.IndexOf(',')) : args.Name.Replace(".dll", "");
dllName = dllName.Replace(".", "_");
if (dllName.EndsWith("_resources")) return null;
System.Resources.ResourceManager rm = new System.Resources.ResourceManager(GetType().Namespace + ".Properties.Resources", System.Reflection.Assembly.GetExecutingAssembly());
byte[] bytes = null;
try
{
bytes = (byte[])rm.GetObject(dllName);
}
catch (Exception ex)
{
}
if (bytes != null)
{
// the following call will return a newly loaded assembly
// every time it is called
// if this function is called more than once for the same
// assembly, you'll load more than one copy into memory
// this can cause the InvalidCastException
// instead of doing this, you keep a global list of loaded
// assemblies, and return the previously loaded assembly
// handle, instead of loading it again
return System.Reflection.Assembly.Load(bytes);
}
return null;
}
My situation involved two copies of the same dll. One was in the bin folder and one was in a sub-folder of the same bin folder. Both were loaded, amazingly some things worked fine, but some things didn't and that's when this error message appeared:
System.InvalidOperationException; There was an error generating the XML document.; Source: System.Xml; TargetSite: Void Serialize(System.Xml.XmlWriter, System.Object, System.Xml.Serialization.XmlSerializerNamespaces, System.String, System.String);
Hidden in this was the following inner exception (this was to do with Microsoft Dynamics CRM 4.0, but could relate to anything)
System.InvalidCastException; [A]XXX.CRMCustomCode.YYY.CreateCompanyRequest cannot be cast to [B]XXX.CRMCustomCode.YYY.CreateCompanyRequest. Type A originates from 'XXX.CRMCustomCode, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null' in the context 'LoadFrom' at location 'C:\Program Files\Microsoft CRM\Server\bin\assembly\XXX.CRMCustomCode.dll'. Type B originates from 'XXX.CRMCustomCode, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null' in the context 'Default' at location 'C:\Program Files\Microsoft CRM\Server\bin\XXX.CRMCustomCode.dll'.;
I simply deleted the duplicate dll (in C:\Program Files\Microsoft CRM\Server\bin) and the error went away.
My particular case - class library referenced in web application was renamed and rebuilt. Older version of library was still in bin folder under old name. The framework loads whatever in bin folder (both libraries) and emits this error. So it happens not only when assemblies are loaded explicitly.
The obvious solution in my case is to clean bin folder.
I got it working when I tried changing this cast:
var t = (TeacherWebPages)Session["TeachersAD"];
To this:
var t = Session["TeachersAD"] as TeacherWebPages;
However the assembly/session/memcache was different and no data got back but no error occurred. So later I still had to delete specific temporary files every time source page was changed from the folder it was complaining about which would require me to kill IIS process from task manager. Or in my case I can just logout and it clears the session state.

Categories

Resources