Tutorials: Mala škola C-a VI
by Milan GOLUBOVIĆ milang@iname.com



Funkcije Amiga OS-a

Najveći broj ljudi koji, recimo, sa BASIC-a prelaze na C (a koji su "kao" ukapirali pointere) ovdje odustaju. Zato će ovaj nastavak na neki način predstavljati prekretnicu u vašem učenju C-a. Dajte sve od sebe da vam i najmanji djelić teksta bude jasan, a ja ću se s moje strane potruditi da sve što bolje objasnim uz dobro komentarirane primjere.

Sve funkcije iz Amiga OS-a su organizirane po bibliotekama prema srodnosti. Neke biblioteke se nalaze u ROM-u vaše prijateljice, a neke se po potrebi učitavaju s diska. Vrlo praktičan način, jer će vaš program koristiti samo one biblioteke koje su mu potrebne, pa će samim tim biti manje zahtjevan. U uglavnom svim BASIC-ima se na vaš program "nakače" sve postojeće funkcije, pa i program s jednom jedinom naredbom kada se kompajlira zauzme 50-ak i visš KB (čast izuzecima).

Da bi koristili funkciju neke biblioteke tu biblioteku moramo prvo otvoriti. To važi za svaku biblioteku osim za "exec". A i logično je, jer da i nju treba otvoriti u startu ne bi imali ni jednu dostupnu funkciju, pa ne bi imali čime otvarati biblioteke. :))

Dakle pogađate - funkcija, koja otvara biblioteku, se nalazi u exec biblioteci. Exec je ustvari kernel AmigaOS-a i, pored otvaranja biblioteka, upravlja taskovima, memorijom, radom s listama i još nekim stvarima. Exec je jedina biblioteka čija se adresa može saznati bez pozivanja funkcija i nalazi se u memorijskoj lokaciji 4. To je jedina memorijska lokacija za koju možete biti sigurni šta sadrži. Namjena svih ostalih se može mijenjati i ako im se direktno obraćate, bez prethodne alokacije VI PROGRAMIRATE PRLJAVO! To je najčešći razlog nekompatibilnosti i pucanja starijih programa.
Recimo lijev gumb miša može se očitati u 6. bitu mem. lokacije 0xbfe001. To je do sada "palilo", ali apsolutno vam ništa ne garantira da će to važiti i za ubuduće!!! Zato se držite OS funkcija. Ok, upozoreni ste; krenimo sada sa praksom.

Većina novih kompajlera sama otvara biblioteke (SAS, Storm, VBCC, GNU), pa nema potrebe da vi to radite, već je dovoljno inkludirati željeni header, ali za svaki slučaj da vidimo kako se i to radi.

Sada na scenu stupa njegovo veličanstvo "Commodore Developers Kit", tzv. programerska biblija. Možda ide uz neke kompajlere, ali ako ga nemate OBAVEZNO ga nabavite. U njemu se nalaze opisi SVIH standardnih funkcija Amiga OS biblioteka. Čak i da imam dobru volju da opišem svaku funkciju to bi i teoretski bilo nemoguće i jednostavno će te MORATI da naučite da se snalazite u njemu sami. A najbolje bi bilo da ga konvertirate iz AutoDoc-a (tekst) u Amiga guide (ili još bolje, presnimite od nekog ko je to već uradio), pa će korištenje biti lakše. Isto to možete naći i u printanom obliku pod imenom "Amiga Rom Kernel Reference Manual".

Bacimo sada pogled na funkcije exec-a:

        AbortIO()
        AddDevice()
        AddHead()

        .        .        .        .

Potražimo neku koja asocira na otvaranje biblioteka. Hmmm, možda "OpenLibrary" ? :) Eto, nije bilo teško.

