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ąć.