Zasoby w EXE

Adam Boduch

Tematem tego artykułu będzie tworzenie animacji. Nie jakiegoś tam przesuwającego się tekstu. Naszym celem będzie stworzenie animacji, która składała się będzie z kilku bitmapek złączonych w jedną całość i poruszających się - będzie to sprawiało efekt animacji. Oprócz tego w jednym pliku EXE znajdować się będą zasoby w postaci tekstu oraz dźwięk WAV.

Ok zacznijmy więc. Przygotuj sobie katalog do którego wrzuć program brcc32.exe - znajduje się on w katalogu z Delphi: Delphi\Bin. Służy on do kompilacji zasobów z postaci *.rc do *.res. Do tegoż katalogu skopiuj także jakiś niedługi plik dźwiękowy WAV. Ja nazwałem go hover.wav. Odpal teraz Delphi i stwórz nowy projekt. Umieść na formie komponent Panel i zmień jego kolor tła na biały. Na Panelu umieść komponent Image i zmień jego właściwość Align na alClient - spowoduje to rozciągnięcie komponentu na cały obszar panelu. Umieść dodatkowo na formularzu komponent Button. Szkielet programu mamy gotowy. Teraz musisz sobie przygotować serie bitmapek. Ja skorzystałem z 4 bitmapek, które po połączeniu sprawiają efekt poruszania dłonią ( macha w Twoją stronę :)). Uruchom więc edytor zasobów - stwórz nowy zasób i właduj do niego te bitmapy. Jeżeli nie wiesz jak to zrobić poczytaj artykuł Zasoby. Zasób zapisz w katalogu z programem pod nazwą Files.res. Teraz pora na włączenie do zasobu dźwięków. Ponieważ standardowy edytor zasobów Delphi tego nie umożliwia trzeba to zrobić ręcznie. W katalogu z Twoim programem stwórz plik wave.rc. Otwórz go za pomocą Notatnika i wpisz taki tekst:

STRINGTABLE
BEGIN
101, "Cześć!"
102, "Jak się masz?"
END

ID_WAVE WAVE "hover.wav"

Zacznę od końca: ostatnia linia informuje o włączeniu do zasobów pliku o nazwie "hover.wav" i podstawienie go pod nazwą "ID_WAVE". Ponieważ jest to dźwięk typu WAV musimy zadeklarować typ pliku, czyli "WAVE". Ok, co robią linie na samej górze. Włączają one do zasobu dwie linie tekstu. Najpierw pomiędzy liniami BEGIN i END trzeba wpisać tzw., identyfikator, czyli pod jaką nazwą Delphi ma rozpoznawać linię tekstu. Najpierw piszemy liczbę ( jaka ona będzie? Zależy od Ciebie ), a następnie w cudzysłowie wpisujemy tekst, który ma być do zasobów włączony. Ufff. Gotowe, zapisz plik. Teraz trzeba go skompilować. W tym celu musisz użyć kompilatora brcc32.exe. Jest to program pracujący w trybie tekstowym, więc musisz go uruchomić w okienku konsoli. W konsoli do katalogu przechodzi się poprzez polecenie:

cd NazwaKatalogu

Natomiast przechodzenie o jeden katalog wyżej umożliwia polecenie:

cd..

Jeżeli już jesteś w katalogu wpisz:

brcc32.exe wave.rc

Spowoduje to utworzenie pliku wave.res. Zasoby są już gotowe - teraz pozostało wpisywanie kodu.

W sekcji Interface masz słowo kluczowe var. Odszukaj je i dodaj zmienną:

  Done : Boolean; // zmienna informująca, czy animacja jest w ruchu

//Teraz również w sekcji Interface dodaj takie linie:

const
  BName = 'ID_BITMAP'; // pierwszy człon nazwy bitmapek
  STRING1 = 101; // wartość pierwszego Stringa umieszczonego w zasobach
  STRING2 = 102; // wartość drugiego Stringa umieszczonego w zasobach


Pamiętasz jak podczas pisania zasobu zadeklarowaliśmy dwa Stringi? Oznaczone one były ( posiadały identyfikatory ) 101 i 102. W programie zamiast pisać wartości cyfrowe można pisać słowa ( łatwiej zapamiętać ) właśnie String1 lub String2. Natomiast stała Bname to pierwszy człon nazwy bitmapek. Nie wiem jak ty nazwałeś swoje bitmapy ale ja nazwałem je: ID_BITMAP1, ID_BITMAP2 itd. Po prostu do tej zmiennej dodawane będą cyferki oznaczające numery bitmap. Teraz pozostało jeszcze napisanie procedury "ButtonOnClick". Wygeneruj więc procedurę OnClick komponentu Button i wpisz do niej takie tekst:

procedure TMainFrm.Button1Click(Sender: TObject);
var
  i : Integer;
  Buff: array [0..MAX_PATH] of Char; // ta zmienna musi mieć taka postać
begin
  while not (Done) do // Jeżeli zmienna Done = False tzn., ze animacja 
