如何将C#和C ++程序集链接到单个可执行文件中?

时间:2010-04-09 16:17:04

标签: c# visual-c++ linker c++-cli

我有VS2008解决方案,其中包含一个生成C#可执行文件的项目,该项目可引用一个生成包含C ++ / CLI和非托管C ++的DLL的项目。

我想将这些合并到一个可执行文件中,因为C ++ dll包含我想要嵌入主可执行文件的安全代码。

我无法使用ILMerge,因为dll包含托管代码和非托管代码。建议的解决方案似乎是使用link.exe将C#程序集与C ++目标文件链接起来。这就是我想要做的。

我手动编辑了c#可执行文件的项目文件以生成netmodule。我在可执行项目中添加了一个构建后步骤,以运行link.exe将c#netmodule和已编译的C ++目标文件链接在一起,然后运行mt.exe来合并两个项目创建的程序集清单。这运行成功,但是exe仍然包含对C ++项目的正常构建过程生成的dll中定义的c ++类型的引用和使用。

然后我在C ++ dll的项目设置中指定了/ NOASSEMBLY,因此它也生成了一个netmodule。在C#项目中,我删除了对C ++项目的引用,但在解决方案中添加了项目依赖性。我手动编辑了C#项目文件,包括类似于:

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

即。引用现在由C ++项目生成的C ++ netmodule。

但是,现在我的帖子构建事件中的链接器步骤失败了:

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

这完全可以理解,因为我没有链接到librarycode netmodule;我正在链接用于生成netmodule的C ++目标文件。

简而言之,如何将c#可执行文件和C ++目标文件合并到一个程序集中?我错过了什么?

到目前为止我的引用来源(来自MSDN上的link.exe命令链接引用等)是以下两篇文章:

非常感谢你。


UPDATE1

我已经完全按照Steve Teixeira博客中的示例进行了操作,并验证了它的有效性。使用反射器,我可以看到生成的可执行文件包含两个netmodule。 c#netmodule包含对另一个netmodule的引用但没有名称?!如果将程序集移动到新目录,第二个netmodule将变为未引用(显然),但可执行文件仍然运行,因为c#netmodule中存在具有正确定义的类型。

请注意,原始c#netmodule确实包含对c ++ netmodule的命名引用,因此它必须是删除名称的链接器步骤。

在我的示例项目中尝试遵循此示例,我在我的post构建链接器步骤中添加了/ ASSEMBLYMODULE参数。链接器现在失败并带有

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

我猜是链接器魔法删除了我缺少的模块引用名称。

欢迎任何想法。


UPDATE2

我已经尽可能简化了我的项目,我正在尝试编译来自命令行。以下批处理文件在Steve Teixeira的博客中成功构建了示例:

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

以下批处理文件无法使用链接器错误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

发现差异的时间: - (

3 个答案:

答案 0 :(得分:3)

以下是一个Nant构建脚本,它完全符合您(和我)想要的内容(如果我阅读了您想要的权利,那就是xD)。

其中一些缺失(如某些变量,并非真正需要),但事实证明它实际上相当容易实现。

这显示了您需要能够合并混合和托管程序集的cl / csc和链接器标志。此外,作为一个额外的“奖励”,所有内部类/方法/字段等在整个新程序集中都是可见的,这意味着它们跨越了项目的边界。

    <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>

答案 1 :(得分:0)

您的业务案例与SQLite非常相似,因此相同的方法应该适合您。基本上,他们将托管程序集作为单独的数据部分插入到非托管dll中。然后,他们能够以正常方式从托管dll调用非托管dll。也可以动态链接到dll中的非托管代码。

答案 2 :(得分:0)

为了正确合并,program.netmodule应该在链接器中指示两次,在输入列表中,并作为ASSEMBLYMODULE选项中的参数。

所以整个命令行将如下:

link /LTCG /CLRIMAGETYPE:IJW /ENTRY:MixedLanguageDemo.Program.Main /SUBSYSTEM:WINDOWS /ASSEMBLYMODULE:program.netmodule /OUT:MixedLanguageDemo.exe managedmessageprovider.obj messageprovider.obj program.netmodule

在此命令行之后,应将program.module类型合并到MixedLanguageDemo.exe中。您始终可以使用.NET反射器(例如ILSpyTelerik)检查生成的程序集中的内容。

快乐的编码。