Ładowanie pliku z dowolnych zasobów na podstawie ID zasobu

0

Jak wiecie rzadko o coś pytam, jednak natrafiłem na dość dziwny problem; Mianowicie potrzebuję stworzyć plik zasobów, w którym umioszczony będzie zwykły plik amorficzny, który chcę załadować w programie za pomocą klasy TResourceStream; Plik .res ma być wkompilowany do zasobów exeka, który będzie z niego korzystał;

Aby dodać do pliku .res plik amorficzny i wywoływać go na podstawie nazwy zasobu, stworzyłem plik .rc z poniższą zawartością:

Config RCDATA "Config.tsinfo"
gdzie nazwa zasobu to Config, typ zasobu to RCDATA, a nazwa pliku na dysku to Config.tsinfo; gotowy plik .res nazywa się test.res; W programie plik ładuję w następujący sposób:

{$R test.res}

var
  rsInput: TResourceStream;
begin
  rsInput := TResourceStream.Create(HINSTANCE, 'Config', 'TR_RCDATA');
  try
    { using the resource }
  finally
    rsInput.Free();
  end;

Wszystko śmiga; Jednak schody zaczynają się, gdy potrzebuję odwołać się do zasobu nie przez nazwę, a przed indeks (ID); Tworzę więc plik .rc z poniższą zawartością:

10 RCDATA "Config.tsibin"
gdzie 10 to ID zasobu, RCDATA to typ zasobu, a Config.tsibin to nazwa pliku dyskowego; Kompiluję plik .rc za tak samo za pomocą kompilatora brcc32.exe dołączonego do Delphi7, i używam go w programie w następujący sposób:

{$R test.res}

var
  rsInput: TResourceStream;
begin
  rsInput := TResourceStream.CreateFromID(HINSTANCE, 10, 'RT_RCDATA');
  try
    { using the resource }
  finally
    rsInput.Free();
  end;

Niestety zarówno pod Delphi7, jak i Lazarusem nie można odnaleźć takiego zasobu; Kompilacja pliku .rc przebiega pomyślnie i plik .res zostaje utworzony z poprawną zawartością (sprawdzałem pod hex-edytorem), jednak nijak nie można odnaleźć takiego zasobu; Przewaliłem sporo artykułów znalezionych w Google i wychodzi na to, że plik .rc ma poprawną budowę, czyli najpierw nazwa lub indeks zasobu, następnie typ zasobu i ścieżka do pliku dyskowego;

Niestety nie potrafię tego pokonać, a już dawno nie bawiłem się zasobami, stąd nie mogę sobie przypomnieć w jaki sposób kiedyś korzystałem ładowałem pliki z zasobów na podstawie ich ID;

Może mnie ktoś wyprostować, w jaki sposób załadować pliki z zasobów na podstawie ich indeksów? Dodam tylko, że potrzebne mi to jest jedynie do testów mojej biblioteki, dlatego wolałbym nie korzystać z aplikacji firm trzecich do budowy zasobów; Chodzi mi tylko o jednorazowe przetestowanie poprawności algorytmu i na tym koniec zabawy z zasobami; Bardzo proszę o wskazówkę, bo na pewno gdzieś jest malutkie niedopatrzenie; Dziękuję z góry.

2

Przede wszystkim nie wiem dlaczego uparłeś się na 'RT_RCDATA'. Pewnie dlatego jest problem. Niby według http://msdn.microsoft.com/en-us/library/windows/desktop/ms648042(v=vs.85).aspx funkcja FindResource jako typ zasobu oczekuje też PChar'a chyba. Ale ja odkąd pamiętam, zawsze dodawałem zasoby jako te typy. To są chyba albo liczbowe albo tworzone poprzez jakieś tam "cuda" z Atom, jak na przykład WC_DIALOG. Pewnie nie rzutując tego odpowiednio, otrzymuje się jakieś bzdety w pamięci. I dlatego bez niczego jako string typu zasobu przeakazać nie można.

