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

发布于 2024-08-28 11:12:39 字数 3652 浏览 8 评论 0原文

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

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

我无法使用 ILMerge,因为该 dll 同时包含托管代码和非托管代码。建议的解决方案似乎是使用 link.exe 将 C# 程序集与 C++ 对象文件链接。这就是我正在努力做的事情。

我手动编辑了 c# 可执行文件的项目文件以生成网络模块。我向可执行项目添加了一个生成后步骤,以运行 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: 

这是完全可以理解的,因为我没有在库代码网络模块中链接;我正在链接用于生成网络模块的 C++ 对象文件。

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

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

预先非常感谢您。


Update1

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

请注意,原始的 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

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

欢迎任何想法。


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

是时候找出差异了 :-(

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:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

小傻瓜 2024-09-04 11:12:39

以下是一个 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 "${filename}"
" 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>

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 "${filename}"
" 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>
无声无音无过去 2024-09-04 11:12:39

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

Your business case is very similar to SQLite, so the same approach should work for you. Basically they insert the managed assembly into the unmanaged dll as a separate data section. They are then able to p/invoke the unmanaged dll from the managed dll in the normal way. It is also possible to dynamically link to the unmanaged code in the dll.

ˉ厌 2024-09-04 11:12:39

为了正确合并,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

快乐编码。

In order to be correctly merged in, program.netmodule should be indicated in linker two times, in input list and as the parameter in ASSEMBLYMODULE option.

So the whole command line will will be as follows:

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

After this command line, program.module types should be merged into MixedLanguageDemo.exe. You can always check what gets into your resulting assembly with .NET reflectors, like ILSpy or Telerik.

Happy coding.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文