Delphi Mobile (NEXTGEN) compiler: the risk of a changed TSymbolName; unsupported data types means unsupported RTTI as well
Posted by jpluimers on 2013/09/05
The NEXTGEN family of Delphi compilers for the Mobile platforms changed quite a bit of things.
Most of it has been covered by various blogs posts. A non exhaustive list of ones I liked:
- Delphi Language for Mobile Development Paper by Marco Cantu with this 40 page PDF white paper.
- Lars Fosdal – Google+ – Differences between Delphi Desktop Compilers and Delphi Mobile Compilers….
- It’s a blong, blong, blong road…: New compiler directives in Delphi XE3 (he doesn’t mention NEXTGEN, but covers zero based strings in addition to compiler directives).
- Zero-based strings (Delphi) – RAD Studio explaining the ZEROBASEDSTRINGS directive.
- Everything TStringHelper is zero-based:
Exploring Delphi XE3 – Record Helpers for simple types – System.SysUtils.TStringHelper | The Road to Delphi – a Blog about programming.
Mark Edington’s Delphi Blog : XE3 RTL Changes: A closer look at TStringHelper. - DWS and zero baded strings: Zero-based Strings indexes? – DelphiTools.info.
- TMS Software | Blog | TMS Aurelius in your iPhone.
- Delphi XE 4 – Is ARC for Everyone? | Olaf’s Thoughts About Development.
- and of course: Migrating Delphi Code to iOS from Desktop – RAD Studio.
Those articles do not contain two things I had’t found about yet though that are important when you do RTTI using NEXTGEN in Delphi XE4:
1. The TSymbolName changed from a types string[255] to Byte
Though TSymbolName is still documented as being a ShortString (classic Turto Pascal style non reference counted single byte string of maximum 255 characters with a length byte as very first (zeroth?) byte), it is not:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{$IFDEF NEXTGEN} | |
TSymbolName = Byte; | |
{$ELSE NEXTGEN} | |
TSymbolNameBase = string[255]; | |
TSymbolName = type TSymbolNameBase; | |
{$ENDIF NEXTGEN} |
This creates all kinds of havoc, especially when you use System.SysUtils.Format or Exceptions to format readable RTTI output like this: as the %s will break when a Byte is passed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function EnumerationName(const Value: Integer; const Info: PTypeInfo): string; | |
//… | |
begin | |
if Info^.Kind <> tkEnumeration then | |
raise Exception.CreateFmt('Info %s is not an enumerated type', [Info^.Name]); | |
//… | |
end; |
So I worked around it using the SetString and Utf8ToUnicode methods: a NEXTGEN compatible GetShortStringString method.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function GetShortStringString(const ShortStringPointer: PByte): string; | |
var | |
ShortStringLength: Byte; | |
FirstShortStringCharacter: MarshaledAString; | |
ConvertedLength: Cardinal; | |
UnicodeCharacters: array[Byte] of Char; // cannot be more than 255 characters, reserve 1 character for terminating null | |
begin | |
if not Assigned(ShortStringPointer) then | |
Result := '' | |
else | |
begin | |
ShortStringLength := ShortStringPointer^; | |
if ShortStringLength = 0 then | |
Result := '' | |
else | |
begin | |
FirstShortStringCharacter := MarshaledAString(ShortStringPointer+1); | |
ConvertedLength := UTF8ToUnicode( | |
UnicodeCharacters, | |
Length(UnicodeCharacters), | |
FirstShortStringCharacter, | |
ShortStringLength | |
); | |
// UTF8ToUnicode will always include the null terminator character in the Result: | |
ConvertedLength := ConvertedLength-1; | |
SetString(Result, UnicodeCharacters, ConvertedLength); | |
end; | |
end; | |
end; |
Now the exception is raised like this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function EnumerationName(const Value: Integer; const Info: PTypeInfo): string; | |
//… | |
begin | |
if Info^.Kind <> tkEnumeration then | |
raise Exception.CreateFmt('Info %s is not an enumerated type', [GetTypeInfoName(Info)]); | |
//… | |
end; |
2. Despite the absence of some types in the NEXTGEN compiler, the TTypeKind declaration in the TypInfo unit has not changed:
The TTypeKind is still declared to be this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//NEXTGEN does declare this in the TypInfo unit: | |
TTypeKind = (tkUnknown, tkInteger, tkChar, tkEnumeration, tkFloat, | |
tkString, tkSet, tkClass, tkMethod, tkWChar, tkLString, tkWString, | |
tkVariant, tkArray, tkRecord, tkInterface, tkInt64, tkDynArray, tkUString, | |
tkClassRef, tkPointer, tkProcedure); |
But in fact these TTypeKind values are not supported in NEXTGEN:
- tkWString: Identifies a WideString (wide string without reference counting) type.
- tkLString: Identifies an AnsiString (single byte string with reference counting) type.
- tkChar: Identifies a AnsiChar single-byte character type.
- tkString: Identifies a ShortString short string type or subtype.
That lead me to this code snippet:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const | |
UnsupportedTypeKinds: TTypeKinds = [ | |
{$IFDEF NEXTGEN} | |
tkWString, | |
tkLString, | |
tkChar, | |
tkString | |
{$ENDIF NEXTGEN} | |
]; |
–jeroen
Sebastian Jänicke said
Regarding the symbol name…
Have a look at TTypeInfoFieldAccessor in unit TypeInfo, it does exactly what you do with your code. And you have already GetTypeName declared as function there.
jpluimers said
Thanks. Didn’t notice those yet. I will try and see which solution is the most ‘portable’ over various versions.