在独立的 64 位可执行文件中混合 .NET 和汇编语言






4.88/5 (23投票s)
本文介绍如何构建一个独立的 64 位 .NET 可执行文件,该文件与编译后的汇编语言对象文件进行静态链接。
引言
Steve Teixeira 在 http://blogs.msdn.com/b/texblog/archive/2007/04/05/linking-native-c-into-c-applications.aspx 中首次阐述了在托管代码和非托管代码混合的情况下构建独立 .NET 可执行文件的技术,如果您对此不熟悉,强烈建议您阅读此文。
我们的文章采用了该技术,但我们将构建一个仅限 64 位的程序。原因是我们将静态链接与 CPU 相关的 64 位汇编语言(或 ASM)编译代码。在配置 .NET 项目的平台目标时,您需要考虑这一点。
使用代码
1. 汇编语言
我开发了两个将从 .NET 调用 的 ASM 例程。一个使用 SSE2 指令执行小型数学计算。另一个是使用 XXTEA 算法(http://en.wikipedia.org/wiki/XXTEA)的非常快速的加密/解密例程。XXTEA 算法是一种非常强大的加密算法,但当国家安全依赖于它时,不应使用它,因为似乎只有少数人知道如何攻击它。无论如何,我们在这里不讨论密码学。
下面是执行数学计算 sin(val1^2 / val2) 的例程,如下所示:
; Calculates sin(val1^2 / val2)
;double AsmMathCalc (double val1, LONGLONG val2);
AsmMathCalc proc FRAME val1:MMWORD, val2:SQWORD
mulsd xmm0, xmm0 ; val1^2
cvtsi2sd xmm1, rdx
divsd xmm0,xmm1
call sin
ret ; result is returned in xmm0
AsmMathCalc endp
由于长度原因,此处未显示执行加密/解密的例程,您需要下载它才能查看。它会打开要加密(或解密)的文件,并创建一个文件用于加密(解密)输出。然后以块的形式读取和加密(或解密)并写入新文件。加密文件以“.enc”扩展名保存在完整文件名中,解密文件添加“.dec”扩展名。请注意,XXTEA 使用 32 位字进行操作;如果文件长度(字节)不能被 4 整除,我们需要发送更多“4-文件大小模 4”个字节来弥补差异。
2. 用于与 ASM 互操作的 C++/CLR 代码
我的方法是创建一个类库 C++ 项目。然后将处理托管代码和非托管代码的部分放在不同的源文件。
托管代码
// CInterop.h
#pragma once
#include "native.h"
using namespace System;
namespace CInterop {
//public ref class ASMCallingClass ; only for testing when compiling as library
ref class ASMCallingClass
{
public :
static double ASMMathCalc(double val1, LONGLONG val2)
{
return runAsmCalc(val1, val2);
}
static int ASMXXTea(LPWSTR fileName, LPDWORD Key, BOOL encode)
{
return runAsmXXTea(fileName, Key, encode);
}
};
}
非托管代码
//native.cpp
#include "Stdafx.h"
extern "C"
{
double AsmMathCalc (double val1, LONGLONG val2);
int AsmXXTEAEncDec(LPWSTR val1, LPDWORD val2, BOOL val3);
}
double runAsmCalc(double val1, LONGLONG val2)
{
return AsmMathCalc(val1, val2);
}
int runAsmXXTea(LPWSTR fileName, LPDWORD Key, BOOL encode)
{
return AsmXXTEAEncDec(fileName, Key, encode);
}
仅用于测试目的,您可以将汇编编译的对象文件添加到属性\链接器\输入\附加依赖项中,但这不会在最终构建过程中使用,因为我们将从命令行进行所有构建。
3. C# Windows 窗体应用程序
在此项目中,添加一个用于 C++/CLR 命名空间的“using
”子句。如果您已构建 C++/CLR 类库,现在可以测试所有内容是否正常工作。如果不正常,请将互操作项目中的类 `ASMCallingClass` 设置为 public 并重新构建库。
构建
总共有三个项目:ASM 项目、C++/CLR 互操作项目和 C# 项目。
要能够构建单个可执行文件,您需要按依赖顺序编译。首先是原生代码,然后是互操作代码,最后是 100% 托管代码。
打开一个控制台窗口,并设置 Visual Studio x64 编译环境(在“开始”菜单中,您可能会找到“Visual Studio x64 Win 64 Command Prompt”,这就是我们需要的)。
- 使用 JWasm 编译器编译 ASM
- 将编译后的 ASM 文件 `asmrotines.obj` 复制到 C++/CLR 互操作项目源文件所在的文件夹,并将控制台窗口切换到该文件夹。
- 编译名为“native.cpp”的原生代码源文件。命令行为:
- 现在,使用以下命令行编译项目中的托管文件“CInterop.cpp”:
- 现在,将 `asmrotines.obj`、`CInterop.obj`、`native.obj` 和 `CInterop.netmodule` 复制到 C# 文件所在的文件夹,并将控制台窗口切换到该文件夹。
- 使用以下命令行运行 C# 编译器:
- 最后,运行 Microsoft 链接器将我们一路构建的所有部分链接在一起。
<path>\jwasm -c -win64 -Zp8 asmrotines.asm
注意:有许多程序可以编译汇编语言源代码,它们称为汇编器。最流行的是 Microsoft 的 MASM。我没有使用 ML64(64 位 MASM),因为它在 64 位模式下不支持“invoke”指令(“invoke”可以大大加快编码过程)以及我们在 32 位 MASM 中习惯的其他一些优点。但 JWasm 向后兼容 MASM(可能是 MASM64 应该具备的功能),这意味着通过适当替换“invoke”、“if else endif”、“user registers”和自动帧等提高生产力的功能,可以在 ML64 下编译此代码。
在编写 ASM 代码时,您必须对其进行测试,因为在 ASM 中插入错误非常容易。有多种方法可以做到这一点。对于我的演示程序,我只是将 `asmrotines.obj` 与用 C 编写的测试程序链接在一起,在 Visual Studio 中。原因是 Visual Studio C++ IDE 具有内置的反汇编器,您可以单步执行 ASM 指令。
cl /c /MD native.cpp
C/C++ 编译器将生成对象文件“native.obj”。
cl /clr /LN /MD CInterop.cpp native.obj asmrotines.obj
`/clr` 开关生成混合模式代码,`/LN` 创建一个 `.netmodule`,`/MD` 与 `MSVCRT.LIB` 链接。因为此模块包含对原生代码的引用,所以我们还需要传递 `native.obj` 和 `asmrotines.obj`,以便在编译器调用链接器生成 `.netmodule` 时将此文件传递到链接行。这基本上是 Steve Teixeira 先生提供的解释。
csc /target:module /unsafe /addmodule:cinterop.netmodule Program.cs
Form1.cs Form1.Designer.cs Properties\AssemblyInfo.cs
link /LTCG /CLRIMAGETYPE:IJW /ENTRY:Charp.Program.Main
/SUBSYSTEM:WINDOWS /ASSEMBLYMODULE:cinterop.netmodule /OUT:NetAndAsm.exe
cinterop.obj native.obj asmrotines.obj program.netmodule
就这样!
常见问题解答
问:是否有在独立的 32 位可执行文件中混合 .NET 和汇编语言的示例?
答:相同的示例,但针对 32 位版本,可在我的网站上找到:http://www.atelierweb.com/articles/NetAndAsm32.htm。
历史
- 2011 年 10 月 11 日:初始发布。
- 2011 年 10 月 12 日:修复了一个错误(独立程序会请求 `.netmodule`)。现已修复。
- 2011 年 10 月 14 日:修复了 ASM 中 `CreateFileW` 的错误(错误返回 `INVALID_HANDLE_VALUE`,而不是零)。
- 2011 年 10 月 18 日:澄清了我们使用 JWAsm 而不是 Masm64 编译 ASM 代码的原因。