Amiga 1200

Minimalny kod startowy

Co trzeba zrobić, żeby program nie wieszał się „z ikonki”
Grzegorz Kraszewski, 17 kwietnia 2016

Pro­gra­mik „Hello World!” omó­wio­ny w po­przed­nim wpi­sie miał tę wadę, że po­praw­nie dzia­łał je­dy­nie uru­cho­mio­ny z kon­so­li. Po do­ro­bie­niu ikony i pró­bie od­pa­le­nia z Work­ben­cha mamy naj­czę­ściej do czy­nie­nia z Guru $87000004. Pora temu za­ra­dzić. Przy oka­zji po­ka­żę, jak wy­świe­tlić wła­sny alert sys­te­mo­wy, czyli wła­śnie Guru.

Wiadomość z Workbencha

Przy­czy­ną wie­sza­nia się pro­gra­mu bez kodu star­to­we­go jest zi­gno­ro­wa­nie wia­do­mo­ści, jaką na star­cie otrzy­mu­je nasz pro­ces od Work­ben­cha. Na samym po­cząt­ku na­szym obo­wiąz­kiem jest tę wia­do­mość ode­brać, na­to­miast po za­koń­cze­niu głów­ne­go pro­gra­mu, ale przed po­wro­tem do sys­te­mu trze­ba na tę wia­do­mość we wła­ści­wy spo­sób od­po­wie­dzieć. W ten spo­sób Work­bench do­wia­du­je się o za­koń­cze­niu wy­ko­ny­wa­nia na­sze­go pro­gra­mu. A więc do kodu. Za­cznę od funk­cji sys­te­mo­wych, które zo­sta­ną użyte w pro­gra­mie:

; exec.library

OpenLibrary         = -552
CloseLibrary        = -414
FindTask            = -294
WaitPort            = -384
GetMsg              = -372
ReplyMsg            = -378
Forbid              = -132

; intuition.library

DisplayAlert        = -90

Warto za­uwa­żyć, że nie użyję żad­nej funk­cji dos.​library. Można spo­tkać się z opi­nią, że kod star­to­wy musi otwo­rzyć dos.​library. Otóż nie musi, ta opi­nia wzię­ła się praw­do­po­dob­nie z kodów star­to­wych kom­pi­la­to­rów ję­zy­ka C. Jed­nak nawet pi­sząc w C, je­że­li stwo­rzy­my wła­sny kod star­to­wy, to bi­blio­te­ki tej otwie­rać nie mu­si­my. Co praw­da w za­sa­dzie każdy nie­try­wial­ny pro­gram używa DOS-a. Można jed­nak sobie wy­obra­zić np. demo, które wszyst­kie dane ma w pliku wy­ko­ny­wal­nym i nic nie do­czy­tu­je z dysku, ani nie używa żad­nych in­nych funk­cji tej bi­blio­te­ki.

Po­nie­waż będę grze­bał w sys­te­mo­wej struk­tu­rze Pro­cess, in­klu­du­ję od­po­wied­ni plik na­głów­ko­wy z na­zwa­mi pól tej struk­tu­ry. Na­stęp­nie roz­po­czy­nam kod. W po­przed­niej czę­ści wspo­mnia­łem, że funk­cje sys­te­mo­we za­wsze za­cho­wu­ją za­war­tość wszyst­kich re­je­strów ad­re­so­wych i da­nych oprócz D0, D1, A0 i A1. W prak­ty­ce wy­god­nie jest roz­sze­rzyć tę kon­wen­cję na wła­sne funk­cje i cały pro­gram, dzię­ki temu wy­god­niej­sze jest trzy­ma­nie czę­sto uży­wa­nych zmien­nych lo­kal­nych w re­je­strach. Stąd zrzu­ce­nie uży­wa­nych re­je­strów na stos na po­cząt­ku kodu.

                    INCLUDE "dos/dosextens.i"

                    MOVEM.L D2/D3/A2/A6,-(SP)
                    CLR.L   D2

W re­je­strze D2 będę trzy­mał adres ode­bra­nej z Work­ben­cha wia­do­mo­ści. Je­że­li stwier­dzę uru­cho­mie­nie pro­gra­mu z kon­so­li, będę miał tam zero. Ko­lej­nym kro­kiem jest otrzy­ma­nie od sys­te­mu ad­re­su struk­tu­ry Pro­cess mo­je­go pro­gra­mu, przez wy­wo­ła­nie funk­cji Find­Task() z ze­ro­wym ar­gu­men­tem w A1.

                    SUBA.L  A1,A1
                    MOVEA.L SysBase,A6
                    JSR     FindTask(A6)

