Scott Hanselman

Mixing Languages in a Single Assembly in Visual Studio seamlessly with ILMerge and MSBuild

October 6, '07 Comments [13] Posted in ASP.NET | Microsoft | Programming | XML
Sponsored By

I really like the new LINQ to XML direct language support in VB9. However, while it's cool, I'm not ready (or willing) to dump C# and start using VB. But, if I could only use VB9 just for the XML stuff...

Sure, I could create a VB assembly and a C# assembly and add them to a solution, but then I'd have two assemblies. I could add a PostBuildEvent batch file and call ILMerge myself (merging the two assemblies into one), but that just doesn't seem pure enough.

solutionWhat I really want is to be able to mark an assembly as merge-able and have it automatically merged in just because it's referenced.

Here's what I came up with. Thanks to Dan Moseley and Joshua Flanagan for their help. Thanks to Jomo Fisher for the Target File.

First, here's the sample project. There's a C# MergeConsole.exe that references a C# MainLibraryILMerge.dll that references a VB XmlStuffLibrary.

If I compile it as it is, I get a folder structure that includes three resulting assemblies.

folder1

Now, here's were we start messing around. Remember, we don't want to have the extra VB-specific XmlStuffLibrary right? We just want to use the XML features.

First, download and install ILMerge in the standard location.

Now, go to C:\Program Files\MSBuild (or C:\Program Files (x86)\MSBuild on x64) and make a new text file called "Ilmerge.CSharp.targets". This file is the start of a hack we're going to borrow from Jomo Fisher.

Note the red bits in the file below. We're creating an "AfterBuild" target...a Post Build Event in the MSBUILD world.

<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> 
    
  <Target Name="AfterBuild">
    <CreateItem Include="@(ReferencePath)" Condition="'%(CopyLocal)'=='true' and '%(ReferencePath.IlMerge)'=='true'">
      <Output TaskParameter="Include" ItemName="IlmergeAssemblies"/>
    </CreateItem>
    <Message Text="MERGING: @(IlmergeAssemblies->'%(Filename)')" Importance="High" />
    <Exec Command="&quot;$(ProgramFiles)\Microsoft\Ilmerge\Ilmerge.exe&quot; /out:@(MainAssembly) &quot;@(IntermediateAssembly)&quot; @(IlmergeAssemblies->'&quot;%(FullPath)&quot;', ' ')" />
  </Target>
  <Target Name="_CopyFilesMarkedCopyLocal"/>
</Project>

In this target, we're going to look for assemblies that are marked CopyLocal but also that have IlMerge equal to true. Then we'll call IlMerge passing in those referenced assemblies.

Is this some undocumented MSBUILD thing? No, you can put whatever you want in an MSBUILD file and refer to it later. Since CSPROJ files (Visual Studio Projects) are MSBUILD files, we can open it in Notepad.

Open the CSPROJ for the C# project that references the VB one and make these changes: 

... 
<ItemGroup>
   <ProjectReference Include="..\XmlStuffLibrary\XmlStuffLibrary.vbproj">  
    <Project>{someguid}</Project>    
    <Name>XmlStuffLibrary</Name>
    <Private>True</Private>
    <IlMerge>True</IlMerge>
  </ProjectReference>
</ItemGroup>  
<Import Project="$(MSBuildExtensionsPath)\Ilmerge.CSharp.targets" />
<!-- <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> -->
...

At the bottom there, we're commenting out Microsoft.CSharp.targets (but notice that it's included back in at the top of the new  Ilmerge.CSharp.targets.

Then a totally made-up element <IlMerge> is added. That's the most important part. We're saying we want this specific Reference (or References) merged into the final assembly. This made-up element is referenced in the conditional "'%(ReferencePath.IlMerge)" above in the AfterBuild.

The addition of this IlMerge element to Jomo's original hack gives me the flexibility to pick which references I want to merge in, and in my specific case, I'll use the VB9 new XML hotness inside my C# assemblies. Schwing.

Close and save. If you're running Visual Studio, switch back there and it'll prompt you to Reload your project file.Because we've messed it it, you'll get this warning dialog. Select "Load project normally" and click OK.

Security Warning for MainLibraryILMerge

At this point we can build either from the command-line using MSBUILD on the Solution (SLN) file, but more importantly we can (of course) build from inside Visual Studio.

MergeConsole - Microsoft Visual Studio

You can see the output in the Output Window in VS.NET.

MERGING: XmlStuffLibrary
"C:\Program Files (x86)\Microsoft\Ilmerge\Ilmerge.exe"
  /out:bin\Debug\MainLibrary.dll "obj\Debug\MainLibrary.dll"   
  "C:\dev\CSharpVBTogetherAtLast\MergeConsole\XmlStuffLibrary
  \bin\Debug\XmlStuffLibrary.dll"

