delphiwinapilocalizationdelphi-run-time-library

Is a resourcestring a string or a record, and do I have to use LoadResString to load it?


In Delphi's Vcl.Buttons unit they call:

Caption := LoadResString(BitBtnCaptions[Value]);

Where BitnBtnCaptions is an array like:

BitnBtnCaptions: array[TBitBtnKind] of Pointer = (
        nil, @SOKButton, @SCancelButton, @SHelpButton, @SYesButton, @SNoButton,
        @SCloseButton, @SAbortButton, @SRetryButton, @SIgnoreButton,
        @SAllButton);

And the BitnBtnCaptions constants are:

resourcestring
  SOKButton = 'OK';
  SCancelButton = 'Cancel';
  SYesButton = '&Yes';
  SNoButton = '&No';
  SHelpButton = '&Help';
  SCloseButton = '&Close';
  SIgnoreButton = '&Ignore';
  SRetryButton = '&Retry';
  SAbortButton = 'Abort';
  SAllButton = '&All';

So it is essentially calling:

resourcestring
  SOKButton = 'OK';

s := LoadResString(@SOKButton);

The declaration of LoadResString is:

function LoadResString(ResStringRec: PResStringRec): string;

This function requires a pointer to a TResStringRec:

PResStringRec = ^TResStringRec;
TResStringRec = packed record
   // 32bit = 8 bytes
   // 64bit = 16 bytes
   Module: ^HMODULE;
   Identifier: NativeUint;
end;

But we are passing a resourcestring.

Does that mean we are passing a string to a function that only accepts a PResStringRec in error?

Why do you ask?

I ask because I am calling:

Result := LoadResString(@SMsgDlgOK);

and with typed pointer evaluation (i.e. {$T+} or {$TYPEDADDRESS ON}) it gives an incompatible type warning:

E2010 Incompatible types: 'PResStringRec' and 'Pointer'

And of course i can just force it with a hard cast:

Result := LoadResString(PResStringRec(@SMsgDlgOK));

Hard Cast?

But that seems a little harsh for something that is supposed to be the correct way to do something. Smells a little funny.

And what is worse is that if i am doing a hard cast, i better know what i'm doing.

And the way i see it, the only way this works is if:

We don't want to force a triangular piece into the square hole, by blindly using a hard cast.

Which brings me to my question: Is a resourcestring

resourcestring
  SMsgDlgOK = 'OK';

a string? Or is it a record?

But do i have to do this at all?

Do i need to actually do what Vcl.Buttons does? Does Vcl.Buttons need to do what it's doing?

Can't we simply replace:

s := LoadResString(@SOKButton);

with

s := SOKButton

Wasn't that the entire point of the resourcestring keyword? It puts the strings into the strings table (where localiziers can localize them), and it does all the magic at runtime (i.e. LoadResString) to expose the resourcestrings as strings?

And if that's not true: why not?

What do i gain by calling:

LoadResString(@SOKButton)

over just using SOKButton?

And if i have to use LoadResString, is it actually, really, truly, pinky-swear, 100% safe to force the typecast?

Bonus Chatter - resourcestrings better not be records

If a resourcestring actually is a TResStringRect, then i should be able to see it. So i inspect what they are:

╔════════════════════════════╤══════════╗
║ Watch Name                 │ Value    ║
╠════════════════════════════╪══════════╣
║ PResStringRec(@SMsgDlgOK)  │ $AD3C54  ║
║ ├──Module                  │ $400000  ║
║ ╰──Identifier              │ 0        ║
╟┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┼┈┈┈┈┈┈┈┈┈┈╢
║ PResStringRec(@SMsgDlgYes) │ $AD3C54  ║
║ ├──Module                  │ $400000  ║
║ ╰──Identifier              │ 0        ║
╟┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┼┈┈┈┈┈┈┈┈┈┈╢
║ PResStringRec(@SMsgDlgNo)  │ $AD3C54  ║
║ ├──Module                  │ $400000  ║
║ ╰──Identifier              │ 0        ║
╚════════════════════════════╧══════════╝

Every resource string:

So something's not right; it doesn't look like a record to me.

So why does the code in Vcl.Buttons work?

Bonus Chatter - Exception.CreateResFmt has the same issue

Anyone calling Exception.CreateResFmt is treated to the same bug/error:

raise EConvertError.CreateResFmt(@SAssignError, [SourceName, ClassName]);

and the declaration of CreateResFmt is:

constructor CreateResFmt(ResStringRec: PResStringRec; const Args: array of const);

Again with the:

Although this time the documentation gives the code as an example:

ResStringRec is a pointer to a resource string. This syntax appears as follows:

resourcestring sMyNewErrorMessage = 'Illegal value: %s';
...
Exception.CreateResFmt(@sMyNewErrorMessage, [-1]);

Again, passing a resourcestring to a function that accepts a PResStringRec.

Delphi 5 documentation

For historical context:


Creates an instance of an exception with a message string that is loaded from the application’s resources and then formatted.

constructor CreateResFmt(Ident: Integer; const Args: array of const); overload;
constructor CreateResFmt(ResStringRec: PResStringRec; const Args: array of const); overload;

Description

Call CreateResFmt to construct an exception with a message string loaded from an application’s resources and then formatted with additional information. Resources are bound into the application executable at compile time, but at design time they exist in a separate RES file.

Ident is the unique ID of the resource as specified in the RES file. If Ident is not a valid resource ID, CreateResFmt creates an empty message string.

ResStringRec is a pointer to a resource string. This syntax appears as follows:

resourcestring sMyNewErrorMessage = 'Illegal value: %s';  
...  
Exception.CreateResFmt(@sMyNewErrorMessage, [-1]);

Args is an array of constants containing values to:

  1. Format according to format specifiers embedded in the string, and
  2. Insert into the message string.

CreateResFmt calls the Format function to transform the message string with the values in Arg.

enter image description here


Solution

  • I don't use ResourceStrings very much. But when I do, I just define them at the top of a unit and treat them as if they're just global string constants and are intended to be referred to directly. They get put into global mamory as singletons, not inside of specific units the way const string declarations would end up as duplicates.

    I think part of what you're pointing at is related to their design to be "overlaid" by different units to handle switching between languages. So if you load a unit with different strings assigned, they get put into that global memory area and every reference in your app that refers to them is affected, like menu captions, button captions, etc.

    The code you found in the VCL is adding another level of indirection (an array of them with an index selector) that your app probably doesn't need.

    You'd say something like:

    resourcestring
       OK_caption = 'OK';
    ...
    myButton.Caption := OK_caption;
    ...
    

    and if you loaded another unit at runtime that defined OK_caption = 'GO!' then every reference to this string would be changed from that point on.

    Again, they're designed to be used as global string constants. They're typically used to list out menu and button captions and hints, warning and error code explanations, stuff like that. You'd use them in place of the same string literal everywhere that makes it hard to change if there are dozens or hundreds of them scattered throughout your code: myButton.Caption := 'OK' and now someone wants to change it to 'Go!' everywhere. Do you want to change it in just ONE PLACE, or hundreds?

    I've never considered making an array of such string constants. The VCL has a specific need that simplifies some other things, but that has nothing to do with normal use.