using ILMerge with .NET 4 libraries
Asked Answered
M

6

52

Two problems:

1) Basic .NET Assembly Not Included in ILMerged Assembly

I'm having trouble using ILMerge in my post-build after upgrading from .NET 3.5/Visual Studio 2008 to .NET 4/Visual Studio 2010. I have a Solution with several projects whose target framework is set to ".NET Framework 4". I use the following ILMerge command to merge the individual project DLLs into a single DLL:

if not $(ConfigurationName) == Debug
  if exist "C:\Program Files (x86)\Microsoft\ILMerge\ILMerge.exe"
    "C:\Program Files (x86)\Microsoft\ILMerge\ILMerge.exe"
      /lib:"C:\Windows\Microsoft.NET\Framework64\v4.0.30319"
      /lib:"C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\PublicAssemblies"
      /keyfile:"$(SolutionDir)$(SolutionName).snk"
      /targetplatform:v4
      /out:"$(SolutionDir)bin\development\$(SolutionName).dll"
      "$(SolutionDir)Connection\$(OutDir)Connection.dll"
      ...other project DLLs...
      /xmldocs 

If I leave off specifying the location of the .NET 4 framework directory, I get an "Unresolved assembly reference not allowed: System" error from ILMerge. If I leave off specifying the location of the MSTest directory, I get an "Unresolved assembly reference not allowed: Microsoft.VisualStudio.QualityTools.UnitTestFramework" error.

The ILMerge command above works and produces a DLL. When I reference that DLL in another .NET 4 C# project, however, and try to use code within it, I get the following warning:

The primary reference "MyILMergedDLL" could not be resolved because it has an indirect dependency on the .NET Framework assembly "mscorlib, Version=4.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" which has a higher version "4.0.65535.65535" than the version "4.0.0.0" in the current target framework.

If I then remove the /targetplatform:v4 flag and try to use MyILMergedDLL.dll, I get the following error:

The type 'System.Xml.Serialization.IXmlSerializable' is defined in an assembly that is not referenced. You must add a reference to assembly 'System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.

It doesn't seem like I should have to do that. Whoever uses my MyILMergedDLL.dll API should not have to add references to whatever libraries it references. How can I get around this?

2) TypeLoadException Only When Using Merged Assembly

Edit: beyond this, even if I do add a reference to System.Xml in the consumer project that uses MyILMergedDLL.dll, making use of some code in MyILMergedDLL.dll gives this exception:

System.TypeLoadException: Could not load type 'System.Func`2' from assembly 'MyILMergedDLL, Version=1.0.1.1, Culture=neutral, PublicKeyToken=...'.

This is the code in my consumer project; the line that caused the TypeLoadException is the second one:

var keys = new[] {"a", "b", "c"};
var row = new Row(keys);

The particular Row constructor that throws the TypeLoadException is defined in a public class in MyILMergedDLL, and when I use this constructor when referencing the individual project DLLs, it works fine. It is only when I use this constructor when referencing the IL-merged DLL that I get the exception. I don't know what's going on.

Here's that constructor:

public Row(IEnumerable<string> keys) : base(keys) { }

And the base to which it is referring has this code:

foreach (string key in keys.Where(
    key => !string.IsNullOrEmpty(key)
))
{
    _dic.Add(key, string.Empty);
}
Mace answered 2/6, 2010 at 20:29 Comment(1)
Although this question does not ask for an alternative, I need to recommend using Costura Fody. It works like a charm. Packing is as simple as adding a NuGet reference :)Respirable
P
48

There was a very recent release to solve x64 problems. Get in touch with Mike Barnett directly if you still have problems (mbarnett at microsoft dot com)


Addendum. There's something very, very wrong about your /lib:"C:\Windows\Microsoft.NET\Framework64\v4.0.30319" option. This has been getting lots of programmers in trouble lately, after .NET 4.5 was released. That directory is not the proper one for .NET 4.0 reference assemblies. Its content gets overwritten with the 4.5 assemblies, you can no longer use it to target a .NET 4.0 install. The runtime error you get is very awkward, the program cannot find certain types anymore. Usually bombing on the [Extension] attribute, sometimes on the ICommand interface.

These types, and some others, were moved from one assembly to another. Using the correct reference assemblies is a rock-hard requirement. You must use:

 /lib:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0"

Adjust to match your particular machine and target framework version.

Premonish answered 2/6, 2010 at 23:32 Comment(6)
Thanks for the info. I can't tell from looking at that page what the releases are for: VS 2010, C#, .NET 4, ILMerge?Mace
Never mind, digging around on Mike Barnett's page I see he works on ILMerge. :)Mace
YES! I didn't download any new release, but I'm still marking yours as the correct answer (the only one at the moment...) because it led me to visit Mike Barnett's page where he described using /targetplatform:v4,<path to your v4 framework directory> instead of /targetplatform:v4 /lib:<path to your v4 framework directory>.Mace
Here's a link to Mike's ILMerge main page: research.microsoft.com/en-us/people/mbarnett/ilmerge.aspxCompassionate
Thanks for the comment - added /targetplatform:v4,C:\Windows\Microsoft.NET\Framework64\v4.0.30319 and it worked for me as well. I'm using .NET 4.0 and Visual Studio 2010 SP1.Abampere
I needed to use /targetplatform:v4,C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0 because I was deploying the application to a machine running .NET 4.0 and not 4.5 (I think): mattwrock.com/post/2012/02/29/…Momentarily
A
23

