This is what I love about Delphi.  After almost 15 years of Delphi’ing there’s still new things to learn, and I don’t just mean new features in the latest releases.  I mean, basic, fundamental things that have been there for years, just undiscovered (by me).  In this case the discovery was a little annoying, but never-the-less educational.

In the case in question I was creating some library code for myself to help with some resource manipulation code that I have in mind.  As part of this I wanted an enum type to represent the normal resource types found in Windows code:

  type
    TResourceType = (
                      rtCURSOR = 1,
                      rtBITMAP,
                      rtICON,
                      rtMENU,
                      rtDIALOG,
                      rtSTRING,
                      rtFONTDIR,
                      rtFONT,
                      rtACCELERATOR,
                      rtRCDATA,
                      rtMESSAGETABLE,
                      rtGROUP_CURSOR,
                      rtGROUP_ICON = 14,
                      rtVERSION = 16,
                      rtDLGINCLUDE,
                      rtPLUGPLAY = 19,
                      rtVXD,
                      rtANICURSOR,
                      rtANIICON,
                      rtHTML,
                      rtMANIFEST
                    );
    TResourceTypes = set of TResourceType;

I used specific ordinal assignments to some of the enum members to create the necessary “gaps” for the resource type values that aren’t (currently) meaningful in Windows and ensure that the ordinal values of the enum members corresponded exactly to Windows resource type values.

The enum member names are also carefully chosen so that they may be quickly converted to the symbolic names of the corresponding Windows resource type identifiers, e.g.:

rtCURSOR >> RT_CURSOR
rtGROUP_ICON >> RT_GROUP_ICON
etc

The plan was to have a function to take a TResourceType value, convert to the enum name and change the “rt” prefix to “RT_”. This is not exactly rocket science and I did not anticipate any problems.

So I was somewhat surprised to find myself thwarted by a compilation error in the code:

      sName := GetEnumName(TypeInfo(TResourceType), Ord(aValue));

ERROR: E2134 – Type ‘TResourceType’ has no typeinfo’

It took me a while to chase this down – my initial thought was to suspect that there was some other definition of a TResourceType type that was hiding my type. That was soon ruled out and a big of further digging eventually revealed the answer.

It turns out that assigning specific ordinal values to enum members (if they are different to the ordinals that the compiler would normally assign automatically) defeats the RTTI system and prevents the creation of type info for the type.  Sadly this doesn’t seem to be mentioned in the documentation, not even for the E2134 error itself which would seem to be an ideal help topic to contain this useful piece of information.

The solution in my case was quite simple.  The number of “unused” members was small so I could change my enum type to:

  type
    TResourceType = (
                      rtNotUsed1,    // = 0
                      rtCURSOR,
                      rtBITMAP,
                      rtICON,
                      rtMENU,
                      rtDIALOG,
                      rtSTRING,
                      rtFONTDIR,
                      rtFONT,
                      rtACCELERATOR,
                      rtRCDATA,
                      rtMESSAGETABLE,
                      rtGROUP_CURSOR,
                      rtNotUsed2,    // = 13
                      rtGROUP_ICON,
                      rtNotUsed3,    // = 15
                      rtVERSION,
                      rtDLGINCLUDE,
                      rtNotUsed4,    // = 18
                      rtPLUGPLAY,
                      rtVXD,
                      rtANICURSOR,
                      rtANIICON,
                      rtHTML,
                      rtMANIFEST
                    );
    TResourceTypes = set of TResourceType;

Notice the additional rtNotUsedN members which fill in the gaps.

This created a slight problem for me in that now I had some undesirable names “polluting” my enum space. In this case I was not too concerned about that – they are self documenting after all (“NotUsed” is pretty unambiguous on that score).

But there were occasions in my code where I was going to wish to test some TResourceType value to ensure that it was valid.

