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).

Przy ręcznym otwieraniu bibliotek mamy do czynienia z dwoma przypadkami specjalnymi: exec.library i dos.library. Pierwsza z tych bibliotek jest zawsze otwarta i nie da się jej zamknąć, jak już wcześniej wspomniano. Baza tej biblioteki (o nazwie SysBase) jest zdefiniowana i zainicjalizowana w kodzie startowym. Jeżeli jest ręcznie deklarowana w kodzie programu, musi być zadeklarowana ze słowem kluczowym extern. Biblioteka dos.library jest otwierana i zamykana jak każda inna, ale ponieważ używa jej kod startowy, jej baza (DOSBase) jest zdefiniowana i inicjalizowana w tymże kodzie. W rezultacie program nie musi otwierać dos.library przed jej użyciem, ani zamykać na zakończenie. Jeżeli DOSBase jest zadeklarowana w kodzie aplikacji, również należy ją zadeklarować jako extern.


Dla zachowania wstecznej kompatybilności z AmigaOS, niektóre najważniejsze bazy bibliotek (SysBase, DOSBase, IntuitionBase, GfxBase i kilka innych) nie są zdefiniowane jako wskaźniki na struct Library, ale jako wskaźniki na struktury specyficzne dla tych bibliotek. Konieczność bezpośredniego dostępu do pól tych struktur była nie do uniknięcia we wczesnych wersjach AmigaOS. W MorphOS-ie jest to niepotrzebne i niezalecane. Tych definicji baz bibliotek można uniknąć (wymuszają zbędne rzutowania typów przy wywołaniach OpenLibrary() i CloseLibrary()) definiując stałą __NOLIBBASE__ przed inkludowaniem plików proto, co blokuje definicje baz bibliotek. Wszystkie bazy muszą być wtedy zdefiniowane w kodzie programu jako wskaźniki na struct Library.

Również z powodów historycznych niektóre bazy bibliotek nie trzymają się schematu nazewnictwa baz [nazwa biblioteki]Base. Najważniejsze odchylenia od standardu to: SysBase dla exec.library, DOSBase dla dos.library (duże litery), GfxBase dla graphics.library, MUIMasterBase dla muimaster.library (duże litery), CyberGfxBase dla cybergraphics.library. Nazwę bazy biblioteki można zawsze sprawdzić w jej pliku nagłówkowym proto.

Użycie poprawnej nazwy bazy biblioteki jest bardzo ważne, ponieważ baza używana jest jako niejawny argument wszystkich wywołań funkcji z danej biblioteki.