Вы находитесь на странице: 1из 9

What you should know about running ILMerge on .Net 4.5 assemblies targeting .

Net
4.0 /February 29, 2012

I might have also entitled this:


How to avoid TypeLoadException: Could not load type
'System.Runtime.CompilerServices.ExtensionAttribute'

But I didnt.

First, the moral of this story


I am about to take you on a debugging journey that will make some laugh and others cry and a
fortunate few will travel through the entire spectrum of human emotion that will take them down
into the seven bowels of hades only to be resurrected into the seven celestial states of ultimate
being that will consummate in a catharsis that unifies soul, body and mind with your favorite My
Little Pony character. If this does not appeal to you then know this:
If you use ILMerge to merge several assemblies into one on a machine with .Net 4.5 Beta
installed and intend to have this merged assembly run on a machine running .Net 4.0, DO NOT
use the following TargetPlatform switch value:
/targetplatform:"v4,c:\windows\Microsoft.NET\Framework\v4.0.30319"

Instead use this:


/targetplatform:"v4,C:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0"

If you are interested in learning some details about targeting different frameworks,
some nice IL debugging tips or what that means in an upgrade like 4.5 that is in
place or does not officially change the runtime version along with some techniques
for debugging such interesting scenarios, then read on.

Twas the night before Beta


So it seems serendipitous that I write this on the eve of the Visual Studio 11 launch. This last
weekend I installed the beta bits on my day to day development environments. As a member of
the team that owns the Visual Studio Gallery and the MSDN Code Samples Gallery and their
integration with the Visual Studio IDE, Ive been viewing bits hot out of the oven for some time
now. The product seems stable and everyone seem to feel comfortable installing it side by side
with VS 10. You can target .Net 4.0 so why not just dev on it full time to enjoy the full, rich
dogfooding experience. At home where I do development on my OSS project RequestReduce, I
work on a 5 year old Lenovo T60P laptop. Its name is dale. So the perf and memory footprint
improvements of VS11 have a special appeal to me. Oh and to Dale too. Right Dale? Thought so.

Most solutions can be loaded in both VS10 and VS11 without


migration
So day 1, Saturday after some initial pain of getting a XUnit test runner up and running it looks
like Im ready to go. I load up RequestReduce and all projects load up fine. Im also happy that
after loading them in VS11, they still load and compile in VS10. Next I build in VS11 and all
unit and integration tests pass. Sweet! Lets get to work and make it happen.

Cut on the bleeding edge


So I had been exchanging emails all week with a developer having issues with getting
RequestReduce to play nice with the Azure CDN. Turns out Azure handles CDN URLs
differently from the other CDNs I have worked with in the past by requiring all CDN content to
be placed in a CDN directory on the origin server. However the CDN URL should not include
the CDN directory in the path. There were some minor changes I needed to make to get the
RequestReduce API to work nice with this setup. Just to be sure my changes were good, I spun
up a new Azure instance and created a CDN endpoint. Then I deployed my test app with
RequestReduce plugged in to do bundling and minification and WHAT?!

[TypeLoadException: Could not load type


'System.Runtime.CompilerServices.ExtensionAttribute' from assembly 'mscorlib,
Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.]
This doesnt look good and my first reaction is Stupid Azure. But I have users who report that
RequestReduce runs fine on azure. To double check I run the same test app on my dev box, all is
good. So despite the promises that .net 4.5 and VS11 will run fine on .net 4.0, I create a clean
VM with .Net 4.0 and run my test app there. Yep. I get the same error. I promptly shout to my
secretary, Marge, get me building 41 and cancel my tennis match with Soma! Perhaps a
cunning power play like this will motivate him to light a proverbial fire under the sorry devs that
are causing these exceptions to be thrown. Then it all hits me: its Saturday, I have no secretary
and lets be honest, I wont be playing tennis with Soma (head honcho at MS DevDiv that pumps
out Visual Studio) any time soon. I weep.

