Different IL when compiling .NET assemblies on different Windows versions?


Someone asked if you compile a .NET assembly on two different operating systems (ex:  Windows 2003 Server versus Windows XP) could there possibly be any differences between the resulting assemblies?



Given that we know that .NET applications compile to an intermediate language and run inside of the CLR we can assume that the assemblies are identical.  But are they?  Let’s test it.


I created a default C# windows application (File->New Project->C# Project->Windows Application) on my tablet and compiled the application.  I copied the source to a Win2k3 box and compiled the same exact source code there.  I then used ILDASM.EXE to dump the IL for the two binaries.  If you read my blog entry about restoring lost code you should be familiar with this technique.


SIDE NOTE:  To get a complete listing of the IL and metadata for the assemblies I launched ILDASM with the /adv switch which enables additional options for dumping and viewing metadata.


I then used the trusty WinDiff tool to do a file comparison of the two IL files.


ADDITIONAL SIDE NOTE:  You can also use Word to do a file comparison I found out today.  Open the document (text file, etc.), go to the Tools menu and select “Compare and Merge Documents…”


WinDiff identified several discrepancies between the files.  I was actually expecting a few differences most likely related to file size and a timestamp for file generation but was surprised by what I saw.  Here are the file differences:


//  Microsoft (R) .NET Framework IL Disassembler.  Version 1.1.4322.573

//  Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.


// PE Header:

// Subsystem:                                                  00000002

// Native entry point address:                          0000285e

// Native entry point address:                          0000287e

// Image base:                                                 00400000

// Section alignment:                                       00002000

// File alignment:                                             00001000

// Stack reserve size:                                      00100000

// Stack commit size:                                      00001000

// Directories:                                                   00000010

// 0           [0        ] address [size] of Export Directory:         

// 2810     [4b      ] address [size] of Import Directory:         

// 2830     [4b      ] address [size] of Import Directory:         

// 4000     [878    ] address [size] of Resource Directory:       

// 0           [0        ] address [size] of Exception Directory:      

// 0           [0        ] address [size] of Security Directory:       

// 6000     [c        ] address [size] of Base Relocation Table:    

// 21ac     [1c      ] address [size] of Debug Directory:          

// 0           [0        ] address [size] of Architecture Specific:    

// 0           [0        ] address [size] of Global Pointer:            

// 0           [0        ] address [size] of TLS Directory:            

// 0           [0        ] address [size] of Load Config Directory:    

// 0           [0        ] address [size] of Bound Import Directory:   

// 2000     [8        ] address [size] of Import Address Table:     

// 0           [0        ] address [size] of Delay Load IAT:           

// 2008     [48      ] address [size] of CLR Header:              


// Import Address Table

//     mscoree.dll

//              00002000 Import Address Table

//              0000284e Import Name Table

//              0000286e Import Name Table

//              0        time date stamp

//              0        Index of first forwarder reference


//                    0  _CorExeMain


// Delay Load Import Address Table

// No data.

// CLR Header:

// 72       Header Size

// 2        Major Runtime Version

// 0        Minor Runtime Version

// 1        Flags

// 6000004  Entrypoint Token

// 2224     [5ec     ] address [size] of Metadata Directory:       

// 2244     [5ec     ] address [size] of Metadata Directory:       

// 20ec     [c0      ] address [size] of Resources Directory:      

// 0        [0       ] address [size] of Strong Name Signature:    

// 0        [0       ] address [size] of CodeManager Table:        

// 0        [0       ] address [size] of VTableFixups Directory:   

// 0        [0       ] address [size] of Export Address Table:     

// 0        [0       ] address [size] of Precompile Header:        

// Code Manager Table:

//  default

// Export Address Table Jumps:

// No data..