Here is the "Post Build String" for Visual Studio 2010 SP1, using .NET 4.0. I am building a console .exe with all of the sub-.dll files included in it.

"$(SolutionDir)ILMerge\ILMerge.exe" /out:"$(SolutionDir)\deploy\$(TargetFileName)" "$(TargetDir)$(TargetFileName)" "$(TargetDir)*.dll" /target:exe /targetplatform:v4,C:\Windows\Microsoft.NET\Framework64\v4.0.30319 /wildcards

Basic hints:

  • Notice the "\deploy\" directory: this is where the output .exe file ends up.
  • Notice the "ILMerge\" directory. I copied the ILMerge utility into my solution directory (so I could distribute the source without having to worry about documenting the install of ILMerge).

Advanced hints:

If you have problems with it not working, add an "echo" before the "Post Build" command. Then, open the "Output" window in Visual Studio (View..Output), and check the exact command that Visual Studio actually generated. In my particular case, the exact command was:

"T:\PhiEngine\CSharp\ILMerge\ILMerge.exe" /out:"T:\PhiEngine\CSharp\Server Side\deploy\NfServiceDataHod.History.exe" "T:\PhiEngine\CSharp\Server Side\NfServiceDataHod\bin\Debug\NfServiceDataHod.History.exe" "T:\PhiEngine\CSharp\Server Side\NfServiceDataHod\bin\Debug\*.dll" /target:exe /targetplatform:v4,C:\Windows\Microsoft.NET\Framework64\v4.0.30319 /wildcards

Update

Added this to my "Post Build" step, it replaces all .exe + .dll files with a single combined .exe. It also keeps the debugging .pdb file intact:

rem Create a single .exe that combines the root .exe and all subassemblies.
"$(SolutionDir)ILMerge\ILMerge.exe" /out:"$(TargetDir)$(TargetName).all.exe" "$(TargetDir)$(TargetName).exe" "$(TargetDir)*.dll" /target:exe /targetplatform:v4,C:\Windows\Microsoft.NET\Framework64\v4.0.30319 /wildcards
rem Remove all subassemblies.
del *.dll
rem Remove all .pdb files (except the new, combined pdb we just created).
ren "$(TargetDir)$(TargetName).all.pdb" "$(TargetName).all.pdb.temp"
del *.pdb
ren "$(TargetDir)$(TargetName).all.pdb.temp" "$(TargetName).all.pdb"
rem Delete the original, non-combined .exe.
del "$(TargetDir)$(TargetName).exe"
rem Rename the combined .exe and .pdb to the original name we started with.
ren "$(TargetDir)$(TargetName).all.pdb" "$(TargetName).pdb"
ren "$(TargetDir)$(TargetName).all.exe" "$(TargetName).exe"
exit 0
Abampere answered 23/3, 2011 at 15:58 Comment(4)
Update: Just tried this string on a rather complex project with 10 custom sub-assemblies, and it worked perfectly.Abampere
For those merging with a .dll as the output, you have to change /target:exe to /target:library AND remove "$(TargetDir)$(TargetFileName)" otherwise it will load the main .dll there and in the wildcard *.dll resulting in a "ILMerge.Merge: ERROR!!: Duplicate type" error.Rhnegative
Do beware that your post has a really nasty bug.Premonish
ILMerge is now a NuGet package. If you install it from there, it is put in packages, so I modified the command line to: "$(SolutionDir)\packages\ilmerge.2.14.1208\tools\ILMerge.exe"Fleet
A
2

Other alternatives:

Abampere answered 23/3, 2011 at 16:8 Comment(1)
SmartAssembly may be pricey in comparison, but what is your time worth? I've spent far more money in billable hours than SmartAssembly costs to find out that the alternatives don't work and are impossible to debug. This "pricey" product is backed by quality, ease-of-use and excellent support.Kotz
J
2

You can also add a config file with the following:

<?xml version ="1.0"?>
<configuration>
  <startup useLegacyV2RuntimeActivationPolicy="true">
    <requiredRuntime safemode="true" imageVersion="v4.0.30319" version="v4.0.30319"/>
  </startup>
</configuration>

Taken from here

Jemie answered 26/2, 2012 at 14:38 Comment(2)
Where do you place this file? What do you name it?Embitter
if not present, you need to create an ildasm.exe.config file with the xml I put in my answerJemie
I
1

Just set PresentationCore and PresentationFramework references to have "Copy Local = True" in the Visual Studio properties window (after selecting the references in the Solution Explorer). It will solve the problem without hard-coding the framework path. I prefer this solution because the path is different depending on whether the developer/build server is 64bit or 32bit and inevitably will change as new .NET/VS versions are released.

Illegal answered 26/4, 2013 at 14:38 Comment(0)
G
0

For those, who use ILMerge from community tasks in .csproj:

<ILMerge InputAssemblies="@(MergeAssemblies)"
         ...
         TargetPlatformVersion="v4"
         TargetPlatformDirectory="$(ProgramFiles)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0"
/>

We have mixed park of CI build agents, so we use $(ProgramFiles) environment variable to point correct path (drive + x86/x64 folder), as it was recommended by MSBuild Team.

Gladine answered 12/6, 2018 at 16:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.