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?
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));
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:
SMsgDlgOk
TResStringRec
.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?
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?
s := SOKButton
s := LoadResString(@SOKButton)
// fails typed pointer checkings := LoadResString(PResStringRec(@SOKButton))
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?
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:
PResStringRec
PResStringRec
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
.
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;
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:
CreateResFmt calls the Format function to transform the message string with the values in Arg.
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.