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

Related posts:

  1. How rdp passwords are encrypted
  2. Executing a Fast User Switch programmatically – part 2
  3. Converting a volume name to a device name
  4. PowerShell 2.0: Changing password through ADSI problem
  5. Using Windows Dialogs from Delphi