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