AssemblyA.dll refers to AssemblyB.dll
AssemblyB was rebuilt with new code, but not AssemblyA. Therefore, we no longer know for sure if AssemblyA is compatible or not. Maybe it will crash at runtime because some method or property was removed.
Theoretically speaking, is it possible to validate whether AssemblyA is compatible or not with AssemblyB, without having to actually rebuild it ?
The scenario you describe is called the DLL Hell. Wich is just the Windows Specific Subset of Dependency Hell. And prior to .NET (and outside of it) it is dang common. It comes from basically only identifying a DLL by it's name and path.
The .NET Developers knew of it and tried their damndest to avoid it. .NET will not just identify a referenced DLL by the name. It will use at least the Name, Version and Zertificate.
Two dll can have the same name. As long as their version is differnt, .NET will have no issue keeping them appart. .NET does not even has issues keeping them both in memory at the same time. You do not just build against the "System.DLL". You build agianst the "System.DLL. Version Y, Zerficate X".
Related
I have an assembly (MYASM.dll) targeting .NETFramework 4.0 (with a strong name)
I want to deploy this assembly in a way it is part of .NETFramework (or the whole system thinks it is) on target machine.
By that I mean:
.NET runtime sees it at it sees System.dll (no need to deploy locally or provide a reference path)
MSBuild sees it when I do <Reference Include="MYASM" /> without needing a hintpath
User is able to make Add reference in Visual Studio and that introduces <Reference Include="MYASM" /> without the strong/full name
I have solved 1. (and apparently 2.) by adding it to the GAC. But this is apparently not sufficient.
I have partially solved 3. by putting my assembly in a special folder ([INSTALLFOLDER]\lib) and set registryKey HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\.NETFramework\v4.0\AssemblyFoldersEx\MyAssemblies
Then I can do Add reference, but then I get:
<Reference Include="MYASM, Version=1.1, Culture=neutral, ..." /> in my csproj instead of just <Reference Include="MYASM" /> as I'd like.
With the second approach, if I manually edit the csproj, everything is OK, but I can't ask my users to do that.
What should I do here?
[EDIT] apparently it’s not obvious I have my own MSI. But yes I have. I don’t control users machines with a magic wand
No, you've taken this as far as it can go. It isn't actually that obvious how VS figures out to put the partial assembly name into the project file. This is not public code and can't be tampered with. Pretty sure it does not use a white-list and it can't pay attention to the reference assembly location.
Most likely detail is the PublicKeyToken of the assembly. The framework assemblies always have to exact same value for them, b77a5c561934e089. Its value is even prescribed in the CLI spec (Ecma-335). Next most likely by a considerable distance is the signing certificate, identifying the assembly as owned by Microsoft. Both however present the exact same problem, you can't get the private key that is required to strong-name or sign the assembly. They are locked inside a vault in Redmond, only trusted build engineers have access to them.
There is another nasty little detail you are overlooking, you are not nearly scared enough of DLL Hell. Cold hard fact is that if you ever expose the assembly in the GAC on another machine that is not in your control then you can never change it again. You can no longer modify the public interface of the assembly. Can't add a new public method or type, can't modify the arguments and return type of a method, can't add an enum member, etc. Even harsher, something Microsoft worries about, is that you can't really change private and internal members either. Programmers have a knack for using Reflection to poke around, terrific bug fixing tool. But at least you can tell them "don't do that!".
Making such modification requires increasing the [AssemblyVersion]. Now you get a different kind of DLL Hell, the machine might not have been updated by your installer. Or worse, a solution uses projects that have different references. Microsoft had to solve this problem for framework assemblies, they did so by modifying the CLR. Automatically forwarding old versions to new ones. The basic reason why using an assembly built for .NET 2.0 can be used in a .NET 4.x project. You can't get that kind of service for your own DLL.
"Don't do it" is the only good advice, getting in DLL Hell trouble is however a terrific learning experience I can recommend for anybody. Hell has to be experienced to be feared.
Best advice is to publish a Nuget package. They do the exact opposite, never deployed in the GAC and version numbers change very rapidly. But always available when a programmer needs it.
There are a few ways...
1) is to create a new setup and package this for the framework you target. You can Package this and have it deployed using the domain controller. When your users log in the domain will update the packages, this way you'll be able to deploy your software to specific users and or user groups. Depending on your infrastructure you'll have a software management infrastructure that you can use (2 links included).
2) Create a NuGet package if you're targeting developers. If your organisation host your own NuGet server limiting the distribution. Add the Package source to Visual studio open the Options Page, type NuGet in the search field and set the URL/ UNC path.
3) use OneClick deployment, this allows you to have the application download the updated dll's and install them on the machine. It requires a Code Sign certificate but you're probably signing your code anyway (better for Anti-Virus tools if you do).
Now linking your MyAsam.dll will be done by the application linking definition or IoC container. Basically, if it finds the dll and no version is defined it will take the first one it finds I think the order is 1 AppFolder, 2 GAC, 3 Path, not sure. This "take what you find" is generally referred to as "DLL-Hell", The NuGet and OneClick solution works best in this as You will always get the Updated dll that works for the application. Placing the DLL in GAC is going to get problematic if you have moe than 1 application using your dll and both need the "right" version where the "right version" differes between them....
If you have the source code available for MYASM.dll, then I would prefer adding a project reference to your consuming application. When doing so, Visual Studio shall create a GUID for all the referenced project.
Since version 3.0, .NET installs a bunch of different 'reference assemblies' under C:\Program Files\Reference Assemblies\Microsoft...., to support different profiles (say .NET 3.5 client profile, Silverlight profile). Each of these is a proper .NET assembly that contains only metadata - no IL code - and each assembly is marked with the ReferenceAssemblyAttribute. The metadata is restricted to those types and member available under the applicable profile - that's how intellisense shows a restricted set of types and members. The reference assemblies are not used at runtime.
I learnt a bit about it from this blog post.
I'd like to create and use such a reference assembly for my library.
How do I create a metadata-only assembly - is there some compiler flag or ildasm post-processor?
Are there attributes that control which types are exported to different 'profiles'?
How does the reference assembly resolution at runtime - if I had the reference assembly present in my application directory instead of the 'real' assembly, and not in the GAC at all, would probing continue and my AssemblyResolve event fire so that I can supply the actual assembly at runtime?
Any ideas or pointers to where I could learn more about this would be greatly appreciated.
Update: Looking around a bit, I see the .NET 3.0 'reference assemblies' do seem to have some code, and the Reference Assembly attribute was only added in .NET 4.0. So the behaviour might have changed a bit with the new runtime.
Why? For my Excel-DNA ( http://exceldna.codeplex.com ) add-in library, I create single-file .xll add-in by packing the referenced assemblies into the .xll file as resources. The packed assemblies include the user's add-in code, as well as the Excel-DNA managed library (which might be referenced by the user's assembly).
It sounds rather complicated, but works wonderfully well most of the time - the add-in is a single small file, so no installation of distribution issues. I run into (not unexpected) problems because of different versions - if there is an old version of the Excel-DNA managed library as a file, the runtime will load that instead of the packed one (I never get a chance to interfere with the loading).
I hope to make a reference assembly for my Excel-DNA managed part that users can point to when compiling their add-ins. But if they mistakenly have a version of this assembly at runtime, the runtime should fail to load it, and give me a chance to load the real assembly from resources.
To create a reference assembly, you would add this line to your AssemblyInfo.cs file:
[assembly: ReferenceAssembly]
To load others, you can reference them as usual from your VisualStudio project references, or dynamically at runtime using:
Assembly.ReflectionOnlyLoad()
or
Assembly.ReflectionOnlyLoadFrom()
If you have added a reference to a metadata/reference assembly using VisualStudio, then intellisense and building your project will work just fine, however if you try to execute your application against one, you will get an error:
System.BadImageFormatException: Cannot load a reference assembly for execution.
So the expectation is that at runtime you would substitute in a real assembly that has the same metadata signature.
If you have loaded an assembly dynamically with Assembly.ReflectionOnlyLoad() then you can only do all the reflection operations against it (read the types, methods, properties, attributes, etc, but can not dynamically invoke any of them).
I am curious as to what your use case is for creating a metadata-only assembly. I've never had to do that before, and would love to know if you have found some interesting use for them...
If you are still interested in this possibility, I've made a fork of the il-repack project based on Mono.Cecil which accepts a "/meta" command line argument to generate a metadata only assembly for the public and protected types.
https://github.com/KarimLUCCIN/il-repack/tree/xna
(I tried it on the full XNA Framework and its working afaik ...)
Yes, this is new for .NET 4.0. I'm fairly sure this was done to avoid the nasty versioning problems in the .NET 2.0 service packs. Best example is the WaitHandle.WaitOne(int) overload, added and documented in SP2. A popular overload because it avoids having to guess at the proper value for *exitContext" in the WaitOne(int, bool) overload. Problem is, the program bombs when it is run on a version of 2.0 that's older than SP2. Not a happy diagnostic either. Isolating the reference assemblies ensures that this can't happen again.
I think those reference assemblies were created by starting from a copy of the compiled assemblies (like it was done in previous versions) and running them through a tool that strips the IL from the assembly. That tool is however not available to us, nothing in the bin/netfx 4.0 tools Windows 7.1 SDK subdirectory that could do this. Not exactly a tool that gets used often so it is probably not production quality :)
You might have luck with the Cecil Library (from Mono); I think the implementation allows ILMerge functionality, it might just as well write metadata only assemblies.
I have scanned the code base (documentation is sparse), but haven't found any obvious clues yet...
YYMV
Short version of the question:
Is there a good way to detect at build time if I have any cases where 2 or more projects reference different versions of the same assembly? (really, I would like to teach our CI server to do this)
Long Version:
So here's an interesting problem (simplified a bit for easy digestion):
Recently encountered a situation where we had 2 projects in a solution, A and B. Both A and B depend upon a 3rd party nuget package C.
A always loads C, B only needs C in rare circumstances.
So, during this sprint, a developer updated Project A to use the latest version of the C package (not realizing that B also depended upon C)
Everything built and the tests that we had passed (we have insufficient test coverage), but when we released to production, we had failures occuring when B attempted to use the dependency (loader issues, because we wanted a different version of the strongly named assembly).
We found the problem, and corrected it, but. I would really love to be able to catch this during development. It would be even cooler if our build server could detect this (TFS 2012) when it does a CI build.
How might I go about detecting this situation?
VS can't do this for you because of the dynamic loading (unless I'm missing something): it just has no way of knowing which assemblies wil be loaded at runtime.
We had the same problem once (using Prism - all our assemblies are loaded at application startup normally, and the order is described in a config file though most are optional). I First thought of making a small tool that basically scans all packages.config or csproj files to see what assemblies are used in what version, and make it complain when two packages of different versions are found. But I ended up with dealing with it at a higher level, more direct and foolproof: we now have a simple class, sort of a stub of the actual application, that just loads all the application's components and modules as described in the config file. This results in all assemblies that can ever get loaded to be loaded so if something goes wrong it will be found. This functionality is simply placed in a unit test.
I have two DLLs, app1.dll and app2.dll, that use the same third dll, util.dll, which might have slightly different behavior when built for a specific app.
I want to achieve that app1.dll and app2.dll are isolated concerning the versions of util.dll that they use. On MSDN, I read that assigning a strong name to util.dll is the solution, i.e. that this strong name contains the name and version of the dll as well as the fingerprint due to signing.
I currently build util.dll...
for app1 with KeyFileApp1.snk and version 1.0.0.1
for app2 with KeyFileApp2.snk and version 1.0.0.2
and this seams to work on the target machine when deploying the apps with the respective util.dll build.
However, I have some ambiguities:
Would it be enough to build util.dll always with version 1.0.0.0 but different key files for both apps and having both apps using the right dll in the end?
Are the strong names only considered when putting a dll in the global assembly cache or also when having it as private assembly?
yes you can have both with the same name and version, but different public key. Is it good/maintainable solution - up to you. Will likely confuse more people than you want. I.e. no regular person will be able to tell you "public key" used by DLL, but most people can right click and check details to see version (if you set it properly on your assemblies).
strong name is always considered. Note that if you have local and GAC copy GAC one will always be used if version in GAC is acceptable.
Notes:
strong names are "viral" as you can't have strongly named assembly depend on one without strong name. Be careful as you'd need many for your assemblies to be strongly signed (not a bad thing by itself) and have good versioning plan.
you may eventually endup with multiple versions of the same assembly loaded into process - either prepare for it or use publisher policy to map all versions to latest.
I'm referencing a signed assembly. In runtime it is ok for me to work with any version of that assembly, not just the one I compiled to.
How to achieve this?
No, that's not going to work. The CLR will verify the assembly version number, expecting to get the one that your main program was compiled against. You would have to use the <bindingRedirect> element in the app.config file to convince it that a different version is okay.
That's a slippery slope. Consider only changing the [AssemblyVersion] attribute if the public interface of the assembly changed and requires clients to be recompiled. Now the exception you'll get is one that identifies a real problem. This is another kind of slippery slope, but one you'll have much more control over.
For comparison, this is the way all the base assemblies in the .NET framework work. There have been many revisions of them between .NET 2.0 RTM and 3.5 SP1, including many invisible hotfixes. But the [AssemblyVersion] is still 2.0.0.0, Microsoft only modifies the [AssemblyFileVersion].
In the IDE, make sure "Specific Version" is set to false against the reference, or add <SpecificVersion>False</SpecificVersion> to the reference in the csproj.