A simple pair of sets helped out with this:

  const
    rtInvalid = [rtNotUsed1, rtNotUsed2, rtNotUsed3, rtNotUsed4];
    rtValidResourceTypes  = [Low(TResourceType)..High(TResourceType)] - rtInvalid;

Which allowed me to perform runtime checking of some arbitrary TResourceType value:

    if NOT (aValue in rtValidResourceTypes) then
      // Invalid resource type ... !

Or of course alternatively:

    if (aValue in rtInvalid) then
      // Invalid resource type ... !

According to taste (you can probably tell which I prefer as the choice of names for the sets yields one which reads quite fluently and naturally, whilst the other is awkward and stilted in comparison, albeit more brief). 🙂

We Don’t Know What We Don’t Know

It’s really quite astonishing to me that in almost 15 years of working with Delphi I’ve not previously encountered this limitation of the type information system (as it relates to enum types). So astonishing that I thought it worth not just blogging about but also posting to stackoverflow as a question with a ready made answer.

I’ve only recently started using stackoverflow seriously – I hope this sort of self-answering isn’t an abuse of the system. I thought it a useful way of adding to the “knowledgebase”.

In any event, Barry Kelly, a CodeGear compiler engineer who works on the Delphi compiler itself no less (some guys get all the luck!) responded to my stackoverflow question+answer by explaining some of the internal details of the RTTI system that makes typeinfo difficult/impossible for such non-contiguous enum types, and offered the opinion that it probably wasn’t worth going to too much bother for since the times when it would be useful were so rare.

I’d have to agree with him.

It’s taken me 15 years to stumble across it – I think I can manage without a “fix” in this case.

Footnote

If this had been a somewhat simpler case where I simply wanted the initial member of the enum to have a value such as 1 (one) rather than the usual 0 (zero), I could have achieved type safety with a simple subrange type (thanks to Ulrich for pointing this out on stackoverflow) renaming the underlying type and using the subrange type as the “concrete” representation of my enum throughout my code (having established the principle, in this final example I have artifically reduced the number of members in the enum type, simply to keep it short and concise):

  type
    TResourceTypeValue = (
                            rtUnused,
                            rtCURSOR,
                            rtBITMAP,
                            rtICON,
                            rtMENU
                 );
    TResourceType = rtCURSOR..High(TResourceTypeValue);
    TResourceTypes = set of TResourceType;

6 thoughts on “Mind the Gap(s)

  1. There’s no reason to be surprised at all. This feature (explicit specification of enumeration members’ values) was firstly introduced in Delphi 7. I’m not sure about the later editions of Delphi’s help, but back then in Delphi 7 days the documentation clearly stated that RTTI is not being generated for such sort of enumerations.

    You are right, though. The one who once said “Live and learn” probably had a clue 🙂

  2. I have been using delphi since 1.0 (actually I started with Turbo Pascal and then Turbo Pascal for Windows) and I still learn something new like that every project. Because of that I asked my team to spend an hour or 2 every week learning about a function, feature, etc… that they either do not know or want to know more about. This has yielded great returns for our team because that information ends up being shared with all of us because they are so excited about what they just learned!

  3. Hi Jolyon,
    Funny that you still answer my questions while working for somebody else. Keep it up Maestro.
    (Some app needed enumvalue range checking)

  4. @Skywalker: So 7 years to discover a limitation rather than 15, but the fact that I hadn’t even noticed that I couldn’t assign specific ordinals prior to Delphi 7 also says something about how common it is (for me) to find myself assigning specific ordinals. 🙂

    @Arjan: No worries. How’s the house coming along? 🙂

    @Gary: A life of learning is a life well spent indeed.

    @Craig: Thanks for that. I can sleep a little easier now. 🙂

  5. Another case of BorCodeDero “non finito”. Never implement a fuctionality fully, especially in seldom used features. That’s been a BorCodeDero trademark since a long time.
    Guess the compiler could generate some sort of identifiers itself for the unnamed values (IIRC they exists anyway) if that’s what stops it generating RTTI.

Comments are closed.