Mixing C# and VB.NET in one assembly

Development, MSBuild, CSMVPs Comments

A couple of weeks ago Jayson Knight invited me to join the CSMVP's' CSModules package. This project was created to incorporate several CSModules written by CS MVP's. So I bought into that project by bringing along my Akismet spam rule.

At that point in time, for each CSModule there was a single Visual Studio project, all bound together in a Visual Studio solution. But our goal was to incorporate all modules into a single assembly, so the user only has to copy one DLL to his /bin web folder.

My first idea was to put all sources into a single project. However, since every CS MVP wrote his module in his favorite language, there were both C# and VB.NET projects. Unfortunately, in Visual Studio projects have a certain type supporting only a single language.

But luckily, even if there's no support by the IDE, you still can compile different .NET languages into one assembly. The solution are modules. A module is a unit of compilation, comparable to .obj files in C++. It can't stand by its own, but must be linked into an assembly before it can be used. Basically, a module is an assembly without a manifest. You can get more details on MSDN at .netmodule Files as Linker Input. Additionally I recommend reading Junfeng Zhang's Netmodule vs. Assembly and Multimodule Assemblies.

Knowing the concept of modules, I was able to write a MSBuild project file to compile all CSModules into a single assembly. Here's an (extremely) simplified version:

<Project
  DefaultTargets="build"
  xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
        
  <!-- Target folders -->
  <PropertyGroup>
    <SourcePath>src</SourcePath>
  </PropertyGroup>

  <!-- Specify the sources to include, excluding any assembly info's -->
  <ItemGroup>
    <CSFiles
      Include="$(SourcePath)/**/*.cs"
      Exclude="$(SourcePath)/**/assemblyinfo.cs"/>
    <VBFiles 
      Include="$(SourcePath)/**/*.vb" 
      Exclude="$(SourcePath)/**/assemblyinfo.vb;$(SourcePath)/**/My Project/*"/>
  </ItemGroup>

  <!-- Specify all referenced assembly -->
  <ItemGroup>
    <References Include="lib/2.1 RTM/*.dll" />
  </ItemGroup>

  <!-- Target files -->
  <PropertyGroup>
    <OutputModule>MyModule</OutputModule>
  </PropertyGroup>

  <!-- builds the CSMVPs.CSModules assembly -->
  <Target 
    Name="build"
    Inputs="@(CSFiles);@(VBFiles);@(References)" 
    Outputs="$(OutputModule).dll">

    <!-- compile C# sources -->
    <CSC
      Sources="@(CSFiles)"
      References="@(References)" 
      OutputAssembly="$(OutputModule).CS.netmodule"
      TargetType="module">
    </CSC>
      
    <!-- compile VB.NET sources -->
    <VBC
      Sources="@(VBFiles)"
      References="@(References)" 
      OutputAssembly="$(OutputModule).VB.netmodule"
      TargetType="module">
    </VBC>

    <!-- link the C# and VB.NET modules -->
    <Exec 
      Command="link /dll /ltcg /out:$(OutputModule).dll *.netmodule" />
  </Target>
</Project>

Comments