Nađite sada gdje je funkcija "OpenLibrary" opisana da vidimo kako ćemo je koristiti. Taj opis izgleda ovako:

 
	NAME
				OpenLibrary -- gain access to a library 

	SYNOPSIS 
					library = OpenLibrary(libName, version)
				  D0                    A1       D0	
				
				  struct Library *OpenLibrary(STRPTR, ULONG);
	
	FUNCTION
				This function returns a pointer to a library that was previously
				installed into the system.  If the requested library is exists, and 
				if the library version is greater than or equal to the requested 
				version, then the open will succeed. 

                        .          .          .



	NOTES
		All calls to OpenLibrary should have matching calls to CloseLibrary!
		
		Libraries on disk cannot be opened until after DOS has been 
		started.

                        .          .          .


	INPUTS
				libName - the name of the library to open
				
				version - the version of the library required.


	RESULTS
				library - a library pointer for a successful open, else zero	
				
	BUGS
				AmigaDOS file names are not case sensitive, but Exec lists are. If
				the library name is specified in a different case than it exists on
				disk, unexpected results may occur.

													.          .          .

	SEE ALSO
				CloseLibrary

Dokumentacija za svaku funkciju se nalazi u ovakvom obliku i sastoji se iz nekoliko blokova:

NAME - iza čega slijedi ime funkcije i kratak opis čemu služi.

SYNOPSIS - pokazuje način pozivanja iz C-a, a u drugom redu je objašnjenje u kom registru CPU-a treba staviti koji parametar ako funkciju pozivamo iz asemblera (nebitno za C).

Treći red je standardna deklaracija C funkcije (iz koje možemo vidjeti koje tipove podataka funkcija očekuje i šta vraća).

FUNCTION - Nešto detaljniji opis funkcije i način korištenja.

NOTES - Važne napomene i upozorenja.

INPUTS - Opisuje ulazne parametre.

RESULTS - Opisuje parametar koji funkcija vraća.

EXAMPLE - Za neku funkciju je dat i primjer korištenja.

BUGS - Greške koje funkcija eventualno ima/pravi.

SEE ALSO - Zadnja stavka koja nas upućuje na funkcije koje su u nekoj relaciji s ovom.

(Može se desiti da poneki od blokova i izostane.)

Da vidimo kako stoje stvari s našom funkcijom (OpenLibrary):
Ime znamo, a iz gore navedenog vidimo da zahtjeva dva parametra: ime i verziju biblioteke. Iz BUGS napomene vidimo da moramo voditi računa o malim i velikim slovima (uglavnom mala). Version je najmanja verzija biblioteke s kojom naš program može da radi, jer možda starija verzije biblioteke nema funkciju koja nam je potrebna ili je ima, ali je bagovana.

Za nesretnike s kompajlerima koji sami ne otvaraju biblioteke slijedi primjer. Ostali ga mogu i preskočiti da se ne bi zbunjivali bez potrebe. Primjer otvara DOS biblioteku i iz nje koristi funkciju Delay (pogledajte autodoc iz Developera za DOS biblioteku) koja pravi pauzu (u pedesetinkama) i ništa više:

#include <proto/exec.h>
#include <proto/dos.h>
struct DosLibrary *DOSBase;
main()
{
        DOSBase=(struct DosLibrary *)OpenLibrary("dos.library",0);
        if(DOSBase) {
                Delay (100);
                CloseLibrary ((struct Library *)DOSBase);
        }
}

Definiramo pointer tipa "DosLibrary" pod imenom "DOSBase" u koji će se smjestiti adresa DOS biblioteke pošto je otvorimo sa OpenLibrary. Zatim pozivamo funkciju Delay s parametrima "dos.library" i 0 (verzija nam nije važna, jer sve verzije imaju ovu funkciju). Da smo recimo za verziju stavili 39 naš program ne bi radio na Kickstart-ima ispod 3.0 , dok bi racimo sa 49 ne bi uopće radio jer ta verzija biblioteke jos uvijek ne postoji.

Kod nekih funkcija u AutoDoc-ovima je također navedeno od koje verzije biblioteke ta funkcija postoji.

Zatim provjeravamo da li je poziv uspješan - biblioteka otvorena. Ako jeste pozivamo funkciju Delay iz nje, ona čeka dvije sekunde i obavezno zatvaramo biblioteku funkcijom "CloseLibrary". Ako je sve bilo uspješno program će pauzirati i završiti se, a ako nije završit će se odmah.

Primjećuje se jedna nelogičnost: u Developer-u je navedeno da OpenLibrary vraca pointer na strukturu tipa Library, a mi sadržaj smještamo u pointer na strukturu tipa DosLibrary ?!? Pogled na

include:dos/dosextens.h

