Zasoby
Zanim zabierzemy się za tworzenie zasobów, warto abyśmy wiedzieli, co to w ogóle jest. Zasoby aplikacji to dane znajdujące się w pliku wykonywalnym, które nie są jednak instrukcjami dla procesora. Mogą to być pliki, jak również łańcuchy znaków, czy nawet skrypty tworzenia menu, czy okien dialogowych.
Przed dodaniem pliku do programu należy się jeszcze zastanowić, czy jest to potrzebne. Równie dobrze można go przecież trzymać w folderze programu i stąd wczytywać. Umieszczanie czegoś w zasobach aplikacji ma kilka negatywnych skutków. Rozmiar pliku programu znacząco się zwiększa, co sprawia, że wolniej się on uruchamia i zajmuje więcej pamięci. W niektórych przypadkach warto jednak umieścić plik w zasobach:
Tworzenie pliku zasobów
Na początku musimy dodać do projektu specjalny plik o rozszerzeniu *.rc, w którym będzie znajdować się lista zasobów dodawanych do programu. W środowisku Dev-C++ należy w tym celu wybrać z menu Plik -> Nowy -> Plik zasobów. Jeśli używasz innego IDE bez możliwości edycji zasobów, możesz ściągnąć darmowy program
ResEdit.
Kompilator zasobów, to nie ten sam program, który zajmuje się plikami *.cpp. Pliki *.rc są kompilowane oddzielnie do postaci plików *.res, a dopiero te są dołączane do reszty programu przez linker. Dawniej trzeba było ręcznie kompilować pliki *.rc, ale obecnie tą pracę wykonuje za nas środowisko.
W naszym projekcie możemy umieścić więcej plików zasobów, jednak po kompilacji otrzymamy tylko jeden plik *.res. Dzieje się tak dlatego, że nasz projekt zawiera jeszcze jeden, niewidoczny w menadżerze projektu plik *.rc, do którego pozostałe pliki są po prostu dołączane dyrektywą
#include.
Dla każdego pliku zasobów warto utworzyć plik nagłówkowy (*.h), w którym będziemy przechowywać identyfikatory zasobów. Będzie o nich mowa za chwilę.
Dodawanie pliku do zasobów
Jeśli chcemy dołączyć do naszego programu jakiś plik, musimy dopisać do pliku zasobów następującą linijkę:
Składnia: nameID typeID filename
Poniżej znajduje się lista, zawierająca stałe najważniejszych typów zasobów:
Teraz spróbujmy dołączyć sobie do naszego programu plik 'DarkCult.bmp', aby później móc wyświetlić logo ukochanej strony na honorowym miejscu w głównym oknie naszego programu ;-)
Dołączamy do projektu pliki 'logo.h' i 'logo.rc' (oczywiście nazwy mogą być dowolne). Następnie w pliku 'logo.h' definiujemy makro, które zastąpi nam liczbowy identyfikator zasobu:
Pliku 'logo.rc' także nie zostawimy pustego. Dołączamy do niego plik nagłówkowy, żeby móc skorzystać z makra, a następnie piszemy instrukcje dołączenie do zasobów naszej bitmapy:
#include "logo.h"
IDB_LOGO BITMAP "DarkCult.bmp"
Kompilator sam zamieni na odpowiednie liczby zarówno nasze makro
IDB_LOGO, jak i stałą
BITMAP. Gdybyśmy chcieli to napisać, używając wyłącznie liczb, byłby to rónież poprawne:
Zwykle nie musimy zawracać sobie głowy zapamiętywaniem wartości poszczególnych stałych. Wyjątek stanowi RCDATA, która w kompilatorze Dev-c++ musi być zapisana w formie liczby, gdyż inaczej pojawi się błąd kompilacji.
|
Dodawanie skryptu menu i okna dialogowego jest nieco trudniejsze. Omówiliśmy to już wcześniej w oddzielnych artykułach:
Menu i
Okna dialogowe, cz. 1.
Dialogami nie będziemy już się zajmować w tej części kursu.
Użycie zasobów w programie
Tyle się namęczyliśmy, żeby dodać do zasobów naszą bitmapę (3 linijki kodu ;-)), a teraz nagle zdaliśmy sobie sprawę z tego, że nie umiemy jej wyświetlić. Nie ma się jednak czym martwić, ponieważ panowie z Microsoftu okazali nam łaskę i stworzyli wyjątkowo łatwe w użyciu funkcje:
Nie jest to trudne do zapamiętania :-) Wywołanie każdej z funkcji wygląda identycznie:
Składnia: LoadCośtam (hInstance, lpName)
Spróbujmy więc wczytać sobie naszą bitmapkę. Pamiętajmy o dołączeniu pliku 'logo.h', abyśmy mogli skorzystać z makra
IDB_LOGO:
#include "logo.h"
HBITMAP hLogo = LoadBitmap( hInstance, MAKEINTRESOURCE( IDB_LOGO ) );
Zasoby typu RCDATA
Teraz omówimy metodę, która umożliwi nam odczytanie zawartości zasobów dowolnych typów, czyli także
RCDATA. Będą nam potrzebne dwie funkcje:
Funkcja
FindResource pozwala ona na uzyskanie uchwytu do zasobu:
Składnia: FindResource (hModule, lpName, lpType)
Jak widzimy, bardzo przypomina ona funkcje z poprzedniego akapitu. Jedyne, co pojawiło się nowego, to ostatni parametr. Jest to ta sama liczba, którą umieściliśmy na drugim miejscu w pliku *.rc. Tutaj również mamy do dyspozycji makra, które zwolnią nas z obowiązku zapamiętywania gołych wartości. Niestety, żeby nie było tak łatwo, mają one inne nazwy:
Jak widać, wystarczy że dodamy przedrostek
RT_.
Funkcja
FindResource zwraca nam liczbę typu HRSRC, czyli uchwyt do zasobu. Będziemy go później wykorzystywać do dalszych operacji na zasobie. Jeśli funkcja zwróci nam
NULL, to znaczy, że jej działanie zakończyło się niepowodzeniem.
Funkcja
LoadResource jest dla najważniejsza, ponieważ zwraca wskaźnik do miejsca w pamięci, w którym znajduje się dany zasób:
Składnia: LoadResource (hModule, hResInfo)
Adres pamięci, który otrzymamy, zawiera treść pliku, który dołączyliśmy do zasobów. Spróbujmy uzyskać wskaźnik do naszego 'DarkCult.bmp':
#include "logo.h"
HRSRC hLogo = FindResource( hInstance, MAKEINTRESOURCE( IDB_LOGO ), RT_BITMAP );
if( hLogo != NULL )
{
HGLOBAL pLogo = LoadResource( hInstance, hLogo );
}
Zapisywanie zasobu do pliku
Niestety czasem plik znajdujący się wyłącznie w pamięci komputera jest dla nas nieprzydatny. Przykładowo jest to plik wykonywalny (*.exe). Najlepiej unikać takich sytuacji i nie dołączać do zasobów tego typu plików. W razie potrzeby, można jednak łatwo zapisać zasób do pliku.
Potrzebna nam do tego funkcja
SizeofResource, która zwraca nam ilość pamięci, zajmowanej przez zasób:
Składnia: SizeofResource (hModule, hResInfo)
Skoro mamy jego adres w pamięci, oraz długość pliku, możemy załatwić sprawę jednym wywołaniem funkcji
WriteFile. Zapiszemy teraz nasz obrazek 'DarkCult.bmp', jako 'Logo.bmp':
#include "logo.h"
HRSRC hLogo = FindResource( hInstance, MAKEINTRESOURCE( IDB_LOGO ), RT_BITMAP );
if( hLogo != NULL )
{
HGLOBAL pLogo = LoadResource( hInstance, hLogo );
DWORD dwDlugosc = SizeofResource( hInstance, hLogo );
HANDLE hPlik = CreateFile( "Logo.bmp", GENERIC_WRITE, NULL, NULL, CREATE_NEW,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL );
DWORD dwBajtyZapisane;
if( !WriteFile( hPlik, pLogo, dwDlugosc, dwBajtyZapisane, NULL ) )
return 0;
if( dwBajtyZapisane != dwDlugosc )
return 0;
CloseHandle( hPlik );
} else
return 0;
Wydaje się to na pierwszy rzut oka skomplikowane, ale jest bardzo proste, jak na Windows. Użyliśmy tu kilku funkcji, o których można przeczytać w artykule
Pliki.
Teraz wiemy już chyba wystarczająco dużo, aby posługiwać się podstawowymi typami zasobów.
Informacje o wersji programu
W plikach .rc można też dodać informacje o wersji programu. Generalnie łatwiej tu pokazać przykład, niż wytłumaczyć wszystkie szczegóły:
1 VERSIONINFO
FILEVERSION 2, 0, 0, 0
PRODUCTVERSION 2, 0, 0, 0
FILETYPE VFT_APP
{
BLOCK "StringFileInfo"
{
BLOCK "041504E4"
{
VALUE "CompanyName", "Dark Cult"
VALUE "FileVersion", "2.0"
VALUE "FileDescription", "Edytor tekstowy"
VALUE "InternalName", "DarkCult.pl"
VALUE "LegalCopyright", "(C) Copyright by Kowalski"
VALUE "LegalTrademarks", ""
VALUE "OriginalFilename", "DarkCult.exe"
VALUE "ProductName", "DarkCult"
VALUE "ProductVersion", "2.0"
}
}
BLOCK "VarFileInfo"
{
VALUE "Translation", 0x0415, 1252
}
}
Tak to mniej więcej powinno wyglądać, by działało (tj. aby informacje o wersji były widoczne, gdy oglądamy właściwości pliku EXE lub DLL). Warto jeszcze dodać, że wartość 0x0415 na końcu oznacza identyfikator języka polskiego, 1252 to nasza strona kodowa, zaś tajemnicze 041504E4 to po prostu te dwie liczby złożone w jedną, 32-bitową, szesnastkową liczbę.
Jeśli chcemy wyświetlać gdzieś w programie te informacje, najprościej zdefiniować poszczególne elementy za pomocą
#define w pliku *.h, include'ować ten plik wewnątrz pliku zasobów i używać tych definicji zamiast bezpośrednio wartości, tzn.:
#define PRODUCT_NAME "DarkCult"
#include "Version.h"
VALUE "ProductName", PRODUCT_NAME