And the results in the bin folder:

folder2

Looks like the VB XmlStuffLibrary is gone! But where it is? Let's load up MainLibrary in Reflector:

reflector1

Looks like they are both in there. To refresh, if I want to merge in VB assemblies:

  • Change the referencing CSPROJ to import "IlMerge.CSharp.targets"
  • Add <IlMerge>True</IlMerge> to the references you want merged in.
  • Save, Reload, Build

C# and VB, living together, mass hysteria!

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web
Saturday, October 06, 2007 8:51:30 AM UTC
Very cool but, what about debugging? Let's say you attach to the process and pause it. Would you still jump from the c# to the vb code seamlessly?
Saturday, October 06, 2007 12:00:38 PM UTC
Scott: Maybe you already know this, but the technical term for programming applications in a mixture of multiple languages is "polyglot programming". And, it's fun to throw big fancy words like this around. Cheers, -Jim
Saturday, October 06, 2007 2:15:34 PM UTC
Simas - I believe ILMerge handles that by merging the PDBs also.
Saturday, October 06, 2007 3:14:08 PM UTC
yum. Sexy color scheme in that code editing window. Has that made an appearance before? How about a little more than a peek? Or even a settings export?
brian
Saturday, October 06, 2007 7:40:21 PM UTC
Very cool! What about debugging?
Cheers, Maor
Saturday, October 06, 2007 9:19:57 PM UTC
Debugging works fine. ILMerge merges the PDBs also.
Sunday, October 07, 2007 1:14:47 AM UTC
This is pretty wacky just to save yourself from shipping an extra assembly...
Sunday, October 07, 2007 9:44:34 AM UTC
This is nice, but still requires multiple projects within the solution.

It would be way cooler if VS allowed you to mix code files written in different languages within one project.
Scott, now that you work for MS and all, can't you just make this happen? ;-)
Chris K
Sunday, October 07, 2007 6:15:32 PM UTC
This is a cute hack, but with the CSC and VBC both supporting the /target:module and /addmodule options, you could actually do this without ILMerge just by using a shell script or make file.

Visual Studio doesn't support the "netmodule" type, but MSBuild does.

Add the VB project to your solution. Unload the project and edit the project file.

Change OutputType to module : <OutputType>module</OutputType>

Instead of adding a reference to the desired project, we add a module. Sadly, again VStudio fails here, but MSBUILD works just fine. Unload the project and edit the project file. Add an item group with AddModules include directives.
<ItemGroup>
<AddModules Include="..\VbXml\bin\Debug\VbXml.netmodule" />
</ItemGroup>

This will tell msbuild to tell CSC to use /addmodule directives, just like the Reference Item Group which Studio does manage.

Major Drawback: No Visual Studio Intellisense for the added module. We already have references, it is too bad we don't have modules.

SharpDevelop has the first step, but the second step, an "Add Module" gui has been open as a low priority item since SD 2.0.
Sunday, October 07, 2007 9:02:55 PM UTC
Jay - Good alternative. I prefer this method over netmodules because I don't want to give up intellisense. I think we all agree that IDE support for this would be the ideal solution.

With both my "cute hack" :P and Jay's solution the result is a single mixed-language assembly. I prefer the IDE experience in mine, but it's cool there are many alternatives - if folks want to get to one assembly, they can, and that's a good thing.
Monday, October 08, 2007 8:14:12 AM UTC
Does ILMerge allow each merged assembly to see internal | Friend scoped members of the other merged assemblies?
Monday, October 08, 2007 11:59:07 AM UTC
You may want to check out ILMerge Tasks. It's just started in development, but it will allow you to add ILMerge as a task in the MSBuild file.
Tuesday, October 09, 2007 6:10:29 AM UTC
DanM from the MSBUILD team said this:

"Neat! I would have just written something like this but it's a matter of preference
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Target Name="AfterBuild">
<Message Text="MERGING: @(ReferencePath->'%(Filename)')" Importance="High" Condition="'%(CopyLocal)'=='true' and '%(ReferencePath.IlMerge)'=='true'/>
<Exec Command="&quot;$(ProgramFiles)\Microsoft\Ilmerge\Ilmerge.exe&quot; /out:@(MainAssembly) &quot;@(IntermediateAssembly)&quot; @(ReferencePath->'&quot;%(FullPath)&quot;', ' ')"
Condition="'%(CopyLocal)'=='true' and '%(ReferencePath.IlMerge)'=='true'/>
</Target>
<Target Name="_CopyFilesMarkedCopyLocal"/>
</Project>"
Comments are closed.

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.