。下例中,EAX总是被送入Str的值,而非地址。
3.3 数据定义和数据类型强制转换(2)
const
Str = 'abcd';
Str2 = 'ab';
asm
// eax = $61626364, OFFSET是无意义的
mov eax, OFFSET Str
// eax = $00006162, 如果字符串长不大于4, 可以送入eax.长度不够时, 在左侧补0
mov eax, Str2
end;
BASM不支持访问数组下标(可以用地址运算来替代这样的语法)。尽管类似“mov eax, TYPE Arr[2]”这样的语句可以编译通过,但它总是返回数组的整个长度(如上一节例子中的值11)。这也正好解释了“mov al, Arr[2]”这样的语句为什么不能被编译——因为要将一个类型长度为11的数据放入al寄存器,是无法做到的。
BASM中支持两种类型强制转换的语法,效果是完全一致的。
type
TCode = Record
I : Integer;
S : String;
end;
var
aRec : TCode;
aInt : Integer;
asm
mov eax, aInt.TCode.I // 使用“表达式.类型”的强制转换格式
mov eax, integer(aRec) // 使用“类型(表达式)”的强制转换格式
end;
这里的强制转换的语义与Delphi是一样的。但是,BASM的强制转换,只是把地址上的变量强制识别成目标类型,而不进行长度校验。因此可以看到,TCode的长度为8,而整型长度为4,它们之间仍然可以转换,这样的转换在Delphi中是行不通的。
BASM代码块中,也可以定义数据。但是,用BASM语句定义的数据总是在代码段里,这也是对Delphi无法在代码段里定义数据的一个弥补。
BASM支持四个用于定义数据的汇编指令DB/DW/DD/DQ。与ASM不同,不能为这些数据命名。例如
:
asm
DB 0FFH // 定义一个字节
aVar DB 0FFH // 在ASM中可用,但在BASM中不支持
end;
可以通过一些技巧来解决命名
问题。但是,必须同时用操作
系统的API来打开代码访问权限,才能真正的写这些数据。下面的例子展示数据定义、命名和读取的方法:
type
TCode = packed Record
CODE : WORD; // jmp @, 2 Bytes
I : Integer;
S1 : array [1..26] of char;
S2 : array [1..11] of byte;
end;
var
I : Integer;
S : String;
Code : ^TCode;
function ReadCode : Integer;
asm
jmp @
DD 12344213
DB 'ABCDEFGHIJKLMJNOQRSTUVWXYZ'
DB 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32
@:
mov Code, offset ReadCode
mov EAX, ReadCode.TCode.I
end;
// ...
I := ReadCode; // I = 12344213
S := Code^.S1; // S = 'ABCDEFGHIJKLMJNOQRSTUVWXYZ'
这个例子以例程名作为变量的地址,但并不是一个好的例子(尽管很多代码这样做)。更方便的方法是使用标号作为变量名,与上例类同的例子是这样:
type
TCode = packed Record
I : Integer;
// ...
end;
var
I : Integer;
function ReadCode : Integer;
asm
jmp @
@CodeRec :
DD 12344213
// ...
@:
mov EAX, @CodeRec.TCode.I // 使用标号作为变量
end;
// ...
I := ReadCode; // I = 12344213
3.4 例程入口参数及调用约定
任何情况下,在寄存器的使用上,BASM遵循如下的规则:
F ASM语句执行过程中,必须保存EDI、ESI、ESP、EBP、EBX的值。
F ASM语句可以任意使用EAX、ECX、EDX。
F 一个ASM代码块开始时,EBP指向当前堆栈,ESP指向栈顶。
F SS存放堆栈段的段地址;DS存放数据段的段地址;CS存放代码段的段地址。
F 通常情况下,段地址寄存器满足如下条件:SS=ES=DS。
F 如果需要,函数总是以EAX(32位)、AX(16位)或AL(8位)作为返回值的寄存器。
Delphi的例程入口参数有以下几种:
procedure TestProc(I : Integer); // 值参数
procedure TestProc(var I : Integer); // 变量参数
procedure TestProc(const I : Integer); // 常数参数
procedure TestProc(out I : Integer); // 输出参数
按照Delphi的语法规定,值参数和常数参数使用相同的传值规则,但值参数只是传入值的备份;变量参数、输出参数总是传入值的地址。至于像“无类型参数”、“开放数组参数”等,都是在上面的基础上声明的,因此也符合其基本规则。
可以直接修改变