I zobacz na projekt w załączniku. U mnie nie ma najmniejszych problemów ze znalezieniem takiego zasobu. Jak widać. Oczywiście bez hasha też się kompiluje i działą ok. Ale zdaje się, że zasoby z ID powinno się poprzedzać dla poprawności właśnie hashem. Chyba, że coś z samym plikiem jest nie tak. W sensie jego zawartością. Raz do tej pory spotkałem się z przypadkiem, że projekt trzeba było buildować do spakowania UPX'em koniecznie z użyciem jakichś tam innych ustawień, bodajże koniecznie z użyciem LZMA. Bo plik w zasobach był specyficzny i dlatego chyba UPX nie pozwalał stworzyć exeka wynikowego na dysku zaraz po kompresji.

Może pokaż przykładowy plik, jaki ma zostać dołaczony do zasobów. Chyba, że nie zrozumiałem w ogóle z czym jest problem. Nawet w dllce to powinno się powieść. Używanie zasobów pod ID ma pewną przewagę, ponieważ raz tworzyłem bazę cheatów do gier. I każdy cheat był osobnym plikiem tekstowym w zasobach. I kiedy takich plików było około tysiąca, to exek się nie uruchamiał, pewnie jakieś obostrzenia formatu PE, ale nie wnikałem. Wedlug helpa od IDE Dephi 7, używanie zasobów po ID numerycznym wpływa na zużycie pamięci itp. A i poza tymi przypadkami opisanymi powyżej, więcej nigdy nie miałem żadnych problemów z zasobami.

1

Nie:

rsInput := TResourceStream.CreateFromID(HINSTANCE, 10, 'RT_RCDATA');

tylko:

rsInput := TResourceStream.CreateFromID(HINSTANCE, 10, RT_RCDATA);
1

No wszystko jasne. W źródłach VCL od Delphi 7 w module types.pas jak byk stoi:

const
  RT_RCDATA       = PChar(10);
  {$EXTERNALSYM RT_RCDATA}

I pewnie przekazując to jako normalny string bez rzutowania czy MakeIntResource masz efekt jakbyś chciał bez kombinacji używać typów string w dllce przekazywanych jako parametry do funkcji exportowanych przez nią. Poza tym dziwne, ze kiedykolwiek wcześniej jak używałeś zasobów dodanych po swojemu w plikach *.rc Tobie to działało. Bo wedle mnie nie miało prawa :/

0

No i wszystko jasne - nie należy przekazywać łańcucha 'RT_RCDATA', tylko stałą RT_RCDATA... To całkowicie rozwiązuje problem;

Dlaczego używałem łańcuchów w konstruktorze? Jak zwykle, tak się nauczyłem dawno temu z kursów on-line i innych materiałów z sieci; Dawno temu zrobiłem sobie nawet aplikację do tworzenia plików .res, o wdzięcznej nazwie Greenource, z której skorzystałem do budowy instalatora do tego programu, w którym pliki wypakowywane były właśnie na podanej zasadzie - do konstruktora strumienia podawany był łańcuch 'RT_RCDATA' (pod Delphi7) i wszystko śmigało; Za wyodrębnianie plików z zasobów odpowiedzialna była pętla, która korzystała z poniższej procedury CreateFile (własna, nie systemowa):

procedure CreateFile(const RESName, FileName, FilePath: String);
var
  Res: TResourceStream;
begin
  Res := nil;
  SetOperationName('Tworzenie pliku ' + FilePath + FileName);

  try
    Res := TResourceStream.Create(hInstance, RESName, 'RT_RCDATA');

    if FileExists(FilePath + FileName) then
    begin
      DeleteFile(FilePath + FileName);
      Res.SaveToFile(FilePath + FileName);
    end
    else
      Res.SaveToFile(FilePath + FileName);
  finally
    FreeAndNil(Res);
    IncProgress;
  end;
end;

Całość działa do dziś bezbłędnie; Jak widać do konstruktora klasy strumienia podawany jest łańcuch 'RT_RCDATA' i działa poprawnie, jednak plik z zasobów pobierany jest na podstawie nazwy, nie ID;

Podsumowując, nie wiem dlaczego w przypadku pobierania pliku za pomocą nazwy wszystko działa (podając łańcuch typu), a w przypadku ID zasobu już nie; Nie będę się raczej w to zagłębiał, w każdym razie tak jak przypuszczałem - małe niedopatrzenie i kod nie będzie działał poprawnie;

