德尔福堆栈错位编组=错误的编组

这不是一个直截了当的问题,因为我刚刚解决了它,但更像是“我是否正确”这类问题,并提醒那些可能会陷入困境的人.

事实证明,Delphi没有在堆栈上对齐变量,也没有指令/选项来控制这种行为.我的XP SP3上的默认COM编组器在编组记录时似乎需要4字节对齐.更糟糕的是,当它遇到未对齐的指针时,它不会返回错误,哦不:它将指针向下舍入到最近的4字节边界并继续这样做.

因此,如果您通过引用将已经在堆栈上分配的记录传递给COM编组功能,那么您就会被搞砸,甚至不知道.

这个问题可以通过使用New / Dispose来分配记录来解决,因为内存管理器倾向于将所有内容对齐为8个字节或更好,但是上帝,这很烦人,未对齐部分和“修剪指针”部分.

这真的是原因,还是我错了?

更新:如何重现(Delphi 2007 for Win32).

uses SysUtils;

type
  TRec = packed record
    a,b,c,d,e: int64;
  end;

  TDummy = class
  protected
    procedure Proc(param1: integer);
  end;

procedure TDummy.Proc(param1: integer);
var a,c: byte;
  rec: TRec;
begin
  a := 5;
  b := 9;
  c := 100;

  rec.a := param1;
  rec.b := a;
  rec.c := b;
  rec.d := c;
  writeln(IntToHex(integer(@rec),8));
  readln;
end;

var Obj: TDummy;
begin
  obj := TDummy.Create;
  try
    obj.Proc(0);
  finally
    FreeAndNil(obj);
  end;
end.

这给出了奇数结果地址,显然没有对齐任何东西.如果没有,请尝试向“a,c:byte”添加更多字节变量(并且不要忘记在函数末尾模拟一些工作).

使用COM的部分更容易重现,但需要更长时间来解释.创建一个名为Sample Server的新VCL应用程序,添加一个实现ISampleObject的COM对象SampleObject,带有类型库,自由线程,单个实例(确保检查ISampleObject在类型库中标记为Ole Automation).打开类型库,声明一个带有五个__int64字段的新SampleRecord.将具有单个SampleRecord * out参数的SampleFunction添加到ISampleObject.通过返回固定值在TSampleObject中实现SampleFunction:

function TSampleObject.SampleFunction(out rec: SampleRecord): HResult;
begin
  rec.a := 1291;
  rec.b := 742310;
  //...
  Result := S_OK;
end;

请注意Delphi如何在自动生成的类型库头代码中将SampleRecord声明为“压缩记录”:

SampleRecord = packed record
  a: Int64;
  b: Int64;
  //...
end;

我已经检查过,至少在Delphi 2010中修复了这个问题.自动生成的记录没有打包在那里.

注册COM服务器.运行.

现在修改上面的源代码(示例1)来调用此服务器而不是仅执行writeln:

uses SysUtils,Windows,ActiveX,SampleServer_TLB;

procedure TDummy.Proc(param1: integer);
var a,c: byte;
  rec: SampleRecord;
  Server: ISampleObject;
begin
  a := 5;
  b := 9;
  c := 100;

  rec.a := param1;
  rec.b := a;
  rec.c := b;
  rec.d := c;
  Server := CoSampleObject.Create;
  hr := Server.SampleFunction(rec);
  writeln('@: 'IntToHex(integer(@rec),8)+',rec.a='+IntToStr(rec.a));
  readln;
end;

var Obj: TDummy;
begin
  CoInitializeEx(nil,COINIT_MULTITHREADED);
  obj := TDummy.Create;
  try
    obj.Proc(0);
  finally
    FreeAndNil(obj);
    CoUninitialize();
  end;
end.

观察到当rec的地址未对齐时,rec字段的值是错误的(具体地,位移到8,16或24位,有时会被包裹到下一个值).

解决方法

你有一个堆栈未对齐的例子吗? Delphi应该将所有内容与4字节边界对齐.我能想到的唯一一个会导致错位的情况是,如果调用链中的某个位置是某些汇编代码,它已经明确地对堆栈做了一些错误的对齐.

相关文章

ffmpeg 是一套强大的开源的多媒体库 一般都是用 c/c++ 调用, 抽空研究了一下该库的最新版 ,把...
32位CPU所含有的寄存器有:4个数据寄存器(EAX、EBX、ECX和EDX)2个变址和指针寄存器(ESI和EDI) 2个指针寄...
1 mov dst, src dst是目的操作数,src是源操作数,指令实现的功能是:将源操作数送到目的操作数中,即:...
有三个API函数可以运行可执行文件WinExec、ShellExecute和CreateProcess。 1.CreateProcess因为使用复杂...
API原型: Declare Function MoveFileEx& Lib "kernel32" Alias "MoveFileExA" (By...