otkriva da je structura "Library" dio strukture "DosLibrary" i za sve ovo postoji racionalno objašnjenje, mada neki ipak vjeruju da je to dio zavjere iskusnih programera protiv programera početnika. :)

Kada se inkluduju neki "proto/nešto.h" headeri oni nadalje inkluduju skoro sve potrebne header fajlove za rad sa tom bibliotekom, ali nisu svi "proto" header fajlovi isti za sve kompajlere (neki ih čak i nemaju!), pa je ponekad potrebno navesti još po neki header fajl. Npr. iako imate:

#include <proto/exec.h>

morate dodati:

#include <exec/memory.h>

da bi mogli koristiti neke stvari u vezi rada s memorijom.

Srećom, ako imate (a najvjerojatnije imate) "pametan" kompajler koji će ovaj zbunjujući posao odraditi umjesto vas, cijeli ovaj program bi izgledao ovako:

#include <proto/dos.h>
main()
{
                Delay (100);
}

Ovako već može! Inkludujemo dos header i odmah nam je na raspolaganju Delay funkcija.

Bacite pogled i na ostale funkcije DOS biblioteke. Tu ćete naći funkcije za rad s fajlovima, alternativne onim objašnjenim u prošlom nastavku (vidi primjer).

Napišimo sada programčić koji će otvoriti prozor i naškrabati nešto u njemu:

Da vidimo prvo koje nam sve biblioteke trebaju.
Prvo, treba nam intuicija, jer se u njoj nalazi funkcija za otvaranje prozora. Intuicija dakle otvara prozore, ekrane, postavlja gadget-e i uopće vodi računa o interfejsu (GUI-u) programa.
Zatim, da bi nesto u tom prozorčiću iscrtali trebaju nam usluge grafičke biblioteke.
I na kraju, da sve to ne bi samo trepnulo pred našim očima inkludujemo i dos biblioteku zbog, sada nam već poznate, funkcije Delay.

#include <proto/intuition.h>
#include <proto/dos.h>
#include <proto/graphics.h>

Ok. Da vidimo sada kako ćemo otvoriti prozor. U developeru među funkcijama intuition biblioteke tražimo funkciju koja to radi i nalazimo "OpenWindow".

NAME
  OpenWindow -- Open an intuition window. 


SYNOPSIS
    Window = OpenWindow( NewWindow )
    D0                   A0

    struct Window *OpenWindow( struct NewWindow * ); 

      .   .   .

Odavde zaključujemo da funkcija OpenWindow kao parametar očekuje pointer na strukturu NewWindow tipa, a da vraća pointer na strukturu Window tipa. Dakle struktura NewWindow tipa sadrži podatke koje mi postavljamo i koji pokazuju kakav prozor želimo da nam intuicija otvori (dimenzije, položaj ...).
Pitanje se nameće kako da saznamo koji članove te strukture?

Ako imate Developer u AmigaGuide formatu riječ NewWindow će biti gadget s linkom na header fajl u kome je deklarirana ta struktura (intuition/intuition.h, koji bude inkludiran iz proto/intuition.h headera).
A ako ne, potražite u dijelu koji opisuje inuition biblioteku željenu strukturu.

Tu ćemo naći nešto nalik na:



/* ======================================================== */
/* === NewWindow ========================================== */
/* ======================================================== */
/*
 * Note that the new extension fields have been removed.  Use ExtNewWindow
 * structure below to make use of these fields
 */
struct NewWindow
{
    WORD LeftEdge, TopEdge;		/* screen dimensions of window */
    WORD Width, Height;			/* screen dimensions of window */

    UBYTE DetailPen, BlockPen;		/* for bar/border/gadget rendering */

    ULONG IDCMPFlags;			/* User-selected IDCMP flags */

    ULONG Flags;			/* see Window struct for defines */

    /* You supply a linked-list of Gadgets for your Window.
     *	This list DOES NOT include system Gadgets.  You get the standard
     *	system Window Gadgets by setting flag-bits in the variable Flags (see
     *	the bit definitions under the Window structure definition)
     */
    struct Gadget *FirstGadget;

    /* the CheckMark is a pointer to the imagery that will be used when
     * rendering MenuItems of this Window that want to be checkmarked
     * if this is equal to NULL, you'll get the default imagery
     */
    struct Image *CheckMark;

