如何将C#和C++程序集链接为一个单独的可执行文件?

10

我的VS2008解决方案中有一个项目生成了一个引用了包含C++/CLI和非托管C++的dll的C#可执行文件。我想将它们合并为一个单独的可执行文件,因为其中的C++ dll包含了我想要嵌入到主可执行文件中的安全代码。

由于这个dll包含了托管和非托管代码,所以我不能使用ILMerge。建议的解决方案似乎是使用link.exe将C#程序集与C++对象文件链接在一起。这就是我正在尝试做的事情。

我手动编辑了C#可执行文件的项目文件以生成netmodule。我添加了一个后期构建步骤来运行link.exe将C# netmodule和编译好的C++对象文件链接在一起,然后运行mt.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: 

由于我没有链接到库文件的netmodule,而是链接到用于生成netmodule的C++对象文件,所以这是完全可以理解的。

因此,简而言之,我如何将一个C#可执行文件和C++对象文件合并成一个单独的程序集?我错过了什么?

迄今为止,我参考的来源(除了MSDN上的link.exe命令链接参考等)是以下两篇文章:

非常感谢您提前的帮助。


更新1

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

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

在我的示例项目中尝试遵循此示例时,我在我的后期构建链接器步骤中添加了/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

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

欢迎任何想法。


更新2

我已经将我的项目简化到最简单的情况,并试图从命令行编译它。以下批处理文件成功地构建了 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

现在是找茬时间 :-(


请参考此帖子(重复)。 - GalacticJello
可能是 https://dev59.com/HnVD5IYBdhLWcg3wJIEK 的重复问题。 - spoulson
问题72264的答案涉及将dll嵌入到程序中,然后在运行时提取它。这简化了安装过程,但并没有提高安全性。第一次运行后,该dll作为单独的程序集可用。然而,这让我考虑将dll嵌入到内存中,并动态加载它,而不是将其提取到文件中。 - swingkid
说实话,这也只是出于学术好奇心;将它们全部链接起来应该是可能的。即使我们最终采用了不同的解决方案,我也想了解如何实现。 - swingkid
我必须问一下,你最终找到了吗? - Alxandr
显示剩余2条评论
3个回答

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

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


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)检查最终程序集中包含的内容。

祝编码愉快。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接