Struk­tu­ra Pro­cess za­wie­ra mię­dzy in­ny­mi pole pr_CLI, za­wie­ra­ją­ce adres obiek­tu kon­so­li, z któ­rej uru­cho­mio­no pro­gram. Pro­gram uru­cho­mio­ny z Work­ben­cha ma tam zero. Gdy wy­kry­ję uru­cho­mie­nie z kon­so­li, po­mi­jam ode­bra­nie wia­do­mo­ści Work­ben­cha.

                    MOVEA.L D0,A2
                    TST.L   pr_CLI(A2)
                    BNE.S   NoWorkbench

Czas na ode­bra­nie wia­do­mo­ści. Wia­do­mość jest umiesz­cza­na przez sys­tem w por­cie pro­ce­su (pole pr_Msg­Port struk­tu­ry Pro­cess). Po­nie­waż nie wiemy, czy w mo­men­cie wy­ko­ny­wa­nia tego kodu wia­do­mość już jest umiesz­czo­na w por­cie, naj­le­piej za­cze­kać na nią funk­cją Wa­it­Port(). Gdy wia­do­mość czeka już przed wy­wo­ła­niem tej funk­cji, funk­cja wróci na­tych­miast. W dru­gim kroku „wyj­mu­je­my” wia­do­mość z portu i za­pa­mię­tu­je­my jej adres w D2.

                    LEA     pr_MsgPort(A2),A0
                    JSR     WaitPort(A6)
                    LEA     pr_MsgPort(A2),A0
                    JSR     GetMsg(A6)
                    MOVE.L  D0,D2

Po­nie­waż re­jestr A0 może zo­stać zmie­nio­ny przez Wa­it­Port(), usta­wiam go jesz­cze raz przed GetMsg(). Czas na wy­wo­ła­nie głów­nej funk­cji pro­gra­mu. Wy­wo­łu­ję ją jako pod­pro­gram, żeby sobie uła­twić np. prze­nie­sie­nie jej do od­dziel­ne­go pliku źró­dło­we­go, mogę też w od­dziel­nym pliku umie­ścić sam kod star­to­wy i uży­wać go w róż­nych pro­gra­mach bez in­ge­ren­cji w źró­dło.

NoWorkbench         BSR.S   Main
                    MOVE.L  D0,D3

Wynik dzia­ła­nia głów­nej czę­ści pro­gra­mu za­pa­mię­tu­ję w re­je­strze D3, żeby póź­niej zwró­cić go sys­te­mo­wi. To nie jest wy­ma­ga­ne i pro­gram może po pro­stu wy­ze­ro­wać D0 przed wyj­ściem. Zwra­ca­nie kodu błędu sys­te­mo­wi jest naj­czę­ściej uży­wa­ne przez po­le­ce­nia kon­so­li. War­tość zero ozna­cza brak błędu.

