Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

List Resources with name and type in Delphi

I'm trying to list all the resources of my program with the name of the resource and resource type as "RT_BITMAP" or any other.

The code :

var
  Form1: TForm1;
  list_resources: string;

function EnumResNameProc(lpszName: PChar; lParam: integer; lpszType: PChar;
  hModule: Cardinal): BOOL;
begin
  list_resources := list_resources + sLineBreak + lpszName + ' - ' + lpszType;
  Result := True;
end;

procedure TForm1.btnListResourcesClick(Sender: TObject);
begin
  EnumResourceNames(0, RT_RCDATA, @EnumResNameProc, 0);
  Memo1.Lines.Add(list_resources);
end;

The code work good but never show the type of resource , what is the problem ?

like image 496
FF2 Avatar asked Oct 31 '25 18:10

FF2


1 Answers

The first problem with your code is that the callback function has the wrong calling convention and indeed the wrong signature. It should be declared like this:

function EnumResNameProc(hModule: HMODULE; lpszType, lpszName: PChar; 
  lParam: LONG_PTR): BOOL; stdcall;

The output that your code was producing was completely accidental. It's really important that you get the signatures of these function correct. I don't know where your signature came from. It looks like you just made it up! The documentation on MSDN has the correct signature. Embarcadero make things hard by declaring the API functions that accept callbacks in a way that means type checking for the signatures is neglected. So the onus falls on your. Read the documentation very carefully.

Once you have fixed that problem, there is still more to be done. Resource types, and indeed resource names, can either be integers or strings. The convention is that values < 65536 are interpreted as integers, otherwise the value is a pointer to a null-terminated character array. Rather than hard-code that magic constant, you can instead call Is_IntResource, the Delphi translation of the Windows macro IS_INTRESOURCE.

In your case you will be receiving named resources, but resource types that are actually integer values. From the Windows unit:

const
  RT_CURSOR       = MakeIntResource(1);
  RT_BITMAP       = MakeIntResource(2);
  RT_ICON         = MakeIntResource(3);
  RT_MENU         = MakeIntResource(4);
  RT_DIALOG       = MakeIntResource(5);
  RT_STRING       = MakeIntResource(6);
  RT_FONTDIR      = MakeIntResource(7);
  RT_FONT         = MakeIntResource(8);
  RT_ACCELERATOR  = MakeIntResource(9);
  RT_RCDATA       = System.Types.RT_RCDATA; //MakeIntResource(10);
  DIFFERENCE = 11;
  RT_GROUP_CURSOR = MakeIntResource(DWORD(RT_CURSOR) + DIFFERENCE);
  RT_GROUP_ICON   = MakeIntResource(DWORD(RT_ICON) + DIFFERENCE);
  RT_VERSION      = MakeIntResource(16);
  RT_DLGINCLUDE   = MakeIntResource(17);
  RT_PLUGPLAY     = MakeIntResource(19);
  RT_VXD          = MakeIntResource(20);
  RT_ANICURSOR    = MakeIntResource(21);
  RT_ANIICON      = MakeIntResource(22);
  RT_HTML         = MakeIntResource(23);
  RT_MANIFEST     = MakeIntResource(24);

The other convention at play is that you use a # symbol to indicate a numeric identifier. So you could adopt the following policy:

  • If Is_IntResource returns True, then convert the numeric value to string and prefix with #.
  • Otherwise treat as a pointer to null-terminated character array.

The code is quite simple:

function ResourceNameToString(lpszName: PChar): string;
begin
  if Is_IntResource(lpszName) then
    Result := '#' + IntToStr(NativeUInt(lpszName))
  else
    Result := lpszName;
end;

It's imperative that you do this, for names as well as types. Otherwise your code will fail with runtime access violation errors when it tries to de-reference a pointer that is actually representing an integer. The code in the question will fail that way if you fixed the callback signature but make no further changes.

If you want your code to be more helpful you will detect the pre-defined resource types and give them special treatment.

function ResourceTypeToString(lpszType: PChar): string;
begin
  case NativeUInt(lpszType) of
  NativeUInt(RT_CURSOR):
    Result := 'RT_CURSOR';
  NativeUInt(RT_BITMAP):
    Result := 'RT_BITMAP';
  NativeUInt(RT_RCDATA):
    Result := 'RT_RCDATA';
  // etc.
  else
    Result := ResourceNameToString(lpszType);
  end;
end;

I'll let you fill in the missing values.

Put it all together like so:

{$APPTYPE CONSOLE}

uses
  SysUtils, Windows;

function ResourceNameToString(lpszName: PChar): string;
begin
  if Is_IntResource(lpszName) then
    Result := '#' + IntToStr(NativeUInt(lpszName))
  else
    Result := lpszName;
end;

function ResourceTypeToString(lpszType: PChar): string;
begin
  case NativeUInt(lpszType) of
  NativeUInt(RT_CURSOR):
    Result := 'RT_CURSOR';
  NativeUInt(RT_BITMAP):
    Result := 'RT_BITMAP';
  NativeUInt(RT_RCDATA):
    Result := 'RT_RCDATA';
  // etc.
  else
    Result := ResourceNameToString(lpszType);
  end;
end;

function EnumResNameProc(hModule: HMODULE; lpszType, lpszName: PChar; 
  lParam: LONG_PTR): BOOL; stdcall;
begin
  Writeln(ResourceTypeToString(lpszType) + ' - ' + ResourceNameToString(lpszName));
  Result := True;
end;

begin
  EnumResourceNames(0, RT_RCDATA, @EnumResNameProc, 0);
  Readln;
end.
like image 86
David Heffernan Avatar answered Nov 03 '25 02:11

David Heffernan