C dla każdego (cz. 20.)
Obsługa argumentów
Tematem dzisiejszego odcinka zamierzamy uczynić
obsługę argumentów, zarówno tych z linii Shella,
jak również tych przechowywanych w polu ToolTypes
ikony.
Norma ANSI języka C przewiduje obsługę linii
argumentowej Shella poprzez dostarczenie do
funkcji "main()" parametrów "argc" i "argv". Jak
wiadomo, "argc" informuje o liczbie elementów w
tablicy "argv", która zawiera poszczególne
argumenty. Ponieważ pierwszy element tej tablicy
jest nazwą programu, to z punktu widzenia ANSI
"argc" nie może być mniejsze od 1. W wypadku
systemu AmigaOS start może nastąpić również z
poziomu Workbencha. Dla łatwego odróżnienia tej
sytuacji dołączany przez kompilator moduł startowy
ustawia w takim wypadku parametr "argc" na 0.
Obsługa Shella
Zacznijmy od obsługi linii argumentowej Shella.
Służy do tego następująca funkcja z biblioteki
"dos.library":
struct RDArgs *ReadArgs( STRPTR arg_template, LONG
*array, struct RDArgs *args );
Parametr "arg_template" powinien zawierać wzorzec
linii argumentowej. Składa się on z nazw
argumentów, oddzielonych przecinkami. Pisownia
małych/dużych liter nie ma znaczenia, ale przyjęło
się używać tylko dużych liter. Dopuszcza się
stosowanie synonimów, np. podanie nazwy "AS=TO"
oznacza, że użytkownik może wpisać którąkolwiek
formę. Domylnie argumenty są opcjonalne, ich
użycie przez użytkownika polega na podaniu w
miejsce nazwy argumentu dowolnego napisu. Np. dla
wzorca "FROM,TO" (dwa argumenty) pasującymi
liniami argumentowymi będą: "" (linia pusta),
"abc" (podany tylko argument "FROM"), "def ghi"
(podane oba argumenty). Po nazwie argumentu można
jednak podać jeden lub więcej znaczników,
informujących o typie argumentu. Oto najważniejsze
z nich:
/A -- Argument jest wymagany.
/N -- Argument powinien być liczbą całkowitą.
/S -- Argument jest włącznikiem. Podanie w linii
argumentowej nazwy argumentu oznacza włączenie
opcji.
/K -- Argument musi być poprzedzony swoją nazwą,
pomiędzy tymi dwoma członami musi się znajdować
odstęp lub znak równoci.
Parametr "array" jest wskaźnikiem na tablicę, w
której będzie zapisany wynik działania funkcji.
Pierwszemu argumentowi z wzorca odpowiada pierwsze
pole w tablicy, drugiemu drugie itd. W wypadku
argumentów typu "/S" w tablicy odnotowywana jest
wartość różna od zera, gdy argument został użyty. W
wypadku argumentów "/N" odnotowywany jest wskaźnik
na liczbę (lub 0, gdy argument nie został podany).
W pozostałych z wyżej wymienionych wypadków
umieszczany jest wskaźnik na podany napis. Przed
wywołaniem funkcji tablica powinna być wypełniona
domyślnymi wartościami.
Ostatniego parametru, "args", używa się tylko do
wyrafinowanych zastosowań funkcji "ReadArgs()". W
typowych sytuacjach podaje się po prostu 0.
Argumenty nie muszą być podawane w tej samej
kolejnoci, co we wzorcu. Dotyczy to przede
wszystkim argumentów typu "/S" i "/K", dla których
funkcja jest w stanie sama "domyśleć się", o który
argument chodzi. W wypadku, gdy linia argumentowa
kończy się argumentem "?", funkcja wypisuje w
charakterze pomocy wzorzec i oczekuje na ponowne
wpisanie linii argumentowej ze strumienia
wejściowego.
Funkcja zwraca wskaźnik na niezbyt nas
interesującą, zdefiniowaną w pliku "dos/rdargs.h",
strukturę "RDArgs" lub 0 w wypadku niepowodzenia
(np. linii nie pasującej do wzorca -- dodatkowe
informacje o błędzie można uzyskać przy użyciu
funkcji IoErr()).
W celu zwrócenia przydzielonej przez ReadArgs()
pamięci, po zakończeniu obsługi argumentów należy
wywołać funkcję:
void FreeArgs( struct RDArgs *args );
Jako parametr trzeba podać wartość zwróconą przez
ReadArgs().
Obsługa Workbencha
Przejdźmy teraz do obsługi uruchomienia programu z
Workbencha. Jak już wspomnielimy, "argc" przyjmuje
w takiej sytuacji wartość 0. "argv" za wskazuje na
strukturę "WBStartup", zdefiniowaną w pliku
"workbench/startup.h":
struct WBStartup
{
struct Message sm_Message;
struct MsgPort* sm_Process;
BPTR sm_Segment;
LONG sm_NumArgs;
char* sm_ToolWindow;
struct WBArg* sm_ArgList;
};
Interesują nas na razie tylko dwa jej pola:
"sm_NumArgs" i "sm_ArgList". "sm_NumArgs"
(analogiczny do "argc") informuje o liczbie
elementów we wskazywanej przez "sm_ArgList"
tablicy struktur "WBArg" (analogicznej do "argv"):
struct WBArg
{
BPTR wa_Lock;
BYTE* wa_Name;
};
Znaczenie pól jest następujące:
wa_Lock -- zatrzask założony na katalogu, w którym
znajduje się ikona (patrz opis funkcji Lock() w
poprzedniej części).
wa_Name -- nazwa obiektu wskazywanego przez ikonę.
Gdy jest to ikona katalogu, dysku lub kosza,
będzie to wskaźnik na pusty napis.
Struktura ta opisuje ikony, rozumiane jako
pojedyncze argumenty programu. Liczba argumentów
jest nie mniejsza niż 1, jako że pierwszy element
tablicy zawsze zawiera dane o uruchomionym
programie (analogicznie do "argv[0]"). Kolejne
argumenty opisują ikony zaznaczone przez tzw.
MultiSelect (pojedyncze kliknięcie na ikonie
programu, następnie zaznaczenie innych ikon z
wciśniętym klawiszem [Shift] i podwójne kliknięcie
na której z nich). W wypadku uruchomienia programu
przez pole "DefaultTool" (lub "Program" w polskich
Locales), omawiana tablica zawiera dwa elementy.
Zanim przejdziemy do przykładu, omówienia wymagają
funkcje:
BPTR CurrentDir( BPTR lock );
Funkcja ta zmienia katalog bieżący. Parametrem
jest zatrzask, założony na katalog, który
zamierzamy uczynić bieżącym. Zwracaną wartością
jest zatrzask na poprzednim katalogu bieżącym.
BPTR Output( void );
Funkcja zwraca identyfikator strumienia
wyjściowego.
LONG StrToLong( STRPTR string, LONG *value );
Funkcja konwertuje podaną w formie napisu "string"
liczbę na "LONG". Wynik zapisuje w "value".
Zwracaną wartością jest liczba przekonwertowanych
znaków lub "-1" w wypadku błędu.
Pierwszym krokiem przy odczycie ikony jest zmiana
za pomocą funkcji "CurrentDir()" bieżącego
katalogu na ten odnotowany w polu "wa_Lock"
struktury "WBArg". Następnie należy odczytać
ikonę, korzystając z funkcji biblioteki
"icon.library":
struct DiskObject *GetDiskObjectNew( UBYTE *name
);
Jedynym parametrem jest nazwa obiektu wskazywanego
przez ikonę (bez rozszerzenia ".info").
Funkcja zwraca wskaźnik na opisaną poniżej
strukturę "DiskObject" lub NULL w wypadku błędu
(bliższe informacje na jego temat można uzyskać za
pomocą funkcji IoErr()).
Gdy ikona nie jest nam już dłużej potrzebna, w
celu zwrócenia przydzielonej pamięci należy
wywołać funkcję:
void FreeDiskObject( struct DiskObject *diskobj );
Jej jedynym parametrem jest wartość zwrócona przez
funkcję "GetDiskObject()".
Wnętrzności ikony
Z programu ikona jest widziana jako struktura
"DiskObject", zdefiniowana w pliku
"workbench/icon.h". Nas interesują jedynie trzy
jej pola:
UBYTE do_Type -- typ ikony. Najważniejsze to:
WBDISK -- dysk
WBDRAWER -- katalog
WBTOOL -- program
WBPROJECT -- dane
WBGARBAGE -- kosz
char* do_DefaultTool -- pole wypełnione tylko w
wypadku ikon dysku i danych. Jest to nazwa
programu, który ma obsługiwać dane, a w wypadku
dysku nazwa programu, który ma być wywołany
podczas kopiowania dysku (gdy ikona jest
źródłem).
char** do_ToolTypes -- adres tablicy zawierającej
wskaźniki na argumenty wywołania (pola "ToolTypes"
ikony). Napisy w tym polu mają zwykle formę
"<nazwa>=<wartość>",
są więc odpowiednikami argumentów
typu "/K" z "ReadArgs()". Odpowiednikami
argumentów typu "/S" są zwykle napisy "<nazwa>", gdy
opcja ma być włączona, choć niektóre programy
preferują formę ze znakiem równości, jako wartości
wymagając "Yes" lub "No" (tak zrobimy w naszym
przykładzie).
Tu pewna uwaga natury stylistycznej. Zaleca się,
aby w ikonie programu umieszczać w nawiasach
wszystkie akceptowane przez program argumenty wraz
z krótkim opisem lub domyślną wartością (np.
"(HIDE=YES)"). Znacznie ułatwia to użytkownikom
życie.
Podstawową funkcją używaną przy obsłudze pola
"ToolTypes" jest:
UBYTE *FindToolType( UBYTE **toolTypeArray, UBYTE
*typeName );
Jako pierwszy parametr podajemy wartoć pola
"do_ToolTypes" ikony, jako drugi -- nazwę
poszukiwanego argumentu. Funkcja przeszukuje
tablicę (ignorując pisownię małych i dużych liter)
i zwraca wskaźnik na wartoć argumentu o podanej
nazwie (tzn. pomijana jest sama nazwa i ew. znak
'=', o ile występuje). Gdy argument nie zostanie
znaleziony, zwracany jest NULL.
Jeżeli wartoć argumentu należy do znanego z góry
zbioru (jak np. wspomniane powyżej "Yes" i "No"),
to można użyć funkcji:
BOOL MatchToolValue( UBYTE *typeString, UBYTE
*value );
Jako pierwszy parametr podajemy napis zwrócony
przez "FindToolType()", jako drugi -- jedną z
możliwych wartoci, jakie ten napis może przyjąć.
Funkcja zwraca TRUE, gdy szukany napis występuje,
FALSE w przeciwnym wypadku. Funkcja ignoruje przy
tym wielkość liter, bierze zaś pod uwagę możliwość
występowania kilku wartoci naraz, oddzielonych
znakiem '|'.
Na koniec pewna uwaga: przekazywanie "argc"
równego 0 przy uruchamianiu z Workbencha jest
pewnym "niepisanym" standardem, do którego,
niestety, nie wszystkie kompilatory się stosują. O
ile nam wiadomo, moduł startowy, dołączany przez
kompilator DICE, przy uruchamianiu z Workbencha
wywołuje nie funkcję "main", ale "wbmain". Listing
ukazuje, jak to w prosty sposób ominąć.