tags:

views:

122

answers:

1

This is not exactly a straight-out question because I have just solved it, but more like "am I getting it right" type of question and a reminder for those who might get stuck into that.

Turns out, Delphi does not align variables on stack and there are no directives/options to control this behavior. Default COM marshaller on my XP SP3 seem to require 4-byte alignment when marshaling records though. Worse, when it encounters unaligned pointer, it does not return an error, oh no: it rounds the pointer down to the nearest 4-byte boundary and continues like that.

Therefore, if you pass a record you have allocated on stack into COM-marshaled function by reference, you're screwed and you won't even know.

The problem can be solved by using New/Dispose to allocate records, as memory managers tend to align everything at 8 bytes or better, but god, this is annoying, both the misalignment part and the "trim-down-pointers" part.

Is this really the reason, or am I wrong somewhere?

Update: How to reproduce (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.

This gives odd result address, clearly not aligned on anything. If it doesn't, try adding more byte variables to "a, b, c: byte" (and don't forget to simulate some work with them at the end of the function).

The part with COM is easier to reproduce but longer to explain. Create a new VCL app called Sample Server, add a COM object SampleObject implementing ISampleObject, with a type library, free-threaded, single instance (make sure to check ISampleObject is marked as Ole Automation in type library). Open the type library, declare a new SampleRecord with five __int64 fields. Add a SampleFunction with a single SampleRecord* out parameter to ISampleObject. Implement SampleFunction in TSampleObject by returning fixed values:

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

Note how Delphi declares SampleRecord as "packed record" in automatically generated type library header code:

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

I have checked, and this, at least, was fixed in Delphi 2010. Automatically generated records are not packed there.

Register the COM server. Run it.

Now modify the source above (sample 1) to call this server instead of just doing 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.

Observe that when the address of rec is not aligned, values of rec fields are wrong (specifically, bitshifted to 8, 16 or 24 bits, sometimes wrapped over to the next value).

+8  A: 

Do you have an example of the stack being misaligned? Delphi should be aligning everything to 4 byte boundaries. The only case I can think of that would cause misalignment is if someplace along the call-chain is some assembler code that has explicitly done something to the stack to mis-align it.

Allen Bauer
Would a field in a packed record be possible, or a byte in say an array?
Ritsaert Hornstra
It would, but how would that be related to stack storage?
Paul-Jan
@Allen - Just a friendly reminder that variable and code alignment options (esp. to allow 16-byte boundary alignment) would be _highly_ desirable.
PhiS
@paul-jan: Well, if you create an fixed sized array as local variable for example, nothing to it..
Ritsaert Hornstra
@Ritsaert, if you define a packed record, it's certainly possible for some of the fields to be misaligned. There's nothing the compiler or the COM marshaller can do about that, though; it's your own fault for defining an unalignable data structure. The solution is to not define such a structure when alignment is important. You can't define the stack itself as "packed," though, so even if some fields in the record might be misaligned, the record variable as a whole should always be four-byte aligned, as long as nothing else has done anything to misalign the stack pointer.
Rob Kennedy
Done, examples added. Playing with these things a bit, I also noticed it packs local byte variables, giving them a byte of space each, gives "boolean" result variables only one byte too. But all of this seems only happen in procedures/functions of object, not in pure procedures/functions. (Have not verified that, just a guess)
himself
@Rob: I just reacted to Allen who said that it is only possible when some asm mumbo jumbo was present along the call chaion. This is clearly not true and hence my examples. Looking at the reraction from himself; loose variables that are bytes in size are packed together withi the same 32bit memory "block" in some cases so even when not using a packed record you could have that. Translated to a non packed struct: if you define aByte, a AnsiChar, a boolean etc after each otgher they are BYTE aligned, not Longword aligned.
Ritsaert Hornstra