Probleme mit "FindResource"

Für allgemeine Fragen zur Programmierung, welche nicht! direkt mit Lazarus zu tun haben.
Antworten
Lorca
Beiträge: 196
Registriert: Di 3. Nov 2020, 12:25

Probleme mit "FindResource"

Beitrag von Lorca »

Hallo zusammen,

nachdem nun die CALL BACK Funktionen gehen, tun sich bei mir neue Probleme auf.
Ich such seit nun fast 24h im Internet nach Lösungen, werde aber nicht fündig. Und das was ich finde führt zu nichts.
Ich denke, ich habe da ein Verständnis Problem.

Es geht darum eine externe DLL (mit einer STRINGTABLE), welche nicht im Programm eingebunden ist einzulesen.
Ein einbinden ins Programm ist keine Option, da es mir um das Grundsätzliche geht.

Da diese DLL nicht im Programm eingebunden ist, klappt es mit der Funktion: GetModuleHandle nicht.
Deshalb verwende ich LoadLibrary. Dies Funktioniert auch und liefert ein gültiges Handle zurück.
Ich kann die Strings aus dieser Library auch auslesen. In einer DO WHILE Schleife laufe ich 5.000 mal durch und lade die Strings.
Deshalb benötige ich die SizeofResource da eine willkürliche Begrenzung weder performant noch zielführend ist.

Um nun die Göße der Resource zu ermittlen (SizeofResource) wird die Funktion: FindResource benötigt, da diese das infoHandle liefert, welches von SizeofResource benötigt wird.

Nun liefert mir jedoch die Funktion FindResource nie ein gültiges Handle zurück. Der gemeldete Fehler ist immer 1813.

Kann jemand erkennen wo ich den Fehler habe, bzw. was für ein Fehler ich mache?
Muss ich vllt. weitere Funktionen aufrufen ?

Gruß
Lorca

PROCEDURE Laden;
VAR mv_LibHdl : TLibHandle; lv_Name : PChar; lv_Error : DWord; lv_ResInfo : HRSRC;
BEGIN
lv_Name := 'E:\Test.DLL';
mv_LibHdl := LoadLibraryA( lv_Name );
lv_Error := GetLastError; // REsultat = OK Error ist 0

lv_ResInfo := FindResourceA ( mv_LibHdl, lv_Name, 'RT_STRING' );
lv_Error := GetLastError; // Fehler Wert = 1813 das Handle ist 0
END;

chmod222
Beiträge: 14
Registriert: Do 19. Aug 2021, 17:29

Re: Probleme mit "FindResource"

Beitrag von chmod222 »

Wenn ich FindResource richtig verstehe erwartet er da an zweiter Stelle den Namen bzw den Index als String mit '#' prefixed ('#123') der Resource die du finden willst, du übergibst aber den Namen der DLL selbst, deshalb wird nichts gefunden.

Das deckt sich auch mit der Erklärung von Fehler 1831: "The specified resource type cannot be found in the image file"

Lorca
Beiträge: 196
Registriert: Di 3. Nov 2020, 12:25

Re: Probleme mit "FindResource"

Beitrag von Lorca »

Hallo chmod222

Danke für Deine nette Hilfe. :)

Aber wie komme ich an den Index dieser DLL?
Ich habe nur den Pfad und den Namen.
Ich kann vor beiden zwar ein HashTag setzen, aber dies ist dann kein Index.
Gibt es da eine Funktion, die aus einem Dateinamen inkl. Pfad einen Index erstellt?

