Wprowadzenie

edytuj

Tym razem zajmiemy się bardziej zaawansowanym narzędziem do programowania, czyli ILINK32. Jest to jak sama nazwa wskazuje linker (bardziej po polsku: konsolidator). Linker, czyli program, który łączy wszystkie "półprodukty" utworzone z plików źródłowych - pliki *.obj z bibliotekami, zasobami programu (o ile je uprzednio stworzymy) itp. Tak naprawdę bez konsolidatora nie byłoby programu takiego, jaki sobie wyobrażamy, ponieważ to on łączy wszystkie elementy na które składa się program w jeden plik EXE (lub w bibliotekę). Mniej doświadczonych programistów już teraz ostrzegam, że bawienie się w samodzielne wywoływanie ILINK32 będzie od was wymagało dodatkowej roboty, którą wcześniej "odwalał" za was kompilator (BCC32), ale ILINK32 w zamian daje jeszcze większy wpływ na to, jak wasz ostateczny program będzie wyglądał.

Wywołanie

edytuj

W przypadku ILINK32 wywołanie jest znacznie bardziej skomplikowane niż w BCC32:

ILINK32 [<opcje>] <rozbiegówka> <obiekt(y)>, [<wynik>], [<mapa>], <lib>, [<defile>], [<zasoby>]
<opcje>
tak jak w przypadku BCC32, tak samo konsolidacji możemy nadać wiele opcji, z których ważniejsze opisuje w następnym podrozdziale.
<rozbiegówka>
spotkałem się kiedyś z takim określeniem i skoro sam nie potrafię tego jakoś sensownie nazwać będę używał tego terminu; <rozbiegówka> to specjalny plik *.obj, który musisz dodać do Twojego kodu w zależności jaką aplikację chcesz uzyskać:
  • c0w32.obj jeśli ma to być zwykła aplikacja Win32 GUI (Graphic User Interface)
  • c0x32.obj jeśli chcesz utworzyć program uruchamiany w konsoli
  • c0d32.obj jeśli masz zamiar wygenerować bibliotekę Win32 DLL
