这不是一个直截了当的问题,因为我刚刚解决了这个问题,但更像是“我是否正确”这类问题,并提醒那些可能会陷入困境的人。
事实证明,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位,有时会被包裹到下一个值)。
答案 0 :(得分:8)
你有一个堆栈未对齐的例子吗? Delphi应该将所有内容与4字节边界对齐。我能想到的唯一一个会导致错位的情况是,如果调用链中的某个地方是某些汇编代码,它已经明确地对堆栈做了一些错误的对齐。