Gerade eben ausprobiert. Name := '#' + Dateiname (als PChar)
Immer noch der gleiche Fehler :(

Also woher bekomme ich diesen Index?

Gruß
Lorca

chmod222
Beiträge: 14
Registriert: Do 19. Aug 2021, 17:29

Re: Probleme mit "FindResource"

Beitrag von chmod222 »

Das hängt davon ab wie genau du an die Resource drankommen möchtest.

Da sich deine vorherige Frage um EnumResourceTypesW handelte, dachte ich mir dass du alle Resourcen in deiner DLL auflistest und dynamisch rausliest. Nochmal als Disclaimer, ich bin mit der WinAPI nicht furchtbar vertraut weil ich mich eher auf der Linux-Seite des Spektrums befinde, aber das ist so das was ich mir aus der API Beschreibung herauslese:

1. Man enumeriert die Resourcen die sich in der Datei befinden via EnumResourceTypes / EnumResourceNames
2. Man kriegt in seinem Callback den Namen der Resource (Bei ENUMRESNAMEPROCA für EnumResourceNames ist der dritte Parameter den man kriegt der Name jeder gefunden Resource)
3. Diesen Namen kann man sich dann merken oder direkt in FindResource werfen um an die eigentliche Resource zu kommen

Dann gibt es noch den anderen Use-Case in dem du deine Resourcen von Anfang an kennst, wenn du genau weißt dass du einen String willst und seine ID ist 42, dann kannst du ihn ohne Enumeration direkt holen via FindResourceA(mv_LibHdl, '#42', 'RT_STRING' ); oder du weißt, dass sein Name 'NETTER_STRING' ist via FindResourceA(mv_LibHdl, 'NETTER_STRING', 'RT_STRING' );

Angaben wieder ohne Gewähr, das ist nur wie ich als Aussenstehender die WinAPI lese.

Edit:
Ich habe das jetzt mal ausprobiert weil ich ungern Halbwissen verbreiten möchte, was ich scheinbar durchaus getan habe. Folgenden Testaufbau habe ich jetzt gebastelt:

Code: Alles auswählen

{$MODE objfpc}{$H+}

program test;

uses
  Classes, Windows, SysUtils;

var
  DllHandle: HMODULE;

function EnumNameCallback(hModule: HMODULE; TypeStr: LPWSTR; Name: LPWSTR; Param: IntPtr): WinBool; StdCall;
var
  FindRes: HRSRC;
  LoadRes: HGLOBAL;
  Size: DWord;
  StrLen: Word;
  StringTableStart: PWideChar;
  Entry: WideString;

  I: Integer;
begin
  { Aussteigen wenn Resourcetyp nicht RT_STRING (6) ist }
  if TypeStr <> MAKEINTRESOURCEW(6) then
    exit(True);

  Writeln('Found STRINGTABLE at Name = ', IntPtr(Name));

  { Größe und Speicherlocation raussuchen }
  FindRes := FindResourceW(hModule, Name, TypeStr);
  Size := SizeofResource(hModule, FindRes);
  
  LoadRes := LoadResource(hModule, FindRes);
  StringTableStart := PWideChar(LockResource(LoadRes));

  I := 1;

  { Auslesen }
  while True do
  begin
    StrLen := Word(StringTableStart[I]);

    if StrLen = 0 then
      break;
    
    Entry := MidStr(WideString(StringTableStart + 1), I + 1, StrLen);
    I += StrLen + 1;
    
    Writeln('From raw: ', UTF8Encode(Entry));
  end;

  Result := True;
end;

begin
  { DLL öffnen }
  DllHandle := LoadLibraryW('.\testlib.dll');
  
  if DllHandle = 0 then
  begin
    Writeln('Could not load resource DLL: ', GetLastError);
    exit;
  end;

  try
    { Alle Typen enumerieren }
    if not EnumResourceNamesW(DllHandle, MAKEINTRESOURCEW(6), @EnumNameCallback, 0) then
      Writeln('Enum names: ', GetLastError);

  finally
    FreeLibrary(DllHandle);
  end;
end.
Im Callback kannst du jetzt mit StringTableStart die Stringtable auslesen. Ist ziemlich roh im Speicher aber die Daten sind da. Einfacher geht es wenn du die IDs schon kennst. Ich habe mit folgender Stringtable getestet:

Code: Alles auswählen

STRINGTABLE { 1, "hello World !"  
              2, "hello world again !"  
              3, "last hello world !" } 
Und kann mit LoadStringA(DllHandle, 2, @StringBuf, 254); (StringBuf: Array[0..255] of Char;) (Oder halt LoadStringW für Widestrings) den String mit der ID=2 sehr viel entspannter auslesen ohne selbst enumerieren zu müssen oder String Tables selbst parsen zu müssen.
Zuletzt geändert von chmod222 am Do 26. Aug 2021, 19:45, insgesamt 1-mal geändert.

Lorca
Beiträge: 196
Registriert: Di 3. Nov 2020, 12:25

Re: Probleme mit "FindResource"

Beitrag von Lorca »

Hallo chmod222,



noch einmal ganz lieben Dank für Deinen Einsatz. Auch wenn Du von Linux kommst, helfen Deine Aussagen mehr und mehr. :)
Dennoch habe ich noch genug Wissenslücken.