Sprawdziłem dodatkowo, czy w pliku .rc można podać samo ID zasobu bez prefiksu # i tak - można, kompilator zasobów widać próbuje konwertować nazwę na liczbę i jeśli się powiedzie to zasób będzie identyfikowany przez ID, a jeśli nie - przez nazwę;

Dziękuję za szybką pomoc, doceniłem Wasze posty plusikami, jednak zaakceptować mogę tylko jedną odpowiedź rozwiązującą problem, więc zaznaczyłem post @olesio, bo był pierwszy;

Jeszcze raz dziękuję - ładowanie plików działa bez zarzutów, i tak samo algorytm z mojej biblioteki, ładujący plik z zasobów zarówno plików wykonywalnych, jak i bibliotek DLL działa idealnie, więc nie musiałem nic poprawiać; To obsługa klas biblioteki była wadliwa, nie same jej mechanizmy :]

0

No niebardzo wynika mi żeby @kAzek był pierwszy. Ale ok. Jak uważasz. A może problem z użyciem ID i tym, że nie przyjmuje stringa jako typ, wynika z samego działania funkcji FindResource, bo ze źródeł VCL, wynika że to samo jest używane w konstuktorach, czyli procedura Initialize klasy TResourceStream. I kiedyś pewnie czytałeś jakieś tutoriale z d**y jak niedawno jeden user na forum. Co uczą złych nawyków. I Ciebie też "nauczyły". Ja się osobiście z takimi nie spotkałem. Zawsze wiedziałem, że ma być jakaś stałą i dlatego uznałem ją za liczbową.

Tutaj pewnie też jest wspomniana konieczność rzutowania. Wiadomo przykładowo w MessageBox'ach musimy rzutować na PChar albo PWideChar tylko przy użyciu zmiennych. Ale tutaj jest inaczej skoro rzutowanie następuje na PChar(10), a nie na PChar('10'). I dlatego string to co innego. W sumie nie ma co też i za bardzo wnikać. Stosować stałe, a nie mieszać je ze stringami, czy to przy dostępie po nazwie czy po ID i nie powinno być problemów :)

0

I kiedyś pewnie czytałeś jakieś tutoriale z d**y jak niedawno jeden user na forum.

Być może ten artykuł był z d**y i nauczył mnie błędnego podejścia do ładowania zasobów, jednak program, w którym po raz pierwszy w ogóle korzystałem z zasobów (i to w wymieniony sposób) napisałem w 2009 roku, więc kawał czasu temu; Od tamtej pory w ogóle nie korzystałem z ładowania zasobów na podstawie ID, stąd aż do tej pory nie spotkałem się z tym problemem;

Na dodatek coś niecoś pamiętam, że podczas pisania tego programu miałem problem z podawaniem stałej RT_RCDATA i jakimś cudem zaskoczyło podanie łańcucha, co podpatrzyłem z materiałów z sieci, dlatego wbiło mi się to do głowy i do tej pory używałem tego źle; Ale pewnie nie zaskoczyło dlatego, że nie sprawdziłem w jakim module jest ta stała zadeklarowana, dlatego nie mogłem jej użyć; Kawał czasu minął, a mimo to stare wyuczone błędy jeszcze się objawiają;


Dziękuję Wam, bo przynajmniej teraz wiem, żeby broń Boże nie przekazywać łańcuchów, tylko stałe z modułu Types; Ale tak to jest, jak się z czegoś korzysta raz na chiński rok, i potem za fiksa nie można sobie przypomnieć jak się tego używało :]

Przed napisaniem tego wątku zaglądnąłem do wielu artykułów (StackOverflow, DelphiPages itd.), ale zamiast sprawdzić tworzenie klasy strumienia, ja skupiłem się nad zawartością plików .rc, dlatego nie zauważyłem, że w artykułach do konstruktora podawana jest stała; Po prostu sądziłem, że problem leży gdzie indziej, a nie w instrukcjach.

1 użytkowników online, w tym zalogowanych: 0, gości: 1