Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to link C# and C++ assemblies into a single executable?

I have VS2008 solution containg a project that generates a C# executable that references a project that generates a dll containing both C++/CLI and unmanaged C++.

I would like to merge these into a single executable, as the C++ dll contains security code that I want to embed in the main executable.

I cannot use ILMerge, as the dll contains both managed and unmanaged code. The suggested solution seems to be to use link.exe to link the C# assembly with the C++ object files. This is what I am trying to do.

I manually edited the project file for the c# executable to generate a netmodule. I added a post build step to the executable project to run link.exe to link the c# netmodule and the compiled C++ object files together, then run mt.exe to merge the assembly manifests created by both projects. This runs successfully, but the exe still contains a reference to and uses the c++ types defined in the dll generated by the normal build process for the C++ project.

I then specified /NOASSEMBLY in the project settings for the C++ dll, so it also generates a netmodule. In the C# project, I removed the reference to the C++ project, but added a project dependancy in the solution. I manually edited the C# project file to include similar to:

<ItemGroup>
    <AddModules Include="..\Debug\librarycode.netmodule" />
</ItemGroup>

i.e. to reference the C++ netmodule that is now generated by the C++ project.

However, now the linker step in my post build event fails with:

error LNK2027: unresolved module reference 'librarycode.netmodule'
fatal error LNK1311: 1 unresolved module references: 

This is entirely understandable, as I am not linking in the librarycode netmodule; I am linking in the C++ object files used to generate the netmodule instead.

So in short, how do I merge a c# executable and C++ object files into a single assembly? What have I missed?

My source of reference so far (appart from the link.exe command link reference etc on MSDN) are the two following articles:

  • http://blogs.msdn.com/texblog/archive/2007/04/05/linking-native-c-into-c-applications.aspx
  • http://www.hanselman.com/blog/MixingLanguagesInASingleAssemblyInVisualStudioSeamlesslyWithILMergeAndMSBuild.aspx I have a demonstration solution that shows my workings so far, if this helps.

Thank you very much in advance.


Update1

I have followed exactly the example in Steve Teixeira's blog, and verified it works. Using reflector, I can see the resulting executable contains two netmodules. The c# netmodule contains a reference to another netmodule but with no name?! If you move the assembly to a new directory, the second netmodule becomes unreferenced (obviously), but the executable still runs, as types with the correct definition exist in the c# netmodule.

Note that the original c# netmodule does contain a named reference to the c++ netmodule, so it must be the linker step that removes the name.

Trying to follow this example in my sample project, I have added an /ASSEMBLYMODULE argument to my post build linker step. The linker now fails with

LNK2022: metadata operation failed (80040427) : Public type 'MixedLanguageLibrary.Class1' is defined in multiple places in this assembly: 'MixedLanguageDemo.exe' and 'mixedlanguagelibrary.netmodule'
LINK : fatal error LNK1255: link failed because of metadata errors

I guess that it is the linker magic that removes the module reference name that I am missing.

Any ideas welcome.


Update2

I have reduced my project to the simplest possible, and am trying to compile is from the command line. The following batch file successfully builds the example in Steve Teixeira's blog:

setlocal    
call "C:\Program Files\Microsoft Visual Studio 9.0\VC\vcvarsall.bat"
if errorlevel 1 goto End
cl /c /MD nativecode.cpp
if errorlevel 1 goto End
cl /clr /LN /MD clrcode.cpp nativecode.obj
if errorlevel 1 goto End
csc /target:module /addmodule:clrcode.netmodule Program.cs
if errorlevel 1 goto End
link /LTCG /CLRIMAGETYPE:IJW /ENTRY:ConsoleApplication1.Program.Main /SUBSYSTEM:CONSOLE /ASSEMBLYMODULE:clrcode.netmodule /OUT:MixedApp.exe clrcode.obj nativecode.obj program.netmodule
:End

The following batch file fails to build my example code with linker error LNK2022:

setlocal
call "C:\Program Files\Microsoft Visual Studio 9.0\VC\vcvarsall.bat"
if errorlevel 1 goto End
cl /c /MD messageprovider.cpp
if errorlevel 1 goto End
cl /clr /LN /MD managedmessageprovider.cpp messageprovider.obj
if errorlevel 1 goto End
csc /target:module /addmodule:managedmessageprovider.netmodule Program.cs Form1.cs Form1.Designer.cs
if errorlevel 1 goto End
link /LTCG /CLRIMAGETYPE:IJW /ENTRY:MixedLanguageDemo.Program.Main /SUBSYSTEM:WINDOWS /ASSEMBLYMODULE:managedmessageprovider.netmodule /OUT:MixedLanguageDemo.exe managedmessageprovider.obj messageprovider.obj program.netmodule
:End

Time for spot the difference :-(

like image 648
swingkid Avatar asked Apr 09 '10 16:04

swingkid


1 Answers

The following is a Nant build-script that does exactly what you (and me) wanted (if I read your wanting right that is xD).

Some of it is missing (like some variables, that aren't really needed), but it turned out to actually be fairly easy to achieve.

This shows the cl/csc and linker-flags you need to be able to merge a mixed and a managed assembly. Also, as an added "bonus" all internal classes/methods/fields etc. is visible within the entire new assembly, meaning that they cross the boundary of the project.

    <delete file="${tmp.cpp}" />
    <foreach item="File" property="filename">
        <in>
            <items basedir="${basedir}/SpotiFire.LibSpotify">
                <include name="**.h" />
            </items>
        </in>
        <do>
            <echo message="#include &quot;${filename}&quot;&#10;" append="true" file="${tmp.cpp}" />
        </do>
    </foreach>

    <cl outputdir="${build.obj}" options="/clr /LN">
        <sources basedir="${basedir}/SpotiFire.LibSpotify">
            <include name="*.cpp" />
            <include name="${tmp.cpp}" asis="true" />
            <exclude name="AssemblyInfo.cpp" />
        </sources>
    </cl>

    <csc target="module" output="${build.obj}/SpotiFire.netmodule">
        <modules basedir="${build.obj}">
            <include name="tmp.obj" />
        </modules>
        <references refid="all_refs" />
        <sources basedir="${basedir}/SpotiFire.SpotifyLib">
            <include name="**.cs" />
        </sources>
    </csc>

    <link output="${build.dir}/${name}.dll" options="/LTCG /FIXED /CLRIMAGETYPE:IJW /NOENTRY /DLL">
        <sources basedir="${build.obj}">
            <include name="*.obj" />
            <include name="*.netmodule" />
            <include name="${basedir}/libspotify.lib" asis="true" />
        </sources>
        <arg value="/DEBUG" if="${build.debug == 'true'}" />
    </link>
like image 200
Alxandr Avatar answered Sep 19 '22 20:09

Alxandr