I have been using the technique of embedding dlls (embedded resource) into an exe and using the following code to resolve the unknown dlls at runtime.
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{
String resourceName = "Project.lib." + new AssemblyName(args.Name).Name + ".dll";
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
{
Byte[] assemblyData = new Byte[stream.Length];
stream.Read(assemblyData, 0, assemblyData.Length);
return Assembly.Load(assemblyData);
}
};
However when I embed the Spark View Engine dll (for example) it falls over. But only in one particular place. Spark itself dynamically generates class's on the fly. These class then reference Spark (using Spark etc). It is at this point I get the following error.
The type 'Spark.Class' is defined in
an assembly that is not referenced.
You must add a reference to the
assembly 'Spark'
I'm pretty sure that this has nothing to do with the Spark view engine but to do with referencing an embedded assembly from within a dynamically generated class.
Update: stacktrace
An Exception has occurred when running
the Project Tasks Message:
Spark.Compiler.BatchCompilerException:
Dynamic view compilation failed.
c:\Users\Adam\AppData\Local\Temp\kdsjyhvu.0.cs(6,14):
error CS0012: The type
'Spark.AbstractSparkView' is defined
in an assembly that is not referenced.
You must add a reference to assembly
'Spark, Version=1.5.0.0,
Culture=neutral,
PublicKeyToken=7f8549eed921a12c' at
Spark.Compiler.BatchCompiler.Compile(Boolean
debug, String languageOrExtension,
String[] sourceCode) at
Spark.Compiler.CSharp.CSharpViewCompiler.CompileView(IEnumerable1
viewTemplates, IEnumerable1
allResources) at
Spark.SparkViewEngine.CreateEntryInternal(SparkViewDescriptor
descriptor, Boolean compile) at
Spark.SparkViewEngine.CreateEntry(SparkViewDescriptor
descriptor) at
Spark.SparkViewEngine.CreateInstance(SparkViewDescriptor
descriptor) at
ProjectTasks.Core.Templater.Populate(String
templateFilePath, Object data) in
\ProjectTasks\Core\Templater.cs:line
33 at
ProjectTasks..Core.EmailTemplates.RenderImpl(String
name, Object data) in
\ProjectTasks\Core\EmailTemplates.cs:line
19 at
ProjectTasks.Tasks.EmailUsersWithIncompleteModules.Run()
in
\ProjectTasks\Tasks\EmailUsersWithIncompleteModules.cs:line
41 at
ProjectTasks.MaintenanceTaskRunner.Run(Boolean
runNow, IMaintenanceTask[] tasks) in
\ProjectTasks\MaintenanceTaskRunner.cs:line
25 at
ProjectTasks.Initialiser.Init(String[]
args) in
\ProjectTasks\Initialiser.cs:line
30
Anyone have any ideas on a resolution if indeed there is one at all?
I guess Spark uses CodeDom for dynamic code generation. CSharpCodeProvider internally generates source code and runs csc.exe to obtain new types. Since csc.exe needs physical files as references then AssemblyResolve trick will not help in this case.
The stack trace strongly suggests that Spark is using System.CodeDom to dynamically generate assemblies. That requires reference assemblies to be files on disk, the C# compiler runs out-of-process. This is normally not a problem because you'd have Spark.dll in the same directory as your EXE.
You cannot make this work.
Fwiw: this technique is horribly wasteful of system resources. You double the amount of memory required for assemblies. It is the expensive kind of memory as well, it cannot be shared between processes and is backed by the paging file instead of the assembly file. You can also buy yourself some serious type identity trouble. .NET already supports deployment in a single file. It is called setup.exe
As others have said, the problem lies with the fact that the CodeDom produces artifacts on disk that it then subsequently needs access to in order to render the views.
Apart from the fact that embedding Spark is a potential memory hog anyway, I believe there's a potential solution to this problem. Given the fact that the problem is caused by dynamic view generation on the fly, why not take advantage of Spark's batch compilation option to generate the dll's for your views as part of your build.
you can use code similar to the following to achieve this:
var factory = new SparkViewFactory(settings)
{
ViewFolder = new FileSystemViewFolder(viewsLocation)
};
// And generate all of the known view/master templates into the target assembly
var batch = new SparkBatchDescriptor(targetPath);
factory.Precompile(batch);
In the end, you should have an output dll which will contain compiled views, and you can then embed that dll the same way you are embedding the main Spark.dll.
Hope that helps
Rob
Related
I would like to know how to unload an assembly that is loaded into the main AppDomain.
I have the following code:
var assembly = Assembly.LoadFrom( FilePathHere );
I need/want to be able to unload this assembly when I am done.
Thanks for your help.
For .net versions core 3.0 and later:
You can now unload assemblies. Note that appdomains are no longer available in .net core. Instead, you can create one or more AssemblyLoadContext, load your assemblies via that context, then unload that context. See AssemblyLoadContext, or this tutorial that simulates loading a plugin then unloading it.
For .net versions before .net core 3, including netframework 4 and lower
You can not unload an assembly from an appdomain. You can destroy appdomains, but once an assembly is loaded into an appdomain, it's there for the life of the appdomain.
See Jason Zander's explanation of Why isn't there an Assembly.Unload method?
If you are using 3.5, you can use the AddIn Framework to make it easier to manage/call into different AppDomains (which you can unload, unloading all the assemblies). If you are using versions before that, you need to create a new appdomain yourself to unload it.
I also know this is very old, but may help someone who is having this issue!
Here is one way I have found to do it!
instead of using:
var assembly = Assembly.LoadFrom( FilePathHere );
use this:
var assembly = Assembly.Load( File.ReadAllBytes(FilePathHere));
This actually loads the "Contents" of the assembly file, instead of the file itself. Which means there is NOT a file lock placed on the assembly file! So now it can be copied over, deleted or upgraded without closing your application or trying to use a separate AppDomain or Marshaling!
PROS: Very Simple to fix with a 1 Liner of code!
CONS: Cannot use AppDomain, Assembly.Location or Assembly.CodeBase.
Now you just need to destroy any instances created on the assembly.
For example:
assembly = null;
You can't unload an assembly without unloading the whole AppDomain. Here's why:
You are running that code in the app domain. That means there are potentially call sites and call stacks with addresses in them that are expecting to keep working.
Say you did manage to track all handles and references to already running code by an assembly. Assuming you didn't ngen the code, once you successfully freed up the assembly, you have only freed up the metadata and IL. The JIT'd code is still allocated in the app domain loader heap (JIT'd methods are allocated sequentially in a buffer in the order in which they are called).
The final issue relates to code which has been loaded shared, otherwise more formally know as "domain neutral" (check out /shared on the ngen tool). In this mode, the code for an assembly is generated to be executed from any app domain (nothing hard wired).
It is recommended that you design your application around the application domain boundary naturally, where unload is fully supported.
You should load your temporary assemblies in another AppDomain and when not in use then you can unload that AppDomain. It's safe and fast.
If you want to have temporary code which can be unloaded afterwards, depending on your needs the DynamicMethod class might do what you want. That doesn't give you classes, though.
Here is a GOOD example how to compile and run dll during run time and then unload all resources:
http://www.west-wind.com/presentations/dynamicCode/DynamicCode.htm
I know its old but might help someone. You can load the file from stream and release it. It worked for me. I found the solution HERE.
Hope it helps.
As an alternative, if the assembly was just loaded in the first place, to check information of the assembly like the publicKey, the better way would be to not load it,and rather check the information by loading just the AssemblyName at first:
AssemblyName an = AssemblyName.GetAssemblyName ("myfile.exe");
byte[] publicKey = an.GetPublicKey();
CultureInfo culture = an.CultureInfo;
Version version = an.Version;
EDIT
If you need to reflect the types in the assembly without getting the assembly in to your app domain, you can use the Assembly.ReflectionOnlyLoadFrom method.
this will allow you to look at they types in the assembly but not allow you to instantiate them, and will also not load the assembly in to the AppDomain.
Look at this example as exlanation
public void AssemblyLoadTest(string assemblyToLoad)
{
var initialAppDomainAssemblyCount = AppDomain.CurrentDomain.GetAssemblies().Count(); //4
Assembly.ReflectionOnlyLoad(assemblyToLoad);
var reflectionOnlyAppDomainAssemblyCount = AppDomain.CurrentDomain.GetAssemblies().Count(); //4
//Shows that assembly is NOT loaded in to AppDomain with Assembly.ReflectionOnlyLoad
Assert.AreEqual(initialAppDomainAssemblyCount, reflectionOnlyAppDomainAssemblyCount); // 4 == 4
Assembly.Load(assemblyToLoad);
var loadAppDomainAssemblyCount = AppDomain.CurrentDomain.GetAssemblies().Count(); //5
//Shows that assembly is loaded in to AppDomain with Assembly.Load
Assert.AreNotEqual(initialAppDomainAssemblyCount, loadAppDomainAssemblyCount); // 4 != 5
}
I'm working on a utility that reports the version number from a .net assembly, given its path. It uses assy = System.Reflection.Assembly.LoadFrom(path) to load the assembly, then parses assy.GetName.ToString() to learn the version number.
This works fine, as long as I don't try to load 2 different files with the same assembly name in the same invocation of the program. If I do that, LoadFrom() always returns the same Assembly object, even if the files are actually different versions.
This is documented behavior, see the "Remarks" in https://msdn.microsoft.com/en-us/library/1009fa28(v=vs.80).aspx
The utility needs to run with .Net 2.0 under Windows CE; it's an industrial application running on a Symbol (now Zebra) MT2000 handheld scanner. This means that I don't have the choice to use Load() or LoadFile().
The obvious workaround is to invoke the utility twice and compare the results, but that's inconvenient for several reasons. Anybody have any better ideas?
You can try using the AssemblyName.GetAssemblyNamemethod, which doesn't Load the Assembly but causes the file to be opened and closed. More details here
AssemblyName assemblyName = AssemblyName.GetAssemblyName("YourExe.exe");
var versionOfAssembly = assemblyName.Version;
If you only need to get the name of the assembly, you can use method ReflectionOnlyLoadFrom. This method loads distinct assemblies even if they have the same assembly name.
var assm = System.Reflection.Assembly.ReflectionOnlyLoadFrom(path);
var name = assm.GetName().ToString();
I have been investigating adding mod support to a Unity3D game. I am new to Unity (but not .NET), so if it seems like I am missing something obvious, please let me know because I probably am.
I would like to be able to export objects, including custom scripts, directly from the editor. It is easy enough to get the assembly associated with the script. Any loose scripts in the project seem to get compiled down to Assembly-CSharp.dll. The problem comes in when I try to load the assembly at runtime in another Unity project. Calling Assembly.Load(byte[]) returns to me the assembly of the current project, not the one represented by the byte array. I am assuming this is because the AssemblyName of the assembly from the mod project and the host project are both identical (Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null).
As a test, I updated my Visual Studio project for the mod project, renaming it from Assembly-CSharp to something different. This gets wiped out by Unity, but I can do a build before that happens and get the assembly I am looking for with a name other than Assembly-CSharp. Loading this via Assembly.Load(byte[]) seems to do the trick in a very simple 'import a script to rotate a cube' sort of scenario. While this appears to work, I am really looking for something more auto-magic single step from the Unity editor.
Now, on to the question...
If I understand the problem, what I am looking for is a programmatic way to change the AssemblyName of an assembly after compilation. All suggestions & advice welcome.
Some possible routes I have investigated or considered:
1) A magic API method to change AssemblyName and save it.
This would be ideal, but I have yet to stumble across anything useful here.
2) Edit raw bytes of an assembly to change the name? Not sure if this is possible, but if I could safely and reliably overwrite some of the raw bytes of an assembly to change its name, that would be swell.
3) Create a new dynamic assembly containing the types in the original assembly. My experience with dynamic assemblies is limited. I can create one, but what I am unsure how to do (if possible) is copy types defined in the original assembly to the new dynamic assembly.
4) Use the CSharpCodeProvider to manually compile the assembly. Assuming Unity supports it, then I assume this might work, seems like a pain though. I have not yet investigated what it would take to locate all scripts and references needed for compilation. I'd like to avoid it if possible.
5) *Some magic with ilasm/ildasm.* Assuming there is unity equivalents.. Is this something do-able with theses tools?
6) Programmatically update the csproj file and recompile. Seems pretty hacky and a pain. Guessing I would need to have code to support both Visual Studio and MonoDevelop.
I would prefer to avoid external tools and recompilation so one of the first three options would be ideal. Thoughts, ideas? Thanks!
Bonus Question:
My 'import a script to rotate a cube' test seemed to work, however in the log during the loading of the assembly, I see the two messages below. That is not the assembly name I used, not sure where it came from. Should I be concerned? Is this going to come back to bite me later?
> Non platform assembly: data-0B9D7940 (this message is harmless)
> Fallback handler could not load library C:/Dev/Test
> Projects/ModTest/Build/ModHost1/ModHost1_Data/Mono/data-0B9D7940.dll
The solution I ended up going with was using Mono.Cecil. Luckily, the Mono.Cecil library is available in the Unity editor, no need to deploy any extra libraries with my mod tools. Mono.Cecil worked great for renaming the assembly after it was compiled. Here is some code I'm using to make it happen:
// Have Mono.Cecil load the assembly
var assemblyDefinition = Mono.Cecil.AssemblyDefinition.ReadAssembly(assemblyFile.FullName);
// Tell Mono.Cecil to actually change the name
assemblyDefinition.Name.Name = newAssemblyNameNoExtension;
assemblyDefinition.MainModule.Name = newAssemblyNameNoExtension;
// We also need to rename any references to project assemblies (first pass assemblies)
foreach (var reference in assemblyDefinition.MainModule.AssemblyReferences)
{
if (Utilities.IsProjectAssembly(reference.Name))
{
reference.Name = Utilities.GetModAssemblyName(reference.Name, this._modName);
}
}
// Build the new assembly
byte[] bytes;
using (var ms = new MemoryStream())
{
assemblyDefinition.Write(ms, new Mono.Cecil.WriterParameters() { WriteSymbols = true });
bytes = ms.ToArray();
}
I have struggled with this for quite some time before finding the right approach.
While it is certainly possible to change the Assembly Name from the one generated by Unity (either by renaming it in the .csproj file before compilation, or after-compilation with Mono.Cecil (although I have not tried that approach myself)), be aware of the fact that if you want to load an asset bundle that has prefabs with scripts associated from the assembly in question, you will not be able to properly load that asset bundle, even if you have loaded your assembly first (Unity issues warnings of the sort: "the referenced script on this Behaviour is missing"). The reason is that Unity stores the Assembly Name with the script name as well, and requires that the type/script come from that specific Assembly (which makes sense).
What this boils down to is that you need to change the Assembly Name from within Unity. To do that, you can create an Assembly Definition asset in the scripts (root) folder and set the name to the one you wish to have for your Assembly Name. The technical details can be found at: https://docs.unity3d.com/Manual/ScriptCompilationAssemblyDefinitionFiles.html
I'm currently trying to dynamically load an assembly from within a asp.net httphandler. I have a dll that is built as part of a seperate library and my project contains a reference to said DLL and is deployed along with the service with CopyLocal true. I create a throwaway object to get the assembly path and I have confirmed the existence of the dll within the Temporary ASP.NET Files folder, but calling GetTypes() throws an exception.
I do something like:
string assemblyPath = new SomeClassInAssembly().GetType().Assembly.Location;
Type[] types = System.Reflection.Assembly.LoadFrom(assemblyPath).GetTypes();
I cannot add the assembly to the GAC since that would defeat what I am trying to do with the service (think sandbox service that loads assemblies when necessary) and I cannot find anything that has been able to fix my problem thus far.
For reference I'm using VS 2008.
Since you compile your web application with a reference to the assembly i don't see your need to load it using Assembly.LoadFrom. The GetTypes should be available using:
Type[] types = typeof(SomeClassInAssembly).Assembly.GetTypes();
Doh, I finally hooked up to the exception and looked at the LoaderMessage and I was missing a referenced assembly.
If you create a class library that uses things from other assemblies, is it possible to embed those other assemblies inside the class library as some kind of resource?
I.e. instead of having MyAssembly.dll, SomeAssembly1.dll and SomeAssembly2.dll sitting on the file system, those other two files get bundled in to MyAssembly.dll and are usable in its code.
I'm also a little confused about why .NET assemblies are .dll files. Didn't this format exist before .NET? Are all .NET assemblies DLLs, but not all DLLs are .NET assemblies? Why do they use the same file format and/or file extension?
ILMerge does merge assemblies, which is nice, but sometimes not quite what you want. For example, when the assembly in question is a strongly-named assembly, and you don't have the key for it, then you cannot do ILMerge without breaking that signature. Which means you have to deploy multiple assemblies.
As an alternative to ilmerge, you can embed one or more assemblies as resources into your exe or DLL. Then, at runtime, when the assemblies are being loaded, you can extract the embedded assembly programmatically, and load and run it. It sounds tricky but there's just a little bit of boilerplate code.
To do it, embed an assembly, just as you would embed any other resource (image, translation file, data, etc). Then, set up an AssemblyResolver that gets called at runtime. It should be set up in the static constructor of the startup class. The code is very simple.
static NameOfStartupClassHere()
{
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(Resolver);
}
static System.Reflection.Assembly Resolver(object sender, ResolveEventArgs args)
{
Assembly a1 = Assembly.GetExecutingAssembly();
Stream s = a1.GetManifestResourceStream(args.Name);
byte[] block = new byte[s.Length];
s.Read(block, 0, block.Length);
Assembly a2 = Assembly.Load(block);
return a2;
}
The Name property on the ResolveEventArgs parameter is the name of the assembly to be resolved. This name refers to the resource, not to the filename. If you embed the file named "MyAssembly.dll", and call the embedded resource "Foo", then the name you want here is "Foo". But that would be confusing, so I suggest using the filename of the assembly for the name of the resource. If you have embedded and named your assembly properly, you can just call GetManifestResourceStream() with the assembly name and load the assembly that way. Very simple.
This works with multiple assemblies, just as nicely as with a single embedded assembly.
In a real app you're gonna want better error handling in that routine - like what if there is no stream by the given name? What happens if the Read fails? etc. But that's left for you to do.
In the rest of the application code, you use types from the assembly as normal.
When you build the app, you need to add a reference to the assembly in question, as you would normally. If you use the command-line tools, use the /r option in csc.exe; if you use Visual Studio, you'll need to "Add Reference..." in the popup menu on the project.
At runtime, assembly version-checking and verification works as usual.
The only difference is in distribution. When you deploy or distribute your app, you need not distribute the DLL for the embedded (and referenced) assembly. Just deploy the main assembly; there's no need to distribute the other assemblies because they're embedded into the main DLL or EXE.
Take a look at ILMerge for merging assemblies.
I'm also a little confused about why .NET assemblies are .dll files. Didn't this format exist before .NET?
Yes.
Are all .NET assemblies DLLs,
Either DLLs or EXE normally - but can also be netmodule.
but not all DLLs are .NET assemblies?
Correct.
Why do they use the same file format and/or file extension?
Why should it be any different - it serves the same purpose!
You can embed an assembly (or any file, actually) as a resource (and then use the ResourceManager class to access them), but if you just want to combine assemblies, you're better off using a tool like ILMerge.
EXE and DLL files are Windows portable executables, which are generic enough to accomodate future types of code, including any .NET code (they can also run in DOS but only display a message saying that they're not supposed to run in DOS). They include instructions to fire up the .NET runtime if it isn't already running. It's also possible for a single assembly to span across multiple files, though this is hardly ever the case.
Note ILMerge doesn't work with embedded resources like XAML, so WPF apps etc will need to use Cheeso's method.
There's also the mkbundle utility offered by the Mono project
Why do they use the same file format and/or file extension?
Why should it be any different - it serves the same purpose!
My 2ยข bit of clarification here: DLL is Dynamic Link Library. Both the old style .dll (C-code) and .net style .dll are by definition "dynamic link" libraries. So .dll is a proper description for both.
With respect to Cheeso's answer of embedding the assemblies as resources and loading them dynamically using the Load(byte[]) overload using an AssemblyResolve event handler, you need to modify the resolver to check the AppDomain for an existing instance of the Assembly to load and return the existing assembly instance if it's already loaded.
Assemblies loaded using that overload do not have a context, which can cause the framework to try and reload the assembly multiple times. Without returning an already loaded instance, you can end up with multiple instances of the same assembly code and types that should be equal but won't be, because the framework considers them to be from two different assemblies.
At least one way that multiple AssemblyResolve events will be made for the same assembly loaded into the "No context" is when you have references to types it exposes from multiple assemblies loaded into your AppDomain, as code executes that needs those types resolved.
https://msdn.microsoft.com/en-us/library/dd153782%28v=vs.110%29.aspx
A couple of salient points from the link:
"Other assemblies cannot bind to assemblies that are loaded without context, unless you handle the AppDomain.AssemblyResolve event"
"Loading multiple assemblies with the same identity without context can cause type identity problems similar to those caused by loading assemblies with the same identity into multiple contexts. See Avoid Loading an Assembly into Multiple Contexts."
I would suggest you to try Costura.Fody. Just don't forget to Install-Package Fody before Costura.Fody (in order to get the newest Fody!)