Debugging Begins
I soon wake up recalling that I dont even like tennis and Im ready to fight. Im gonna win this.
From the stack trace I can see that the error is coming from my StructureMap container setup.
StructureMap is a very cool IOC container used for managing Dependency Injection. It allows
you to write code that references types by their interfaces so that you can easily swap out
concrete implementation classes via configuration or API. There are many reasons for doing this
that is beyond the scope of this post, but the most widely used application I use it for is testing. If
I have all my services and dependencies handed to me from StructureMap as Interfaces, then I

can create tests that also pass in interfaces with mock implementations. Now I have tests that
dont have to worry if all that code in the dependent services actually works. I have other tests
that test those. I can control the data that these services will pass into the method I am testing and
that allows my test to hyper focus on just the code in my method Im testing. If this sounds
unfamiliar or confusing, I urge you to research the topic. It transformed the way I write code for
the better.
So one thing StructureMap has to do to make all of this possible at app startup time is scan my
assemblies for interfaces and the concrete classes that I tell it to use. It is here where I am
running into this TypeLoadException. So who are you
'System.Runtime.CompilerServices.ExtensionAttribute' and what is your game? Why do you
play with me?
After some googling, I get a little dirt on this Type. Apparently it is an Attribute that decorates
any class that contains an extension method In C#, you will never have to include this attribute
directly. The compiler will do it for you. You will see it in the IL. Well this type has moved
assemblies in .Net 4.5. Apparently it was too good for System.Core and has moved to an
executive suite in mscorelib. This is all complicated by the fact that the upgrade from 4.0 to 4.5
is what they call in the industry an In Place Upgrade. That means that you will not see a
v4.5.078362849 folder in your c:\windows\Microsoft.Net\Framework directory. No. The 4.5
upgrade gets paved over the 4.0 bits and simply updates the DLLs in the
C:\Windows\Microsoft.NET\Framework\v4.0.30319 folder. Not a real fan of this and I dont
know what the reasoning is but thats how its done.
So now Im thinking that this must be some edge case caused by StructureMap using Reflection
in such a way where it demands to load types from what assemblies are there at compile time. I
should also mention that I have a class in my assembly that has an extension method. So I find a
way to tell StructureMap to go ahead and scan the assemblies but just dont worry about any
attributes. Since you can decorate classes with special StructureMap attributes that tell
StructureMap that a class is a type that can be plugged into a specific interface, it will try to load
all attributes it finds to see if it is one of these special attributes. Well I dont use those so I tell
StructureMap IgnoreStructureMapAttributes().
Ahhh. I am convinced that I have it. while my code builds and deploys to my VM all on my 5
year old laptop (remember Dale?), I have time to file a Connect bug nice and snug in the self
righteous knowledge that I am doing the right thing. I have been wronged by these bleeding edge
bits but Im not angry. I am simply informing the authorities of the incident in the hopes that
others will not need to suffer the same fate.
Ok. My code has now been built and deployed and is ready to run. Its gonna be great. Ill see my
asp.net web page with bundled and minified javascript and I can move on with my weekend

chores. I launch the test app in a browser andCriminey! Same error but different stack trace.
Now its coming from code that Instantiates a ResourceManager. This is not instilling confidence.
This isnt even technically my code. It is code auto generated by VS when you add Resources to
the Resources tabs in the VS Project properties. Really? VSs own code isnt even backwards
compatible? The rubbish I have to work with. So it turns out the ResourceManager does
something similar as StructureMaps initialization, it scans an assembly for resources. It iterates
over every type to see if it matches what you have told the ResourceManager what to look for.
Ok Ok. I guess Im just gonna have to refactor this too. And what next? When does it stop?
When is enough enough?!
So I do refactor my Resource.
using (var myStream = Assembly.GetExecutingAssembly()
.GetManifestResourceStream("RequestReduce.Resources.Dashboard.html")){
using (var myReader =
new StreamReader(myStream))
{
dashboard = myReader.ReadToEnd();
}}