W przypadku kompilacji przez BCC32 nie musisz o tym pamiętać, kompilator automatycznie dodaje ten plik po wpisaniu odpowiedniej :opcji, jeśli sam wywołujesz ILINK32 - Ty musisz zrobić.
<obiekt(y)>
plik(i) *.obj generowane przez kompilator wpisujesz tutaj oddzielając każdy od siebie spacją. Jeśli ktoś nie wie, jak zmusić kompilator do kompilacji pliku tylko do *.obj (bez samodzielnego wywoływania ILINK32), to przypominam, że wystarczy dodać do linii poleceń BCC32 opcję -c
<wynik>
możesz tu sam zdefiniować nazwę swojego pliku wynikowego; może to być zarówno program (*.exe) jak i biblioteka statyczna (*.lib) lub dynamiczna (*.dll)
<mapa>
w tym miejscu możesz wpisać nazwę pliku MAP, który wygeneruje ILINK32 (jeżeli tego nie zrobisz linker nazwie go tak jak twój wynik (obcinając rozszerzenie). Plik MAP przechowuje adresy poszczególnych segmentów aplikacji i adresy funkcji Twojego programu, jest tworzony domyślnie.
<lib>
po prostu biblioteka/i które masz zamiar użyć w programie; możesz dodać każdą o ile znajduje się w lokalizacji podanej przy opcji -L w parametrach wywołania ILINK32 lub w pliku ilink32.cfg. Dokumentacja FCLT określa prawidłową kolejność w jakich powinno się te biblioteki podawać:
  1. bilioteki pomocne w analizowaniu i debugowaniu kodu (tzw. code guards)
  2. inne (własne) biblioteki
  3. biblioteka import32.lib - jeśli ma to być program dla Windows
  4. biblioteki operacji matematycznych
  5. tzw. runtime libaries (taka rozbiegówka, ale tym razem z rozszerzeniem lib) w przypadku programów dla Windows będzie to cw32.lib
w praktyce aby skompilować zwykły program dla Windows należy podać tu przynajmniej: import32.lib cw32.lib
<defile>
tutaj określasz plik DEF; w nim możesz określić wiele parametrów Twojego programu, które dokładnie opisuje poniżej.
<zasoby>
zasoby, ale w wersji skompilowanej (*.res) - wcześniej musisz użyć kompilatora zasobów

Bardzo ważną rzeczą w wywoływaniu ILINK32 są przecinki, nawet jeśli nie chcesz wpisywać wszystkich parametrów masz obowiązek zaznaczyć wszystkie przecinki! Oto przykładowe wywołanie ILINK32 generujące aplikację Win32 GUI:

 ILINK32 /aa /Tpe /Gk /t /M c0w32.obj Helo.obj klasa.obj,\
 Hello.exe,,import32.lib cw32.lib,,zasoby.RES

Źródła

edytuj
opcja opis
@<nazwa> tu możesz zdefiniować nazwę <nazwa> response file dla ILINK32
/j<lokalizacja> (dodatkowa) lokalizacja plików *.obj
/L<lokalizacja> dodatkowa lokalizacja bibliotek
opcja opis
/aa aplikacja Windows GUI
/ap aplikacja Windows Console
/ad sterownik urządzenia
/Tpe program *.exe
/Tpd biblioteka dynamiczna *.dll
/Gl biblioteka statyczna (*.lib)
/Gn ILINK32 przy każdej konsolidacji generuje 4 pliki (*.ilc, *.ilf, *.ild, *.ils), dzięki którym szybciej konsoliduje Twój program, jeżeli nie chcesz aby zaśmiecał Ci tym katalogu użyj tej opcji
/Gk kiedy pojawią się jakieś błędy podczas konsolidacji linker automatycznie usuwa plik wynikowy(!) co czasami może być niepożądaną operacją, aby temu zapobiec możesz użyć tej opcji

Plik MAP

edytuj

ILINK32 pozwala na określenie stopnia zaawansowania pliku MAP:

opcja opis
/x nie generuj pliku MAP
/M generuj mapę najmniej zaawansowaną
/m generuj mapę publiczną, przydatną podczas debugowania programu
/s generuj szczegółową mapę

Ostrzeżenia

edytuj
opcja opis
-q pomijaj nagłówek ILINK32
/t pokaż czas spędzony na linkowanie
-w-dup nie pokazuj ostrzeżenia "duplicate symbol"

Plik DEF

edytuj

Plik DEF określa wiele aspektów programu, które możesz zdefiniować w zwykłym pliku ASCII, w Notatniku (pamiętając, żeby plik ten miał rozszerzenie *.def). Aspekty te określamy wpisując odpowiednie selektory i w tej samej linii dopisując do nich wartości. Standardowo jeśli nie określimy przy wywoływaniu ILINK32 pliku DEF, będzie on wyglądał tak:

 CODE	PRELOAD MOVEABLE DISCARDABLE
 DATA	PRELOAD MOVEABLE MULTIPLE   ; (for applications)
       PRELOAD MOVEABLE SINGLE   ; (for DLLs)
 HEAPSIZE	4096
 STACKSIZE	1048576

Już w tym kodzie widzisz 4 selektory i określone dla nich wartości. Są to podstawowe oznaczenia pliku DEF (CODE, DATA, HEAPSIZE i STACKSIZE). Od razu można się domyślić co one oznaczają, ale aby zbytnio nie mącić przejdę teraz do omówienia tych i innych parametrów pliku DEF.

Jak wiemy plik EXE jest podzielony na dwa podstawowe segmenty:

  • kod (ciąg instrukcji np. w C++ które sprawiają, że program "żyje")
  • zasoby (ikony, bitmapy, menu itp.)

Selektor CODE pliku DEF określa specyficzne opcje dla tego pierwszego segmentu (kod):

wartość selektora standard opis
PRELOAD nie kod jest wczytywany, gdy wywoływany program jest wczytywany
LOADONCALL tak kod jest wczytywany, kiedy jest wywoływany przez program
EXECUTEONLY nie kod może być tylko wykonywany
EXECUTEREAD tak kod może być czytany i wykonywany
FIXED tak segment pozostaje w pamięci
MOVEABLE/ nie segment może być przeniesiony
DISCARDABLE nie segment może być odrzucony kiedy nie jest potrzebny (zakłada opcję MOVEABLE).
NONDISCARDABLE tak segment nie może być odrzucony

Parametr DATA określa zachowanie się segmentu zasobów:

wartosć standard opis
NONE nie nie ma segmentu DATA
SINGLE tak dla DLL jeden segment zasobów jest tworzony i współdzielony dla wszystkich procesów
MULTIPLE tak dla EXE każdy segment zasobów jest tworzony dla jednego procesu
READONLY nie segemnt może być tylko czytany
READWRITE tak segment może być i czytany i nadpisywany
PRELOAD nie zasoby są wczytywane kiedy moduł, który ich używa jest wczytywany
LOADONCALL tak segment zasobów jest wczytywany kiedy jest po raz pierwszy przetwarzany (ta opcja jest ignorowana przez aplikacje 32-bitowe)
SHARED nie jedna kopia jest współdzielona między wszystkie procesy
NONSHARED tak dla DLL kopia segmentu jest wczytywana dla każdego procesu potrzebującego zasoby

HEAPSIZE

edytuj

Jak sama nazwa wskazuje wpisujesz tutaj wielkość sterty (standardowo: 4096). Jeżeli w Twoim programie jest wiele obiektów zadeklarowanych na stercie możesz zwiększyć tę liczbę.

STACKSIZE

edytuj

Tym razem określamy wielkość stosu (tu znajdują się wszystkie zmienne lokalne i parametry funkcji), dlatego wielkość tego parametru powinna być nieporównywalnie większa (standardowo: 1048576).

Nazwa pliku *.exe, który jest wynikiem konsolidacji, dodatkowo możesz tu umieścić po spacji jeden z dwóch znaczników:

WINDOWAPI
jeżeli Twoja aplikacja będzie korzystać z WinAPI (to samo, co opcja linkera /aa)
WINDOWCOMPAT
jeżeli Twój program będzie uruchamiany w konsoli (to samo, co opcja linkera /ap)

LIBRARY

edytuj

Nazwa biblioteki DLL, która jest wynikiem konsolidacji i tutaj masz do wyboru 2 znaczniki:

INITGLOBAL
biblioteka jest wczytywana do pamięci tylko raz
INITINSTANCE
biblioteka jest wczytywana za każdym razem kiedy jest potrzebna

EXPORTS

edytuj

Tego selektora mogą używać tylko biblioteki Jego wartością są nazwy wszystkich funkcji udostępniane przez bibliotekę. Więcej o tym selektorze przeczytasz w artykule: Biblioteki DLL w FCLT

IMPORTS

edytuj

Wartością tego selektora są funkcje importowane z bibliotek zadeklarowane w kodzie źródłowym. I o tym selektorze przeczytasz w artykule o bibliotekach DLL.

DESCRIPTION

edytuj

Ujęty w pojedynczy cudzysłów: opis programu (mogą tu się znajdować również np. prawa autorskie do programu). Oto jak może wyglądać plik

DEF w praktyce:

 NAME        Hello WINDOWAPI
 DESCRIPTION '(c) 2003 by Karol Ossowski'
 CODE	      PRELOAD MOVEABLE DISCARDABLE
 DATA	      PRELOAD MOVEABLE MULTIPLE
 
 HEAPSIZE	4096
 STACKSIZE	1048576

Zasoby

edytuj

Jak już pisałem aby sprowadzić skrypt zasobów z rozszerzenia *.rc do skompilowanego pliku *.RES należy użyć brcc32 z katalogu BIN. Wywołanie brcc32 będzie wyglądać przykładowo tak:

 brcc32 <zasoby>.rc

Nie będę się zagłębiał tutaj w opcje, jakich może użyć ten kompilator (jeśli Cię one interesują możesz sprawdzić je w dokumentacji FCLT), ważne że prze konwertuje on nasz plik <zasoby>.rc na <zasoby>.RES. Dopiero teraz możesz go dołączyć do wywołania ILINK32.