// jest  uruchomiona
  begin
{
  Pętla ta powoduje ładowanie po kolei obrazków z zasobów. Dzięki temu
  odnosi się wrażenie animacji. Stała "BNAME" określa słowo "ID_BITMAP" do
  którego dodawane są cyfry oznaczające numery bitmapy.
}
  for i:=1 to 4 do
  begin
    Application.ProcessMessages;  // daj odetchnąć systemowi
      Sleep(100); // czekaj 100 milisekund ( 1000 milisekund = 1 sek. )
    Image1.Picture.Bitmap.LoadFromResourceName( // załaduj bitmapę 
      hInstance, BName + IntToStr(i));
  end;

{
   Poniższe polecenia kolejno odgrywają oraz lądują tekst z zasobowa.
   Pierwsza funkcja "PlaySound" powoduje załadowanie z zasobów muzyki i
   odtworzenie jej.
   Druga funkcja ładuje z zasobów tekst i przypisuje go do zmiennej
   "Buff". Następnie tekst ten wyświetlony jest na pasku formy.

   "STRING1" to stała oznaczająca cyfrę 101. Pod to cyfra kryje się
   tekst, który ma być zlasowany z zasobów.
}
    PlaySound('ID_WAVE', hInstance, SND_ASYNC or SND_RESOURCE);
    LoadString(hInstance, STRING1, Buff, SizeOf(Buff));
    Caption := Buff;

  Sleep(50);  // czekaj 50 milisekund

{
  Teraz ta petla odtwarza animacje od tylu tzn., idea jest taka
  sama jak w poprzedniej petli tyle ze bitmapy ladowane sa od
  tylu.
}
  for i:=4 downto 1 do
  begin
    Application.ProcessMessages;
      Sleep(100);   // czekaj 100 milisekund
    Image1.Picture.Bitmap.LoadFromResourceName( // załaduj bitmapki
      hInstance, BName + IntToStr(i));
  end;

{ To samo co wyzej tyle, ze ladowany jest inny tekst....        }
   PlaySound('ID_WAVE', hInstance, SND_ASYNC or SND_RESOURCE);
   LoadString(hInstance, STRING2, Buff, SizeOf(Buff));
   Caption := Buff;

  end;
end;

No... nie przerażaj się. Większość tej procedury stanowią komentarze. Całość stanowi jedna pętla while w której wnętrzu znajdują się dwie pętle for. Jeżeli zmienna "Done" nie przybierze wartości False to animacja odtwarzana będzie w nieskończoność :) Aha! Do listy uses dodaj nazwę modułu MMSystem - to ten moduł odpowiedzialny jest za odtwarzanie dźwięków poprzez funkcję PlaySound. W funkcji tej podawana jest nazwa identyfikatora pod którą kryje się dźwięk. Ale o działaniu tej funkcji poczytaj artykuł "Dźwięki". Kolejna linia ładuje z zasobów tekst. Służy do tego linia "LoadString", która przypisuje linię z zasobów do zmiennej Buff. Następnie ta zmienna przypisana jest na pasku stanu formy. Drugi człon to kolejna pętla, która ładuje bitmapy od tyłu co daje efekt animacji. Na samym końcu także następuje załadownie dźwięku i testu.
Teraz pozostaje jeszcze uzupełnienie procedury "OnClose" formy. Zmienia ona wartość zmiennej Done na False co powoduje zakończenie animacji.

Done := True; // zakończ animacje i zamknij program....

Cały listing powinien wyglądać tak:


(**********************************************************)
(*         Resource Test programme for Delphi 5           *)
(*            Copyright © 2001 by Adam Boduch             *)
(*                All rights reserved                     *)
(*               Build:  17 luty 2001 r.                  *)
(*              E-mail:    [email protected]              *)
(*       SERVICE FOR PROGRAMMERS:                         *)
(*          HTTP://WWW.PROGRAMOWANIE.OF.PL                *)
(**********************************************************)

unit MainForm;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ExtCtrls, StdCtrls, MMSystem {moduł umożliwiającey odtwarzanie muzyki };

{ Oto zasoby zawierajace bitmapki oraz muzyke }
{$R FILES.RES}
{$R WAVE.RES}

type
  TMainFrm = class(TForm)
    Button1: TButton;
    Panel1: TPanel;
    Image1: TImage;
    procedure Button1Click(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  MainFrm: TMainFrm;
  Done : Boolean; // zmienna informująca, czy animacja jest w ruchu

const
  BName = 'ID_BITMAP'; // pierwszy człon nazwy bitmapek
  STRING1 = 101; // wartość liczbowa pierwszego Stringa umieszczonego w zasobach
  STRING2 = 102; // wartość liczbowa drugiego Stringa umieszczonego w zasobach

implementation

{$R *.DFM}

procedure TMainFrm.Button1Click(Sender: TObject);
var
  i : Integer;
  Buff: array [0..MAX_PATH] of Char; // ta zmienna musi mieć taka postać...
begin
  while not (Done) do  // Jeżeli zmienna Done = False tzn., ze animacja 
//jest uruchomiona
  begin
{
  Petla ta powoduje ladowanie po kolei obrazkow z zasobow. Dzieki temu
  odnosi sie wrazenie animacji. Stala "BNAME" okresla slowo "ID_BITMAP" do
  ktorego dodawane sa cyfry oznaczajace numery bitmapy.
}
  for i:=1 to 4 do
  begin
    Application.ProcessMessages;  // daj odetchnąć systemowi
      Sleep(100); // czekaj 100 milisekund ( 1000 milisekund = 1 sek. )
    Image1.Picture.Bitmap.LoadFromResourceName( // załaduj bitmape z zasobów
      hInstance, BName + IntToStr(i));
  end;

{
   Ponizsze polecenia kolejno odgrywaja oraz laduja tekst z zasobow.
   Pierwsza funkcja "PlaySound" powoduje zaladowanie z zasobow muzyki i
   odtworzenie jej.
   Druga funkcja laduje z zasobow tekst i przypisuje go do zmiennej
   "Buff". Nastepnie tekst ten wyswietlony jest na pasku formy.

   "STRING1" to stala oznaczajaca cyfre 101. Pod to cyfra kryje sie
   tekst, ktory ma byc zladowany z zasobow.
}
    PlaySound('ID_WAVE', hInstance, SND_ASYNC or SND_RESOURCE);
    LoadString(hInstance, STRING1, Buff, SizeOf(Buff));
    Caption := Buff;

  Sleep(50);  // czkej 50 milisekund

{
  Teraz ta pętla odtwarza animacje od tylu tzn., idea jest taka
  sama jak w poprzedniej pętli tyle ze bitmapy ładowane są od
  tylu.
}
  for i:=4 downto 1 do
  begin
    Application.ProcessMessages;
      Sleep(100);   // czekaj 100 milisekund
    Image1.Picture.Bitmap.LoadFromResourceName( // załaduj bitmapki
      hInstance, BName + IntToStr(i));
  end;

{ To samo co wyzej tyle, ze ladowany jest inny tekst....        }
   PlaySound('ID_WAVE', hInstance, SND_ASYNC or SND_RESOURCE);
   LoadString(hInstance, STRING2, Buff, SizeOf(Buff));
   Caption := Buff;

  end;
end;

procedure TMainFrm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Done := True; // zakończ animacje i zamknij program....
end;

end.

5 komentarzy

=> rafal__

Trzeba się posłużyć funkcją ImageList_AddIcon z API.
...a zamiast hInstance (czyli uchwytu instancji własnej aplikacji) dać uchwyt do EXE / DLL / innego pliku WinPE, z którego toto chcemy "wyciągnąć"

A sam uchwyt zdobywamy łądując plik WinPE jako bibliotekę do pamięci...

Var H: DWORD;
Begin
If OpenDialog1.Execute Then
Begin
H:=LoadLibrary( PChar(OpenDialog1.FileName) );
If H<>0 Then {jak się udało załadować plik z zasobami...}
Begin
ImageList_AddIcon( ImageList1.Handle , LoadIcon(H,'NAZWA_IKONKI') );
FreeLibrary(H);
End;
End;
End;

Chyba, że idzie ci o wyciągnięcie ikony z własnego programu:

ImageList_AddIcon( ImageList1.Handle , LoadIcon(hInstance,'NAZWA_IKONKI') );

Przykład - wyciągnięcie grupy ikon z indeksem 100 z Eksploratora Windows do ImageList:

H:=LoadLibrary( 'Explorer.exe' );
If H<>0 Then
Begin
ImageList_AddIcon( ImageList1.Handle , LoadIcon(H,PChar(100)) );
FreeLibrary(H);
End;

...w ogóle to miałem zamiar zamieścić na 4programmers parę przykładów bardziej zaawansowanej zabawy z zasobami (enumerator zasobów / przykład edycji zasobów innego pliku przez własny program [=>merdog555] (kompatybilny z Kernelem w Windowsach 9x !!)), ale niestety nie chodzi mi formularz uploadu, więc z przysyłania plików nici... :/

Dobra, to powiedzcie mi jak wczytać ikony z zasobów do ImageList.
I jeszcze jak wczytać ikony z biblioteki dll.

Wszystko ok tylko po co kompilować pliki *.rc przy pomocy brcc32.exe?
Szybciej będzie Menu>>Project>>Add to Project...
W dialogu zmieniamy tylko filtr z *.pas na *.rc i po sprawie ;> Delphi sam skompiluje plik zasobów a poza tym będzie można edytować ten plik w oknie edycji kodu :)

Wszystko ok tylko po co kompilować pliki *.rc przy pomocy brcc32.exe?
Szybciej będzie Menu>>Project>>Add to Project...
W dialogu zmieniamy tylko filtr z *.pas na *.rc i po sprawie ;> Delphi sam skompiluje plik zasobów a poza tym będzie można edytować ten plik w oknie edycji kodu :)

No tak ale jad zmienic jakieś stringi w innym exe???