If you are familiar with RequestReduce, this is the HTML page that is the RequestReduce
Dashboard. I embed this as a resource in the RequestReduce DLL. Now I load it through a
ManifestResourceStream into a static string and to be honest this does seem much more efficient
than scanning every class for resources when I know exactly which file contains my resource
and only need to load the contents of that file.
So I build and deploy again. And now, sweet victory, I see the beautiful blue background that is
the default ASP.NET project home page and I see my minified CSS. But waitoh no
somethings not right. The navigation tabs are stacked on top of each other and dont look like
tabs at all. Where is my JavascriptjQuery152004988980293273926_1358662903428 It is
completely gone? If RequestReduce has a zero byte string after minification (maybe it was just a
comment that gets minified out) then it will remove the script all together. So diving into the
code deeper and running several tests to narrow the possibilities, I discover that the call into the
MS Ajax minifier returns an empty string. Now thats Minification!!

Its not .net 4.5s fault


So all of the sudden I begin to wonder. Oh no! Is this not the Framework itself causing this
mayhem but perhaps related to my use of ILMerge.exe which takes RequestReduce.dll,
AjaxMin.dll, Structuremap.Dll and nQuant.dll and merges them all into one RequestReduce.Dll?
I do this because the principle behind RequestReduce is to make Website optimizations as easy
and automatic as dropping a single DLL into your bin. After I replace my merged DLL with the
original unmerged ones in my test app on my .Net 4.0 VM, everything magically works. So I
know now that it is either a problem in ILMerge or perhaps still a problem in the framework that
is surfaced when interacting with ILMerge. Either way I want to get back up and running so I
need to figure out what is going on with AjaxMin specifically. Is there something I can fix with
the way I use AjaxMin or can I figure the more root problem with the Framework or ILMerge?

Wouldnt it be great if I were using an old version of ILMerge and simply updating it would fix
everything?
I am using a version of ILMerge that was last updated in May and there have been two updates
since the last being in November. Im hopeful that since November was after the Preview
Release of Visual Studio 11, this latest update will address .Net 4.5 issues. I update my ILMerge
bits and alas, the problem still exists. So now Im hoping that some thumbing through the
ILMerge documentation or searching online will turn up some clues. Nothing. This is always the
problem with working with bleeding edge bits. You dont often get to learn from other peoples
problems. You are the other people. I write this today as that other person who will hopefully
shine light on your problem.
In a last ditch effort I send an email to Mike Barnett, the creator of ILMerge in hopes he may
have come across this before and can provide guidance to get me around the issue and on my
way to running code. He responded as I expected. He hadnt played with the 4.5 bits yet and was
not surprised that there would be issues especially given the fact of the in place upgrade. He was
gracious enough to offer to look at the problem if I could provide him with the breaking code and
access to a machine with.Net 4.5 installed.
Im not one to quickly hand off problems on to someone else. First because it is rude and second
I enjoy being able to solve problems on my own especially these kinds. In fact I had walked into
the exact kind of problem that (while frustrated that my code was not running) I enjoy the most
and often have a nack for getting to the bottom of figuring out what is going on. This is not
because I am smart but because I am stubborn and off balance and will stick with problems long
after smarter people have moved on with their lives.
The first thing I do is pull down the source code of the Ajax Minifier. I have a suspicion that the
minifier is stumbling on the same exception I have been fighting with and it simply catches it
and gracefully returns nothing. I discover that if there are any internal errors in the minification
of content given to the minifier, it will store these errors away in a collection accessible from the
minifiers ErrorList property. When I inspect this property, there is one error reporting a type
initialization error in Microsoft.Ajax.Utilities.StringMgr. So I look up that class and bang:
// resource manager for retrieving stringsprivate static readonly ResourceManager
s_resourcesJScript =
GetResourceManager(".JavaScript.JScript");private static readonly
ResourceManager s_resourcesApplication =
GetResourceManager(".AjaxMin");
// get the resource manager for our stringsprivate static ResourceManager
GetResourceManager(string resourceName){ string ourNamespace =
MethodInfo.GetCurrentMethod().DeclaringType.Namespace; // create our resource manager return
new ResourceManager(
ourNamespace + resourceName,
Assembly.GetExecutingAssembly()
);}

