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?

Johan
  • 74,508
  • 24
  • 191
  • 319
Eric
  • 552
  • 3
  • 14

2 Answers2

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

Stefan Glienke
  • 20,860
  • 2
  • 48
  • 102
  • You omitted FixTypeInfoAttribute.Create. And there's a name mismatch between `FixTypeInfoAttribute` and `FixTypeInfo`. But +1 for a nice workaround. – David Heffernan Oct 02 '12 at 11:00
  • 2
    @David: Added the constructor implementation. And there is no name mismatch as you can omit the `Attribute` part on attributes. – Stefan Glienke Oct 02 '12 at 11:07
  • 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;` – Remy Lebeau Jan 19 '23 at 17:47
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.

Mason Wheeler
  • 82,511
  • 50
  • 270
  • 477
  • 1
    There was a previuos post here, someone told to add {$RTTI FIELDS([])} – Eric Oct 01 '12 at 19:09
  • 2
    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... – Eric Oct 01 '12 at 19:10
  • 1
    @Eric - After Mason warned me that the answer was wrong I tested and it indeed suppressed generation of RTTI for fields. – Sertac Akyuz Oct 01 '12 at 19:13
  • 2
    @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. – Mason Wheeler Oct 01 '12 at 19:16
  • 1
    @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 working – Eric Oct 01 '12 at 19:16
  • 1
    @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 fixed – Eric Oct 01 '12 at 19:17
  • 1
    @Eric - Does substituting TArray for TBytes as in the linked report work? – Sertac Akyuz Oct 01 '12 at 19:17
  • 2
    by substituting TBytes by TArray it will generate the RTTI – Eric Oct 01 '12 at 19:25
  • 2
    TBytes should be expunged. `TArray` is the canonical way to declare this type. – David Heffernan Oct 01 '12 at 19:45
  • I believe that TBytes maps to TArray, I´m not sure... I changed my fields that required RTTI from TBytes to TArray and it worked, but the odd thing is that if I create a new type like TMyBytes = TArray and use TMyBytes in the class definition I also don´t get the RTTI information! Something is really messed up in the compiler... – Eric Oct 02 '12 at 17:31