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

时间:2010-06-24 16:02:17

标签: delphi com

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

事实证明,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, b, 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,b,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, b, 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位,有时会被包裹到下一个值)。

1 个答案:

答案 0 :(得分:8)

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