Po po­wro­cie z głów­ne­go pro­gra­mu nad­cho­dzi pora na zwrot wia­do­mo­ści star­to­wej. Ro­bi­my to tylko wtedy, gdy pro­gram był uru­cho­mio­ny z WB, a więc za­war­tość re­je­stru D2 prze­cho­wu­ją­ce­go nam adres tej wia­do­mo­ści jest nie­ze­ro­wa. Dal­szy kod może się wydać za­ska­ku­ją­cy, bo­wiem wy­wo­ły­wa­na jest funk­cja For­bid(), która za­trzy­mu­je wie­lo­za­da­nio­wość, a ści­ślej prze­łą­cza­nie pro­ce­so­ra mię­dzy pro­gra­ma­mi. Do­pie­ro potem funk­cją Re­plyMsg() od­sy­łam wia­do­mość Work­ben­cho­wi. Na pozór nie ma to sensu, bo żeby dzia­ła­ła wy­mia­na wia­do­mo­ści, mul­ti­ta­sking musi być włą­czo­ny. Spra­wę wy­ja­śnia fakt, że je­że­li pro­ces wy­wo­ła For­bid() i za­koń­czy dzia­ła­nie, to prze­łą­cza­nie pro­ce­sów zo­sta­je au­to­ma­tycz­nie przy­wró­co­ne. W ten spo­sób uzy­sku­je­my pew­ność, że Work­bench otrzy­ma wia­do­mość o za­koń­cze­niu pro­ce­su do­pie­ro wtedy, gdy on się fak­tycz­nie za­koń­czy i można jego kod bez­piecz­nie usu­nąć z pa­mię­ci. Bez tego mo­gło­by dojść do sy­tu­acji, że z pa­mię­ci zo­sta­je usu­nię­ty kod, który jesz­cze jest wy­ko­ny­wa­ny i mo­gło­by to się skoń­czyć za­wie­sze­niem 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 pro­gram głów­ny pro­po­nu­ję zmo­dy­fi­ko­wa­ną wer­sję „Hello World!”, która wy­świe­tla ten napis w sys­te­mo­wym aler­cie. Dla­cze­go tak? Wy­pi­sa­nie tek­stu do kon­so­li w przy­pad­ku uru­cho­mie­nia pror­ga­mu z Work­ben­cha nie da żad­ne­go efek­tu, bo wtedy pro­gram nie ma okien­ka kon­so­li. Co­kol­wiek wy­sła­ne na stan­dar­do­we wyj­ście idzie wtedy „w ko­smos”. Na­to­miast funk­cja Di­splay­Alert() dzia­ła w obu wy­pad­kach tak samo. Za­czy­nam od otwar­cia in­tu­ition.li­bra­ry, na­stęp­nie wy­wo­łu­ję funk­cję, potem za­my­kam bi­blio­te­kę i wy­cho­dzę.

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 ar­gu­men­tów funk­cji Di­splay­Alert(). W re­je­strze D0 umiesz­cza­my numer błędu sys­te­mo­we­go. Po­nie­waż nasz alert jest tylko żar­tem, numer jest do­wol­ny (nie jest wy­świe­tla­ny), mu­si­my jed­nak pa­mię­tać, że naj­star­szy bit nu­me­ru wy­bie­ra ro­dzaj Guru. Je­że­li jest ska­so­wa­ny, tak jak w przy­kła­dzie, otrzy­mu­je­my Guru żółte, po któ­rym na­stę­pu­je po­wrót do pro­gra­mu. Usta­wie­nie tego bitu daje Guru czer­wo­ne, które koń­czy się re­star­tem Amigi. W D1 mamy ilość linii ob­ra­zu po­trzeb­nych do wy­świe­tle­nia aler­tu. Sama ramka zaj­mu­je 16 linii ob­ra­zu, zaś jedna linia tek­stu 8 linii, bo tekst jest za­wsze pi­sa­ny sys­te­mo­wą czcion­ką Topaz/8. W A0 znaj­du­je się tekst do wy­świe­tle­nia, ale jest on po­prze­dzo­ny dwie­ma da­ny­mi. Naj­pierw na­stę­pu­je 16-bi­to­wa licz­ba bę­dą­ca po­zio­mą po­zy­cją tek­stu w pik­se­lach, potem po­zy­cja pio­no­wa, rów­nież w pik­se­lach, ale dla od­mia­ny jako jeden bajt. Na­stęp­nie mamy łań­cuch tek­sto­wy za­koń­czo­ny zerem, po czym jesz­cze jeden bajt sy­gna­li­zu­ją­cy, czy po nim na­stę­pu­ją ko­lej­ne czę­ści tek­stu do wy­świe­tle­nia (war­tość różna od zera), czy nie (zero). Ewen­tu­al­ne ko­lej­ne czę­ści mają taki sam układ.

IntuiName           DC.B    "intuition.library",0
Hello               DC.W    16
                    DC.B    16,"Hello World!",0,0

Sys­te­mo­wy alert jest dość to­por­ną formą ko­mu­ni­ka­cji z użyt­kow­ni­kiem i po­wi­nien być uży­wa­ny tylko przy na­praw­dę ka­ta­stro­fal­nych błę­dach. Jego istot­ną wadą jest to, że za­wsze jest wy­świe­tla­ny w try­bie PAL, więc użyt­kow­ni­cy mo­ni­to­rów nie wy­świe­tla­ją­cych tego trybu nie zo­ba­czą go, o ile nie mają scan­double­ra.

Oma­wia­ny kod na GitHubie