问题
今天遇到一个很奇怪的问题,有一个dephi2006写的dll,使用了stdcall的调用约定,参数传递了结构体,在函数中收到的结构体值和传入的不一致,最后一个boolean类型,应为False,收到的是True,如下图:
代码
//结构体定义
RStruct = packed record
i1: Integer;
i2: Integer;
i3: Integer;
i4: Integer;
i5: Integer;
b1: Boolean;
b2: Boolean;
b3: Boolean;
end;
procedure Fun(Param: RStruct);stdcall;
begin
Writeln('函数收到参数:');
Param.Print();
end;
var
Param: RStruct;
begin
Param.i1 := 0;
Param.i2 := 1;
Param.i3 := 2;
Param.i4 := 3;
Param.i5 := 4;
Param.b1 := true;
Param.b2 := False;
Param.b3 := false;
//Writeln('调用参数:');
//Param.Print();
//Writeln(LR + LR);
Fun(Param);
Readln;
end.
分析
为简化生成的汇编代码,删除掉了输出日志代码,只保留了必要的部分:
这里先看一个正确的例子,删除掉结构体中的i5,让结构体变小一点(在这种情况下,结果是正确的)
汇编代码如下:
关键的部分为以下内容,是对结构体中后3个布尔类型的传参
//eax 是一个32位的寄存器
//[edx + $12] 位置存储的是 b3 的值,为true(即数值1)
//此处把b3值写入到 eax
movzx eax,[edx + $12] //eax值变为 $00000001
//eax左移16位
shl eax, $10 //eax的值变为 $00010000
//ax寄存器为eax寄存器的低16位
//[edx + $10] 为b1,由于b1 占一个字节,b2占一个字节
//此处把b1 b2 移动到ax中(eax两个低字节)
mov ax, [edx + $10]
//eax 的布局为 [空] [b3] [b2] [b1]
//把eax 压入堆栈(压入堆栈是为了给函数传参用)
push eax
再看一下错误的例子
汇编代码如下图:
movzx eax,[edx + $14]
shl eax, $10
mov ax, [edx + $14]
push eax
//除了结构体大小变化引起的偏移量不同外,剩下的就是
//movzx eax,[edx + $14] 和 mov ax, [edx + $14],后面的地址是相同的,都指向了b1,而前面正确的示例中 movzx指向的是b3
//这就导致最终eax中的布局变成了 [空] [b1] [b2] [b1]
总结
只有在Fun
声明为stdcall时会出现这种错误,改为默认的调用约定可以正常工作, 结构体去掉packed声明也可以正常工作,改变结构体大小后,增大一些或减小一些也可以正常工作
应该是delphi的编译器在做优化时的一个bug
为避免这种莫名其妙的问题,在使用stdcall时,尽量不要使用packed record
在xe7下,测试了一下,没有这个问题
标签:delphi,Param,eax,2006,b1,b2,stdcall,edx,bug From: https://www.cnblogs.com/qmcode/p/18165468