views:

114

answers:

3

I have to do some record sub-data size calculation so created something like

function GetSubDataSize(const Rec: TRecord): integer;
begin
  Result:=integer(@Rec.Field2) - integer(@Rec.Field1);
end;

Everything is ok except for one case, if one of Field is a procedure or function pointer, so in case of

TRecord = record
   Field2: procedure(Sender: TObject) of object;
end;

The function gets the address of the procedure itself. Is there a way to typecast the field to get the address of the field not the address of the function? I know that I can solve it with variant part records, but just prefer not to use it.

Thanks,

Max

+1  A: 

Unfortunately, there doesn't seem to be any easy way to do this the way you're doing it. Variant part records would probably work, but I agree, that's kinda ugly.

But if you're on Delphi 2010, there's a better way. It looks like you're trying to go over all the fields of the record and determine their offsets. That can be done with extended RTTI, like so:

procedure test;
var
   ctx: TRttiContext;
   recType: TRttiType;
   recField: TRttiField;
begin
   ctx := TRttiContext.Create;
   recType := ctx.GetType(TypeInfo(TMyRec));
   for recField in recType.GetFields do
     writeln(format('Field %s is at offset %d.', [recField.Name, recField.Offset]));
end;

(The writeln assumes you're in a console app written purely for demonstrative purposes, like I wrote up to test this. Feel free to modify it to suit your needs...)

Mason Wheeler
+3  A: 

As your Field2 is a method pointer, you should make use of that:

This code

type
  RRecord = record
    Field1: Integer;
    Field2: procedure (Sender: TObject) of object;
    Field3: Integer;
  end;

var
  rec: RRecord;
begin
  Memo1.Lines.Add(Format('@rec.Field1 %d', [Integer(@rec.Field1)]));
  Memo1.Lines.Add(Format('@rec.Field2 %d', [Integer(@rec.Field2)]));
  Memo1.Lines.Add(Format('@TMethod(rec.Field2).Code %d', [Integer(@TMethod(rec.Field2).Code)]));
  Memo1.Lines.Add(Format('@TMethod(rec.Field2).Data %d', [Integer(@TMethod(rec.Field2).Data)]));
  Memo1.Lines.Add(Format('@rec.Field3 %d', [Integer(@rec.Field3)]));
end;

put in the OnCreate of a form with a memo on it, produces:

@rec.Field1 1244820
@rec.Field2 4052
@TMethod(rec.Field2).Code 1244828
@TMethod(rec.Field2).Data 1244832
@rec.Field3 1244836

The second line shows a random value, as nothing was assigned to the local record variable. The third and fourth lines show the addresses of the TMethod members.

Please note that there is (may be) filling in the record because of the fact that methods seems to be always 8 byte aligned. (At least in D2009/D2010).

Marjan Venema
> "..filling in the record.." - @Marjan, I take it you've tested with a, say, `Word` Field1 and a `packed` record? There's no alignment with D2007..
Sertac Akyuz
@Sertac: Nope, sorry. I spotted the extra bytes in the example I gave and know from past experience that D2010 and D2009 seem to align method pointers on 8 byte boundaries. Detected this in a class that must be an exact copy (field wise) of another class which happens to have a nested record definition. We have a run time check on the instance size to detect inadvertent changes to one but not the other. This check would sometimes succeed and sometimes fail, always without changes to either class but with changes to other code. Putting {$A8} around our copy solved this.
Marjan Venema
@Marjan - I'm confused a bit; AFAIK `{$A8}` is already the default alignment. So a 'Byte' or a 'TMethod' would aling on a 8 byte boundary for a non-packed record by default.. Nevertheless thanks for the explanation, I think I somehow misunderstood something..
Sertac Akyuz
@Sertac: ah, no, you probably didn't misunderstand. In our projects we still use {$A4} for various reasons...
Marjan Venema
+10  A: 

Have you tried @@?

type
  TRecord = record
    Field1:integer;
    Field2:TNotifyEvent;
  end;

function GetSubDataSize(const Rec: TRecord): integer;
begin
  result := integer(@@Rec.Field2) - integer(@Rec.Field1);
end;

Gives me 8, which is what I would expect on Delphi 2010.

N@

Nat
+1 - From the docs (Procedural Types): "To get the memory address of a procedural variable (rather than the address stored in it), use @@."
Sertac Akyuz
Thanks, Nat, seems that this is a good question for testing Delphi skills, you seem to pass it easily ;)
Maksee
@Maksee: Thanks! I wouldn't say my skills are better than others (especially Mason!), I just happened to have had to solve a similar problem myself once. :)
Nat
@Nat: Don't tell anyone I said this, but that's where most of my "skills" come from too. I'm just faster at typing than most people here. :P
Mason Wheeler
@Mason: ***looks around, whispers*** Don't worry, your secret is safe with me! :)
Nat