Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
504 views
in Technique[技术] by (71.8m points)

visual c++ - 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="..Debuglibrarycode.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:

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 FilesMicrosoft Visual Studio 9.0VCvcvarsall.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 FilesMicrosoft Visual Studio 9.0VCvcvarsall.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 :-(

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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>

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

2.1m questions

2.1m answers

60 comments

57.0k users

...