. .assembly /*20000001*/ WindowsApplication1


  .custom /*0C000001:0A000007*/ instance void [mscorlib/* 23000003 */]System.Reflection.AssemblyCopyrightAttribute/* 01000009 */::.ctor(string) /* 0A000007 */ = ( 01 00 00 00 00 )

  .custom /*0C000002:0A000001*/ instance void [mscorlib/* 23000003 */]System.Reflection.AssemblyKeyNameAttribute/* 01000003 */::.ctor(string) /* 0A000001 */ = ( 01 00 00 00 00 )

  .custom /*0C000003:0A000002*/ instance void [mscorlib/* 23000003 */]System.Reflection.AssemblyKeyFileAttribute/* 01000004 */::.ctor(string) /* 0A000002 */ = ( 01 00 00 00 00 )

  .custom /*0C000004:0A000003*/ instance void [mscorlib/* 23000003 */]System.Reflection.AssemblyDelaySignAttribute/* 01000005 */::.ctor(bool) /* 0A000003 */ = ( 01 00 00 00 00 )

  .custom /*0C000005:0A000006*/ instance void [mscorlib/* 23000003 */]System.Reflection.AssemblyTrademarkAttribute/* 01000008 */::.ctor(string) /* 0A000006 */ = ( 01 00 00 00 00 )

  .custom /*0C000006:0A00000A*/ instance void [mscorlib/* 23000003 */]System.Reflection.AssemblyConfigurationAttribute/* 0100000C */::.ctor(string) /* 0A00000A */ = ( 01 00 00 00 00 )

  // — The following custom attribute is added automatically, do not uncomment ——-

  //  .custom /*0C000007:0A00000D*/ instance void [mscorlib/* 23000003 */]System.Diagnostics.DebuggableAttribute/* 0100000F */::.ctor(bool,

  //                                                                                                                                  bool) /* 0A00000D */ = ( 01 00 01 01 00 00 )

  .custom /*0C000008:0A000009*/ instance void [mscorlib/* 23000003 */]System.Reflection.AssemblyCompanyAttribute/* 0100000B */::.ctor(string) /* 0A000009 */ = ( 01 00 00 00 00 )

  .custom /*0C000009:0A000008*/ instance void [mscorlib/* 23000003 */]System.Reflection.AssemblyProductAttribute/* 0100000A */::.ctor(string) /* 0A000008 */ = ( 01 00 00 00 00 )

  .custom /*0C00000A:0A00000B*/ instance void [mscorlib/* 23000003 */]System.Reflection.AssemblyDescriptionAttribute/* 0100000D */::.ctor(string) /* 0A00000B */ = ( 01 00 00 00 00 )

  .custom /*0C00000B:0A00000C*/ instance void [mscorlib/* 23000003 */]System.Reflection.AssemblyTitleAttribute/* 0100000E */::.ctor(string) /* 0A00000C */ = ( 01 00 00 00 00 )

  .hash algorithm 0x00008004

  .ver 1:0:1540:591

  .ver 1:0:1540:526




. .module WindowsApplication1.exe

// MVID: {D0722759-767F-4DB0-8280-A3E80637CE26}

// MVID: {414187D5-EACD-480D-964C-E787190ACC40}/

.imagebase 0x00400000

.subsystem 0x00000002

.file alignment 4096

.corflags 0x00000001

// Image base: 0x06cf0000

// Image base: 0x06ba0000






As you can see there were six differences between the IL of the two compiled binaries.  The only two I could readily explain without any research is the assembly version and the MVID. 


  • In the AssemblyInfo file for my project the Assembly version attribute is marked to as having an auto-increment version number ([assembly: AssemblyVersion(“1.0.*”)]).   This change and the remaining changes are all metadata for the assembly.


  • The MVID is known as the Module Version ID.  This is a unique GUID that identifies the assembly, this value is changed every time the assembly is recompiled.


The metadata is used by the CLR for various things such as to figure out where in the binary the starting address is of the 1st instruction, also referred to as the entry point, for the managed code, etc.  If you want more information on both PE headers and .NET assembly metadata read the following two great MSDN articles:



PE Header – Inside Windows: An In-Depth Look into the Win32 Portable Executable File Format.


.NET Assembly MetaData – Avoiding DLL Hell: Introducing Application Metadata in the Microsoft .NET Framework



Comments (4)

  1. denny says:

    the "real" question is what differences if any are made in the x86 code ?

    for example does the jit see different paths of "Optimal code" for an SMP box vs. a Uni-Proc?

    does it gen different x86 code for a PII Vs a P4?

    does it gen different code on a Server OS than a CLient (XP Pro Vs. Server 2003)

    Now that would be a much more interesting thing to see…..

    I would hope that each of the kinds of cases I listed would show a few differences as for example an SMP box with xeons and 2000 or 2003 server should have a different runtime profile than say a PIII running 98 right?

  2. Robert Gruen says:


    Excellent question!

    There is a different JITer for each architecture. There is also a different flavor of the .NET runtime (mscorwks.dll [workstation] and mscorsvr.dll [server]). The JITer is responsible for doing the MSIL to native code conversion, it’ll make optimizations based on the processor type (Intel vs vs. AMD, PII vs. PII vs. P4, etc.).

    The .NET runtime version (server/workstation) is responsible for running apps based on application type: server vs. workstation and would do more of the thread handling, memory management, etc. Explicitly there’s no supported way of specifing which version of the CLR to use (mscorwks vs mscorsvr). You have to host the necessary CLR version, one implementation example are ASP.NET apps which are run against the mscorsvr version of the CLR.

    So, to summarize you will see different native code based on the CPU you’re running against. For the OS version you won’t see much difference (apps compiled on a P4 on Win98 will have the same native code as a Win2k3 box running on a P4), except of course the metadata info as I discussed above.

  3. Gurudev says:

    I think the IL generated for a given C# program will be different on a WIn2K machine and on a Windows XP/2003 machine. This is because, Win2K came before dotnet came while WinXP/2003 came after dotnet and these support x86/IA64 processor architecture as opposed to WIN2K which supports only x86. So the dll entry points for mscoree.dll should be different for assemblies generated on WIn2K and those on WinXP/2003

  4. Robert Gruen says:

    The entry points for the dlls can be different along where they are mapped within memory due to the differences in the OS. So some of the header information and PE info may have different addresses. But once you get into the actuall application you will not see a difference in the IL.

    Good catch!

    You may see a difference if you compare a C# app to a VB.NET app since they use different compilers but accross platforms you should see the same IL code for the program.