I'm new to using GAC and I am trying to get a handle on when you should and shouldn't use it. Here is my situation:
I develop software that is primarily add-ins to another commercial product so that product loads my products into it's code. There are several assemblies that I have developed that are used by all of my applications (my license module for example). The .dll file for these libraries get installed to the main application directory so the parent program loads them from there.
The problem is when a user has two of my software titles installed, there can be a conflict since the parent program only loads the first copy of an assembly it finds regardless of version. So if they have Software A version 1.0 with License1.0.dll in it and Software B version 2.5 with License 2.0 in it that has different methods and/or arguments than License1.0.dll it only loads 1.0 and then throws an exception on Software B because it can't find the right license methods.
In initial research it seemed like GAC was intended to be the answer to this and several sites seem to say it is, but then I also found this subject and the link in the answer that seems to be saying no don't use GAC for this.
I'm confused. Can someone give some direct guidance on if I should look into using GAC for this?
I wouldn't recommend use the GAC at all as you depend on registered dlls and I often had problems with that. Anyway, you can load the assembly that you like manually. I have a parent application that is either 64 bit or 32 bit and I have to load the corresponding SQLite x64 or x86 dll.
The referenced assembly has copy local false. That's the first place where the runtime will look to resolve the reference. If there is anything then it checks the GAC. The dll isn't registered there so the AssemblyResolve event will happen where I can say what I want to load:
AppDomain.CurrentDomain.AssemblyResolve += ResoveAssembly;
private static Assembly ResoveAssembly(object sender, ResolveEventArgs e)
{
string fullPath = Assembly.GetExecutingAssembly().Location;
string path = Path.GetDirectoryName(fullPath);
if (e.Name.StartsWith("System.Data.SQLite"))
{
return Assembly.LoadFrom(Path.Combine(path, Environment.Is64BitProcess
? "x64\\System.Data.SQLite.DLL"
: "x86\\System.Data.SQLite.DLL"));}
return null;
}
}
In case someone wonders why I'm doing that: As far as I know the new SQLite NuGet package handles this issue now. But it wasn't available when we want to use it.
Related
I have a DLL I generate from a C# project. I then register it via regasm so that the library can be used inside several legacy VB scripts.
Recently I created a new project (C# console app) that will reuse certain modular aspects of the original library, and per good programming practice it made sense to add the library to the GAC for reuse by this and any future projects.
I've found that it plays nice at first, but after the server is rebooted, the VB scripts crash and burn, claiming they are unable to create an object of one of the types defined in the library.
The fix involves removing the library from the GAC and re-registering the library via regasm.
The libraries in the registry and GAC come from the same physical DLL file - same directory and everything.
I've confirmed the existence of registry entries for the library every step of the way, which says regasm did its job.
GAC entries only exist when the library is installed, and properly disappear when it is uninstalled. They only ever appear under GAC_MSIL, where, to my knowledge, they should be.
Any ideas why this is happening?
EDIT: I did not read the fine print, haha. On the regasm documentation I just saw this: "Creates a Codebase entry in the registry. The Codebase entry specifies the file path for an assembly that's not installed in the global assembly cache. Don't specify this option if you will subsequently install the assembly that you're registering into the global assembly cache. It is strongly recommended the assemblyFile argument that you specify with the /codebase option be a strong-named assembly." I was using that switch, so I will dig deeper. In the meantime any additional insights are greatly appreciated.
I would guess you didn't renew the GUIDs and/or distinguish the fully qualified type names of the new library and when you installed it with regasm, the old entries in the registry got overwritten. Registering the old library again has overwritten the new library's registry, but as you don't use it through COM that didn't affect it and now the scripts work again.
I'm working on a .net project, which is using third party .net DLLs. Some of this DLLs are using common DLLs (e.g. Microsoft.Practices.EnterpriseLibrary.Logging). Now we want to use the same common DLLs in a newer version. We are not allowed to use the GAC (politics).
We have separated the parts in different directories.
Third party \ Third party.dll
old common (Microsoft.Practices.EnterpriseLibrary.Logging.dll)
Our libs \ our lib.dll
new common (Microsoft.Practices.EnterpriseLibrary.Logging.dll)
Surprise, Surprise, it did not work. In our dll an error is thrown saying, some option is not valid. Yes, I did not find it in the old common, but in the new. So, I guess, the wrong executable was taken.
In Visual Studio (2015) we have enabled the "Spezific Version" in the reference, and in the Debug / module windows, both DLLs are loaded.
Edit: All Dlls have strong names.
How do I determine which DLL was executed (stepping with F11 just jump to the catch block)? How do I force using the correct DLL?
(The architecture loads first the third party DLL, then our own dll. This is not changeable without a few years rewriting...)
You can use assembly binding redirects and hope the universe doesn't break (there's no guarantee the newer DLL is backward compatible) or you can strong name the dlls.
Why? .NET generally does not allow you to load the "same" assembly more than once in the same AppDomain, unless it is strong-named. What's strong naming? It is a form of identity and digital signing that consists of:
Assembly filename
Assembly version
Assembly culture
Assembly public key
When it's strong-named, both dlls run side-by-side in the same AppDomain within the same process with perfect backward compatibility.
Alternatively if you don't want to use strong-naming (because many files may require signing) or binding redirects, you can always create additional AppDomains and load a version of the dlls into each domain.
Though it gets around the problem of fiddling with files, it does require considerable rework of the rest of the app making it an arguably bad choice at this point in your development.
EDIT: I see now you are using strong names on both.
How do I force using the correct DLL
To distinguish between the two types in the exact same namespace, you might have to create an alias for the newer assembly in your dll reference. Tell me more...
So basically I coded something and when I press start, it uses content from dlls etc..
And when I do it everything works fine I got all the .dlls in the same folder as the .exe,
but on someone else's computer it just wont work, it crashes, boom.
Even though he has everything 100% like me
I don't know what to do to find what causes this, any way to know?
private void nsButtonAutoStart_Click(object sender, EventArgs e)
{
secondThread = new Thread( () => Start(dsflfsdl, sgdsgd, sggdsg, etc));
secondThread.Start();
}
What it does is :
if (!Directory.Exists(user))
Directory.CreateDirectory(user);
than create more folders with a given name etc.. well I won't describe everything, just need to know how to spot what's wrong.
There are several exceptions that can be thrown from this, so I'll go over causes of the most likely culprits...
Framework: this can be the case when the .NET Framework is not installed, or the right version isn't installed. Note that the System.___ Dlls are ususally included in the GAC, but can sometimes be missing. Install a GAC Browser tool and make sure everything you reference in your project is not only installed, but also show up properly in the GAC.
Permissions: It's possible that since the program is rexognized as coming from somewhere else and lacks publisher signing, it doesn't have filesystem permissions, and is thus crashing for even trying to look at files. This can be resolved by trying to have the other computer Run as Administrator in Windows. This can also cause DLL Not Found exceptions because of being unable to reach into the running directory for required Dlls.
How to find the exception: If the client sees a "program has stopped working" window, there'll often be the option to see "more details," in which one of the lines of "More Data X" will be the actual exception type.
Missing Method Exception: this can happen if it can't find a method. This can happen if you're "providing" DLLs that are also present on the GAC (say, System or mscorlib). Becuase Assembly Resolution checks the GAC first, it will use the GAC version if it's present, even if you've provided a copy. This can cause issues if you don't specify the requirement of a specific version, becuase it could hook into a .NET 4.0 assembly where it should be grabbing a 4.5 one. Make sure the right version of .NET is installed, and try making your project Require Sepcific Version: True for all references
I found the problem, basically it was the .dll that they had , it was the good one but it lacked some data in it, it was 300 kb instead of 454 kb
How can I use different dll's (other Version) with the same name in one directory?
For Example, LibA (ExternalLib.dll) has Version 1 and LibB (ExternalLib.dll) has Version 2.
I'm deploying all my programs to the same directory (this is our companys standard and I can't change this fact). The problem is if ProgramB which is using the LibB is deployed in the directory where ProgramA is using the LibA then ProgrammA would not longer work.
For my own Libs I use a Major-Version-Number (.01, .02) if there are big changes. But the Lib I'm using is an external Lib and each version of it requires different licensing-keys (which are handled by the programs itself).
I tried to rename the external libs from "ExternalLib.dll" to "ExternalLib.v1.dll" and "ExternalLib.v2.dll", but when I run my fresh compiled programm it throws an exception that says "ExternalLib.dll could not be found". The reference in my project is set to "ExternalLib.v1.dll" and compilation works fine.
Any ideas / suggestions to handle different assembly versions in the same directory?
Unfortunately, the filename of the DLL file has very little do do with how .Net is loading these types. The actual name is written into the meta data of the assembly as part of the compilation process. So at runtime, it will be probing for ExternalLib.dll regardless of what you renamed the file to. The usual way to fix this is to install to the GAC and use Strong Naming to reference the specific version.
Given you may not be able to do this, there are 4 things you could try:
Ask the vendor to produce version specific DLL's for you. They could compile such that the version name is part of the filename and included in the assembly manifest. This would be the simplest solution for you.
Handle the AssemblyResolve event and manually try and use Assembly.Load to point at the file you want such that you can specify specifically which dll to use. See http://support.microsoft.com/kb/837908 for more information, but effectively you'll be using Assembly.LoadFrom(specific_path) to choose the file where the code will load from.
If possible, you might also be able to use ildasm.exe to decompile the dll's to Intermediate Language (IL), then use ilasm.exe to recompile it to a new dll name. You would then reference this new DLL name in your project.
If the assembly is not signed, then you may be able to edit the manifest yourself; you can either use a compatible binary editor or possibly MT.exe.
I have a program which needs to function in both an x86 and an x64 environment. It is using Oracle's ODBC drivers. I have a reference to Oracle.DataAccess.DLL. This DLL is different depending on whether the system is x64 or x86, though.
Currently, I have two separate solutions and I am maintaining the code on both. This is atrocious. I was wondering what the proper solution is?
I have my platform set to "Any CPU." and it is my understanding that VS should compile the DLL to an intermediary language such that it should not matter if I use the x86 or x64 version. Yet, if I attempt to use the x64 DLL I receive the error "Could not load file or assembly 'Oracle.DataAccess, Version=2.102.3.2, Culture=neutral, PublicKeyToken=89b483f429c47342' or one of its dependencies. An attempt was made to load a program with an incorrect format."
I am running on a 32 bit machine, so the error message makes sense, but it leaves me wondering how I am supposed to efficiently develop this program when it needs to work on x64.
Thanks.
This is purely a deployment problem, you should never have to maintain different projects. It is an awkward one though, and boo on Oracle for not taking care of this themselves. Another consideration is that this assembly really should be ngen-ed on the target machine. Some options
Create two installers, one for x64 and one for x86. The customer picks the right one, based on the operating system she uses. Simple enough, you just copy the right file.
Deploy both assemblies to the GAC. Now it is automatic, .NET picks the right one on either type of machine. Big companies should almost always use the GAC so they can deploy security updates, not sure why Oracle doesn't do this.
Deploy the assemblies to a x86 and x64 subdirectory of the install directory. You'll need to write an AppDomain.AssemblyResolve event handler that, based on the value of IntPtr.Size, picks the right directory.
Change the target platform on your EXE project to x86. Given that your code needs to work on a 32-bit machine as well as on a 64-bit machine, there isn't/shouldn't be a reason to build for AnyCPU.
This is a working solution for your problem:
Add the 2 DLL's (x86 and x64) to your solution in a subfolder. Make them "Copy if newer"
Reference the correct DLL you use for development for debugging from the 2 DLL's you added. Make it Copy Local=false.
What this does is that when you app starts the DLL is not autoloaded. It will not be loaded until you use a Type from that assembly. Once that happens an event will be triggered in .Net that asks where it can find your assembly.
So sometime before the first use of that assembly make sure you attach yourself to that event.
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
In the content of the handler make sure you load the DLL (x86 or x64) when it asks for it.
static System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) {
if (args.Name.Equals("MyFullAssemblyName")) {
var path = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
if (IntPtr.Size > 4) {
var dll = System.IO.Path.Combine(path, #"MySubDir\MyDLL_x64.dll");
return System.Reflection.Assembly.LoadFile(dll);
}
else {
var dll = System.IO.Path.Combine(path, #"MySubDir\MyDLL.dll");
return System.Reflection.Assembly.LoadFile(dll);
}
}
return null;
}
Voila. You can now run your app as both 32 bit and 64 bit.
Alternatively to adding the DLLs in a subfolder, you can make them as Embedded Resources, and then load them like this:
static System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) {
if (args.Name.Equals("MyFullAssemblyName")) {
var ass = Assembly.GetExecutingAssembly();
if (IntPtr.Size > 4) {
var strm = ass.GetManifestResourceStream("the.resource.name.for.MyDLL_x64.dll");
var data = new byte[strm.Length];
strm.Read(data, 0, data.Length);
return Assembly.Load(data);
}
else {
var strm = ass.GetManifestResourceStream("the.resource.name.for.MyDLL.dll");
var data = new byte[strm.Length];
strm.Read(data, 0, data.Length);
return Assembly.Load(data);
}
}
return null;
}
This does not work for all assemblies. Some "hybrid" assemblies tends to fail unless they are loaded from disk (can be solved by writing them to disk just before loading).
If you're running on a 32-bit machine, then you have to load the 32-bit version of the Oracle DLL. A 32-bit program can't reference a 64-bit DLL. And, a 64-bit program can't reference a 32-bit DLL.
"Any CPU" is the correct target if you have multiple versions of the external DLL. The trick is making sure that the proper Oracle DLL is located and loaded. Your best bet is to locate the 64-bit version of the DLL on your 32-bit system and rename it so that the runtime can't find it.
Using AnyCPU with native early bindings is just not going to work, for that you need two separate solutions and builds as you saw. You have to get hold of a 64-bit system to develop or at least test x64 compiled dlls on.
However, with late binding, you can use AnyCPU and System properties to figure out what architecture you're running as and link to the correct dll, if you keep the named like Oracle.DataAccess.x86.dll. If they're installed into the GAC, it's even easier, you can bind without even bothering to test for architecture first, but I believe you still have to late bind.
Note that VMware can run a 64-bit guest on a 32-bit host, if you really can't be bothered to reinstall Windows.
You shold be able to configure the same solution to build x86/x64 versions separately. You may also need to add post build steps to copy correct version of DLL to corresponding output folders...
At least if you have to build 2 solutions - use the same source (add files as refernce to second solution, not copy into second solution).