I'm trying to programmatically create satellite assemblies for .NET5 and .NET6 apps using code I've written which targets .NET7. The code extracts the various resource properties, like form/button text, sizes and positions, using ResourceReader and stores names, values and types of each property, so that later on, we can recreate this information in the satellite assemblies using ResourceWriter. Some of these types are simple like System.Int32, System.String, and others are a bit more complex such as System.Drawing.Size.
There are issues with this approach because types extracted from a given localisable assembly are instantiated using the currently executing runtime i.e. if my code targets .NET 6 and the assembly targets .NET5, calling GetType on each resource from the dictionary returned by ResourceReader creates .NET 6 types, not 5. So, in effect, each type's target assembly version is being bumped up just by virtue of being instantiated.
If my code and a localisable assembly target the same runtime, it works fine, but if my target runtime is newer than the assembly's, the assembly will crash on startup because it's trying to load "foreign" types that belong to a different set of .NET reference assemblies.
I've spent absolutely ages trying to figure this one out, and the suggestions I've read include creating the satellite assembly using .NET Framework instead, but it doesn't solve my problem as I still need to "downgrade" my types to the same version as what was in the main assembly.
I've also overriden the ResourceWriter.TypeNameConverter delegate to change the assembly version properties in each type, but that didn't work either.
Related
tl;dr Is there a way to accomplish creation of .NET6 satellite assemblies, using .NET7?
I'm trying to extract localisable resources from a .NET 6 assembly, using a .NET 7 class library, so I can create satellite assemblies for that assembly.
I'm not even sure at this point if it's possible to do, but I'm hoping it is because otherwise I'll have to build a different version of my class library to target each runtime version, and also add code to detect the incoming assembly version and instantiate the version of my class library which targets the same runtime as the assembly I'm trying to localise.
Assuming what I want to do is possible, when I load an assembly to be localised and extract the resources into a dictionary using ResourceReader, I extract the type information for each resource and store it so it can be written into a satellite assembly later on. However, I noticed that for certain reference and value type resources extracted from the assembly, such as Int32, System.Drawing.Size, System.String and others, the type information returned by GetType() corresponds to that of the executing runtime, in this case, .NET7, whereas the assembly itself targets .NET6.
This is causing problems after I create the satellite assemblies, because when the newly localised assembly is trying to load the localised resources, it throws exceptions saying that binary serialization is not supported. I think this is a red herring, because when I looked into it further, the types that are being loaded from the satellite assembly are .NET7 types, so the .NET 6 runtime is unable to load those types.
Any help appreciated,
Thanks
I have a project that uses System.Runtime.Serialization assembly. I am using the type DataContractSerializer from that assembly, but I have a problem.
There are two assemblies:
C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework.NETFramework\v4.0\System.Runtime.Serialization.dll
C:\Windows\Microsoft.net\Framework\v4.0.30319\System.Runtime.Serialization.dll
Both of them have the same version - v4.0.30319. The first one have 429kb size, and the second one 1037kb. I used reflector to see the list of classes, and the first one doesn't have the class that I need (DataContractSerializerSettings). However, the second one does have it.
Why are there some big difference in size and classes for that assembly? Will it be ok, if I use the second one, instead of the first?
.NET version 4.0 made a big change in the way framework reference assemblies are done. Previously, the reference assembly was a simple copy of the runtime assembly, the one stored in the GAC. That however caused some painful problems. Notable is the WaitHandle.WaitOne(int) overload, it was added in the .NET 2.0 Service Pack 2 update (aka .NET 3.5). Programmers used it without noticing that it was an added method, the mscorlib assembly version number was still 2.0.0.0. But then discovered their program failed when running on an unpatched version of .NET 2.0. Very nasty kaboom, MissingMethodException without a hint why such a common method could be missing.
To prevent this kind of breakage, the .NET 4.0 reference assemblies are kept separate, in the "%programfiles%\Reference Assemblies" directory as you found out. And they are special assemblies, they only contain the metadata with all the IL stripped out. Which is why the assembly is so much smaller.
Microsoft now can improve the .NET 4 code and add public classes and methods without causing this kind of breakage. And have done so profusely, updates 4.01, 4.02 and 4.03 have shipped since the original 4.0 release.
The reason you are having trouble with the DataContractSerializerSetting class is thus easily explained, it just doesn't appear in the reference assembly. It got added, probably in one of those incremental updates. And you should not try, your program will break on a machine that doesn't have the update. You should wait until .NET 4.5, the version that added it to the reference assembly. You can invoke DLL Hell if you really want to.
I have been looking for an answer to that but so far I have not been able to. For those who are not familiar with the basis of my question, here is some explanation to that.
Context
You can have two types of assembly in .NET when it comes to naming your assemblies:
Strong-named Assemblies
Simple-named Assemblies (aka Weakly-named Assemblies as coined by Jeffrey Richter in his CLR via C# book).
Unlike Simple-named Assemblies, the Strong-named assemblies are signed with a public/private key. Related to my question, one of the benefits of signing the assembly is the following:
Now the version number of your assembly becomes important. Assemblies
contain a static reference to other assemblies that they use the types of. When they reference a strong-named assembly, you must give them
that same exact assembly with the exact signature (name, version,
culture, and public key token). Otherwise, the fusion assembly loader
will fail at resolving it.
Also, strong-named assemblies can only reference other strong-named assemblies but not simple-named assemblies.
Question
Based on what I have described, I also expect the same rules to be applied to referencing .NET Framework assemblies. Meaning that my assemblies will search for exact versions of the referenced assemblies that are shipped with a .NET Framework that I originally used the assemblies of in my compiled application. However, when you upgrade from .NET 4.5 to 4.7, without re-compiling your application, your application is still working and supposed to be working. How is that possible and how does Microsoft manages that? Do they apply an exception to their assemblies in the fusion loader so that it always loads the latest .NET Framework assemblies for user-developed applications? Does it use a bindingRedirect internally for each possible old version to new version? Do they use publishers policies?
I feel like the answer is hidden in this text:
The runtime uses the following steps to resolve an assembly reference:
Determines the correct assembly version by examining applicable
configuration files, including the application configuration file,
publisher policy file, and machine configuration file. If the
configuration file is located on a remote machine, the runtime must
locate and download the application configuration file first.
... -- Microsoft Doc
Other notes:
Specific Version
For some, there is a misconception about Specific Version = true flag, which can be changed in Visual Studio when referencing an assembly. This flag is for compile-time checking only and has nothing to do with the runtime assembly resolution. In fact, the assembly will still have the strong name including assembly name, version, and public key token (if exists).
An Extra Quick Question: By Default?
By default, the runtime attempts to bind with the exact version of an
assembly that the application was built with.
says, Microsoft. It confuses me a little bit. Does it mean that version number is important for both weakly-named and strong-named assemblies to get resolved? For instance, if I drop a weakly-named assembly with an incremented version number, then will it not be resolved by other assemblies that are referencing the older version of it?
Ok so I have a pretty unique problem here. I'm getting an error basically because I'm referencing the latest version of a dll which I still want to keep references to by default because most of the code in my project is supposed to be using this dll. The error occurs because it's trying to use an object that is only available in the older version of the dll. So I want to use this older version of the dll for this particular section of code. I have tried to load this older version of the dll using Assembly.LoadFrom(pathToAssembly) but it still appears to reference the newer version of the dll. Does anyone have any ideas on how I can replace the reference to this dll to the older version?
Only real option you have is to make sure that assembly is strongly signed, make sure there is no assembly binding redirect to newer version and than manually (with Assembly.LoadFrom) load second version into your appDomain. This way code will be able to use precise version of assembly and both assemblies can be loaded into same appDomain at the same time.
Note that this will lead to complete nightmare if you ever need to pass references to such objects between pieces of code linked against different assemblies.
If you want extra painfun - load both assemblies from bytes and use reflection to construct types for each version...
I'd strongly recommend avoiding all the pain by loading code using different versions of assembly to at least separate appDomains, but preferably to separate processes. If you still decide to take adventurous path of loading multiple versions of assembly to same appDomain make sure to read all aassembly loading blog post from https://blogs.msdn.microsoft.com/suzcook/2003/09/19/loadfile-vs-loadfrom/
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