SORRY, vergiss diesen Post. Erst jetzt habe ich dein Beispiel Coding gesehen. Verzeih es mir mit meinem Alter :)



Ich hoffe ich gehe Dir nicht allzu sehr auf den Geist :)

Meine DLL ist folgendermaßen aufgebaut:
- String Table
-HTML

Mich interessiert jedoch nur diese String Table

Diese String Table ist in ganz viele Blöcke aufgeteilt.
z.B:
79 : 1033
80 : 1033
104:1033

Wenn ich mir nun den 1. Block 79 : 1033 mit dem Programm ResHacker anschaue sehe ich folgende Einträge:
STRINGTABLE
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
{
1248, "Orkney"
1250, "Unknown Mine Field"
1258, "Orkney Jump Hole"
1259, "SAC-T45b Asteroid Field"
1261, "SAC-T45c Ice Field"
1263, "Alamo Base"
}

Und nun frage ich mich: was ist was ?

Mit FOR i := 0 to "Resource".Count - 1 DO BEGIN END; Komme ich nicht weiter, weil es eine derartige Funktionalität nicht gibt.

Also muss ich zunächst String Table aus der Ressourcen DLL heraus lesen.
Die Aufrufe lv_ResInfo := FindResourceA ( mv_LibHdl, 'String Table', 'RT_STRING' ) oder
lv_ResInfo := FindResourceA ( mv_LibHdl, 'STRINGTABLE', 'RT_STRING' ) funktionieren genau so wenig wenn ich vor dem Namen einen HashTag setze (also #STRINGTABLE oder #String Table).

Danach muss ich (ich denke mit SizeofResource ) die Anzahl der vorhandenen Blöcke ermitteln.
Diese Anzahl kann ich dann für meine FOR i := xx(=Anzahl der Blöcke) Schleife nutzen.
Nun muss ich nur noch irgendwie die einzelnen Strings lesen. Hier konnte evtl. die Funktion EnumResourceNames helfen. (Was ich jedoch zu Zeit noch nicht weiß.

Weiter kann ich die Enumerierungs Methoden erst aufrufen wenn die Ressource gefunden wurde und das Handle gesetzt wurde.
Denn dieses Handle wird von diesen Funktionen benötigt.
Bei den CALL Back Funktionen handelt sich um Funktionen die der Programmierer selbst auskleiden muss. Kommt also z.B. im Parameter: lParam der Wert 6 rein, so wird in dem Typenfeld der Wert: 'RT_String' hinein gestellt werden und der Returnwert muss auf TRUE gesetzt werden. So jedenfalls habe ich es verstanden.

Ich kann dies jedoch wegen dem Fehlschlagen der Funktion: FindResourceA nichts testen, deshalb ist das alles rein hypotethisch.



Gruß
Lorca
Zuletzt geändert von Lorca am Do 26. Aug 2021, 20:02, insgesamt 1-mal geändert.

chmod222
Beiträge: 14
Registriert: Do 19. Aug 2021, 17:29

Re: Probleme mit "FindResource"

Beitrag von chmod222 »

Freut mich das ich trotzdem helfen kann, ich lerne nebenbei auch etwas mit was ich zwar vermutlich nie brauchen werde, aber beim Lernen kann auch der Weg das Ziel sein.
Und nun frage ich mich: was ist was ?
So wie ich das verstanden habe macht er für jede weitere Stringtable ja eine einen neuen Eintrag in der Resourceliste, also sind die 79:1033, 80:1033, 104:1033 unterschiedliche Resourcen (also möglicherweise String Tables).

Für jeden dieser Einträge wird EnumResourceNamesW deinen Callback aufrufen und in dem Callback kannst du dann mit den Parametern die du kriegst diesen Eintrag auslesen (siehe dazu nochmal meinen letzten Beitrag, den hatte ich nochmal editiert nachdem du geantwortet hast).
Also muss ich zunächst String Table aus der Ressourcen DLL heraus lesen.
Die Aufrufe lv_ResInfo := FindResourceA ( mv_LibHdl, 'String Table', 'RT_STRING' ) oder
lv_ResInfo := FindResourceA ( mv_LibHdl, 'STRINGTABLE', 'RT_STRING' ) funktionieren genau so wenig wenn ich vor dem Namen einen HashTag setze (also #STRINGTABLE oder #String Table).
Meine vorherigen Informationen bezüglich FindResourceW sind quatsch weil ich die WinAPI Dokumentation falsch verstanden habe. In meinem Code kannst du sehen wie ich den Namen und TypeString den ich im Callback von EnumResourceNamesW direkt unverändert in FindResourceW werfe. Die Typen sagen zwar "LPWSTR" aber die WinAPI macht da hässliche Tricks und gibt auch potenziell normale Integer getarnt als Pointer rüber.

Das Ergebnis von FindResourceW schmeiße ich dann in SizeofResource um die Größe zu kriegen und nochmal in LoadResource um ein Zwischenergebnis zu kriegen das ich wiederum in LockResource werfe um letztendlich den Pointer zur Stringtable selbst zu kriegen.

Die Stringtable selbst ist im Format <länge1><string1>[<länge2><string2>,...] bis länge = 0 ist (siehe meinen Code der die ganze Table [sicherlich amateurhaft] ausliest).
Danach muss ich (ich denke mit SizeofResource ) die Anzahl der vorhandenen Blöcke ermitteln.
Fast, die SizeofResource gibt dir die gesamtgröße der Stringtable inklusive Stringlängen (siehe oben).

Was ich jetzt aber nicht weiß ist, ob und wie man von der rohen Stringtable wie ich sie auslese zu den IDs kommt die LoadString frisst und die in der Res-Datei der String Table sind (die 1248, 1250, 1258, ...).

Lorca
Beiträge: 196
Registriert: Di 3. Nov 2020, 12:25

Re: Probleme mit "FindResource"

Beitrag von Lorca »

Hallo chmod222 ,

nochmals gaaaannnz viiiiiel Danke :D

Ich habe die Aufrufreihenfolge aus der WinAPI völlig falsch verstanden.
Dein Coding Funzt wunderbar. :D
Durch meine falsche Aufruf Reihenfolge lieferte die Funktion FindResource immer eine 0 als Handel zurück.
Ja und die ID's sind dann jetzt dran :)

Auch ich werde jetzt die "W" Funktionen nutzen.



Liebe Grüße
Lorca

chmod222
Beiträge: 14
Registriert: Do 19. Aug 2021, 17:29

Re: Probleme mit "FindResource"

Beitrag von chmod222 »

Und jetzt nochmal meine neusten Erkenntnisse wie man von der rohen Stringtable wie man sie im Callback kriegt zu den IDs kommt mit denen sie definiert wurden:

Es werden immer für 16 Strings Blöcke in den Resourcedateien allokiert. Beispiel:

Code: Alles auswählen

STRINGTABLE { 0, "String Nummer 1"  
              1, "String Nummer 2"  
              2, "String Nummer 3"
              10, "aaa"
              11, "bbb"
              12,  "ccc"
              14, "vorletzer"
              15, "letzter"
              16, "neuer block"
              100, "fff"
              111, "ggg" } 
Diese Stringtable resultiert in 3 Resourcen: 1, 2 und 7. Um jetzt herauszufinden welche IDs die individuellen Strings haben kann man ganz einfach rechnen:

Code: Alles auswählen

(Index - 1) * 16
.

Die Strintable im Resourceblock 7 fängt an mit "00 00 00 00 00 00 00 00" (also 4 WideChar Nullwerte) und dann folgt "fff". Rechnen wir jetzt (7 - 1) * 16 + 4 landen wir auf 100 => passt genau auf die Definition "100 = fff".

Der Index ist in diesem Fall was im Parameter "Name" auftaucht. Obwohl es als LPWSTR definiert ist, ist es wie gesagt nur die Zahl 1, 2, ... des Resourceblocks. In unserem Fall zumindest, es gibt sicherlich auch Resourcen wo der Name tatsächlich ein String ist, deshalb muss man checken!

Konkret umgesetzt:

Code: Alles auswählen

function EnumNameCallback(hModule: HMODULE; TypeStr: LPWSTR; Name: LPWSTR; Param: IntPtr): WinBool; StdCall;
var
  FindRes: HRSRC;
  LoadRes: HGLOBAL;
  Size: DWord;
  StrLen: Word;
  StringTableStart: PWideChar;
  Entry: WideString;

  Offset, N: Integer;
begin
  { Aussteigen wenn Resourcetyp nicht RT_STRING (6) ist }
  if TypeStr <> MAKEINTRESOURCEW(6) then
    exit(True);

  { Größe und Speicherlocation raussuchen }
  FindRes := FindResourceW(hModule, Name, TypeStr);
  Size := SizeofResource(hModule, FindRes);

  Writeln('Found STRINGTABLE at Name = ', IntPtr(Name), ' Size=', Size);
  
  LoadRes := LoadResource(hModule, FindRes);
  StringTableStart := PWideChar(LockResource(LoadRes));

  Offset := 0;
  N := 0;

  { Auslesen }
  for N := 0 to 15 do
  begin
    StrLen := Word(StringTableStart[Offset]);

    if StrLen <> 0 then
    begin   
      SetString(Entry, StringTableStart + Offset + 1, StrLen);

      Writeln((UIntPtr(Name) - 1) * 16 + N, ' = ', Entry);
    end;

    Offset += StrLen + 1;
  end;

  Result := True;
end;
Resultiert mit meinem Beispiel in:
Found STRINGTABLE at Name = 1 Size=172
0 = String Nummer 1
1 = String Nummer 2
2 = String Nummer 3
10 = aaa
11 = bbb
12 = ccc
14 = vorletzer
15 = letzter
Found STRINGTABLE at Name = 2 Size=54
16 = neuer block
Found STRINGTABLE at Name = 7 Size=44
100 = fff
111 = ggg

PascalDragon
Beiträge: 825
Registriert: Mi 3. Jun 2020, 07:18
OS, Lazarus, FPC: L 2.0.8, FPC Trunk, OS Win/Linux
CPU-Target: Aarch64 bis Z80 ;)
Wohnort: München

Re: Probleme mit "FindResource"

Beitrag von PascalDragon »

Lorca hat geschrieben:
Do 26. Aug 2021, 20:20
Ja und die ID's sind dann jetzt dran :)
Ich weiße mal noch auf meine Antwort im anderen Thread hin, falls du dir das Leben einfacher machen möchtest. ;)
FPC Compiler Entwickler

chmod222
Beiträge: 14
Registriert: Do 19. Aug 2021, 17:29

Re: Probleme mit "FindResource"

Beitrag von chmod222 »

Das ist natürlich eine viel nettere Lösung als mein zusammengefrickeltes System. Immerhin haben wir uns mal von Grund auf durchgewühlt, das ist auch schon wertvoll.

Antworten