    UBYTE *Title;			  /* the title text for this window */

    /* the Screen pointer is used only if you've defined a CUSTOMSCREEN and
     * want this Window to open in it.	If so, you pass the address of the
     * Custom Screen structure in this variable.  Otherwise, this variable
     * is ignored and doesn't have to be initialized.
     */
    struct Screen *Screen;

    /* WFLG_SUPER_BITMAP Window?  If so, put the address of your BitMap
     * structure in this variable.  If not, this variable is ignored and
     * doesn't have to be initialized
     */
    struct BitMap *BitMap;

    /* the values describe the minimum and maximum sizes of your Windows.
     * these matter only if you've chosen the WFLG_SIZEGADGET option,
     * which means that you want to let the User to change the size of
     * this Window.  You describe the minimum and maximum sizes that the
     * Window can grow by setting these variables.  You can initialize
     * any one these to zero, which will mean that you want to duplicate
     * the setting for that dimension (if MinWidth == 0, MinWidth will be
     * set to the opening Width of the Window).
     * You can change these settings later using SetWindowLimits().
     * If you haven't asked for a SIZING Gadget, you don't have to
     * initialize any of these variables.
     */
    WORD MinWidth, MinHeight;	    /* minimums */
    UWORD MaxWidth, MaxHeight;	     /* maximums */

    /* the type variable describes the Screen in which you want this Window to
     * open.  The type value can either be CUSTOMSCREEN or one of the
     * system standard Screen Types such as WBENCHSCREEN.  See the
     * type definitions under the Screen structure.
     */
    UWORD Type;

};

Sada je potrebno da definiramo našu strukturu koja će biti tipa NewWindow, i da redom popunimo sve članove iste. Svi članovi te strukture opisani su gdje je opisana i OpenWindow funkcija (korisnici AmigaGuide verzije treba samo da kliknu na "Retrace" gadget).

Dakle, nazovimo tu našu strukturu, recimo, defwin (kao što rekosmo tipa NewWindow, čija je definicija već inkludirana):

        struct NewWindow defwin={

Zatim gledamo članove:

    WORD LeftEdge, TopEdge;		/* screen dimensions of window */
    WORD Width, Height;			/* screen dimensions of window */

i popunjavamo :

        struct NewWindow defwin={0, 0, 320, 256, .....

i tako redom.

IDCMPFlags i Flags su ULONG-ovi čiji bitovi govore da li je neka opcija uključena (1) ili ne (0). Oni su također opisani, a postoje i definirane konstante, pa treba samo željenu konstantu OR-ati (i tako setirati željeni bit) da bi se odabrala željena opcija:

WFLG_DRAGBAR | WFLG_DEPTHGADGET,

Kod imena prozora, gdje je potrebno proslijediti pointer na string (niz karaktera) stavljamo samo tekst pod znacima navoda, a ostalo će odraditi kompajler.

IDCMP flegovi i njihova upotreba će detaljnije biti opisani u slijedećem nastavku.

Funkcija OpenWindow, kao što rekosmo vraća pointer na strukturu tipa Window, pa je potrebno prije toga i stvoriti takav pointer (neka se zove "win"):

        struct Window *win;

On će nam kasnije koristiti pri obraćanju našem novom prozoru.
Pogledajte također koje sve članove sadrći ta struktura. Neki nam mogu biti vrlo korisni (npr. položaj pointera u prozoru, dimenzije prozora...). Te parametre možete samo čitati (read only), a ne smijete ih upisivati direktno, već funkcijama predviđenim za to.

Na kraju je potrebno pozvati funkciju:

win = OpenWindow ( &defwin );

Sve je jasno: proslijeđuje se adresa nasš strukture, a funkcija vraća pointer na win strukturu u kojoj se nalaze podaci o novonastalom prozoru. Ako poziv nije uspio funkcija će vratiti nulu (NULL), pa je u tom slučaju potrebno samo izaći iz programa. Zato se poziv f-je stavlja unutar if-a:

	if (win = OpenWindow ( &defwin ))

  {

    sve je Ok. Uradi sta treba.

    zatvori prozor

  }
  
  kraj

Sada, kada je prozor otvoren da vidimo kako ćemo nešto iscrtati u njemu.
Gledamo funkcije grafičke biblioteke, na primjer "RectFill".


  NAME 
      RectFill-- Fill a rectangular region in a RastPort.

  SYNOPSIS


      RectFill(rp, xmin, ymin, xmax, ymax)
              a1  d0:16 d1:16 d2:16 d3:16

      void RectFill( struc RastPort *, SHORT, SHORT, SHORT, SHORT );

   .    .    .

Jasno nam je čemu služi (iscrtava pupunjeni pravoukutnik) i jasno nam je kako se zadaju koordinate (X i Y gornjeg lijevog ugla, pa X i Y donjeg desnog ugla). Jedino nam nije jasno ono "RastPort".
Čemu li to služi?
Pa nekako moramo objasniti grafičkoj biblioteci gdje želimo crtati. U strukturi RastPort se nalaze svi podaci o prozoru (ili nečem drugom - ne mora se samo crtati u prozoru) u koji želimo da se iscrta pravokutnik i funkcija RectFill ih odatle koristi.
Ali odakle nama pointer na RastPort strukturu?
To je lako. Jedan od članova strukture koju stvara funkcija OpenWindow i čiji pointer ona vraća (win) je i pointer na RastPort strukturu našeg prozora.

Dakle, možemo crtati po svemu čiji RastPort imamo. Potrebno je samo proslijediti adresu te RastPort strukture.

Za razliku od Window tipa strukture koji sadrži pointer na strukturu tipa RastPort i taj pointer se zove RPort, pri otvaranju ekrana (vrlo sličan postupak) dobijamo pointer na strukturu tipa Screen koja ne sadrži pointer na RastPort strukturu, već samu RastPort strukturu. Pročitajte još nekoliko puta prethodnu rečenicu i biće vam jasnije. :))

