Delphi XE2 RTTI broken?
Asked Answered
L

2

12

I recently migrated from D2010 to DXE2 and found a showstopper bug (Or feature?) in XE2 and XE3 (Tested in my friend XE3) related to RTTI generation for TBytes fields inside classes.

I found that the RTTI information for a TBytes variable inside a class is never generated.

The following code works well in D2010, but shows the message "Error" in XE2/XE3

Does anyone have any clue? This will totally break all our software data serialization implementation

To test the code please add Rtti unit to the uses declaration

type

  TMyClass = class
  public
    Field1: Integer;
    Field2: TBytes;
  end;


procedure TForm2.Button1Click(Sender: TObject);
var
  i: Integer;
  Data: TMyClass;
  Rtti: TRttiContext;
  RttiClassType: TRttiInstanceType;
begin

  Data := TMyClass.Create;
  try

    // Get the context
    Rtti := TRttiContext.Create;
    try

      // Get the type for the class
      RttiClassType := TRttiInstanceType(Rtti.GetType(Data.ClassInfo));

      // Check the fields
      for i := 0 to High(RttiClassType.GetFields) do
      begin

        // Check the field type
        if not Assigned(RttiClassType.GetFields[i].FieldType) then
          ShowMessage('Error');

      end;

    finally
      Rtti.Free;
    end;

  finally
    Data.Free;
  end;

end;

The error message will be displayed when checking for Field2 that is a TBytes becayse the FieldType is always nil!!!

Does anyone has any clue of what have changed in the RTTI from D2010 do XE2? Maybe because the TBytes type was changed from array of Byte to the generic array?

Legist answered 1/10, 2012 at 18:54 Comment(2)
Please let us know the QC report number you filed this under.Superadd
Sure, I just opened #109190, but apparently it is a duplicated report of #97748 Hope they will fix it!Legist
E
9

This is a known issue that was fixed in XE3. Unfortunately, upgrading is the only way to get a fix for it; bug fixes don't usually get ported back.

EDIT: Or not. Apparently this is not actually fixed, as it still occurs in XE3. Reporting it as a new case and mentioning 103729 would probably be the best course of action.

Echevarria answered 1/10, 2012 at 19:7 Comment(10)
There was a previuos post here, someone told to add {$RTTI FIELDS([])}Legist
and it actually works... the problem is that I use {$RTTI EXPLICIT METHODS([]) PROPERTIES([]) FIELDS([vcPublic])} in order to not generate RTTI for private class fields... and by using vcPublic it does not generate RTTI for public TBYtes fields...Legist
@Legist - After Mason warned me that the answer was wrong I tested and it indeed suppressed generation of RTTI for fields.Causeuse
@Eric: That "solution" works by not generating any RTTI for fields at all. Not generating a type for TBytes is a different issue; that's a compiler-level problem. If it's not fixed in XE3, try posting this as a new issue, with your code as an example, and mention the other QC case.Echevarria
@SertacAkyuz Yep, I tested and it really suppressed the generation of RTTI What I need is RTTI for public fields on classes Tested in XE3 and it is not workingLegist
@MasonWheeler Thanks Mason, I will open a QC report bug.. but unfortunally I will have to change my code because now I can´t go back to D2010 and I will NOT upgrade to any other delphi until this stupid error is fixedLegist
@Legist - Does substituting TArray<Byte> for TBytes as in the linked report work?Causeuse
by substituting TBytes by TArray<Byte> it will generate the RTTILegist
TBytes should be expunged. TArray<Byte> is the canonical way to declare this type.Illuse
I believe that TBytes maps to TArray<Byte>, I´m not sure... I changed my fields that required RTTI from TBytes to TArray<Byte> and it worked, but the odd thing is that if I create a new type like TMyBytes = TArray<Byte> and use TMyBytes in the class definition I also don´t get the RTTI information! Something is really messed up in the compiler...Legist
D
15

You can fix this error (it is actually not the same bug as the one Mason mentioned).

type
  FixTypeInfoAttribute = class(TCustomAttribute)
  public
    FTypeInfo: PPTypeInfo;
    constructor Create(TypeInfo: PTypeInfo);
  end;

