I try to get GUID from many assemblies.
I chose the same way as Reflector read GUID - but so far it is extremely slow process. Moreover,I receive many exceptions, when some (connected) dll files are missing
Assembly CurrentDomain_ReflectionOnlyAssemblyResolve(object sender, ResolveEventArgs args)
{
string missedAssemblyFullName = args.Name;
Assembly assembly = Assembly.ReflectionOnlyLoad(missedAssemblyFullName);
return assembly;
}
//... and code
string exe = "test.exe";
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve +=CurrentDomain_ReflectionOnlyAssemblyResolve;
Assembly assembly = null;
if (assembly != null)
{
assembly = Assembly.ReflectionOnlyLoadFrom(exe);
if (assembly != null)
{
var test = CustomAttributeData.GetCustomAttributes(assembly);
foreach (var elem in test)
{
if (elem.AttributeType.Name == "GuidAttribute")
assemblyGuid = (string)elem.ConstructorArguments[0].Value;
}
}
}
How can I make a GUID reading process faster?
Is there any other way? I just want to open manifest file and read the same GUID which is available in AssemblyInfo.cs file [assembly: Guid("98c11478-7295-4c10-a97f-4b6805df6191")
Do I need ReflectionOnlyLoad method?
You can check out the code by Vijay Mukhi, who wrote his version of ILDASM (source code included). It parses the manifest of an assembly prett much the same way Microsoft's ILDASM does. You can then trim the code down to just the functionality you need.
Related
I have a project which needs to load extra assemblies dynamically at runtime for reflection purposes. I need custom code because the path to the DLLs is not known by the framework. This code works fine with normal DLLs, they load fine and I can reflect over them. However, when I attempt to load types which statically uses embedded resources (i.e. a resx) this code fails.
Without my custom assembly resolution code this works fine. Here is my assembly resolution code:
static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
bool isName;
string path = GetAssemblyLoadInfo(args, out isName);
return isName ? Assembly.Load(path) : Assembly.LoadFrom(path);
}
static string GetAssemblyLoadInfo(ResolveEventArgs args, out bool isAssemblyName)
{
isAssemblyName = false;
var assemblyName = new AssemblyName(args.Name);
string path = String.Concat(new FileInfo(DllPath).Directory, "\\", assemblyName.Name, ".dll");
if (File.Exists(path))
{
return path;
}
if (path.EndsWith(".resources.dll"))
{
path = path.Replace(".resources.dll", ".dll");
if (File.Exists(path)) return path;
}
var assemblyLocation = AssemblyLocations.FirstOrDefault(al => al.Name.FullName == assemblyName.FullName);
if (null == assemblyLocation)
{
isAssemblyName = true;
return args.Name;
}
else
{
return assemblyLocation.Location;
}
}
Here is a link to a project which recreates the entire issue:
https://drive.google.com/file/d/0B-mqMIMqm_XHcktyckVZbUNtZ28/view?usp=sharing
Once you download the project, you first need to build TestLibrary, and then run ConsoleApp4. It should work fine and write the string "This is the value of the resource" to the console, which comes from the resx file. However, uncomment line 23 in Program.cs and run it again and it will fail with an exception, which indicates that it failed to load the embedded resources.
The solution in this question solved my issue:
AppDomain.CurrentDomain.AssemblyResolve asking for a <AppName>.resources assembly?
Basically, add the following code to the assembly being loaded:
[assembly: NeutralResourcesLanguage("en-GB", UltimateResourceFallbackLocation.MainAssembly)]
I'm writing a WinForms program that uses MEF to load assemblies. Those assemblies are not located in the same folder than the executable.
As I need to perform some file maintenance, I implemented some code in the file Program.cs, before loading the actual WinForm, so the files (even if assemblies) are not loaded (or shouldn't if they are) by the program.
I'm performing two operations:
- Moving a folder from one location to an other one
- Unzipping files from an archive and overwrite dll files from the folder moved (if file from the archive is newer than the one moved)
The problem is that after moving the folder, files in it are locked and cannot be overwritten. I also tried to move files one by one by disposing them when the move is finished.
Can someone explain me why the files are blocked and how I could avoid that
Thanks
private static void InitializePluginsFolder()
{
if (!Directory.Exists(Paths.PluginsPath))
{
Directory.CreateDirectory(Paths.PluginsPath);
}
// Find archive that contains plugins to deploy
var assembly = Assembly.GetExecutingAssembly();
if (assembly.Location == null)
{
throw new NullReferenceException("Executing assembly is null!");
}
var currentDirectory = new FileInfo(assembly.Location).DirectoryName;
if (currentDirectory == null)
{
throw new NullReferenceException("Current folder is null!");
}
// Check if previous installation contains a "Plugins" folder
var currentPluginsPath = Path.Combine(currentDirectory, "Plugins");
if (Directory.Exists(currentPluginsPath))
{
foreach (FileInfo fi in new DirectoryInfo(currentPluginsPath).GetFiles())
{
using (FileStream sourceStream = new FileStream(fi.FullName, FileMode.Open))
{
using (FileStream destStream = new FileStream(Path.Combine(Paths.PluginsPath, fi.Name), FileMode.Create))
{
destStream.Lock(0, sourceStream.Length);
sourceStream.CopyTo(destStream);
}
}
}
Directory.Delete(currentPluginsPath, true);
}
// Then updates plugins with latest version of plugins (zipped)
var pluginsZipFilePath = Path.Combine(currentDirectory, "Plugins.zip");
// Extract content of plugins archive to a temporary folder
var tempPath = string.Format("{0}_Temp", Paths.PluginsPath);
if (Directory.Exists(tempPath))
{
Directory.Delete(tempPath, true);
}
ZipFile.ExtractToDirectory(pluginsZipFilePath, tempPath);
// Moves all plugins to appropriate folder if version is greater
// to the version in place
foreach (var fi in new DirectoryInfo(tempPath).GetFiles())
{
if (fi.Extension.ToLower() != ".dll")
{
continue;
}
var targetFile = Path.Combine(Paths.PluginsPath, fi.Name);
if (File.Exists(targetFile))
{
if (fi.GetAssemblyVersion() > new FileInfo(targetFile).GetAssemblyVersion())
{
// If version to deploy is newer than current version
// Delete current version and copy the new one
// FAILS HERE
File.Copy(fi.FullName, targetFile, true);
}
}
else
{
File.Move(fi.FullName, targetFile);
}
}
// Delete temporary folder
Directory.Delete(tempPath, true);
}
Check the implementation of the GetAssemblyVersion() method used in this part of code:
if (File.Exists(targetFile))
{
if (fi.GetAssemblyVersion() > new FileInfo(targetFile).GetAssemblyVersion())
{
// If version to deploy is newer than current version
// Delete current version and copy the new one
// FAILS HERE
File.Copy(fi.FullName, targetFile, true);
}
}
fi variable has type FileInfo, GetAssemblyVersion() looks like an extension method. You should check how assembly version is retrieved from the file. If this method loads an assembly it should also unload it to release the file.
The separate AppDomain is helpful if you need to load the assembly, do the job and after that unload it. Here is the GetAssemblyVersion method implementation:
public static Version GetAssemblyVersion(this FileInfo fi)
{
AppDomain checkFileDomain = AppDomain.CreateDomain("DomainToCheckFileVersion");
Assembly assembly = checkFileDomain.Load(new AssemblyName {CodeBase = fi.FullName});
Version fileVersion = assembly.GetName().Version;
AppDomain.Unload(checkFileDomain);
return fileVersion;
}
The following implementation of the GetAssemblyVersion() could retrieve the assembly version without loading assembly into your AppDomain. Thnx #usterdev for the hint. It also allows you to get the version without assembly references resolve:
public static Version GetAssemblyVersion(this FileInfo fi)
{
return AssemblyName.GetAssemblyName(fi.FullName).Version;
}
You have to make sure that you are not loading the Assembly into your domain to get the Version from it, otherwise the file gets locked.
By using the AssemblyName.GetAssemblyName() static method (see MSDN), the assembly file is loaded, version is read and then unloaded but not added to your domain.
Here an extension for FileInfo doing so:
public static Version GetAssemblyVersion(this FileInfo fi)
{
AssemblyName an = AssemblyName.GetAssemblyName(fi.FullName);
return an.Version;
}
The below statement locks the file
destStream.Lock(0, sourceStream.Length);
but after that you havent unlocked the file. Perhaps that is the cause of your problem.
I would start checking if you program has actually already loaded the assembly.
two suggestions:
1 - Call a method like this before calling your InitializePluginsFolder
static void DumpLoadedAssemblies()
{
var ads = AppDomain.CurrentDomain.GetAssemblies();
Console.WriteLine(ads.Length);
foreach (var ad in ads)
{
Console.WriteLine(ad.FullName);
// maybe this can be helpful as well
foreach (var f in ad.GetFiles())
Console.WriteLine(f.Name);
Console.WriteLine("*******");
}
}
2 - In the first line of Main, register for AssemblyLoad Event and dump Loaded Assembly in the event handler
public static void Main()
{
AppDomain.CurrentDomain.AssemblyLoad += OnAssemlyLoad;
...
}
static void OnAssemlyLoad(object sender, AssemblyLoadEventArgs args)
{
Console.WriteLine("Assembly Loaded: " + args.LoadedAssembly.FullName);
}
You definitely load assembly using AssemblyName.GetAssemblyName, unfortunately .NET has no conventional ways of checking assembly metadata without loading assembly. To avoid this you can:
Load assembly in separated AppDomain as Nikita suggested, I can add: load it with ReflectionOnlyLoad
Or get assembly version using Mono.Cecil library as Reflector does
Just for completeness: actually you can load assembly into same AppDomain without locking assembly file in two stage: read file contents into byte[] and using Assembly.Load(byte[] rawAssembly) but this way has serious "Loading Context" issues and what will you do with several loaded assemblies :)
Here is how my application folders looks like:
Application:
+ App.exe
+ App.exe.config
Application/Plugins:
+ Plugin1 (folder)
Application/Plugins/Plugin1:
+ Plugin1.dll
+ SomeDll.dll
So main application App.exe looking for plugins folder and load {PluginName}.dll into memory to run it. This plugin usually uses it's own dependant assemblies which must be loaded (like SomeDll.dll). It appears that it make serious troubles sometimes. I receive exception that for example dependant assembly of dependant assembly cannot be found and I don't know why.
For example, My plugin must load lots of additional dlls becouse plugin runs OwinSelfHost service.
So it must load for example:
System.Web.Http.Owin
Owin
Microsoft.Owin
Microsoft.Owin.Host.HttpListener
Microsoft.Owin.Hosting
and when load Microsoft.Owin.Hosting then throw exception that cannot load Microsoft.Owin
Exception looks like:
Could not load file or assembly 'Microsoft.Owin, Version=2.0.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' or one of it's dependencies. File not found.
I wrote this method to resolve assemblies. It is tweaked to fit my needs.
It basically hooks a AssemblyResolve event to the current application domain to retrieve an requested assembly from a list of directories.
There is no easy way to find where the assembly file that match the namespace to resolve, except by loading an assembly file and check to which namespace it belongs to.
Plus, it discards some unwanted assemblies (like serializers, resources...) and detects dlls or exes that are not .NET assemblies.
A better approach would consist in using the Global Assembly Cache, but we want our plugins to be fully moveable. So here it is.
public static class AssemblyResolver
{
internal static void Hook(params string[] folders)
{
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{
// Check if the requested assembly is part of the loaded assemblies
var loadedAssembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.FullName == args.Name);
if (loadedAssembly != null)
return loadedAssembly;
// This resolver is called when an loaded control tries to load a generated XmlSerializer - We need to discard it.
// http://connect.microsoft.com/VisualStudio/feedback/details/88566/bindingfailure-an-assembly-failed-to-load-while-using-xmlserialization
var n = new AssemblyName(args.Name);
if (n.Name.EndsWith(".xmlserializers", StringComparison.OrdinalIgnoreCase))
return null;
// http://stackoverflow.com/questions/4368201/appdomain-currentdomain-assemblyresolve-asking-for-a-appname-resources-assembl
if (n.Name.EndsWith(".resources", StringComparison.OrdinalIgnoreCase))
return null;
string assy = null;
// Find the corresponding assembly file
foreach (var dir in folders)
{
assy = new[] { "*.dll", "*.exe" }.SelectMany(g => Directory.EnumerateFiles(dir, g)).FirstOrDefault(f =>
{
try { return n.Name.Equals(AssemblyName.GetAssemblyName(f).Name, StringComparison.OrdinalIgnoreCase); }
catch (BadImageFormatException) { return false; /* Bypass assembly is not a .net exe */ }
catch (Exception ex) { throw new ApplicationException("Error loading assembly " + f, ex); }
});
if (assy != null)
return Assembly.LoadFrom(assy);
}
throw new ApplicationException("Assembly " + args.Name + " not found");
};
}
}
Here is how it works:
AssemblyResolver.Hook("\Plugins", "\CommonReferences");
Everytime some assemblies needs to be resolved, it will get the one that is loaded in memory, otherwise it will search in any given folders.
You can use "AssemblyResolve Event" (Method 3):
https://support.microsoft.com/en-us/kb/837908
I have a structure:-
\bin\debug\abc.exe and
\Libs\win32\xyz.dll.
Now I need to refer xyz.dll so as to run my abc.exe. I tried with "probing" tag in app.config but in that case the possibility was only when I had 'Libs' folder in 'debug' folder i.e. where .exe is present. But I want to come 2 folders out from .exe and then go into \Libs\win32 to refer to .dll . Please suggest me what should I do.
One option is handling AssemblyResolve event, every time .NET couldn't find required assembly in current path, it will trigger AssemblyResolve event:
{
// Execute in startup
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainOnAssemblyResolve;
}
private Assembly CurrentDomainOnAssemblyResolve(object sender, ResolveEventArgs args)
{
string RESOURCES = ".resources";
try
{
/* Extract assembly name */
string[] sections = args.Name.Split(new char[] { ',' });
if (sections.Length == 0) return null;
string assemblyName = sections[0];
/* If assembly name contains ".resource", you don't need to load it*/
if (assemblyName.Length >= RESOURCES.Length &&
assemblyName.LastIndexOf(RESOURCES) == assemblyName.Length - RESOURCES.Length)
{
return null;
}
/* Load assembly to current domain (also you can use simple way to load) */
string assemblyFullPath = "..//..//Libs//" + assemblyName;
FileStream io = new FileStream(assemblyNameWithExtension, FileMode.Open, FileAccess.Read);
if (io == null) return null;
BinaryReader binaryReader = new BinaryReader(io);
Assembly assembly = Assembly.Load(binaryReader.ReadBytes((int)io.Length));
return assembly;
}
catch(Exception ex)
{}
}
*Another option is loading all of your required assemblies to current domain at your project start-up.
You use ..\ in the file path to move up a directory.
So if you're in \bin\debug\abc.exe then your reference to \Libs\win32\xyz.dll would be
..\..\Libs\win32\xyz.dll
This should only be necessary when building your projects, when it's built if your executable is referencing the dll correctly it only needs to be put in the same folder as the dll.
Unless of course you're using dllimport or something where you need to know the exact path of the dll during runtime.
I'm using the Activator to instantiate a new class based on the short name of an assembly (e.a. 'CustomModule'). It throws a FileNotFoundException, because the assembly isn't there. Is there a way to check if a certain assembly name is present?
I'm using the following code:
System.Runtime.Remoting.ObjectHandle obj =
System.Activator.CreateInstance(assemblyName, className);
The main objective is to rather test for the presence of the assembly than to wait for the exception to occur.
If you'll notice my comment to your question it will be evident that I'm not rightly sure exactly how you want or need to go about this, but until we have a more elaborate description I can only offer you this in the hope it fits well to your situation (the key is in 'searching' the assemblies):
var className = "System.Boolean";
var assemblyName = "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089";
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
var assembly = (from a in assemblies
where a.FullName == assemblyName
select a).SingleOrDefault();
if (assembly != null)
{
System.Runtime.Remoting.ObjectHandle obj =
System.Activator.CreateInstance(assemblyName, className);
}
.NET 2.0 Compatible Code
Assembly assembly = null;
var className = "System.Boolean";
var assemblyName = "mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089";
foreach (var a in AppDomain.CurrentDomain.GetAssemblies())
{
if (a.FullName == assemblyName)
{
assembly = a;
break;
}
}
if (assembly != null)
{
System.Runtime.Remoting.ObjectHandle obj =
System.Activator.CreateInstance(assemblyName, className);
}
If you want to determine whether or not the file exists before trying to load it (a good practice) then, given you have its name and know the desired location, simply try to find the file when the assembly is being resolved:
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
var className = "StackOverflowLib.Class1";
var assemblyName = "StackOverflowLib.dll";
var currentAssemblyPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
var obj = Activator.CreateInstance(Path.Combine(currentAssemblyPath, assemblyName), className);
static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
var currentAssemblyPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
if (File.Exists(Path.Combine(currentAssemblyPath, args.Name)))
{
return Assembly.LoadFile(Path.Combine(currentAssemblyPath, args.Name));
}
return null;
}
I think it is better not to try to avoid the exception. The reason is that if you have code like
if (DoesAssemblyExist(asmName)) {
object = Activator.CreateInstance(typeName);
}
else {
MessageBox.Show("Assembly does not exist");
}
there is always a risk in a pre-emptive multitasking OS that the assembly might be added/removed between the check and the actual creation. Yes, I realize this risk is minimal, but I still think the exception variant looks better because it is atomic.
Missing assembly definitely constitutes an exception, try/catch FileNotFoundException and handle the situation as per your logic.
I hope it will help for someone in the future:
On every external .dll you are using, create its own uniqe key, like so:
string key = "fjrj3288skckfktk4owoxkvkfk4o29dic";
And then, when you load your form, for every single external .dll that you have got, simply check if the key is exists like so:
If(isMyLib.Variables.key == key)
// continue
else
// .dll does not exists or broken.