My friend the ResourceManager again. Unfortunately because this is not my code, I cant refactor
it as easily. Sure it is an open source project that I think takes pull requests and whose owner,

Ron Logan, is very responsive to bug fixes, but refactoring these run ins with ExtensionAttribute
is beginning to feel like an unwinnable game of Whack-A-Mole and since the error does not
occur without ILMerge, I need to figure out what is going on there instead of cleaning up after
the mess. As far as Im concerned at this point, the only viable options are to find a way to work
with ILMerge that will prevent these errors or gather enough data that I can give to either Mike
Barnett or the .net team to pursue further. Im hoping for the former but thinking the later
scenario is more likely.

Isolate and minify the problem space


I often find with these sorts of problems, the best thing to do at this point is to widdle down the
problem space to as small of a surface area as possible. I create a new VS11 solution with two
projects:
Project 1
static class Program{
static void Main()
{
System.Console.Out.WriteLine(Resources.String1);
public static string AnotherTrim2(this string someString)
someString.Trim();
}}

}
{

return

This project also contains a simple resource string and the auto generated file
produced by Visual Studio that contains the following line of code that reproduces
the error:
global::System.Resources.ResourceManager temp =
"ReourceManUnMerged.Properties.Resources",

new global::System.Resources.ResourceManager(
typeof(Resources).Assembly
);

Project 2

An empty class file. I just need a second assembly produced that I can merge.
I ILMerge them together on my .Net 4.5 machine and then I copy ILMerge.exe and my
unmerged bits to my .Net 4.0 VM and merge the same bits on the 4.0 platform. I then run both
merged versions on .net 4.0 and sure enough the one that was merged on the .Net 4.5 machine
breaks and the one merged on .Net 4.0 runs just fine. I now know I can work with these
assemblies to troubleshoot. With the minimal code, there is a lot les to look at and get confused
by. I did mention that I get easily confused right? Hell Im confused as I type right now. Did I
also mention that I am one of the individuals responsible for deploying MSDN Win 8/Vs11 Beta
documentation in the next hour? Dont let my boss know about the whole confusion thing. Some
things are better kept a secret.

Pop open the hood and look at IL


The first thing I want to do is look at both assemblies using a new tool put out by JetBrains, the
makers of such popular tools like Resharper, called DotPeek. This is the equivilent of the highly

popular tool Reflector except it is free. It lets you view the C# source code of the disassembled
IL of any .net assembly. A very handy tool when you cannot access the source of an assembly
and want to peek inside. Im curious if ILMerge reassembled these assemblies in such a way that
they have different source that would clue me in to something useful.
They do not. The only difference between the two sets of source code is that the assembly that
works includes a reference to System.Core the .Net 4.0 home of the offending
ExtensionAttribute. While this is interesting at one level, its not very actionable data since my
source code explicitly references System.Core. So I cant just add the reference and expect things
to fix themselves.
Next thing I do is I use ILDasm.exe, a tool that ships with the .Net SDK that can decompile a
.Net DLL down to its IL code. Ive mentioned IL a couple of times now. This is the Intermediate
Language that all .net languages get compiled to. I use this tool to view the actual IL emitted by
ILMerges compile. You can access this tool from any VS command prompt or from C:\Program
Files\Microsoft SDKs\Windows. Now Im seeing something more interesting. The two sets of IL
are identical except the one merged on 4.5 contains three instances of this line:
.custom instance void [mscorlib]
= ( 01 00 00 00 )

System.Runtime.CompilerServices.ExtensionAttribute::.ctor()

and the one merged on 4.0 has the same line but slightly different:
.custom instance void [System.Core]
System.Runtime.CompilerServices.ExtensionAttribute::.ctor()

= ( 01 00 00 00 )

See the difference? Now Im thinking that one option I have is to modify my build script to use
ILDasm to extract the IL, do a simple string replacement to get ExtensionAttribute to load from
System.Core and then use ILAsm to reassemble the transformed IL back to a usable assembly.
This is certainly a workable option but it does not feel ideal. What would be ideal is to find some
way to tell ILMerge to use System.Core instead of mscorelib. Oh ILMerge, cant we work
together on this. Why must we fight. I love you, you love me, were a happy fam Whoa.
Where am Ioh yeah. When do I need to deploy those Beta bits?

Whats the compiler doing and how to get it to target 4.0


So I ask myself what is VS doing that is different when you tell it to target .Net 4.0 as opposed to
4.5? Since it seems that the outdated 4.0 bits are simply blown away when you install 4.5, how
does the compiler know to use mscorelib when targeting 4.0? If I could answer this question,
maybe that would reveal something I could work with. To discover this I go to Tools/Options
in VS11 and then I select Projects and Solutions > Build and Run on the left. This gives me
options to adjust the verbosity of the MSBuild output displayed in the output window at compile
time. I switch this from Detailed to Diagnostic. I want to see the actual call to CSC.exe, the C#
compiler and what switches Visual Studio is passing into it. Thankfully I get just that. When
targeting 4.0, the command line call looks like this:

C:\Windows\Microsoft.NET\Framework\v4.0.30319\Csc.exe
/noconfig
/nowarn:1701,1702,2008
/nostdlib+
/platform:AnyCPU
/errorreport:prompt
/warn:4
/define:DEBUG;TRACE
/errorendlocation
/highentropyva/reference:"C:\Program
Files\Reference Assemblies\Microsoft\Framework \.NETFramework\v4.0\Microsoft.CSharp.dll"
/reference:"C:\Program Files\Reference Assemblies\Microsoft\Framework
\.NETFramework\v4.0\mscorlib.dll"
/reference:"C:\Program Files\Reference
Assemblies\Microsoft\Framework \.NETFramework\v4.0\System.Core.dll"
/reference:"C:\Program
Files\Reference Assemblies\Microsoft\Framework
\.NETFramework\v4.0\System.Data.DataSetExtensions.dll"
/reference:"C:\Program Files\Reference
Assemblies\Microsoft\Framework \.NETFramework\v4.0\System.Data.dll"
/reference:"C:\Program
Files\Reference Assemblies\Microsoft\Framework \.NETFramework\v4.0\System.dll"
/reference:"C:\Program Files\Reference Assemblies\Microsoft\Framework
\.NETFramework\v4.0\System.Xml.dll"
/reference:"C:\Program Files\Reference
Assemblies\Microsoft\Framework \.NETFramework\v4.0\System.Xml.Linq.dll"
/debug+
/debug:full
/filealign:512
/optimize/out:obj\Debug\ReourceManUnMerged.exe
/resource:obj\Debug\ReourceManUnMerged.Properties.Resources.resources
/target:exe
/utf8output
Program.cs
Properties\AssemblyInfo.cs
Properties\Resources.Designer.cs
"C:\Users\mwrock\AppData\Local\Temp\.NETFramework, Version=v4.0.AssemblyAttributes.cs"
(TaskId:26)

When targeting 4.5 I get this:


C:\Windows\Microsoft.NET\Framework\v4.0.30319\Csc.exe
/noconfig
/nowarn:1701,1702,2008
/nostdlib+
/platform:AnyCPU
/errorreport:prompt
/warn:4
/define:DEBUG;TRACE
/errorendlocation
/highentropyva+
/reference:"C:\Program
Files\Reference Assemblies\Microsoft\Framework \.NETFramework\v4.5\Microsoft.CSharp.dll"
/reference:"C:\Program Files\Reference Assemblies\Microsoft\Framework
\.NETFramework\v4.5\mscorlib.dll"
/reference:"C:\Program Files\Reference
Assemblies\Microsoft\Framework \.NETFramework\v4.5\System.Core.dll"
/reference:"C:\Program
Files\Reference Assemblies\Microsoft\Framework
\.NETFramework\v4.5\System.Data.DataSetExtensions.dll"
/reference:"C:\Program Files\Reference
Assemblies\Microsoft\Framework \.NETFramework\v4.5\System.Data.dll"
/reference:"C:\Program
Files\Reference Assemblies\Microsoft\Framework \.NETFramework\v4.5\System.dll"
/reference:"C:\Program Files\Reference Assemblies\Microsoft\Framework
\.NETFramework\v4.5\System.Xml.dll"
/reference:"C:\Program Files\Reference
Assemblies\Microsoft\Framework \.NETFramework\v4.5\System.Xml.Linq.dll"
/debug+
/debug:full
/filealign:512
/optimize/out:obj\Debug\ReourceManUnMerged.exe
/resource:obj\Debug\ReourceManUnMerged.Properties.Resources.resources
/target:exe
/utf8output
Program.cs
Properties\AssemblyInfo.cs
Properties\Resources.Designer.cs
"C:\Users\mwrock\AppData\Local\Temp\.NETFramework,Version=v4.5.AssemblyAttributes.cs"
obj\Debug\\TemporaryGeneratedFile_E7A71F73-0F8D-4B9B-B56E-8E70B10BC5D3.cs
obj\Debug\\TemporaryGeneratedFile_036C0B5B-1481-4323-8D20-8F5ADCB23D92.cs (TaskId:21)

Telling ILMerge where to find mscorelib the devil in the


details
There are a few differences here. The one that is most notable is where the framework references
are being pulled from. They are not pulled from the Framework directory in
c:\windows\Microsoft.Net which is where I would expect and where one usually looks for
framework bits. Instead they are coming from c:\Program Files\Reference Assemblies. The
MSBuild team talks about this folder here.

When you call ILMerge to merge your assemblies, you pass it a /targetplatform swich which tells
it which platform to build for. Currently this switch can take v1, v1.1, v2 or v4 followed by the
Framework directory. When I build for 4.0 I use this command line call via powershell:
.\Tools\ilmerge.exe
/t:library
/internalize
/targetplatform:"v4,$env:windir\Microsoft.NET\Framework$bitness\v4.0.30319"
/wildcards
/out:$baseDir\RequestReduce\Nuget\Lib\net40\RequestReduce.dll
"$baseDir\RequestReduce\bin\v4.0\$configuration\RequestReduce.dll"
"$baseDir\RequestReduce\bin\v4.0\$configuration\AjaxMin.dll"
"$baseDir\RequestReduce\bin\v4.0\$configuration\StructureMap.dll"
"$baseDir\RequestReduce\bin\v4.0\$configuration\nquant.core.dll"

Most point the directory like I am to the actual platform directory that can always be located off
of %windir%\Microsoft.net Its the obvious location to use. Oh waithold on
Ok Im back. The win8 and VS11 beta docs are now deployed. Now where was I. Oh yeahthe
framework directory passed to ILMerge. According to the ILMerge documentation it just needs
the directory containing the correct version of mscorelib.dll. So Im thinking, lets use this
Reference Assemblies directory. I do that and re merge on .Net 4.5. I run on my 40 VM and
Hallelujah!! Theres my javascript in all of its minified glory.
I hope this helps someone out because I didnt find any help on this error.
Also, if you have made it this far, as I promised in the beginning, you should now have a sense of
unity with your favorite My Little Pony character. Mine is Bon Bon what's yours?

Вам также может понравиться