procedure FixFieldType(TypeInfo: PTypeInfo);
var
  ctx: TRttiContext;
  t: TRttiType;
  f: TRttiField;
  a: TCustomAttribute;
  n: Cardinal;
begin
  t := ctx.GetType(TypeInfo);
  for f in t.GetFields do
  begin
    for a in f.GetAttributes do
    begin
      if (a is FixTypeInfoAttribute) and f.ClassNameIs('TRttiInstanceFieldEx') then
      begin
        WriteProcessMemory(GetCurrentProcess, @PFieldExEntry(f.Handle).TypeRef,
          @FixTypeInfoAttribute(a).FTypeInfo, SizeOf(Pointer), n);
      end;
    end;
  end;
end;

constructor FixTypeInfoAttribute.Create(TypeInfo: PTypeInfo);
begin
  FTypeInfo := PPTypeInfo(PByte(TypeInfo) - SizeOf(Pointer));
end;

Then you add the attribute to your class definition:

type
  TMyClass = class
  private
    Field1: Integer;
    [FixTypeInfo(TypeInfo(TBytes))]
    Field2: TBytes;
  end;

and make sure the FixFieldType routine is called:

initialization
  FixFieldType(TypeInfo(TMyClass));

Tested on XE

Depute answered 2/10, 2012 at 9:27 Comment(3)
You omitted FixTypeInfoAttribute.Create. And there's a name mismatch between FixTypeInfoAttribute and FixTypeInfo. But +1 for a nice workaround.Illuse
@David: Added the constructor implementation. And there is no name mismatch as you can omit the Attribute part on attributes.Depute
Using WriteProcessMemory(GetCurrentProcess, ...) is typically unnecessary, you can use Move() or equivalent instead, eg: Move(FixTypeInfoAttribute(a).FTypeInfo, PFieldExEntry(f.Handle).TypeRef, SizeOf(Pointer)); Or, just a plain assignment will suffice, eg: PFieldExEntry(f.Handle).TypeRef := FixTypeInfoAttribute(a).FTypeInfo;Bonina
E
9

This is a known issue that was fixed in XE3. Unfortunately, upgrading is the only way to get a fix for it; bug fixes don't usually get ported back.

EDIT: Or not. Apparently this is not actually fixed, as it still occurs in XE3. Reporting it as a new case and mentioning 103729 would probably be the best course of action.

Echevarria answered 1/10, 2012 at 19:7 Comment(10)
There was a previuos post here, someone told to add {$RTTI FIELDS([])}Legist
and it actually works... the problem is that I use {$RTTI EXPLICIT METHODS([]) PROPERTIES([]) FIELDS([vcPublic])} in order to not generate RTTI for private class fields... and by using vcPublic it does not generate RTTI for public TBYtes fields...Legist
@Legist - After Mason warned me that the answer was wrong I tested and it indeed suppressed generation of RTTI for fields.Causeuse
@Eric: That "solution" works by not generating any RTTI for fields at all. Not generating a type for TBytes is a different issue; that's a compiler-level problem. If it's not fixed in XE3, try posting this as a new issue, with your code as an example, and mention the other QC case.Echevarria
@SertacAkyuz Yep, I tested and it really suppressed the generation of RTTI What I need is RTTI for public fields on classes Tested in XE3 and it is not workingLegist
@MasonWheeler Thanks Mason, I will open a QC report bug.. but unfortunally I will have to change my code because now I can´t go back to D2010 and I will NOT upgrade to any other delphi until this stupid error is fixedLegist
@Legist - Does substituting TArray<Byte> for TBytes as in the linked report work?Causeuse
by substituting TBytes by TArray<Byte> it will generate the RTTILegist
TBytes should be expunged. TArray<Byte> is the canonical way to declare this type.Illuse
I believe that TBytes maps to TArray<Byte>, I´m not sure... I changed my fields that required RTTI from TBytes to TArray<Byte> and it worked, but the odd thing is that if I create a new type like TMyBytes = TArray<Byte> and use TMyBytes in the class definition I also don´t get the RTTI information! Something is really messed up in the compiler...Legist

© 2022 - 2024 — McMap. All rights reserved.