Sada ćemo pozvati funkciju RectFill:

RectFill(win->RPort,100,100,150,150);

Dakle, RectFill očekuje pointer na RastPort, a mi imamo pointer na strukturu koja sadrži taj pointer koji se zove RPort, pa zato pišemo "win->RPort.
"->" označava da win nije struktura, već pointer na nju.

U slučaju da smo otvarali ekran, a ne prozor (funkcijom "OpenScreen") od funkcije bi dobili pointer na strukturu tipa Screen (ima istu funkciju kao i kod otvaranja prozora), a ta struktura bi sadržala samu strukturu RastPort.
Sada, ako bi htjeli da crtamo po ekranu, stavili bi nešto ovako:

RectFill ( &(scr->RastPort)),100,100,150,150);

Dakle, proslijedili bi adresu (pointer na) RastPort strukturu.

Uh!

Svim funkcijama grafičke biblioteke se također proslijeđuje pointer na RastPort i objašnjenje je isto. Ostali parametri tih funkcija su očigledni i mislim da ih ne treba objašnjavati.

Na kraju obavezno treba zatvoriti otvoren prozor funkcijom CloseWindow, kojoj se proslijeđuje pointer "win" koga smo pri otvaranju dobili.

Evo najzad cijelog programa:

#include <proto/intuition.h>
#include <proto/dos.h>
#include <proto/graphics.h>
main()
{
	struct NewWindow defwin={
0, 0,                            // Gornji lijevi kut
320, 256,                        // Dimenzije prozora
1,0,                             // Boje kojima ce se po default-u crtati po prozoru
0,                               // IDCMP flag-ovi, o njima kasnije
WFLG_DRAGBAR | WFLG_DEPTHGADGET, // promena velicine i skrivanje prozora
NULL,                            // Bez gadget-a
NULL,                            // Bez slicice
"Prozorcic",                     // Ime prozora
NULL,                            // Na kome ekranu da se otvori prozor
NULL,                            // Ne alociramo sami mem. za prozor 
0,0,                             // minimalne dimenzije prozora
320,256,                         // maksimalne dimenzije prozora
WBENCHSCREEN,                    // Na workBench-u
};


	struct Window *win; //pointer na strukturu tipa "Window"


	if (win = OpenWindow ( &defwin )) // da li je otvaranje prozora uspelo?
	{

		RectFill(win->RPort,100,100,150,150); // crtaj pravougaonik
		Move(win->RPort,300,200);            // pomeri graficki kursor na(300,200)
		SetAPen(win->RPort,2);               //nadalje koristi boju 2
		Draw(win->RPort,150,50);           //  crtaj liniju (300,200)-(150,50)
		Move(win->RPort,100,100);         // pomeri G.K. na (100,100)
		SetAPen(win->RPort,1);             // boja 1
		Text (win->RPort,"Text",4);     // ispisi tekst "Text" duzine 4 slova
		Delay(150);	

		CloseWindow (win);   // Obavezno moramo zatvoriti sve sto smo otvorili

	}

}

