Minimalny kod startowy
Programik „Hello World!” omówiony w poprzednim wpisie miał tę wadę, że poprawnie działał jedynie uruchomiony z konsoli. Po dorobieniu ikony i próbie odpalenia z Workbencha mamy najczęściej do czynienia z Guru $87000004. Pora temu zaradzić. Przy okazji pokażę, jak wyświetlić własny alert systemowy, czyli właśnie Guru.
Wiadomość z Workbencha
Przyczyną wieszania się programu bez kodu startowego jest zignorowanie wiadomości, jaką na starcie otrzymuje nasz proces od Workbencha. Na samym początku naszym obowiązkiem jest tę wiadomość odebrać, natomiast po zakończeniu głównego programu, ale przed powrotem do systemu trzeba na tę wiadomość we właściwy sposób odpowiedzieć. W ten sposób Workbench dowiaduje się o zakończeniu wykonywania naszego programu. A więc do kodu. Zacznę od funkcji systemowych, które zostaną użyte w programie:
; exec.library OpenLibrary = -552 CloseLibrary = -414 FindTask = -294 WaitPort = -384 GetMsg = -372 ReplyMsg = -378 Forbid = -132 ; intuition.library DisplayAlert = -90
Warto zauważyć, że nie użyję żadnej funkcji dos.library. Można spotkać się z opinią, że kod startowy musi otworzyć dos.library. Otóż nie musi, ta opinia wzięła się prawdopodobnie z kodów startowych kompilatorów języka C. Jednak nawet pisząc w C, jeżeli stworzymy własny kod startowy, to biblioteki tej otwierać nie musimy. Co prawda w zasadzie każdy nietrywialny program używa DOS-a. Można jednak sobie wyobrazić np. demo, które wszystkie dane ma w pliku wykonywalnym i nic nie doczytuje z dysku, ani nie używa żadnych innych funkcji tej biblioteki.
Ponieważ będę grzebał w systemowej strukturze Process, inkluduję odpowiedni plik nagłówkowy z nazwami pól tej struktury. Następnie rozpoczynam kod. W poprzedniej części wspomniałem, że funkcje systemowe zawsze zachowują zawartość wszystkich rejestrów adresowych i danych oprócz D0, D1, A0 i A1. W praktyce wygodnie jest rozszerzyć tę konwencję na własne funkcje i cały program, dzięki temu wygodniejsze jest trzymanie często używanych zmiennych lokalnych w rejestrach. Stąd zrzucenie używanych rejestrów na stos na początku kodu.
INCLUDE "dos/dosextens.i" MOVEM.L D2/D3/A2/A6,-(SP) CLR.L D2
W rejestrze D2 będę trzymał adres odebranej z Workbencha wiadomości. Jeżeli stwierdzę uruchomienie programu z konsoli, będę miał tam zero. Kolejnym krokiem jest otrzymanie od systemu adresu struktury Process mojego programu, przez wywołanie funkcji FindTask() z zerowym argumentem w A1.
SUBA.L A1,A1 MOVEA.L SysBase,A6 JSR FindTask(A6)
Struktura Process zawiera między innymi pole pr_CLI, zawierające adres obiektu konsoli, z której uruchomiono program. Program uruchomiony z Workbencha ma tam zero. Gdy wykryję uruchomienie z konsoli, pomijam odebranie wiadomości Workbencha.
MOVEA.L D0,A2 TST.L pr_CLI(A2) BNE.S NoWorkbench
Czas na odebranie wiadomości. Wiadomość jest umieszczana przez system w porcie procesu (pole pr_MsgPort struktury Process). Ponieważ nie wiemy, czy w momencie wykonywania tego kodu wiadomość już jest umieszczona w porcie, najlepiej zaczekać na nią funkcją WaitPort(). Gdy wiadomość czeka już przed wywołaniem tej funkcji, funkcja wróci natychmiast. W drugim kroku „wyjmujemy” wiadomość z portu i zapamiętujemy jej adres w D2.
LEA pr_MsgPort(A2),A0 JSR WaitPort(A6) LEA pr_MsgPort(A2),A0 JSR GetMsg(A6) MOVE.L D0,D2
Ponieważ rejestr A0 może zostać zmieniony przez WaitPort(), ustawiam go jeszcze raz przed GetMsg(). Czas na wywołanie głównej funkcji programu. Wywołuję ją jako podprogram, żeby sobie ułatwić np. przeniesienie jej do oddzielnego pliku źródłowego, mogę też w oddzielnym pliku umieścić sam kod startowy i używać go w różnych programach bez ingerencji w źródło.
NoWorkbench BSR.S Main MOVE.L D0,D3
Wynik działania głównej części programu zapamiętuję w rejestrze D3, żeby później zwrócić go systemowi. To nie jest wymagane i program może po prostu wyzerować D0 przed wyjściem. Zwracanie kodu błędu systemowi jest najczęściej używane przez polecenia konsoli. Wartość zero oznacza brak błędu.
Po powrocie z głównego programu nadchodzi pora na zwrot wiadomości startowej. Robimy to tylko wtedy, gdy program był uruchomiony z WB, a więc zawartość rejestru D2 przechowującego nam adres tej wiadomości jest niezerowa. Dalszy kod może się wydać zaskakujący, bowiem wywoływana jest funkcja Forbid(), która zatrzymuje wielozadaniowość, a ściślej przełączanie procesora między programami. Dopiero potem funkcją ReplyMsg() odsyłam wiadomość Workbenchowi. Na pozór nie ma to sensu, bo żeby działała wymiana wiadomości, multitasking musi być włączony. Sprawę wyjaśnia fakt, że jeżeli proces wywoła Forbid() i zakończy działanie, to przełączanie procesów zostaje automatycznie przywrócone. W ten sposób uzyskujemy pewność, że Workbench otrzyma wiadomość o zakończeniu procesu dopiero wtedy, gdy on się faktycznie zakończy i można jego kod bezpiecznie usunąć z pamięci. Bez tego mogłoby dojść do sytuacji, że z pamięci zostaje usunięty kod, który jeszcze jest wykonywany i mogłoby to się skończyć zawieszeniem się Amigi.
TST.L D2 BEQ.S NoReplyNeeded JSR Forbid(A6) MOVEA.L D2,A1 JSR ReplyMsg(A6) NoReplyNeeded MOVE.L D3,D0 MOVEM.L (SP)+,D2/D3/A2/A6 RTS
Wyświetlenie GuruMeditation
Jako program główny proponuję zmodyfikowaną wersję „Hello World!”, która wyświetla ten napis w systemowym alercie. Dlaczego tak? Wypisanie tekstu do konsoli w przypadku uruchomienia prorgamu z Workbencha nie da żadnego efektu, bo wtedy program nie ma okienka konsoli. Cokolwiek wysłane na standardowe wyjście idzie wtedy „w kosmos”. Natomiast funkcja DisplayAlert() działa w obu wypadkach tak samo. Zaczynam od otwarcia intuition.library, następnie wywołuję funkcję, potem zamykam bibliotekę i wychodzę.
Main MOVE.L A6,-(SP) LEA IntuiName(PC),A1 MOVEQ #36,D0 MOVEA.L SysBase,A6 JSR OpenLibrary(A6) TST.L D0 BEQ.S NoIntui MOVEA.L D0,A6 MOVE.L #$01234567,D0 MOVEQ #28,D1 LEA Hello(PC),A0 JSR DisplayAlert(A6) MOVEA.L A6,A1 MOVEA.L SysBase,A6 JSR CloseLibrary(A6) NoIntui CLR.L D0 MOVEA.L (SP)+,A6 RTS
Słów kilka na temat argumentów funkcji DisplayAlert(). W rejestrze D0 umieszczamy numer błędu systemowego. Ponieważ nasz alert jest tylko żartem, numer jest dowolny (nie jest wyświetlany), musimy jednak pamiętać, że najstarszy bit numeru wybiera rodzaj Guru. Jeżeli jest skasowany, tak jak w przykładzie, otrzymujemy Guru żółte, po którym następuje powrót do programu. Ustawienie tego bitu daje Guru czerwone, które kończy się restartem Amigi. W D1 mamy ilość linii obrazu potrzebnych do wyświetlenia alertu. Sama ramka zajmuje 16 linii obrazu, zaś jedna linia tekstu 8 linii, bo tekst jest zawsze pisany systemową czcionką Topaz/8. W A0 znajduje się tekst do wyświetlenia, ale jest on poprzedzony dwiema danymi. Najpierw następuje 16-bitowa liczba będąca poziomą pozycją tekstu w pikselach, potem pozycja pionowa, również w pikselach, ale dla odmiany jako jeden bajt. Następnie mamy łańcuch tekstowy zakończony zerem, po czym jeszcze jeden bajt sygnalizujący, czy po nim następują kolejne części tekstu do wyświetlenia (wartość różna od zera), czy nie (zero). Ewentualne kolejne części mają taki sam układ.
IntuiName DC.B "intuition.library",0 Hello DC.W 16 DC.B 16,"Hello World!",0,0
Systemowy alert jest dość toporną formą komunikacji z użytkownikiem i powinien być używany tylko przy naprawdę katastrofalnych błędach. Jego istotną wadą jest to, że zawsze jest wyświetlany w trybie PAL, więc użytkownicy monitorów nie wyświetlających tego trybu nie zobaczą go, o ile nie mają scandoublera.