System.Reflection.Assembly.LoadFrom - Duplicate assembly name - Need Better workaround - c#

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();

Related

Load an assembly from plugin

INTRODUCTION
I'm writing a plugin for Navisworks and i'm using the Dropbox api to download/upload documents from a repository.
PROBLEM
Dropbox.Api uses the Newtonsoft.Json.dll version 7.0, the problem is that Navisworks uses the 4.0 version of the same assembly, so I cannot use the Dropbox api because it throws an exception every time:
System.AggregateException: One or more errors occurred. ---> System.IO.FileLoadException: Could not load file or assembly 'Newtonsoft.Json, Version=7.0.0.0, ...
So as I understand the program has the assembly 4.0v so Dropbox.Api cannot execute properly.
By now, what I've done is to use another process which I can load the right assembly and download/upload the files from there, but I would like to avoid using a second process.
I'm trying to use reflection to load the assembly at runtime, but it takes no effect, the program still cannot find the newer assembly.
//Load the assembly at the beginning of the plugin
var ass = System.Reflection.Assembly.Load(Properties.Resources.Newtonsoft_Json);
//Use the Dropbox api
//Exception...
Can I force, somehow, the program to use the newer assembly (temporary)?
Is there some solution that I've missed?
You are encountering this issue because you cannot load two different non-strong-named versions of a .NET assembly (no matter where it is on the file system) into the same AppDomain. By default you start with a single AppDomain called the Primary AppDomain for your process.
A strong-named assembly is one that takes the filename; version; signing key and culture to produce a unique assembly.
By now, what I've done is to use another process which I can load the right assembly and download/upload the files from there, but I would like to avoid using a second process.
No need to create a second process, you can instead create a 2nd AppDomain in the same process. Each AppDomain can load different versions of assemblies including Newtonsoft.Json without conflict.
I'm trying to use reflection to load the assembly at runtime, but it takes no effect, the program still cannot find the newer assembly.
That won't work, it's essentially the same as letting .NET doing it for you automatically.
The only time when you can load multiple versions of .NET assemblies into the same AppDomain is if the assembly (in this case the NuGet package) and dependent assemblies are all strong-named. For some reason I have never been able to fathom is why most open source .NET devs refuse to strong-name assemblies.

Issue with AppDomain.CreateInstanceFromAndUnwrap

I'm using AppDomain.CreateInstanceFromAndUnwrap() to create an object in a different AppDomain. I couldn't get it to work because it kept throwing the following error at me:
Could not load file or assembly 'COMon, Version=2.0.4960.27874, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The module was expected to contain an assembly manifest.
However, I found that it is because it tries to load my DLL (which has the same name as my .NET assembly).
This is how I call the method:
_script = (Script)_appDomain.CreateInstanceFromAndUnwrap(Assembly.GetExecutingAssembly().Location, "COMon.Scripting.Script");
It works fine as long as there isn't a native DLL file with the same name as my .NET assembly. Why does this happen when I'm passing it the full path and filename of my .NET assembly?
when I'm passing it the full path and filename of my .NET assembly?
That's not how the method works. The first argument is the display name of the assembly. It is not a file name. The MSDN article recommends that you take a look at Assembly.FullName to learn more about display names.
So the normal CLR search rules will be in effect for finding the assembly. It will look in the GAC first, then in the probing path for the AppDomain. With a quirk that you didn't count on, the CLR does not pay attention to the filename extension for a file. The display name for an assembly doesn't specify it. So it considers an EXE and a DLL equivalent. Something you can see back in the trace for Fuslogvw.exe, the utility you always want to use when you have trouble like this. And in other places, adding a reference to an EXE works fine for example.
So it finds COMon.exe and that's a kaboom, it is not a managed assembly.
It isn't that clear what the proper workaround might be in your case, other than simply renaming the assembly. When you tinker with AppDomains then you typically also want to use AppDomainSetup and set the ApplicationBase or PrivateBinPath property.

Get Assembly Version Info

We have a large solution with about 30 projects. We also have some common code utilities and as part of that utility class we have included a GetAssemblyVersionInfo method that returns the version info of that assembly. However, we want to get the Assembly Version Info of the assembly for the project not the utility DLL. Is there any way of having a utility function that returns the version info of another assembly based on execution and not passing around filenames?
EDIT:
For those who are curious, I solved it using:
System.Reflection.Assembly.GetCallingAssembly().GetName().Version
If you have the assembly name, you can dynamically load it into the current AppDomain to retrieve the version info.
Assembly.LoadFrom(string.Format("{0}.dll", "assemblyshortname")).GetName().Version
I think you should use GetEntryAssembly() instead of GetCallingAssembly().

Visual c# project fails to run due to different versions of the same assembly

My c# application uses two different pre-packaged parsers, that each use a different strong version of Antlr.Runtime.dll.
The application throws a FileLoadException, apparently because it correctly loaded only one of the versions and could not find the other.
The <app>\bin\x86\Debug directory contains only one copy of the dll.
Note that I cannot use assembly aliases since my code doesn't directly call the versioned assemblies - they are only indirectly called via the parser assemblies.
Is this a matter of instructing Visual Studio to place the differently versioned dlls in separate directories since they have the same name? I'd like to avoid using the GAC as far as possible.
I've read many "side by side" and related articles, but none seem to address this question.
I'm in no way a SxS expert but at my company when this happens they create a different folder for each assembly and a manifest to load them.
Solved this problem by hooking AppDomain.AssemblyResolve:
AppDomain.CurrentDomain.AssemblyResolve += (s, e) =>
{
Assembly result = null;
if (e.Name ==
#"Antlr3.Runtime, Version=3.1.3.42154, Culture=neutral, PublicKeyToken=3a9cab8f8d22bfb7")
result = Assembly.LoadFile(
#"C:\src\NHibernate-3.0.0.GA\lib\net\3.5\Antlr3.Runtime.dll");
return result;
};

Embedding assemblies inside another 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!)

Categories

Resources