65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (23投票s)

2011年10月11日

CPOL

5分钟阅读

viewsIcon

74041

downloadIcon

1194

本文介绍如何构建一个独立的 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 项目的平台目标时,您需要考虑这一点。

asmwnet1.jpg

asmwnet2.jpg

使用代码

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”,这就是我们需要的)。

  1. 使用 JWasm 编译器编译 ASM
  2. <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 指令。

  3. 将编译后的 ASM 文件 `asmrotines.obj` 复制到 C++/CLR 互操作项目源文件所在的文件夹,并将控制台窗口切换到该文件夹。
  4. 编译名为“native.cpp”的原生代码源文件。命令行为:
  5. cl /c /MD native.cpp

    C/C++ 编译器将生成对象文件“native.obj”。

  6. 现在,使用以下命令行编译项目中的托管文件“CInterop.cpp”:
  7. cl /clr /LN /MD CInterop.cpp native.obj asmrotines.obj

    `/clr` 开关生成混合模式代码,`/LN` 创建一个 `.netmodule`,`/MD` 与 `MSVCRT.LIB` 链接。因为此模块包含对原生代码的引用,所以我们还需要传递 `native.obj` 和 `asmrotines.obj`,以便在编译器调用链接器生成 `.netmodule` 时将此文件传递到链接行。这基本上是 Steve Teixeira 先生提供的解释。

  8. 现在,将 `asmrotines.obj`、`CInterop.obj`、`native.obj` 和 `CInterop.netmodule` 复制到 C# 文件所在的文件夹,并将控制台窗口切换到该文件夹。
  9. 使用以下命令行运行 C# 编译器:
  10. csc /target:module /unsafe /addmodule:cinterop.netmodule Program.cs 
       Form1.cs Form1.Designer.cs Properties\AssemblyInfo.cs
  11. 最后,运行 Microsoft 链接器将我们一路构建的所有部分链接在一起。
  12. 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 代码的原因。
© . All rights reserved.