在.NET应用程序中混合使用托管代码和非托管代码,特别是静态链接64位汇编语言时,需要特别注意平台目标的配置。本文将介绍如何构建这样的应用程序,包括汇编语言的编写、C++/CLR代码的互操作以及C# Windows Forms应用程序的集成。
首先,需要编写两个汇编语言例程,这些例程将从.NET调用。一个用于执行使用SSE2指令的小型数学计算,另一个是使用XXTEA算法的快速加密/解密例程。XXTEA算法是一种非常强大的加密算法,但不建议在国家安全依赖于它的情况下使用。这里不讨论密码学的细节。
数学计算的汇编例程如下所示:
ASM
;
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
calls
sin
ret
;
result is returned in xmm0
AsmMathCalc endp
加密/解密例程由于其长度,这里没有展示。它打开要加密(或解密)的文件,并创建一个加密(解密)输出文件。然后分块读取并加密(或解密)并写入新文件。加密文件的扩展名为".enc",解密文件的扩展名为".dec"。
创建了一个类库C++项目,并将处理托管代码和非托管代码的部分放在不同的源代码文件中。托管代码如下:
MC++
// 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);
}
};
}
非托管代码如下:
C++
//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);
}
为了测试目的,可以将汇编编译的对象文件添加到项目属性的链接器输入附加依赖项中,但在最终构建过程中不会使用它,因为将从命令行构建所有内容。
在这个项目中,添加一个"using"子句用于C++/CLR命名空间。如果已经构建了C++/CLR类库,现在可以测试是否一切正常。如果没有,请将互操作项目中的类ASMCallingClass设置为public并重建库。
总共有三个项目:汇编项目、C++/CLR互操作项目和C#项目。要构建单个可执行文件,需要按依赖顺序编译。首先编译原生代码,然后是互操作代码,最后是100%托管代码。
打开一个控制台窗口,设置为Visual Studio x64编译环境(在开始菜单中,可以找到Visual Studio x64 Win 64 Command Prompt,这是需要的)。
使用JWasm编译器编译汇编:
\jwasm -c -win64 -Zp8 asmrotines.asm
有许多程序可以编译汇编语言源代码,它们被称为汇编器。最流行的是微软的MASM。尽管没有使用ML64(64位MASM),因为它在64位模式下不支持"invoke"指令("invoke"大大加快了编码过程),并且还有一些在32位MASM中习惯的好东西。但是JWasm与MASM向后兼容(它可能就是MASM64应该有的样子),这意味着通过适当替换像"invoke"、"if else endif"、"用户寄存器"和自动框架这样的生产力增强功能,这个代码可以在ML64下编译。
在编写汇编例程时必须测试它们,因为很容易在汇编中插入错误。有多种方法可以做到这一点。对于演示程序,只是将asmrotines.obj与在Visual Studio下用C编写的测试程序链接起来。原因是Visual Studio C++ IDE内置了反汇编器,可以单步执行汇编指令。
将编译好的汇编文件asmrotines.obj复制到C++/CLR互操作项目的源文件所在的文件夹中,并将控制台窗口更改为该文件夹。
编译名为native.cpp的原生代码源文件。命令行是:
cl /c /MD native.cpp
C/C++编译器将生成对象文件native.obj。
现在,使用以下命令行编译项目中的托管文件CInterop.cpp:
cl /clr /LN /MD CInterop.cpp native.obj asmrotines.obj
/clr开关生成混合模式代码,/LN创建.netmodule,/MD与MSVCRT.LIB链接。因为这个模块包含了对原生代码的引用,所以还需要传递native.obj和asmrotines.obj,以便在编译器调用链接器生成.netmodule时,这个文件可以传递到链接线。这基本上就是Steve Teixeira先生给出的解释。
现在,将asmrotines.obj、CInterop.obj、native.obj和CInterop.netmodule复制到C#文件所在的文件夹中,并将控制台窗口更改为该文件夹。
使用以下命令行运行C#编译器:
csc /target:module /unsafe /addmodule:cinterop.netmodule Program.cs
Form1.cs Form1.Designer.cs Properties\AssemblyInfo.cs
最后,运行Microsoft链接器将一路走来构建的所有部分链接在一起。
link /LTCG /CLRIMAGETYPE:IJW /ENTRY:Charp.Program.Main
/SUBSYSTEM:WINDOWS /ASSEMBLYMODULE:cinterop.netmodule /OUT:NetAndAsm.exe
cinterop.obj native.obj asmrotines.obj program.netmodule