Organizacja API MorphOS-a
From MorphOS Library
Grzegorz Kraszewski
Ten artykuł w innych językach: angielski
API (interfejs programisty aplikacji) systemu operacyjnego składa się zazwyczaj z tysięcy funkcji. MorphOS nie jest tu wyjątkiem. Jego kernel nie jest jednak monolityczny, API jest funkcjonalnie i fizycznie podzielone na biblioteki. Tylko kilka największych bibliotek zawiera więcej niż 50 funkcji. Zestaw najważniejszych bibliotek znajduje się w startowym pliku kernela (ang. boot image). Pozostałe umieszczone są na partycji systemowej dysku w katalogu MOSSYS:Libs (biblioteki dostarczane z systemem) i SYS:Libs (zewnętrzne biblioteki dodatkowe). Biblioteki znajdujące się na dysku są ładowane do pamięci na żądanie. Wszystkie te biblioteki są współdzielone, co oznacza, że wszystkie procesy używające danej biblioteki wykonują ten sam kod, umieszczony w pamięci jednokrotnie.
Przegląd najważniejszych bibliotek
Dystrybucja MorphOS-a zawiera ponad 100 różnych bibliotek. Oczywiście poniżej wymienione są tylko najważniejsze. Opis pozostałych można znaleźć w dokumentacji znajdującej się w SDK.
- exec.library, główna biblioteka systemu, tu znajdują się podstawowe elementy systemu takie jak zarządzanie procesami i zarządzanie pamięcią, ładowanie innych bibliotek, kontrola stanu systemu i tak dalej. Jako jedyna biblioteka jest zawsze otwarta i nie da się jej zamknąć.
- dos.library, odpowiada za operacje wejścia/wyjścia na plikach i innych urządzeniach (np. konsola tekstowa). Jest interfejsem do bardziej złożonych funkcji systemów plików (np. przeszukiwanie katalogów). Współdziała z exec.library przy tworzeniu procesów. Zawiera podstawowe funkcje zegara czasu rzeczywistego.
- graphics.library, zawiera niskopoziomowe funkcje graficzne, np. rysowanie pojedynczych pikseli i prostych figur geometrycznych, kopiowanie prostokątnych obszarów obrazu, przewijanie (scroll) itp. Wiele programów nie korzysta z niej bezpośrednio.
- intuition.library, zarządza podstawowymi obiektami interfejsu użytkownika, takimi jak ekrany i okna. Zajmuje się obsługą urządzeń wejściowych (mysz, klawiatura i inne). Zawiera w sobie BOOPSI (Basic Object Oriented System for Intuition), który jest bazą dla niezależnego od języka programowania obiektowego. Baza ta jest używana przez wiele innych komponentów systemu.
- muimaster.library, jest głównym interfejsem MUI (Magic User Interface) – podstawowej wysokopoziomowej biblioteki do tworzenia graficznego interfejsu użytkownika programów. MUI jest rozwinięciem BOOPSI, daje programiście kompletny szkielet do programowania obiektowego sterowanego zdarzeniami i dostarcza bogaty zestaw gadżetów (kontrolek).
- locale.library, służy do tworzenia wielojęzycznych wersji programów. Oprócz umożliwienia prostej lokalizacji interfejsu użytkownika, pozwala również programom na pobranie od systemu ogólnych danych specyficznych dla kraju i języka użytkownika (np. waluta, format daty, strefa czasowa, grupowanie cyfr w dużych liczbach itp.)
- bsdsocket.library, jest biblioteką obsługującą protokół TCP/IP, kompatybilną z interfejsem BDS sockets. Co ciekawe biblioteka ta nie znajduje się ani w kernelu ani na dysku – jest tworzona dynamicznie w pamięci przez stos TCP/IP.
Jak używać bibliotek w programach
Najczęściej użycie biblioteki w programie jest praktycznie automatyczne. Jedyna rzecz, jaką trzeba zrobić, to zainkludowanie głównego pliku nagłówkowego biblioteki, którego nazwa tworzona jest według schematu <proto/[nazwa biblioteki].h>, na przykład <proto/exec.h>, <proto/muimaster.h> i tak dalej. Otwarcie i zamknięcie biblioteki jest wykonywane automatycznie w kodzie startowym, pochodzącym z libnix-a albo ixemul.library. W programie można więc po prostu używać funkcji danej biblioteki.
Kilka największych bibliotek posiada własne podkatalogi w drzewie plików nagłówkowych systemu. Przykładami mogą tu być exec.library, dos.library, czy graphics.library. Pliki nagłówkowe w tych katalogach zawierają definicje stałych, struktur danych, atrybutów itp. podzielone według zastosowania. Inkludowanie tych plików w kodzie programu uzależnione jest od tego, które funkcje biblioteki są wykorzystane w programie. Przykładowo używając funkcji alokacji pamięci z exec.library powinniśmy zainkludować plik <exec/memory.h>.
Inne biblioteki mogą posiadać pojedynczy plik nagłówkowy w katalogu libraries. Na przykład <libraries/locale.h> lub <libraries/mui.h> (ten drugi przykład pokazuje małą niekonsekwencję w nazewnictwie...). Ten plik może być automatycznie inkludowany z bazowego proto, ale czasem musi być inkludowany ręcznie w kodzie programu.
W kilku przypadkach automatyczna obsługa bibliotek nie działa, albo nie można z niej skorzystać:
- Dodatkowe biblioteki pisane przez niezależnych programistów. Większość z nich nie jest dodana do listy bibliotek otwieranych automatycznie.
- Program korzystający z własnego kodu startowego (linkowany z opcją −nostartfiles).
- Biblioteki otwierane w podprocesie.
- Biblioteki otwierane w programie dynamicznie na żądanie.
- Baza biblioteki zdefiniowana w kodzie programu. W tym przypadku funkcja automatycznego otwierania jest wyłączana dla tej konkretnej biblioteki.
W powyższych przypadkach biblioteka musi być otwarta i zamknięta ręcznie.
Ręczne otwieranie i zamykanie bibliotek
Ręczne otwieranie i zamykanie bibliotek jest proste, choć, rzecz jasna, nie tak wygodne jak automatyczne. Przy ręcznej obsłudze potrzebna jest zmienna, tak zwana baza biblioteki. Następnie korzystamy z dwóch funkcji exec.library: OpenLibrary() i CloseLibrary().
Baza biblioteki jest zdefiniowana w jej pliku proto (głównym pliku nagłówkowym biblioteki), jako zmienna globalna. Jest ona wskaźnikiem na strukturę Library. Strukturę tę powinno się zasadniczo traktować jako "czarną skrzynkę". W zmiennej umieszczamy wynik zwrócony przez funkcję OpenLibrary(), trzeba to zrobić zanim wywołamy jakąkolwiek funkcję otwieranej biblioteki. W momencie, gdy biblioteka nie jest już potrzebna, zamykamy ją funkcją CloseLibrary(), podając jej bazę biblioteki jako argument. Oto przykład użycia hipotetycznej biblioteki foobar.library w programie:
/* wewnątrz <proto/foobar.h> */ struct Library *FoobarBase;
/* wewnątrz kodu programu */ #include <proto/foobar.h> if (FoobarBase = OpenLibrary((STRPTR)"foobar.library", 7)) { /* tu można używać funkcji z biblioteki */ CloseLibrary(FoobarBase); }
Funkcja OpenLibrary() ma dwa parametry. Pierwszy to nazwa biblioteki do otworzenia. Podaje się samą nazwę, bez ścieżki. MorphOS szuka biblioteki w kilku standardowych lokacjach, w następującej kolejności:
- MOSSYS:Libs/
- LIBS:
- katalog bieżący programu
- PROGDIR:Libs/
W czwartej ścieżce PROGDIR: jest automatycznym przypisaniem wskazującym na katalog, w którym znajduje się plik wykonywalny programu.
Drugi parametr OpenLibrary() to minimalna wymagana wersja biblioteki. Podanie zera pozwala na dowolną wersję, każda liczba większa od zera oznacza "ta wersja lub wyższa". Nie ma prostego sposobu na zażądanie konkretnej wersji biblioteki. Jak łatwo zauważyć takie podejście do bibliotek wymaga, aby nowsze wersje były zawsze kompatybilne wstecz ze starszymi. Z drugiej strony pozwala to uniknąć istnienia w systemie wielu wersji tej samej biblioteki, co jest powszechnym problemem z linuksowymi bibliotekami w formacie shared object.
Program powinien zawsze sprawdzić, czy wartość zwracana przez OpenLibrary() jest różna od zera. Nawet otwarcie biblioteki wbudowanej w kernel systemu może się nie powieść (z powodu braku pamięci lub za niskiej wersji biblioteki). Wypisanie w takim przypadku komunikatu o błędzie jest z pewnością dobrym pomysłem.
Każde udane otwarcie biblioteki przez OpenLibrary() powinno mieć odpowiadające mu wywołanie CloseLibrary(). Pozostawienie otwartej biblioteki może skutkować wyciekiem pamięci, uniemożliwia też usunięcie biblioteki z pamięci (tzw. flush).
There are two special cases for manual library opening and closing: exec.library and dos.library. The first one is always open and cannot be closed, as stated above. The library base for it (named SysBase) is defined and initialized in the startup code. If declared manually in an application's source code, it should be declared as an extern. The dos.library is opened and closed as with any other library, but because the startup code needs it, the DOSBase is already defined and initialized there. As a result the application does not need to open dos.library before using it. If declared in an application, DOSBase should be an extern too.
As with the Amiga historical heritage, some of the most important library bases (SysBase, DOSBase, IntuitionBase, GfxBase and a few more) are not defined as struct Library* but as pointers to library specific structures. Direct poking of these structures was unavoidable in early AmigaOS versions. In MorphOS it is neither needed nor recommended.
One can avoid the above definitions (which forces unnecessary typecasting in OpenLibrary() and CloseLibrary()) by #defining __NOLIBBASE__ symbol before including proto files. This disables library bases definitions. All bases can (and must) be then explicitly defined in the code as pointers to struct Library.
Also for traditional reasons, names of some library bases do not follow the [Libname]Base scheme. The most important deviations are: SysBase for exec.library, DOSBase for dos.library (capitalization), GfxBase for graphics.library, MUIMasterBase for muimaster.library (capitalization), CyberGfxBase for cybergraphics.library. In any case the base name can be checked by looking at the library proto header.
Using the proper base name is very important, as it is used as an implicit argument in all the calls of library functions.