Na kraju vam dajem pomenutu funkciju za PRLJAVO čitanje gumba miša. U principu, tako ne bi trebalo raditi, ali kako ovaj način ne pravi probleme ni na jednom dosadašnjem tipu Amige... a za buduće ćemo tek vidjeti šta ćemo i kako ćemo.

#include <exec/types.h> //Zbog UBYTE tipa-dodajte kad god koristite ovu f-ju!
#include <stdio.h>
int MouseKey(void);

main()
{
	while (MouseKey()!=3)  // Dok se ne pritisnu oba gumba
 	 printf("%d\n",MouseKey());
}

int MouseKey(void)
{
	register UBYTE *LMB=(UBYTE *)0xbfe001;
	register UBYTE *RMB=(UBYTE *)0xdff016;
	register int a=0;
	if ((*LMB & 64)== 0) a=1;
	if ((*RMB & 4) == 0) a=a+2;
	return a;
}

Ne zaboravite samo da inkludirate exec/types.h .

Kad smo već gore spomenuli ostale funkcije DOS biblioteke neće škoditi još jedan primjer. To je, ustvari, isti onaj primjer iz prošlog nastavka, ali sada s korištenjem funkcija DOS biblioteke:

#include <stdio.h>
#include <proto/dos.h>        // Dakle DOS
main (int argc, char *argv[]) // Ovo više ne treba objašnjavati.
{
	BPTR in;  // 2 BPTR-a. Ne pointera!
	BPTR out; // jer treba otvoriti 2 fajla
	char c;   // pomoćna varijabla za privremeni smještaj podataka
	if (argc!=3) // da li je pogrešno pozvan program?
	{
		Printf("Usage: convert pcfile amigafile\n");
		return(0); // ako jeste izađi iz programa.
	}
	in=Open(argv[1],MODE_OLDFILE); // otvaramo fajl za čitanje 
	if (in==0)  // Jel' fajl pronađen ?
	{
		Printf("Can't open file %s.\n",argv[1]);
		return(1); // Ako nije opet je greška po sredi
	}
	out=Open(argv[2],MODE_NEWFILE); // otvori fajl za snimanje 
	if(out==0)  // jel' to uspješno obavljeno?
	{
		Close(in); // Ako nije potrebno je zatvoriti prethodno otvoreni 
		Printf("Can't save file.\n");  // ispisati prigodnu poruku
		return(2);    // i izaći iz programa
	}
	Printf("Converting %s to %s.\n",argv[1],argv[2]); // najzad je sve Ok.

	while ((c=FGetC(in))!=EOF) // Char-ovi se uzimaju dok se ne naiđe na EOF 
	{
		if (c!=13)  // ako c nije 13 (CR)
			FPutC(out,c); // treba ga snimiti
	}
	Close(in);  // sve što je otvoreno
	Close(out); // mora se uvijek i zatvoriti.
	Printf("Finished!\n");  // gotovo (napokon)
}

Umjesto fopen koristimo ekvivalentnu Open funkciju iz DOS-a i sl. Također, kod FPutC funkcije primjetite da je zamijenjen redoslijed parametara. Još jedna interesantna stvar koju ste, nadam se, primetili: "printf" funkcija iz stdlib-a je zamijenjena "Printf" funkcijom iz DOS biblioteke (veliko prvo slovo).
Uporedite dužine kompajliranih programa.

Za ovaj broj toliko, a vi eksperimentirajte što više, probajte da ubacite i neku funkciju iz grafičke biblioteke koju gornji program ne sadrži. Ili npr. napravite da program umjesto 3 sekunde čeka lijevi gumb miša i sl.

Happy coding!