A few days ago I wrote about Using Windows Dialogs in your own programs, wouldn’t it be nice to be able to use Windows Resource Strings for the same reasons?
Loading a resource string is not difficult, let’s look at some examples:
function LoadResourceString(const DllName: String; ResourceId: Integer): String;
var
hDLL: THandle;
Buffer: array[0..MAX_PATH] of Char;
begin
hDLL := LoadLibrary(PChar(DllName));
if hDLL = 0 then
Exit;
if LoadString(hDll, ResourceId, Buffer, Length(Buffer)) > 0 then
Result := Buffer;
end;This uses the LoadString api to load a Resource String from an Executable or Dll by it’s resource Id. An Example might call might be:
LoadResourceString('dsadmin.dll', 226)This loads the string with ResourceId 226 from dsadmin.dll(.mui):
STRINGTABLE
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
{
224, "Windows cannot complete the password change for %2 because:\n%1"
225, "Windows cannot access object %2 because:\n%1The object may have been deleted by another administrator in this enterprise."
226, "The New and Confirm passwords must match. Please re-type them."
228, "Object %2 has been disabled."
229, "Windows cannot disable object %2 because:\n%1"
231, "Object %2 has been enabled."
232, "Windows cannot enable object %2 because:\n%1"
233, "Create a new object..."
237, "Mo&ve...\nMoves the selected object"
238, "The password for %2 cannot be set due to insufficient privileges. Windows will attempt to disable this account. If this attempt fails, the account will become a security risk. Contact an administrator as soon as possible to repair this. Before this user can log on, the password should be set, and the account must be enabled."
}As you can see in this example, some resource strings have identifiers such as %1 and %2 which are used in the FormatMessage Api. How can we use that from Delphi?
I wrote a very simple wrapper for it:
function FormatMsg(const Source: String; const Args: array of const): String;
var
i: Integer;
ArgArray: array of Pointer;
Buffer: PChar;
begin
for i := Low(Args) to High(Args) do
begin
SetLength(ArgArray, i+1);
case Args[i].VType of
vtExtended:; // not supported, should raise Exception
else
ArgArray[i] := Args[i].VPointer;
end;
end;
if FormatMessage(FORMAT_MESSAGE_FROM_STRING or
FORMAT_MESSAGE_ALLOCATE_BUFFER or FORMAT_MESSAGE_ARGUMENT_ARRAY,
PChar(Source), 0, 0, @Buffer, 0, @ArgArray[0]) > 0 then
begin
Result := Buffer;
// replace \n (linefeed) with #13#10
Result := StringReplace(Result, '\n', #13#10, [rfReplaceAll]);
LocalFree(DWORD(Buffer));
end
else begin
SetLength(Result, 0);
end;
end;And here is a usage example:
Memo1.Lines.Add (FormatMsg(
'Windows cannot complete the password change for %2 because:\n%1',
['the password doesn''t meet complexity requirements', 'John Doe']));The Result of this is:
Windows cannot complete the password change for John Doe because:
the password doesn’t meet complexity requirements