home *** CD-ROM | disk | FTP | other *** search
/ Compendium Deluxe 2 / LSD and 17bit Compendium Deluxe - Volume II.iso / a / prog / cprog / ctutorial.lha / CTutorial.txt
Encoding:
Text File  |  1994-09-29  |  115.0 KB  |  2,533 lines

  1.  
  2.  
  3.              /*** Copyright © 1988 by Joseph M. Hinkle ***/
  4.  
  5.     This is raw text prepared for articles and a book on programming the
  6. Amiga for novices with the intent of quickly getting the reader into the
  7. more powerful features fairly quickly.  There are some errors of
  8. explanation of more abstruse things which I need to clean up, but in the
  9. main() (Ha! a little c joke) you will find this useful.  Because it is a
  10. kind of super-outline of the book it moves along at a pace you may find
  11. breathtaking if you are new to programming the machine, but careful study
  12. of this and the ROM Kernel Manuals will help you greatly.
  13.     The tutorial explains lists, tasks, and startup code with comments on
  14. programming style and example programs which I have tested thoroughly.  The
  15. discussion and examples are valid for Workbench v1.2 (v1.3 is just becoming
  16. available as I post this, so I haven't seen anything on it yet, but I doubt
  17. there would be much trouble using that version).  I use the Lattice v4.01
  18. compiler and don't have Manx, so can't say much about compiling with that
  19. one.  Expect some minor trouble, but the examples are Amiga specific, not
  20. compiler specific, for the most part.
  21.     I hang around A-Link in Everett, Washington at (206) 774-4735, run by
  22. the charming and gracious John (He paid me to say this) Willott, and I go
  23. by the name Marty Hinkle on the board.  Please send me questions and
  24. comments there.
  25.     I also have a tutorial on making shared libraries yourself without using
  26. assembly code (other than the skeleton in the RKM) which does not require
  27. you to be proficient in assembly coding.  If someone is interested in that,
  28. I'll straighten it up and upload it.
  29.    This production comes from extensive study of the machine itself with
  30. little input from Commodore (It is probably quite shortsighted of me to not
  31. be in their developer's program).  A shareware style donation will not be
  32. inappropriate, and you will get a free copy of the whole book if I can ever
  33. get a publisher.
  34.  
  35.                                                   Joseph M. Hinkle
  36.                                                   Route 2, Box 2647
  37.                                                   Lopez, Washington 98261
  38.  
  39.                                                   Posted September 20, 1988
  40.  
  41. /*************************************************************************/
  42.  
  43. Programming the Amiga
  44. From Novice to Advanced
  45.  
  46.     Anyone getting the idea to write a useful program on the Amiga decides
  47. to get the Rom Kernel Manual and DOS manual to learn how the system works.
  48. The size of the manuals gives one pause.  Reading them is daunting.  After
  49. a bit of study one realizes there is no good overview of the executive and
  50. disk operating system, but there is plenty of detail.  Trying out some of
  51. the example programs can lead to odd results as there are many small
  52. mistakes in them which one can overlook.  After getting something to work
  53. many questions are left, such as "What will make this program work under
  54. Workbench?", "How can this program be made to multitask?", "What is a
  55. process?", and "How can this program be loaded by another?".  All of these
  56. are mentioned in the books and there is much discussion of them, but
  57. nowhere are there clear examples of useful programs that implement these
  58. things.  You will find, after some experience with the machine, that nearly
  59. every point is mentioned somewhere in the books.  Tying them all together
  60. is the difficult part.
  61.     I will cover these questions and more by showing a simple program to
  62. establish a style, then using that program to build ever more involved
  63. programs which will take you through the heart of the Amiga.  I will refer
  64. to the RKM (ROM Kernel Manual, Addison-Wesley, in four volumes: ROM Kernel
  65. Reference Manual: Libraries and Devices,  ROM Kernel Reference
  66. Manual:Exec, Intuition Reference Manual, and Hardware Reference Manual),
  67. and the AmigaDOS Manual, Bantam Books.  These might appear pricey, but for
  68. serious programming on the Amiga they are indispensable.  If you are new
  69. to the c programming language you should also have a text on c such as the
  70. original language definition "The c Programming Language" by Kernighan and
  71. Ritchie, Prentice-Hall, or another textbook on the subject.  Make sure you
  72. understand data structures because there will be a lot of references to
  73. them.  Let's get started:
  74.     We will need a timer for this experiment, something that goes tick,
  75. tick, tick, every second or so.  There is a section in the RKM: Libraries
  76. and Devices called Timer Device. To see if everything will work as
  77. advertised, let's try a simple delay.  We need to open a timer, run it,
  78. and then close it.  In order to open a timer we need to fill in a
  79. timerequest structure (RKM:Libraries and Devices, Include Files,
  80. devices/timers.h.  Note the .i structures are for assembly language
  81. writers).  Note the structure is tagged timerequest, not timeRequest as in
  82. the text.  The structure is:
  83.  
  84. struct timerequest {
  85.     struct IORequest tr_node;
  86.     struct timeval    tr_time;
  87. }
  88.  
  89. Aha! A typical Amiga data structure!  It consists of nothing more than
  90. other structures!  Sigh.  There is hope, though.  The struct timeval is in
  91. the same include file:
  92.  
  93. struct timeval {
  94.     ULONG tv_secs;
  95.     ULONG tv_micro;
  96. }
  97.  
  98. But what is a ULONG?  Look in RKM:Exec, Include Files, exec/types.h:
  99.  
  100. typedef unsigned long ULONG;
  101.  
  102. Now we know the timerequest structure is a template for an IORequest
  103. structure and two 32 bit quantities.  At the top of the file timer.h there
  104. is a line:
  105.  
  106. #include exec/io.h
  107.  
  108. So, while you're thumbing through RKM:Exec, look at exec/io.h.  You will
  109. find:
  110.  
  111. struct IORequest {
  112.     struct Message    io_Message;
  113.     struct Device    *io_Device;
  114.     struct Unit        *io_Unit;
  115.     UWORD    io_Command;
  116.     UBYTE    io_Flags;
  117.     BYTE    io_Error;
  118. }
  119.  
  120. That file #includes exec/ports.h, which defines the struct Message and
  121. struct MsgPort.  That file in turn references exec/nodes.h, exec/lists.h
  122. and exec/tasks.h, which contain the definitions needed to completely
  123. specify the struct timerequest (In my compiler's include files there is
  124. no mention of #including exec/types.h, so I have to do that myself.
  125. #including devices/timer.h gets all the rest).  The reason for belaboring
  126. all the various data structures is to show you what happens when a request
  127. is made, that is, just what data is passed around the machine and what
  128. Exec does with it.  Exec, the executive program, supervises the use of the
  129. microprocessor, responding to interrupts and checking tables of things to
  130. do.  It is generally invisible to you but it helps to know it is there,
  131. and we will be using its library of functions often.
  132.     This exercise also gets you used to referencing the RKM, which you will
  133. also be doing often.  Continuing with the full definition of struct
  134. timerequest,  we see it is a template for memory arranged like this,
  135. assuming we call the structure TR:
  136.  
  137. LONG TR.tr_node.io_Message.mn_Node.ln_Succ ; points to the following node
  138. LONG TR.tr_node.io_Message.mn_Node.ln_Pred ; points to the preceding node
  139. BYTE TR.tr_node.io_Message.mn_Node.ln_Type ; defines the type of node
  140. BYTE TR.tr_node.io_Message.mn_Node.ln_Pri  ; this node's priority
  141. LONG TR.tr_node.io_Message.mn_Node.ln_Name ; points to a text string name
  142. LONG TR.tr_node.io_Message.mn_ReplyPort    ; points to a message port
  143. WORD TR.tr_node.io_Message.mn_Length       ; length of this whole block
  144. LONG TR.tr_node.io_Device                  ; points to a struct Device
  145. LONG TR.tr_node.io_Unit                    ; points to a struct Unit
  146. WORD TR.tr_node.io_Command                 ; a command
  147. BYTE TR.tr_node.io_Flags                   ; flags set
  148. BYTE TR.tr_node.io_Error                   ; error returned
  149. LONG TR.tr_time.tv_secs                    ; seconds requested
  150. LONG TR.tr_time.tv_micro                   ; microseconds requested
  151.  
  152.     These 40 bytes are the complete timerequest structure.  We fill in some
  153. members ourselves and functions provided in the system fill in others.  In
  154. particular, we need to fill in what kind of timer it is, how we want the
  155. timer to behave (the command), and when we want the timer to reply to us.
  156.     First, however, we have to allocate some memory for the structure.
  157. There is a routine available which will do that for us, CreateExtIO(), but
  158. one of the things it needs is the address of a message port (struct
  159. MsgPort), which, as you recall from looking in exec/ports.h, is a little
  160. structure itself, starting with a Node structure.  Are you getting the
  161. idea that everything starts with a Node structure?  Just about everything
  162. does, because Exec uses these to link them into Lists (RKM:Exec, Lists)
  163. which are scanned periodically to see if anything needs doing, or when a
  164. routine wants to see if it has any mail.  Whole data structures are not
  165. passed around in the machine, just addresses of nodes, which also happen
  166. to be the first addresses of most structures.  We will get into detail on
  167. that later; suffice to say that if we ever get this memory allocated we
  168. will ask somebody to link our structure into some list or another so it
  169. will get processed.
  170.     There is a good discussion of memory allocation in RKM:Exec, Memory
  171. Allocation but fortunately there is a routine for that, too, for certain
  172. cases such as we need now.  CreatePort(), which needs only a name and a
  173. priority (RKM:Exec, Tasks, et al), will provide us with the address of a
  174. MsgPort structure we can use to call CreateExtIO(), which will provide us
  175. with an address of a timerequest structure, which in turn we will use to
  176. open a timer device.  For now, we will make everything priority 0.
  177.     These routines should be declared as functions returning something to
  178. make life easier as we write the program.  Some, like CreatePort(), always
  179. return the address of a MsgPort structure, so we can declare them
  180. globally. Others, like CreateExtIO() return the address of differently
  181. sized blocks, depending on what we are doing, so they should be cast as
  182. the type we need as we call them.
  183.     We also have to remember to free up the memory when we are done, and
  184. the routines DeletePort() and DeleteExtIO() will do that for us.  After
  185. all this discussion, the problem is again beginning to look simpler.  We
  186. will eventually fix things so they will get simpler yet.  These routines,
  187. by the way, are described in RKM:Libraries and Devices, Library Summaries.
  188.     So here it is:
  189.  
  190. /***** Tick.c ***********************************************************/
  191.  
  192. #include "exec/types.h"
  193. #include "devices/timer.h"
  194.  
  195. struct MsgPort *CreatePort();
  196.  
  197. void
  198. main()
  199. {
  200.     struct MsgPort *TP;
  201.     struct timerequest *TR;
  202.     int error;
  203.  
  204.     TP = CreatePort(NULL, 0);
  205.     if (TP == NULL) {
  206.         printf("Not enough memory for the Message Port\n");
  207.         exit(0);
  208.     }
  209.  
  210.     TR = (struct timerequest *)
  211.                     CreateExtIO(TP, sizeof(struct timerequest));
  212.     if (TR == NULL) {
  213.         printf("Not enough memory for the timerequest\n");
  214.         DeletePort(TP);
  215.         exit(0);
  216.     }
  217.  
  218.     error = OpenDevice("timer.device", UNIT_VBLANK, TR, 0);
  219.     if (error > 0) {
  220.         printf("The timer won't open\n");
  221.         DeleteExtIO(TR, sizeof(struct timerequest));
  222.         DeletePort(TP);
  223.         exit(0);
  224.     }
  225.  
  226.     TR->tr_node.io_Command = TR_ADDREQUEST;
  227.     TR->tr_time.tv_secs = 5;
  228.     TR->tr_time.tv_micro = 0;
  229.  
  230.     DoIO(TR);
  231.  
  232.     printf("Tick\n");
  233.  
  234.     CloseDevice(TR);
  235.     DeleteExtIO(TR, sizeof(struct timerequest));
  236.     DeletePort(TP);
  237.  
  238. }
  239.  
  240. /***********************************************************************/
  241.  
  242.     You did read the section RKM: Timer Device didn't you?  So you know
  243. what a UNIT_VBLANK is, and that the constants in capital letters are
  244. defined in the appropriate include files.  I also included error checks
  245. in a crude way for good programming practice.  Compile this and play with
  246. it a minute or two.
  247.     All we have done here is allocate memory for the two structures that we
  248. use, initialized them with our values, requested the system to link them
  249. into the appropriate lists, and waited.  Five seconds later, our routine
  250. wakes up, prints a "Tick" message, and exits.  If you don't believe that,
  251. change the value of TR->tr_time.tv_secs to about ten or fifteen, recompile
  252. it, and RUN Tick.  A new CLI will be created, and for the time you have
  253. called for you will be able to perform a DOS command like Date in your
  254. original CLI.  In the time you have specified, the "Tick" message will
  255. appear and the background CLI will end.  You've been multitasking!  The
  256. trouble is that the manuals don't clearly show how you can accomplish
  257. that from within a program.
  258.     You have read the RKM: Exec, Input/Output so you saw the mention of
  259. DoIO() and the other functions for doing IO.  In the program we tried, the
  260. function DoIO() was used to make things simple.  We built the appropriate
  261. structures and DoIO() requested the timerequest structure to be linked
  262. into a message port somewhere.  Did you notice in your earlier perusal of
  263. the exec/ports.h file that the last item in a struct MsgPort is a struct
  264. List?  Not a pointer to a structure, but an actual structure.  The ln_Pred
  265. and ln_Succ fields of your timerequest structure are changed by Exec to
  266. point to the proper elements of that list.  When your time is up Exec
  267. changes the pointers to link the message (timerequest structure) onto the
  268. MsgPort you called for in CreatePort().  DoIO() sees there is mail, sets
  269. any error codes or flags, and exits.  We will be doing quite a bit with
  270. this timer program, so it would be better if we had more convenient
  271. routines to handle allocation and deallocation for us, as well as setting
  272. and stopping a timer.  So, before we continue, let's build a file of timer
  273. utilities to help us.  You might want to modify these somewhat to suit your
  274. specific needs.  In particular, SetTimer() does not now set the
  275. microseconds part of the timerequest so only even second periods are
  276. available.  If you wanted, you could change the formal variable to a double
  277. and do the math to get microseconds right in the routine.
  278.     An important note about compilers:  The stack checking routines various
  279. compilers put into the code will not work in task code.  Since we expect to
  280. be using these routines within tasks, they and any of the following routines
  281. which will become part of task code must be compiled with stack checking
  282. disabled.  There is a further consideration if you are using Lattice v4.0:
  283. When task code is added to the system task list, register A4 is not
  284. preserved, thereby destroying the code's reference to its data sections.
  285. There is a compiler option to cause it to generate register saving code at
  286. the beginning of each function call.  It costs only 24 bytes per function,
  287. and is necessary only for functions referenced by AddTask() or
  288. CreateTask() (q.v.).  It's unlikely any of these would be, but you never
  289. know.  For Lattice 4.0 the compiler command line would be:
  290.  
  291.     lc -v -y TimerUtilities
  292.  
  293.     Carefully check your compiler documentation for any special treatment
  294. required for task code.
  295.  
  296. /***** TimerUtilities.c *************************************************/
  297. /*                                                                                                */
  298. /*        A package of utilities to control timer devices.    This package    */
  299. /* contains:                                                                                */
  300. /*                                                                                                */
  301. /*        CreateTimer(unit, priority)                                                    */
  302. /*            returns: Pointer to a struct timerequest or zero if trouble        */
  303. /*        AbortTimer(timerequest)                                                            */
  304. /*            returns: Nothing                                                                */
  305. /*        SetTimer(time)                                                                        */
  306. /*            returns: Nothing                                                                */
  307. /*        DeleteTimer(timerequest)                                                        */
  308. /*            returns: Nothing                                                                */
  309. /*                                                                                                */
  310. /************************************************************************/
  311.  
  312. #include "exec/types.h"
  313. #include "devices/timer.h"
  314.  
  315. extern struct MsgPort * CreatePort();
  316.  
  317. struct timerequest *
  318. CreateTimer(unit, priority)
  319.     ULONG unit;
  320.     LONG priority;
  321. {
  322.     struct MsgPort *TP;
  323.     struct timerequest *TR;
  324.  
  325.     if ((TP = CreatePort(NULL, priority)) == NULL)
  326.         return(NULL);
  327.  
  328.     if ((TR = (struct timerequest *)
  329.             CreateExtIO(TP, sizeof(struct timerequest))) == NULL) {
  330.         DeletePort(TP);
  331.         return(NULL);
  332.     }
  333.  
  334.     if (OpenDevice(TIMERNAME, unit, TR, 0) != NULL) {
  335.         DeleteExtIO(TR, sizeof(struct timerequest));
  336.         DeletePort(TP);
  337.         return(NULL);
  338.     }
  339.  
  340.     return(TR);
  341. }
  342.  
  343. void
  344. AbortTimer(T)
  345.     struct timerequest *T;
  346. {
  347.     if (AbortIO(T) == NULL) {
  348.         Wait(1 << T->tr_node.io_Message.mn_ReplyPort->mp_SigBit);
  349.         GetMsg(T->tr_node.io_Message.mn_ReplyPort);
  350.     }
  351. }
  352.  
  353. void
  354. SetTimer(T, time)
  355.     struct timerequest *T;
  356.     int time;
  357. {
  358.         AbortTimer(T);
  359.         T->tr_node.io_Command = TR_ADDREQUEST;
  360.         T->tr_time.tv_secs = time;
  361.         T->tr_time.tv_micro = 0;
  362.         SendIO(T);
  363. }
  364.  
  365. void
  366. DeleteTimer(TR)
  367.     struct timerequest *TR;
  368. {
  369.     struct MsgPort *TP;
  370.  
  371.     if (TR != 0) {
  372.         AbortTimer(TR);
  373.         TP = TR->tr_node.io_Message.mn_ReplyPort;
  374.         CloseDevice(TR);
  375.         DeleteExtIO(TR, sizeof(struct timerequest));
  376.         DeletePort(TP);
  377.     }
  378. }
  379.  
  380. /************************************************************************/
  381.  
  382.     Compile these, but don't link them.  There's nothing to link to yet.
  383. Notice the routines don't use DoIO().  They use SendIO() instead.  That
  384. will allow us to use them in a task we will create Real Soon Now.  First,
  385. let's write a test routine to see if everything works.  We'll include an
  386. argument this time so we will be able to set the timer to anything we
  387. want.  We'll use the function WaitPort() to wait for completion of the
  388. timer.  This gets us closer to a full multitasking program.  The reason it
  389. isn't full multitasking is that we aren't doing anything else while
  390. waiting for a message (the timerequest structure) to arrive at our message
  391. port.
  392.  
  393. /***** Test.c ***********************************************************/
  394.  
  395. #include "exec/types.h"
  396. #include "devices/timer.h"
  397.  
  398. struct timerequest *CreateTimer();
  399.  
  400. void
  401. main(argc, argv)
  402.     int argc;
  403.     char *argv[];
  404. {
  405.     struct timerequest *T;
  406.     struct MsgPort *P;
  407.     int delay;
  408.  
  409.     if (argc != 2) {
  410.         printf("Please provide a delay time\n");
  411.         exit(0);
  412.     }
  413.  
  414.     delay = atoi(argv[1]);
  415.  
  416.     T = CreateTimer(UNIT_VBLANK, 0);
  417.     P = T->tr_node.io_Message.mn_ReplyPort;
  418.     SetTimer(T, delay);
  419.     WaitPort(P);
  420.     printf("Tock\n");
  421.     DeleteTimer(T);
  422. }
  423.  
  424. /************************************************************************/
  425.  
  426.     Compile this and link it with TimerUtilities.o to get a complete
  427. program.  Alternatively, you could combine both files together just for
  428. test purposes.  Now run the test program, remembering to include a delay
  429. value: Test 3, for example.
  430.     Allright! The utility package is quite a help.  Now, look carefully at
  431. AbortTimer().  A brief mention should be made here about the function
  432. AbortIO().  It isn't described in some books.  If the IO has completed
  433. when AbortIO() is called, it returns a -1 from a timer device (or  a
  434. meaningless value from some other devices), and no message is attached to
  435. the message port.  If the IO has not completed and is aborted, the
  436. function returns a 0 and the timerequest structure (our message) is
  437. attached to the message port.  We then call GetMsg() to remove it from the
  438. port, something unnecessary in this application, but possibly required in
  439. others that use this routine.  We will see more of GetMsg() later.  The
  440. way AbortTimer() waits for completion is Wait().  This function waits for
  441. a particular bit to be set.  While waiting, of course, just as in the case
  442. of WaitPort() and WaitIO(), Exec can be doing other things.  To see that
  443. effect, try Running several copies of Test at the same time: Run Test 30
  444. <CR> Run Test 20 <CR> Run Test 10 <CR>.  You should see the word "Tock"
  445. printed three times, ten seconds apart.
  446.     I said Wait() waits for a particular bit to be set.  This bit is called
  447. a signal, and every message port gets one allocated.  They have to be
  448. allocated because for every task, like the one we are running when we run
  449. Test, has a longword (32 bits) in a structure available for signalling.
  450. Each bit has to have a unique meaning.  Exec takes the lower sixteen for
  451. itself, leaving the upper sixteen available to us.  We don't know which
  452. bits have been allocated already, or to what, so we use a routine
  453. AllocSignal() to find out our bit number.  The signal must be freed with
  454. FreeSignal() when we don't need it any more.  These details have been
  455. taken care of for us by CreatePort() and DeletePort().  The point is that
  456. there is a number in a message port that is the number of a bit (from 0 to
  457. 31) that says when this bit is set in a certain location, mail has arrived
  458. at the port.  It stays set until you Wait() for it in some fashion or
  459. another ( WaitPort() or WaitIO() ).  We can recreate that bit by shifting
  460. a 1 left the number of times equal to the signal number.  The signal
  461. number is in a struct MsgPort, element mp_SigBit.  Remembering that in our
  462. timerequest structure there is an IORequest structure containing a
  463. Message structure which contains a pointer to a message port, the
  464. reference is mn_ReplyPort->mp_SigBit.  Since that Message structure is
  465. part of an IORequest structure we have to say
  466. io_Message.mn_ReplyPort.mp_SigBit.  That IORequest in turn is a named part
  467. of a timerequest structure called tr_node.  So, we obtain the number,
  468. shift a 1 left that number of times, and Wait() for it to be set, which
  469. yields the mouthful operation:
  470.  
  471. Wait(1 << T->tr_node.io_Message.mn_ReplyPort->mp_SigBit);
  472.  
  473.     We won't say that very often, but we will be using Wait() on specific
  474. bits.
  475.     Let's review what we've done.  We allocated and initialized a MsgPort
  476. structure.  That sits off to the side as our mailbox.  We allocated and
  477. initialized a timerequest structure, among other things putting the
  478. address of our mailbox in it so the system will know where to send the
  479. structure back to when it's done with it, a return address, if you like.
  480. That's our message.  We put some stuff in the message (the command and
  481. time values), sent it off to Exec, and waited for mail to arrive at our
  482. mailbox.  Because of the particular functions used for waiting, the
  483. microprocessor can go to sleep if Exec isn't doing anything else.  We
  484. could have done busy loop waiting, which consists of looking at some value
  485. in the MsgPort to see if it has changed, and if it hasn't, looking again.
  486. That kind of program won't quite choke up the machine, depending on the
  487. priority it has, but it eats up all the spare processor time and can make
  488. some operations very slow.  We will demonstrate that with our timer
  489. routines by and by.
  490.     We have been operating as a task spawned by the CLI (A process,
  491. actually, but that is a sort of super task run by DOS.  More on that
  492. later).  We communicated with the rest of the system by sending messages
  493. and looking for signals.  You cannot write a function and call it with
  494. values as in ordinary c programming and have it be a separate task.  You
  495. must use the message passing system.  Exec, whose name we have been
  496. invoking all along, is primarily a program which manipulates lists, lists
  497. of messages being a large part of a busy program.  It is properly called
  498. the executive.  Every time a clock interrupt occurs, Exec is activated,
  499. and it goes looking for things to do by scanning various lists.  You
  500. don't ever call Exec, you just readdress a message.  Exec will know about
  501. it soon enough and take suitable action.
  502.     Let's examine lists more closely (You have been diving into the RKM to
  503. look things up all along, haven't you?  See RKM: Exec, Lists).  A list is
  504. a simple little structure of three pointers and a byte defining its type:
  505.  
  506. LONG lh_Head        ; points to the first node in the list
  507. LONG lh_Tail        ; is always zero
  508. LONG lh_TailPred    ; points to the last node in the list
  509. BYTE lh_Type        ; defines the types of nodes in the list
  510. BYTE lh_pad            ; is here to make the structure an even number of bytes
  511.  
  512.     The lh_Type field signifies what kind of a list it is.  Those
  513. definitions are in the include file exec/nodes.h, and can be tasks,
  514. interrupts, memory, all the things that Exec keeps track of.
  515.     If the list is empty the lh_Head element points to its lh_Tail and the
  516. lh_TailPred points to its lh_Head.
  517.  
  518.         ---- lh_Head <------
  519.         |                  |
  520.         ---> lh_Tail = 0   |
  521.                            |
  522.              lh_TailPred ---
  523.  
  524.                 Figure 1
  525.  
  526.     Now if a node is added to the list, the list's head is made to point to
  527. the node, that is, the first location of the node.  The node has no
  528. successors, so the ln_Succ field is made to point to the list's lh_Tail,
  529. which is always zero so a search routine has a place to stop.  The node's
  530. predecessor is the list, so that field is made to point to the first
  531. location of the list.  As more nodes are added on to the list, the ln_Succ
  532. field is made to point to the first location of the following node and the
  533. following node's ln_Pred field is made to point to the first location of
  534. the preceding node.  That way all nodes are linked forwards and backwards.
  535. A list and the nodes linked into it could be represented like this:
  536.  
  537.                                      Node:
  538.                               -------> ln_Succ -------<-|
  539.                               |   ---- ln_Pred       |  |
  540.               List:           |   |    ln_Type       |  |
  541.                 lh_Head -------<---    ln_Pri        |  |
  542.          (zero) lh_Tail <--------      ln_Name       |  |
  543.                 lh_TailPred --- |                    |  |
  544.                               | |                    |  |
  545.                               | |    Node:           |  |
  546.                               | |      ln_Succ <======<-+--|
  547.                               | |      ln_Pred ------+--|  |
  548.                               | |                    |     |
  549.                               | |                    |     |
  550.                               | |    Node:           |     |
  551.                               |_====>  ln_Succ <-----|     |
  552.                                        ln_Pred ------------|
  553.  
  554.                                         Figure 2
  555.  
  556.     A node has a type, just like a list, and generally agrees with the type
  557. of list it is in.  It also has a priority so that Exec can establish the
  558. order in which it will process a node.  There is a name field which can be
  559. a pointer to a character string such as "Initial CLI" to give the
  560. structure an identifier that can be searched for without knowing which
  561. list it is in.
  562.     Nodes by themselves aren't of any use, but they are the beginning
  563. structure of almost every structure in the system.  Recalling the
  564. discussion of messages and the timerequest structure, you can see how
  565. messages can be sent to a message port just by finding where the
  566. lh_TailPred field of the message port points, putting that address in the
  567. ln_Pred of the message, and changing the contents of that address to point
  568. to the first location of the message.  Since it will be the latest message
  569. attached to the port, its ln_Succ field will be filled with the address of
  570. the lh_TailPred field of the message port.  Exec has to change just those
  571. three addresses and three more in the list the message came from, if it
  572. was in one, to send it, no matter how large the message.  You don't have
  573. to bother yourself with these details as there are routines that perform
  574. these functions, allowing you to think of "sending" or "putting" a message
  575. somewhere and waiting for a "reply".  The list structure comes more
  576. readily to mind when we link something into one, such as a task (See RKM:
  577. Exec, Tasks and include/exec/tasks.h).
  578.     Tasks are jobs that Exec performs when the conditions warrant, such as
  579. a timer running out, or a key being pressed.  There are certain
  580. limitations to tasks as such.  They are procedures which cannot be called
  581. by another function (Exec does that when the time is right, and it is best
  582. not to interfere with the opertion of Exec.  It bytes).  A task cannot
  583. return a result, and as you shall see, should probably not return at all.
  584. A task cannot call any DOS related input or output functions that require
  585. multitasking like printf(), although way down the line I'll give some
  586. pointers on accomplishing those functions.
  587.   Communication with a task is done by message passing.  There are
  588. standard system messages like we have been using (the timerequest
  589. structure), and you can construct messages to suit your taste as long as
  590. Exec understands them (the beginning structure is a Message structure) and
  591. your task understands them (the remainder of the structure contains
  592. information meaningful to it).  There is a special kind of message called
  593. a semaphore, used to provide a means of mutual exclusion, and
  594. communication is also done by signals, which are single bits of an
  595. unsigned longword. One other means not supported by Exec is by global
  596. variables.  If your task can see a variable changed by another program,
  597. your task can act on it.  However, a very important point for a
  598. multitasking system, you must not busy wait in a task as you will eat up
  599. all the spare time the microprocessor has.  Notice I said spare time and
  600. not necessarily all its time.  That depends on your task's priority.  Busy
  601. waiting is exemplified by this:
  602.  
  603.     s = 0;
  604.     while (s != 999999999) {
  605.         s = s + 1;
  606.     }
  607.  
  608. Later on we will try a busy waiting routine to see what happens.
  609.     Signals are the simplest executive system for intertask communication.
  610. Every task has available an unsigned longword called tc_SigAlloc.  I'm
  611. sure you have your RKM:Exec book open to the include/exec/tasks section
  612. this very moment, so have a look at the other signal related elememts
  613. tc_SigWait and tc_SigRecvd.  Signals are single bits of the longword
  614. tc_SigAlloc.  As I mentioned earlier in the discussion of Wait() the lower
  615. sixteen are reserved for use by Exec.  The upper sixteen are available for
  616. your use. You could define a certain bit as meaning a certain thing to
  617. your task, but the burden of maintaining that bit is large.  You would
  618. have to keep a copy in your main program, one in your task, and provide
  619. one for Exec so it would know what to do if that signal bit was set or
  620. cleared.  The overhead is taken care of neatly by the functions
  621. AllocSignal() and FreeSignal().  AllocSignal() returns a number which is
  622. the number of times a 1 bit must be shifted left to obtain the signal bit.
  623. FreeSignal() takes that number as its argument, so it must be saved until
  624. FreeSignal() is called.  You can specify which bit you want allocated by
  625. providing AllocSignal() with the bit number, but in the case of several
  626. possible paths and times of signalling a task, the preferred method is to
  627. call AllocSignal(-1), which returns the next signal number available.  If
  628. no signal is available, the function returns a minus one.  If a signal is
  629. allocated, that bit is set in the longword tc_SigAlloc.  Would you like to
  630. see that?  I said a program run from the CLI could be seen as a task.  You
  631. can find your own task structure by calling FindTask(0), which returns the
  632. address of a task structure.  Well, a process structure, really, but a
  633. process structure starts off with a task structure and structures are
  634. nothing more than a representation to the programmer of a bunch of
  635. locations in contiguous memory.  FindTask() returns an address, and if we
  636. tell the compiler that address is a task structure, it will believe us.
  637. The snare and delusion is that block of memory must have been built as a
  638. task structure originally or our results will be meaningless.  It was
  639. built that way.  Trust me.
  640.  
  641. /***** Tasking.c *********************************************************/
  642.  
  643. #include "exec/types.h"
  644. #include "exec/tasks.h"
  645.  
  646. void
  647. main()
  648. {
  649.     struct Task *T;                            /* Declare your variables */
  650.     unsigned int s;                            /* Sounds like eat your vegiables */
  651.  
  652.     T = (struct Task *) FindTask(0);        /* Find out where we are  */
  653.     printf("%08x\n", T->tc_SigAlloc);    /* Print our allocated signals */
  654.     s = AllocSignal(-1);                        /* Get another one */
  655.     printf("%08x\n", T->tc_SigAlloc);    /* Print the allocated signals */
  656.     FreeSignal(s);                                /* Give it up */
  657.     printf("%08x\n", T->tc_SigAlloc);    /* Print the allocated signals */
  658.     printf("%d\n", s);                        /* Print the bit number */
  659. }
  660.  
  661. /*************************************************************************/
  662.  
  663. You should have obtained the results 0000ffff, 8000ffff, 0000ffff, and 31.
  664. If you didn't you shouldn't be reading this.  It's wrong.  Anyway, we see
  665. that the most significant bit was allocated, then deallocated.  The bit
  666. number is 31 decimal, which agrees with the hexadecimal printout of the
  667. bits themselves, and we see all lower sixteen already allocated.
  668.     If we make a task, the signals we expect to receive must be allocated
  669. within that task, not by another one, so that will be part of the
  670. initialization.  We must also inform other tasks just what bit we expect to
  671. receive for a particular action.  We must use one of the methods described
  672. before, namely message passing, signalling (rather tricky in this case; a
  673. problem left as an exercise for the student), or global data.  We'll use
  674. the last method in this demonstration because the programs are small.
  675. (There is another way if a message port is constructed:  Give the port a
  676. name, let the other task find it with FindPort("name"), then get the
  677. mp_SigBit of that port.)
  678.     Now that you know something about signals, you must know how to deal
  679. with them within the task.  I mentioned waiting before.  The Wait()
  680. function is the essential part of multitasking as the programmer sees it.
  681. Wait() causes the particular bits specified in its argument to be put into
  682. the tc_SigWait element of the task structure and calls Exec.  Until that
  683. bit gets set by some other task, Exec can give the processor to some other
  684. task.  Your task is essentially halted.  When that bit gets set, Exec moves
  685. your task to the ready queue (attaches your task structure to the TaskReady
  686. list) in order of its priority.  When all higher priority tasks have run
  687. and gone into the wait state (have been attached to the TaskWait list),
  688. yours will get processor time.
  689.     WaitPort() and WaitIO() are alternate methods of waiting, as I mentioned
  690. before.  Both can wait for one event.  True, WaitPort() could be waiting
  691. for messages from different sources, but it is still waiting for the SigBit
  692. of that one port to be set, indicating message arrival.  Wait() can wait
  693. for as many as sixteen events at a time.  Thirty-two are possible, but
  694. remember the reservation of sixteen bits by Exec.  The reason is that the
  695. argument supplied to Wait() is a mask of bits.  Wait() returns a
  696. collection of bits received.  If you allocated three bits for signals in
  697. your task, calling them x, y, and z, you would use the form:
  698.  
  699.     ULONG signals, x, y, z;
  700.  
  701.     signals = Wait( x | y | z );
  702.     if (signals & x) {
  703.         /* Do job x */
  704.     }
  705.     if (signals & y){
  706.         /* Do job y */
  707.     }
  708.     if (signals & z){
  709.         /* Do job z */
  710.     }
  711.  
  712. The jobs that are done can be separately defined procedures.  Those
  713. procedures take on the priority of your task because they are using
  714. processor time while your task is active.  They can operate at another
  715. priority if a task of a different priority calls them.  A small point, but
  716. one worth remembering.
  717.     What makes a program of such a form a task is the Task structure (RKM:
  718. Exec, Tasks) that we allocate for it which contains information Exec needs
  719. to run it, like the signals allocated for it we played with before, the
  720. stack it will use, and so on, and a call to the function AddTask(), which
  721. informs Exec of the address of the program.  There are many variations on
  722. the use of the structure, so here we will use the functions CreateTask()
  723. and DeleteTask() which take care of the details in a simple but useful way.
  724. The structure will be linked into the system task list in the way I
  725. discussed in the section on lists.
  726.     I have discussed task initialization well enough to make a small task,
  727. that is, the startup part of the task itself, and the body of the task and
  728. waiting.  The other essential part is ending a task.  Recall I said a task
  729. should probably not exit at all.  The reason comes from the way a task is
  730. created.  You allocate space for a task structure, fill in various fields,
  731. including the address of your task code and the size of the stack the task
  732. will need, and ask Exec to link the structure into its task list.  After
  733. you give the task a chance to run (that doesn't happen until your main
  734. program waits) there will come a time to exit.  When you do you must either
  735. leave the task in memory, still operating, or inform Exec that it is no
  736. longer needed, at which time Exec will deallocate the task's stack, memory
  737. attached to the task structure (another whole subject we won't need at the
  738. moment, see RKM:Exec, Memory Allocation), and the task structure itself.
  739. If your task exits on its own for some reason, with your main program still
  740. running, Exec will go through the deallocation process without informing
  741. your main program.  When your main program exits, attempting to deallocate
  742. the same task, Exec will become strange.  You might not see an effect until
  743. another program is run, at which time the Guru may visit you, or there may
  744. be the AMIGA_FIREWORKS_DISPLAY.  So responsibility for task cleanup must be
  745. thought out beforehand.  If your task is to end with the end of the main
  746. program, then the task must not exit on its own without informing your main
  747. program.  If it is to survive the ending of your main program, then the
  748. task itself must deallocate any resources such as close open libraries,
  749. deallocate memory other than that in the task's stack and memory lists
  750. (tc_MemEntry), close devices, and turn off the lights before the iceman
  751. comes.  In our first example we will put responsibility of deallocation in
  752. the main program, so we won't let the task exit.  We do that with the
  753. Wait() function.  Remember that Wait() waits for some bit of a mask, or
  754. collection, of bits to be set.  If we don't specify a mask, then the task
  755. will Wait() forever.  This isn't busy waiting: remember that Wait() gives
  756. Exec control of the microprocessor.  Wait(0) is sufficient.  Later on we
  757. will leave the task running after the main program exits and have it
  758. terminate on some external event.
  759.     It is helpful to have a free memory checking routine available while
  760. you're playing with programs of this kind to see the room your program
  761. takes, and more importantly, that everything is returned to free memory
  762. after your program exits.  Avail in its various incarnations is perfectly
  763. good for that.
  764.     Here we use the functions you compiled already.  Don't forget to link
  765. that object file with these two when you compile.  The first program is
  766. the task itself: in this case one that will provide a stream of "ticks" at
  767. one second intervals. It first allocates a signal the main program will
  768. use to tell it when it is no longer needed, then creates a timer,
  769. discovers the signal bit it uses so we know when the timer has expired,
  770. and sets the timer to get it running.  Then it waits, either for a tick or
  771. an abort signal.  If it is a tick it immediately resets the timer and
  772. signals the main program whose identity we know from the global
  773. declaration of MT, and then loops back to waiting for another event.  If
  774. it receives an abort signal it deallocates the abort signal bit and the
  775. timer structure, signals the main program it has finished, and waits to be
  776. deallocated itself.    A priority for the timer is available from the main
  777. program as a global variable that will be of use when experimenting.  Since
  778. this is going to be used as a task, you must compile it with stack checking
  779. disabled and register preserving code generation enabled.
  780.  
  781. /***** TimeTick.c ********************************************************/
  782.  
  783. #include "exec/types.h"
  784. #include "devices/timer.h"
  785.  
  786. extern struct timerequest *CreateTimer();
  787. extern void SetTimer();
  788. extern void DeleteTimer();
  789.  
  790. extern struct Task *MT;
  791. extern unsigned int ready;
  792. extern unsigned int tick;
  793. extern int priority;
  794.  
  795. unsigned int abort;
  796.  
  797. void
  798. TimeTick()
  799. {
  800.     struct timerequest *T;
  801.     int ab;
  802.     unsigned int t;
  803.     unsigned int signals;
  804.  
  805.     ab = AllocSignal(-1);
  806.     abort = 1 << ab;
  807.  
  808.     T = CreateTimer(UNIT_VBLANK, priority);
  809.     t = 1 << T->tr_node.io_Message.mn_ReplyPort->mp_SigBit;
  810.     SetTimer(T, 1);
  811.  
  812.     for(;;) {
  813.         signals = Wait(t | abort);
  814.         if (signals & t) {
  815.             SetTimer(T, 1);
  816.             Signal(MT, tick);
  817.         }
  818.         if (signals & abort) {
  819.             FreeSignal(ab);
  820.             DeleteTimer(T);
  821.             Signal(MT, ready);
  822.             Wait(0);
  823.         }
  824.     }
  825. }
  826.  
  827. /*************************************************************************/
  828.  
  829.     Now for the main program: It provides the timer with the address of
  830. this task and a bit the main program expects to see as a "ready" signal,
  831. used for the ticks themselves and as a signal that the abort procedure has
  832. completed when it comes time to deallocate the task.  It installs the task,
  833. waits for a few ticks, printing out a message on the screen at each tick,
  834. then deletes the task.  Compile it, link it with the object files you got
  835. by compiling TimeTick.c and TimerUtilities.c, and run it.
  836.  
  837. /***** ShowTicks.c *******************************************************/
  838.  
  839. #include "exec/types.h"
  840. #include "exec/tasks.h"
  841.  
  842. extern struct Task *FindTask();
  843. extern struct Task *CreateTask();
  844.  
  845. extern void TimeTick();
  846.  
  847. extern unsigned int abort;
  848.  
  849. struct Task *MT;
  850. unsigned int ready;
  851. unsigned int tick;
  852. int priority = 0;
  853.  
  854. void
  855. main()
  856. {
  857.     struct Task *T;
  858.     int r, t;
  859.     int i;
  860.  
  861.     MT = FindTask(0);
  862.     r = AllocSignal(-1);
  863.     ready = 1 << r;
  864.     t = AllocSignal(-1);
  865.     tick = 1 << t;
  866.  
  867.     T = CreateTask("Ticks", priority, TimeTick, 4000);
  868.  
  869.     for (i = 0; i < 10; i++) {
  870.         Wait(tick);
  871.         printf("Tick\n");
  872.     }
  873.  
  874.     Signal(T, abort);
  875.     Wait(ready);
  876.     FreeSignal(r);
  877.     FreeSignal(t);
  878.     DeleteTask(T);
  879.  
  880. }
  881.  
  882. /*************************************************************************/
  883.  
  884.     Note that when CreateTask() is called, Exec links the task into its task
  885. list, and if it has a priority higher than than the program that spawned
  886. it, the task will begin execution before the spawning program retains
  887. control of the microprocessor.  If the priority is lower, the task will not
  888. run until the main program gives up control of the microprocessor with a
  889. Wait() instruction (or some other form of Wait() such as WaitIO()).  In
  890. this case it makes no difference because immediately after CreateTask() is
  891. called, Wait() is called to wait for tick events.  Note, too, that after
  892. the task is signalled to abort, the main program Waits for a ready signal
  893. assuring that the task has gone through its abort procedure.  If the
  894. priority of the task was lower than that of the main program and the
  895. Wait() was not present, the main program would continue to have control of
  896. the microprocessor until it exited.  The last instruction is DeleteTask(),
  897. so that would be performed before the task ever saw the abort signal.  The
  898. timer would be left in memory with no easy way to later delete the memory
  899. it occupies.  The important thing to keep in mind is Signal() can cause
  900. Exec to halt the calling program and run the signalled task before
  901. returning to the calling program.  Fine points like this are not
  902. immediately obvious in source code.  I provided the global int priority so
  903. you could experiment with it.  For reference, the priority of a program run
  904. from CLI is zero unless you've changed it.  It would be helpful to add code
  905. to accept a priority from the command line (argc & argv).
  906.     Now try a busy waiting program to investigate the Amiga's behavior.  It
  907. is easy enough to avoid such a programming practice, but the real value of
  908. doing such an investigation is learning the effects of tasks which take up
  909. a good deal of processor time. Let's set up two tasks: one for giving time
  910. ticks and one for trying to occupy all of the processor for a while. We'll
  911. make a hog task and a new main program, but use the same TimeTick with a
  912. temporary modification and the same TimerUtility routines.  Modify
  913. TimeTick() by declaring an extern int count, and after the SetTimer() call
  914. in the Wait() loop but before the call to Signal(MT, tick), increment count
  915. with count++;.  This agrees with the declaration of count in ProcHog() and
  916. provides the means for determining the relative actions of the two tasks.
  917. The result is printed out as the main program exits.
  918.     The main routine expects two priority values to be provided: ProcHog 2 1
  919. for example.
  920.     The function BusyWait() will be installed as a task, so you must compile
  921. this with stack checking disabled and register preserving code enabled.
  922.  
  923. /***** ProcHog.c *********************************************************/
  924.  
  925. #include "exec/types.h"
  926. #include "exec/tasks.h"
  927.  
  928. extern void TimeTick();
  929. extern LONGBITS abort;
  930.  
  931. struct Task *MT, *FindTask(), *CreateTask();
  932.  
  933. int count = 0;
  934. int priority;
  935. LONGBITS ready, tick;
  936.  
  937. void
  938. BusyWait()
  939. {
  940.     int i;
  941.  
  942.     for (i = 0; i < 1000000; i++);
  943.  
  944.     Signal(MT, ready);
  945.     Wait(0);
  946. }
  947.  
  948. void
  949. main(argc, argv)
  950.     int argc;
  951.     char *argv[];
  952. {
  953.     struct Task *T, *H;
  954.     int r, t;
  955.     LONGBITS signals;
  956.  
  957.     if (argc != 3) {
  958.         printf("Please provide priority values for TimeTick and BusyWait\n");
  959.         exit(0);
  960.     }
  961.  
  962.     MT = FindTask(0);
  963.     r = AllocSignal(-1);
  964.     ready = 1 << r;
  965.     t = AllocSignal(-1);
  966.     tick = 1 << t;
  967.  
  968.     T = CreateTask("Ticks", atoi(argv[1]), TimeTick, 4000);
  969.     H = CreateTask("Hog", atoi(argv[2]), BusyWait, 4000);
  970.  
  971.     for (;;) {
  972.         signals = Wait(tick | ready);
  973.         if (signals & tick) {
  974.             printf("Tick\n");
  975.         }
  976.         if (signals & ready) {
  977.             Wait(tick);
  978.             Signal(T, abort);
  979.             Wait(ready);
  980.             DeleteTask(T);
  981.             DeleteTask(H);
  982.             FreeSignal(r);
  983.             FreeSignal(t);
  984.             break;
  985.         }
  986.     }
  987.     printf("Ticks counted: %d\n", count);
  988. }
  989.  
  990. /*************************************************************************/
  991.  
  992.     Here I used the exec/types typedef LONGBITS to replace "unsigned int" to
  993. clarify the role of the variable.  BusyWait() is the task designed to hog
  994. the microprocessor, and main(), of course, is the task spawning program
  995. running at priority 0.  If you invoke ProcHog 2 1 so that both tasks run at
  996. a higher priority than the main program with the timer running at a
  997. priority higher than BusyWait(), here's what happens: At creation of task T
  998. ("Ticks") the timer is started, that is, TimeTick is set to running
  999. immediately.  It will be a full second before the timer runs out, so as
  1000. soon as Wait() is reached in TimeTick(), control is returned to the main
  1001. program, which proceeds with creating the task H ("Hog").  Since its
  1002. priority is also higher than that of the main program it begins running
  1003. immediately, preventing control from returning to the main program for
  1004. about ten seconds.  Meanwhile, every second TimeTick() sets the signal
  1005. "tick", but it is not seen by the main program because it is prevented from
  1006. running by the looping of BusyWait().  When BusyWait() finally quits and
  1007. sets the signal "ready" the main program can run.  The main program sees
  1008. that the signal "tick" has been set (remember signals get cleared by Wait()
  1009. so there is no way of telling how many signals there were, just that there
  1010. was at least one) and prints the word "Tick".  It also sees that "ready"
  1011. was set so it signals TimeTick() to abort itself.  The reason for waiting
  1012. for one more "tick" will become apparent when we change the priorities of
  1013. the tasks.  After the main program signals TimeTick() to abort it waits for
  1014. the ready signal to ensure TimeTick() has deallocated its resources before
  1015. continuing for the reasons discussed above.  If both are run at the same
  1016. priority above that of the main program the same thing happens, although
  1017. in this case Exec doesn't run TimeTick() for the same reason.  Here you see
  1018. the effect of the Exec "quantum" which allows a task to run for a certain
  1019. length of time whereupon Exec halts the running task to examine its task
  1020. list to see if there are any of higher priority that need to run.  There
  1021. are none in our program suite, but Exec places the task just halted at the
  1022. bottom of the list of tasks at that same priority so others of the same
  1023. priority can run for a while.  The effect is that TimeTick() and BusyWait()
  1024. each get to run.  Since both are still at a higher priority than the main
  1025. program, they will keep control of the microprocessor until BusyWait()
  1026. expires.  As you can see, busy waiting doesn't exactly stop everything in
  1027. the Amiga, but it can interfere with lower priority tasks.
  1028.     Now if you run ProcHog 1 2 so that both tasks have a higher priority
  1029. than the main program, as before, but with BusyWait() at a higher priority
  1030. than TimeTick(), much the same thing appears to happen, but in this case
  1031. TimeTick() runs down to its Wait() when created as a task, and is prevented
  1032. from doing more until BusyWait() expires, when it again gets control and
  1033. sees that at least one second has passed.  It resets the timer and waits
  1034. again.  The main program regains control, waits for another tick, aborts
  1035. TimeTick(), and exits, so only two tick events are recorded instead of ten.
  1036.     If you run all three at the same priority by invoking ProcHog 0 0, then
  1037. the quantum effect allows the main program to share the processor equally
  1038. with the two spawned tasks, and each tick event is shown by the main
  1039. program as the word "Tick".  Eleven are counted instead of ten because the
  1040. main program waits for a tick before aborting TimeTick().  If you invoke
  1041. ProcHog -1 -2 to put both tasks below the priority of the main program then
  1042. the main program keeps control until it waits.  TimeTick() runs as needed
  1043. because the main program is waiting most of the time, taking only enough
  1044. processor time to print the word "Tick", and BusyWait() gets whatever time
  1045. is left over.
  1046.     The final case is both tasks having a lower priority than the main
  1047. program but with BusyWait() having a higher priority than TimeTick() (e.g.
  1048. ProcHog -2 -1).  You see that the word "Tick" is never printed, and that
  1049. TimeTick() has recorded only one event.  Here is the sequence of events:
  1050. The main program creates the task "Tick" (TimeTick()), but since the task
  1051. has a lower priority than that of the main program, it does not yet run.
  1052. The main program then creates the task "Hog" (BusyWait()), which also has a
  1053. lower priority so does not run.  The main program keeps control until it
  1054. waits for a signal (tick or ready).  BusyWait(), having the next lower
  1055. priority, takes control of the microprocessor and keeps it until the loop
  1056. is exhausted, whereupon it signals the main program with "ready".  The main
  1057. program then proceeds to abort TimeTick().  TimeTick() has never had a
  1058. chance to even initialize!  If we tried to signal an abort the bit would be
  1059. undefined (zero, actually, since it is declared an extern, and external
  1060. variables are supposed to be initialized to zero at compile time) because
  1061. it is defined within TimeTick().  Remember, signals must be defined within
  1062. the task which expects to receive them.  So, we would signal TimeTick()
  1063. with nothing and wait for a ready from it.  As soon as we waited,
  1064. TimeTick() would get control, proceed with counting ticks, signalling the
  1065. main program with "tick" each time, but the main program would ignore it
  1066. because it would be waiting for "ready".  The main program would hang up.
  1067. By waiting for a tick at this point, we can be sure that TimeTick() has
  1068. initialized, that "abort" has been defined and that TimeTick() will respond
  1069. to it.
  1070.     Now is the time to put all this into useful programs.  A simple
  1071. demonstration is a little digital clock that can fit in the title bar of an
  1072. existing window.  We can obtain the date and time in seconds from January
  1073. 1, 1978 by reading the system clock with the timer.device command
  1074. TR_GETSYSTIME.  A little arithmetic massaging will yield the time of day.
  1075. We can use the same kind of timer as we did TimeTick() to provide a signal
  1076. to update the clock.  The display can be a window the same height as a title
  1077. bar.  I won't get into much of a discussion of windows here because of the
  1078. thorough treatment already given in the Intuition Reference Manual.  It
  1079. would probably be helpful to read this source code while referencing that
  1080. manual to gain an understanding of practical window implementations.
  1081.     The function main() opens the little window in the title bar of the
  1082. Workbench screen, sets the colors to use, makes a one second repeatable
  1083. timer,  and waits for either a tick signal or a signal that you clicked on
  1084. the close box of the clock window.  To avoid complexity, main() will exit
  1085. on reception of any signal from the window, but because of the way
  1086. NewWindow is defined, the only signal Wait() can get is that from a click
  1087. on the close box. Notice the loop for Wait() and decoding of signals is
  1088. defined by the macro FOREVER.  This is a definition in
  1089. intuition/intuition.h of the function for(;;) which helps clarify the
  1090. source code.
  1091.     The function cia() is just a routine to convert an integer to a two
  1092. digit ASCII string and place it in a string buffer for the display.
  1093.     Notice that the one second ticks are used only as an alert to main() to
  1094. read the clock, rather than being used as actual clock ticks.  The reason
  1095. for this is that in a multitasking system, the ticks will not necessarily
  1096. come at exact one second intervals depending on other system activities
  1097. (tasks), such as resizing other windows and so forth.
  1098.     Compile and link this by itself.  I purposely didn't use any functions
  1099. in TimerUtilities.c to make it small.  Also, I put main() before cia() and
  1100. commented out the two lines referring to SysBase because an experiment soon
  1101. to come will need that order and that reference.
  1102.     Try Clock as a command, and also try Run Clock several times to see
  1103. several invocations of it.  The clock display itself is in the drag bar of
  1104. its own window so you can move one out of the way to uncover another.  You
  1105. might use Avail to see how much memory each invocation takes, for future
  1106. reference.  I will be discussing the reasons for that later on.
  1107. Compilation does not now need any special options.
  1108.  
  1109. /***** Clock.c ***********************************************************/
  1110.  
  1111. #include <exec/types.h>
  1112. #include <intuition/intuition.h>
  1113.  
  1114. extern struct Window *OpenWindow();
  1115. extern struct Task *FindTask();
  1116. extern struct MsgPort *CreatePort();
  1117. extern struct IntuiMessage *GetMsg();
  1118. extern struct timerequest *CreateExtIO();
  1119.  
  1120. /* Use this when compiling as NewClock.c */
  1121. /* struct ExecBase *SysBase; */
  1122.  
  1123. struct IntuitionBase * IntuitionBase;
  1124. struct GfxBase * GfxBase;
  1125.  
  1126. char timestring[9];    /* The ASCII time buffer */
  1127. BYTE t_i;                /* The buffer index */
  1128.  
  1129. struct NewWindow NewWindow = {
  1130.     489, 0, 150, 10, 3, 2, CLOSEWINDOW,
  1131.     WINDOWCLOSE | WINDOWDRAG | WINDOWDEPTH | BORDERLESS,
  1132.     NULL, NULL, NULL, NULL, NULL, 0, 0, 0, 0, WBENCHSCREEN
  1133. };
  1134.  
  1135. void
  1136. main()
  1137. {
  1138.     extern void cia();
  1139.  
  1140.     struct Window *W;
  1141.     struct IntuiMessage *IM;
  1142.     struct timerequest *TR;
  1143.     struct MsgPort *TP;
  1144.     struct Task *T;
  1145.     LONGBITS t;
  1146.     LONGBITS w;
  1147.     LONGBITS signals;
  1148.     int secs, h, m, s;
  1149.  
  1150. /* Use this when compiling as NewClock.c */
  1151. /*    SysBase = *((struct ExecBase **) 4);  */
  1152.  
  1153.     IntuitionBase = (struct IntuitionBase *)        /* Open the window */
  1154.                             OpenLibrary("intuition.library", 0);
  1155.     GfxBase = (struct GfxBase *)
  1156.                             OpenLibrary("graphics.library", 0);
  1157.     W = OpenWindow(&NewWindow);
  1158.     w = 1 << W->UserPort->mp_SigBit;
  1159.     SetAPen(W->RPort, 2);
  1160.     RectFill(W->RPort, 27, 0, 98, 9);
  1161.     SetAPen(W->RPort, 3);
  1162.     SetBPen(W->RPort, 2);
  1163.  
  1164.     T = FindTask(0);         /* Make the timer priority the same as this task */
  1165.     TP = CreatePort(NULL, T->tc_Node.ln_Pri);         /* Make a timer */
  1166.     TR = CreateExtIO(TP, sizeof(struct timerequest));
  1167.     OpenDevice(TIMERNAME, UNIT_VBLANK, TR, 0);
  1168.     t = 1 << TP->mp_SigBit;
  1169.     TR->tr_node.io_Command = TR_ADDREQUEST;        /* Start the timer */
  1170.     TR->tr_time.tv_secs = 0;
  1171.     TR->tr_time.tv_micro = 1000;
  1172.     SendIO(TR);
  1173.  
  1174.     FOREVER {
  1175.         signals = Wait(t | w);
  1176.         if (signals & t) {                                    /* The timer ticked */
  1177.             TR->tr_node.io_Command = TR_GETSYSTIME;
  1178.             DoIO(TR);
  1179.             secs = TR->tr_time.tv_secs;
  1180.             s = secs % 60;                    /* Extract hours, minutes, seconds */
  1181.             secs -= s;
  1182.             m = secs % 3600;
  1183.             secs -= m;
  1184.             m /= 60;
  1185.             h = secs % 86400;
  1186.             h /= 3600;
  1187.             t_i = 0;                            /* Initialize the buffer pointer */
  1188.             cia(h);                            /* Convert values to ASCII */
  1189.             cia(m);
  1190.             cia(s);
  1191.             Move(W->RPort, 30, 7);        /* Display all but the final ':' */
  1192.             Text(W->RPort, timestring, 8);
  1193.  
  1194.             TR->tr_node.io_Command = TR_ADDREQUEST;    /* Restart the timer */
  1195.             TR->tr_time.tv_secs = 1;
  1196.             TR->tr_time.tv_micro = 0;
  1197.             SendIO(TR);
  1198.         }
  1199.         if (signals & w) {                    /* You clicked on the close box */
  1200.             IM = GetMsg(W->UserPort);
  1201.             if (AbortIO(TR) == NULL) {
  1202.             Wait(t);
  1203.             }
  1204.             CloseDevice(TR);
  1205.             DeleteExtIO(TR, sizeof(struct timerequest));
  1206.             DeletePort(TP);
  1207.             ReplyMsg(IM);
  1208.             CloseWindow(W);
  1209.             CloseLibrary(IntuitionBase);
  1210.             CloseLibrary(GfxBase);
  1211.             break;
  1212.         }
  1213.     }
  1214. }
  1215.  
  1216. void
  1217. cia(i)
  1218.     int i;
  1219. {
  1220.     timestring[t_i++] = 0x30 + i / 10;
  1221.     timestring[t_i++] = 0x30 + i % 10;
  1222.     timestring[t_i++] = ':';
  1223. }
  1224.  
  1225. /*************************************************************************/
  1226.  
  1227.     This could be done in a different way by passing messages between two
  1228. tasks.  One task will be a window as in Clock.c, and the other will be a
  1229. task which gets system time, converts it to a string, and replies to the
  1230. message.  We must first define a message structure:  We need a command byte
  1231. to tell the time task whether to send a time value or abort itself, and we
  1232. need a longword that is a pointer to a string.  The command bytes will be
  1233. defined with #define statements.  These #defines and the structure
  1234. definition will be put into an include file so they will be available to
  1235. any source code using them:
  1236.  
  1237. /***** timetask.h ********************************************************/
  1238.  
  1239. #ifndef EXEC_PORTS_H
  1240. #include "exec/ports.h"
  1241. #endif
  1242.  
  1243. #define TM_REQUEST 0
  1244. #define TM_ABORT   1
  1245.  
  1246. struct TimeMsg {
  1247.     struct Message    tm_Msg;
  1248.     UBYTE                tm_Cmd;
  1249.     char *            tm_Tim;
  1250. };
  1251.  
  1252. /*************************************************************************/
  1253.  
  1254.     The task which makes the time string will have the arithmetic routines
  1255. of Clock.c, a timer.device to obtain system time, and a message port.  An
  1256. important point here is that many different tasks can send messages to the
  1257. time task to get the string.  It does not have to be compiled or linked
  1258. with the tasks using it because its message port can be found in the system
  1259. by name by calling FindPort("TimeTaskPort").  Since it can stand alone it
  1260. takes responsibility for its own task cleanup and actually exits, unlike
  1261. the previous tasks.  For this reason, DeleteTask() should not be called on
  1262. it once you have sent it a TM_ABORT message.  On reception of the TM_ABORT
  1263. message the task deletes the timer it uses and scans the message port for
  1264. any outstanding messages (including the TM_ABORT message).  If there are
  1265. any, it sets the timestring pointer of each to NULL and replies.  It then
  1266. deletes the message port and exits. When the message port is deleted,
  1267. DeletePort() removes it from the Exec list of named ports and it cannot be
  1268. mistakenly found after that.  This routine needs stack checking disabled
  1269. and registers saved.
  1270.  
  1271. /***** TimeTask.c *********************************************************/
  1272.  
  1273. #include <exec/types.h>
  1274. #include <devices/timer.h>
  1275. #include "timetask.h"
  1276.  
  1277. extern struct MsgPort * CreatePort();
  1278. extern struct MsgPort * FindPort();
  1279. extern struct TimeMsg * GetMsg();
  1280. extern struct timerequest *CreateTimer();
  1281. extern void SetTimer();
  1282. extern void DeleteTimer();
  1283.  
  1284. char timestring[9];
  1285. int t_i;
  1286.  
  1287. void
  1288. cia(i)
  1289.     int i;
  1290. {
  1291.     timestring[t_i++] = 0x30 + i / 10;
  1292.     timestring[t_i++] = 0x30 + i % 10;
  1293.     timestring[t_i++] = ':';
  1294. }
  1295.  
  1296. void
  1297. TimeTask()
  1298. {
  1299.     struct timerequest *TR;
  1300.     struct MsgPort *MP;
  1301.     struct TimeMsg *M;
  1302.     int secs, h, m, s;
  1303.  
  1304.     TR = CreateTimer(UNIT_VBLANK, 0);                /* Make a timer */
  1305.     TR->tr_node.io_Command = TR_GETSYSTIME;
  1306.  
  1307.     MP = CreatePort("TimeTaskPort", 0);                /* Make a message port */
  1308.  
  1309.     for (;;) {
  1310.         WaitPort(MP);                                        /* Wait for mail */
  1311.         M = GetMsg(MP);
  1312.         if (M->tm_Cmd == TM_REQUEST) {                /* Time to go to work */
  1313.             /* The timer command is always TR_GETSYSTIME */
  1314.             DoIO(TR);
  1315.             secs = TR->tr_time.tv_secs;
  1316.             s = secs % 60;                    /* Extract hours, minutes, seconds */
  1317.             secs -= s;
  1318.             m = secs % 3600;
  1319.             secs -= m;
  1320.             m /= 60;
  1321.             h = secs % 86400;
  1322.             h /= 3600;
  1323.             t_i = 0;                            /* Initialize the buffer pointer */
  1324.             cia(h);                            /* Convert values to ASCII */
  1325.             cia(m);
  1326.             cia(s);
  1327.             M->tm_Tim = timestring;        /* Put the address in the message */
  1328.             ReplyMsg(M);
  1329.         }
  1330.         if (M->tm_Cmd == TM_ABORT) {                    /* Boss says quit */
  1331.             DeleteTimer(TR);
  1332.             do {                                                /* Tell everybody */
  1333.                 M->tm_Tim = 0;
  1334.                 ReplyMsg(M);
  1335.             } while (M = GetMsg(MP));
  1336.             DeletePort(MP);
  1337.             break;    /* Drop out of the function, let Exec delete the task */
  1338.         }
  1339.     }
  1340. }
  1341.  
  1342. /*************************************************************************/
  1343.  
  1344.     The window we will use is much like the one in Clock.c, but since
  1345. TimeTask() does the arithmetic, the window has only to send a request
  1346. message to it.  The program creates a message port for replies, a timer as
  1347. in Clock.c and a task for the previous program, TimeTask().  We are running
  1348. the ports, timers and task at the same priority of the main program, so
  1349. when CreateTask() is called, the main program retains control of the
  1350. microprocessor.  In order for TimeTask() to initiate, the main program has
  1351. to halt briefly.  A handy way to do that here is to Wait() for a tick from
  1352. the timer.  As soon as Wait() is called, Exec will give control to
  1353. TimeTask(), which will then create its message port and timer, and then
  1354. gives up control by WaitPort(), to sit idle until a message is sent to its
  1355. port.  As soon as the main program's timer ticks, control will return to
  1356. it, whereupon it will be able to find the port that the task created.  It
  1357. can then wait for a tick (which signals it to send its message to the task
  1358. port), a reply from the task, or a click on the window close gadget.
  1359.     When the main program gets a tick, signalled by the bit "t", it makes up
  1360. a message to send to the task.  Part of the message initialization was done
  1361. right after creation of the reply port.  The initialization is the
  1362. references to m.tm_Msg and m_tm_Cmd.  Notice that the message is declared
  1363. as a TimeMsg structure (struct TimeMsg m), not a pointer to a structure
  1364. (structure TimeMsg *M, for example, for the pointer to the reply).  This
  1365. lets the compiler allocate space for the message.  Quite often you will
  1366. see a call to AllocMem() to allocate this space dynamically. An example is
  1367. in RKM: Exec Appendix B in the source code for CreateExtIO(). This is done
  1368. to allow the program itself to deallocate the memory used by the message
  1369. when it is no longer needed.  In this case we use the memory until the
  1370. program exits, so we let the compiler handle it.
  1371.     Refer to the include file exec/ports.h.  You see there that a message
  1372. structure is defined as a node, an address to a reply port, and a length.
  1373. Our extension of that structure, struct TimeMsg, also has a command byte
  1374. and a string address.  We initialize the command portion of the structure
  1375. and fill in the address of our reply port.  The length word would be used
  1376. if we were allocating memory for the structure dynamically.  You would use
  1377. it by filling it in with sizeof(struct TimeMsg) and refer to it when
  1378. deallocating it.  We don't need to do that here.  The node portion is
  1379. filled in by Exec.  The ln_Pred and ln_Succ fields are filled in by
  1380. PutMsg() and ReplyMsg() to link the message into a message port message
  1381. list (mp_MsgList) (recall my discussion of Lists, above).  The ln_Type
  1382. field is also filled in by PutMsg() and ReplyMsg(): PutMsg() sets the field
  1383. to be NT_MESSAGE and ReplyMsg() sets it to NT_REPLY.  We use that to
  1384. advantage to tell who has control of the message.  If the type is
  1385. NT_MESSAGE then we have sent it to another task and it has not yet replied
  1386. to it.  If it is NT_REPLY then we have control of it.  This is a very
  1387. important consideration because once we send the message off to another
  1388. task, we should not be changing it.  The other task just might be trying to
  1389. change portions of it itself, and could get very confused by outside
  1390. interference.  It is actually of little consequence here but we do want to
  1391. be sure TimeTask() is done with it before the main program exits and
  1392. deallocates it.  Otherwise TimeTask() could be trying to reply to a
  1393. non-existent port with a non-existent message.  This kind of thing
  1394. distresses the Amiga.  The ln_Pri and ln_Name fields could be filled in by
  1395. a task.  Exec does not initialize these fields but uses the ln_Pri field to
  1396. order the position of the message in a message port list, allowing the
  1397. sending of "priority mail".  The name field is of little use in a message
  1398. structure.  By the way, the name field is helpful in other structures such
  1399. as message ports.  We had CreatePort() fill in the name field of the
  1400. TimeTask() port, which allowed FindPort() to find it by scanning the system
  1401. list PortList defined in exec/execbase.h.  That's all there is to it:
  1402. Allocate memory for a message structure, fill in the ReplyPort and perhaps
  1403. Length fields and any extensions of it of importance to your programs, and
  1404. PutMsg() and ReplyMsg() with it, remembering to deallocate its memory when
  1405. you're done with it.
  1406.     No special options are needed to compile this, but link it with
  1407. TimeTask.o and TimerUtilities.o.
  1408.  
  1409. /***** Window.c **********************************************************/
  1410.  
  1411. #include <exec/types.h>
  1412. #include <intuition/intuition.h>
  1413. #include "timetask.h"
  1414.  
  1415. extern struct Task *FindTask();
  1416. extern struct MsgPort *CreatePort(), *FindPort();
  1417. extern struct TimeMsg *GetMsg();
  1418. extern struct timerequest *CreateTimer();
  1419. extern void SetTimer();
  1420. extern void DeleteTimer();
  1421. extern void TimeTask();
  1422.  
  1423. struct IntuitionBase * IntuitionBase;
  1424. struct GfxBase * GfxBase;
  1425.  
  1426. struct NewWindow NewWindow = {
  1427.     489, 0, 150, 10, 3, 2, CLOSEWINDOW,
  1428.     WINDOWCLOSE | WINDOWDRAG | WINDOWDEPTH | BORDERLESS,
  1429.     NULL, NULL, NULL, NULL, NULL, 0, 0, 0, 0, WBENCHSCREEN
  1430. };
  1431.  
  1432. void
  1433. main()
  1434. {
  1435.     struct Window *W;
  1436.     struct timerequest *TR;
  1437.     struct MsgPort *TP, *RP, *P;
  1438.     struct TimeMsg m, *M;
  1439.     LONGBITS signals, p, t, w;
  1440.     int pri;
  1441.  
  1442.     IntuitionBase = (struct IntuitionBase *)
  1443.                             OpenLibrary("intuition.library", 0);
  1444.     GfxBase = (struct GfxBase *)
  1445.                             OpenLibrary("graphics.library", 0);
  1446.     W = (struct Window *) OpenWindow(&NewWindow);
  1447.     w = 1 << W->UserPort->mp_SigBit;
  1448.     SetAPen(W->RPort, 2);
  1449.     RectFill(W->RPort, 27, 0, 98, 9);
  1450.     SetAPen(W->RPort, 3);
  1451.     SetBPen(W->RPort, 2);
  1452.  
  1453.     pri = FindTask(0)->tc_Node.ln_Pri;    /* Use our own process's priority */
  1454.  
  1455.     RP = CreatePort("", pri);                /* Make our own reply port    */
  1456.     p = 1 << RP->mp_SigBit;                    /* Use its signal bit        */
  1457.     m.tm_Msg.mn_ReplyPort = RP;            /* Prepare the message        */
  1458.     m.tm_Cmd = TM_REQUEST;
  1459.  
  1460.     TR = CreateTimer(UNIT_VBLANK,  pri);    /* Make the one second timer */
  1461.     TP = TR->tr_node.io_Message.mn_ReplyPort;
  1462.     t = 1 << TP->mp_SigBit;
  1463.     SetTimer(TR, 1);
  1464.  
  1465.     CreateTask("TimeTask", pri, TimeTask, 4000);
  1466.     Wait(t);                        /* Give the system time to start the task */
  1467.     SetTimer(TR, 1);            /* Reset the timer */
  1468.     P = FindPort("TimeTaskPort");            /* Find the task port */
  1469.  
  1470.     FOREVER {
  1471.         signals = Wait(p | t | w);
  1472.         if (signals & t) {        /* The timer ticked */
  1473.             PutMsg(P, &m);                        /* Send the message */
  1474.             SetTimer(TR, 1);                    /* Reset the one second timer */
  1475.         }
  1476.         if (signals & p) {        /* Our request has been answered */
  1477.             M = GetMsg(RP);                    /* Get the reply */
  1478.             Move(W->RPort,  30, 7);
  1479.             Text(W->RPort, M->tm_Tim, 8);    /* Display its contents */
  1480.         }
  1481.         if (signals & w) {        /* You want to close the window */
  1482.             DeleteTimer(TR);            /* Stop the one second timer */
  1483.                 /* See who has the message. If them, wait for a reply */
  1484.             if (m.tm_Msg.mn_Node.ln_Type == NT_MESSAGE) {
  1485.                 WaitPort(RP);
  1486.             }
  1487.             m.tm_Cmd = TM_ABORT;
  1488.             PutMsg(P, &m);
  1489.             WaitPort(RP);
  1490.             DeletePort(RP);
  1491.             CloseWindow(W);
  1492.             CloseLibrary(IntuitionBase);
  1493.             CloseLibrary(GfxBase);
  1494.             break;
  1495.         }
  1496.     }
  1497. }
  1498.  
  1499. /*************************************************************************/
  1500.  
  1501.     Compile Window.c and link it with TimeTask.o and TimerUtilities.o.  When
  1502. you run Window you will see the same display as in Clock.  There is a great
  1503. difference, however.  Compile this program to see why (No special options):
  1504.  
  1505. /***** TestPort.c ********************************************************/
  1506.  
  1507. #include <exec/types.h>
  1508. #include "timetask.h"
  1509.  
  1510. void
  1511. main()
  1512. {
  1513.     struct MsgPort *P, *RP, *CreatePort(), *FindPort();
  1514.     struct TimeMsg t, *M, *GetMsg();
  1515.  
  1516.     P = FindPort("TimeTaskPort");
  1517.  
  1518.     if (P) {
  1519.         RP = CreatePort("", 0);
  1520.         t.tm_Msg.mn_ReplyPort = RP;
  1521.         t.tm_Cmd = TM_REQUEST;
  1522.         PutMsg(P, &t);
  1523.         WaitPort(RP);
  1524.         M = GetMsg(RP);
  1525.         printf("%s\n", M->tm_Tim);
  1526.         DeletePort(RP);
  1527.     } else {
  1528.         printf("TimeTaskPort not found\n");
  1529.     }
  1530. }
  1531.  
  1532. /*************************************************************************/
  1533.  
  1534.     Now run TestPort.  It will respond "TimeTaskPort not found" because
  1535. there is no message port in the system by that name.  Now Run Window so the
  1536. clock is visible on the screen and you have a CLI to type into.  Invoke
  1537. TestPort again.  Hello! what's this?  TestPort has found the message port
  1538. of the task TimeTask(), has sent it a message just like the clock is doing,
  1539. and has printed out the time on the CLI.  This shows how different tasks
  1540. can communicate with each other even though they have not been compiled
  1541. together.  It also shows how a Process (The process CLI is running for you
  1542. when you invoke TestPort) can communicate with a Task and use AmigaDOS
  1543. functions to print out the results, something forbidden to a task.  It
  1544. isn't forbidden in the sense that Exec won't let you do it, but if you try
  1545. it, you'll destroy Exec and DOS, as I mentioned before.
  1546.     There are many other aspects of tasks to investigate.  I didn't address
  1547. stack allocation.  I had CreateTask() allocate 4000 bytes for everything
  1548. and let it go at that.  I didn't discuss semaphores as a means of intertask
  1549. communications.  I didn't go into separate routines for task deletion
  1550. (finalPC, which CreateTask() sets to NULL to let Exec do it), nor task
  1551. traps.  I also said a task could not be called with formal parameters as a
  1552. normal c function, but that's not strictly true.  You can preset values on
  1553. the task stack when it is allocated, and when the task initializes it can
  1554. pull off those values.  These things are mentioned in RKM Exec.  I
  1555. recommend reading of the Memory Allocation section as well as the Tasks and
  1556. Messages and Ports sections.
  1557.     You are now ready for examining AmigaDOS functions which will load code
  1558. into memory.  Refer to the AmigaDOS Reference Manual.  Its style and the
  1559. style of the RKM are different, and it often isn't clear that things said
  1560. in the AmigaDOS manual are spelled out in RKM.  AmigaDOS was written in
  1561. BCPL, a language that is a forerunner of c.  You don't really have to know
  1562. much about BCPL even though it may seem so by reading the AmigaDOS manual.
  1563. What you do need to keep in mind are the differences between pointers and
  1564. strings in BCPL and c.  A c pointer is just a number which points to a
  1565. particular byte in memory.  A c string is just a string of bytes with a
  1566. null (0) character at the end.  A BCPL pointer is a memory address divided
  1567. by four.  When it is multiplied by four (shifted left two places) it will
  1568. refer to a memory location starting on a longword boundary.  A BCPL string
  1569. is a string of bytes, the first being the length of the string and the rest
  1570. being the string characters themselves with no terminating character.  A
  1571. BCPL pointer is referred to in c as a BPTR, and a BSTR is a BCPL pointer to
  1572. a BCPL string.  A conversion definition from a BPTR to a c pointer is in
  1573. libraries/dos.h: #define BADDR( bptr )   (((ULONG)bptr) << 2).  Matthew
  1574. Dillon, writing in the first issue of Transactor for the Amiga (The Packet
  1575. Interface to DOS, in C) has provided #defines for both conversions:
  1576.  
  1577.     #define BTOC(bptr) ((long)(bptr) << 2)
  1578.     #define CTOB(cptr) ((long)(cptr) >> 2)
  1579.  
  1580.     There is also the definition:
  1581.  
  1582.     #define BADDR(bptr) (bptr << 2)
  1583.  
  1584. in libraries/dos.h.
  1585.     We can now begin to make sense of the DOS functions given in the
  1586. AmigaDOS Reference Manual and use them for implementing programs in a
  1587. different way than we have before.  Recalling what we did with Clock.c:  We
  1588. compiled it into the current directory with the compiler supplied startup
  1589. code which interfaces main() to the Amiga system.  It allows the program to
  1590. be run from either CLI or Workbench, and we usually don't think much of it.
  1591. However, Clock.c can stand alone.  That is, it doesn't need DOS functions
  1592. for input or output since it writes directly to a window it creates and it
  1593. cleans up all resources it allocates before it exits, so it can be compiled
  1594. and linked in a way that will make it less than half its former size.
  1595. Since it won't have startup code the first lines of code in the source file
  1596. have to be the main program (startup code calls main() by name so main()
  1597. can be anywhere in the source file).  Startup code also defines the
  1598. variable SysBase, the pointer to the ExecBase structure.  We'll have to do
  1599. that ourselves.  It is always located in memory address 4, the only fixed
  1600. address in the system.
  1601.     Make NewClock.c out of the source for Clock.c by uncommenting the two
  1602. lines referring to SysBase: The structure pointer declaration and the
  1603. structure pointer definition inside main().  You can rename main() as
  1604. NewClock() because the function name is not important any more although its
  1605. position in the source file now is important.
  1606.     We will be using this as a task, so compile it with stack checking
  1607. disabled and register saving enabled.  Do not link it with startup code or
  1608. the utility files.  To save a few more bytes you could keep the linker from
  1609. including debugging information in the final file.  If you are using Blink
  1610. the command line would be:
  1611.  
  1612.     Blink NewClock.o LIBRARY LIB:lc.lib LIB:Amiga.lib NODEBUG
  1613.  
  1614.     When you run it, it will behave just like Clock.
  1615.     Save NewClock to the current directory.  Recall that CreateTask()
  1616. requires the address of the code that is going to be implemented as the
  1617. task.  We can use AmigaDOS to load NewClock and tell us the address where
  1618. it has been loaded.  Pull out your AmigaDOS Reference Manual, look up
  1619. LoadSeg() under "Calling AmigaDOS".  We will use this function to get
  1620. NewClock, find its address, implement it as a task, and exit, leaving the
  1621. task running by itself on the system.  In order to find it later we will
  1622. give it a name.  We also will need to know the BPTR to the loaded segment
  1623. later on, so we will save it in the task structure itself for future
  1624. reference.
  1625.     LoadSeg() returns a BPTR to a segment list (q.v. under AmigaDOS data
  1626. structures).  We will need to convert that to a real address before we can
  1627. use it.  A segment list is a forward linked list.  The first word of it is
  1628. a BPTR to the following segment.  We won't need to scan the list, but we
  1629. will need the address of the first byte of code in the list.  We get that
  1630. simply by adding 4 to the address that we obtained from LoadSeg() converted
  1631. to a c address.  We add 4 because the segment list address is declared as a
  1632. long here, but if you get fancy in your own code and declare it as a pointer,
  1633. remember c adds the size of the declared pointer to a pointer so that if:
  1634.  
  1635.     long *lp = (long *) 50;
  1636.  
  1637. then lp + 1 will equal 54.
  1638.     CreateTask() also requires a character string for a name.  This program
  1639. will exit after making the task, so any character string within it will
  1640. disappear upon exit and the string pointer put into the task structure will
  1641. be meaningless.  We will need to find the task by name when it comes time
  1642. to delete the clock, so the character string itself has to be attached to
  1643. the task structure.  Doing this will serve as an example of memory
  1644. management related to tasks.  There is a memory List structure within a
  1645. Task structure, namely tc_MemEntry.  If you tried reading the source code
  1646. to CreateTask() you might have noticed that the memory allocated for the
  1647. task stack was linked into this list.  The reason for doing it that way is
  1648. that upon task exit or deletion, Exec will deallocate all memory linked
  1649. into the list automatically.  You won't have to.  The key function for this
  1650. is AllocEntry().  It accepts a MemList structure, which is the usual Node
  1651. structure and some information on the block of memory you want.  It returns
  1652. a MemList structure filled in with information on the block of memory it
  1653. allocated.  You can then use the allocated memory for whatever you like,
  1654. then link it into the task memory list.  Here we need only one entry long
  1655. enough for the task name.  We fill in the requirements and call
  1656. AllocEntry().  When it returns we copy the characters of the name string
  1657. into the part of the structure that is the memory we asked for.
  1658.     Now we're ready to make the program a task.  We call CreateTask with the
  1659. address of the name string we just made, a priority, the address of the
  1660. code in the segment we loaded, and the usual 4000 stack size request.  (It
  1661. would be fruitful to investigate just how much stack this routine needs.  I
  1662. suspect it is much less.)  Now that we have an address of a task structure
  1663. we can link the newly allocated memory into the task list with AddTail() so
  1664. that it will stay with the task when we exit.
  1665.     Once we exit, the address of the code segment will be lost, so for the
  1666. deleting program we put that information into another task structure
  1667. element: tc_UserData.  It is intended to be a pointer to anything you want
  1668. to relate to the task, but here we have only a longword, so it fits nicely.
  1669.     You will have to provide the full pathname of NewClock to LoadSeg(), so
  1670. alter this source code accordingly.  Compile and link this without any
  1671. special options.
  1672.  
  1673. /***** GetClock.c ********************************************************/
  1674.  
  1675. #include <exec/types.h>
  1676. #include <exec/memory.h>
  1677. #include <libraries/dosextens.h>
  1678.  
  1679. #define PRIORITY 0
  1680. #define STACK 4000
  1681. #define MAXNAME 20
  1682.  
  1683. struct Task *CreateTask();
  1684. struct MemList *AllocEntry();
  1685.  
  1686. void
  1687. main()
  1688. {
  1689.     BPTR segment;
  1690.     ULONG newclock;
  1691.     struct Task *T;
  1692.     struct MemList ml, *ML;
  1693.  
  1694.     segment = LoadSeg("NewClock"); /* Expects it in the current directory */
  1695.     newclock = BADDR(segment);     /* Convert the BPTR to an address */
  1696.     newclock += 4;                 /* Get the address of the code */
  1697.  
  1698.     ml.ml_NumEntries = 1;          /* Build the MemList request */
  1699.     ml.ml_me[0].me_Reqs = (MEMF_PUBLIC | MEMF_CLEAR);
  1700.     ml.ml_me[0].me_Length = MAXNAME;
  1701.     ML = AllocEntry(&ml);          /* Allocate memory */
  1702.  
  1703.     strcpy(ML->ml_me[0].me_Addr, "NewClockTask");   /* Copy name to it */
  1704.  
  1705.     T = CreateTask(ML->ml_me[0].me_Addr, PRIORITY, newclock, STACK);
  1706.  
  1707.     AddTail(&T->tc_MemEntry, ML);    /* Link in the new memory allocation */
  1708.  
  1709.     T->tc_UserData = (APTR) segment;    /* Save the segment BPTR cast to */
  1710.                                                 /* agree with tc_UserData def.   */
  1711.  
  1712.                 /* Exit and let NewClock work */
  1713. }
  1714.  
  1715. /*************************************************************************/
  1716.  
  1717.     When you run this you will have a clock just like Clock.c produced in
  1718. the title bar.  Before you do, run Avail to find out how much memory you
  1719. have available.  Then Run Clock and run Avail again to see how much memory
  1720. is taken by doing things that way.  If you do that with GetClock and click
  1721. on the close gadget, you will notice about 2000 bytes less memory than you
  1722. had before.  That's the loaded segment of NewClock.  You caused NewClock to
  1723. drop out of its function, which causes Exec to delete the task, but Exec
  1724. doesn't know anything about the segment.  Its address is lost.  So maybe I
  1725. better supply a routine to properly close everything up.
  1726.     We took care to make a name for the task, so now we find it with
  1727. FindTask().  We have to tell NewClock that we are going to delete the
  1728. segment from memory (return its memory to the free pool).  That way
  1729. NewClock can properly exit, just as it does when you click on the close
  1730. gadget.  We do that by sending a message to the window UserPort it looks at
  1731. in its Wait() function.  A message arriving sets the SigBit.  In this case
  1732. the message contents don't matter.  We can find that port with FindPort()
  1733. since we know its name is "IDCMP".  The trouble is, Intuition calls two
  1734. ports in every window "IDCMP" so there can be a lot of ports in the Exec
  1735. PortList with the same name.  There are two such ports associated with the
  1736. CLI window you are using to run these programs.  The distinguishing thing
  1737. is that we had Intuition create ports for NewClock in the task we created
  1738. and there are no others by that name in that task.  How do we tell them
  1739. apart?  One is the UserPort, which we use to detect window events like a
  1740. click on the close gadget.  The other is the WindowPort, which is the port
  1741. we reply to when we ReplyMsg() to an IntuiMessage.  The UserPort has a
  1742. message arrival action of signalling its task.  The WindowPort doesn't.
  1743. So, we use FindPort() to find the first port named "IDCMP" in the list.  We
  1744. scan through the list with FindName() because it has the property that it
  1745. will find the next following occurence of the name given to it.  We scan
  1746. through the list until we find a port named "IDCMP" that is in the task we
  1747. found with FindTask().  We pick the one that has a message arrival action
  1748. of PA_SIGNAL.  Now we pick up the task's tc_UserData value before we tell
  1749. the task to delete itself.
  1750.     We then create a port for accepting a message reply and send a message
  1751. to the port we found.  Its contents don't matter because NewClock responds
  1752. only to the action of a message arriving.  It won't reply to the WindowPort
  1753. as it usually does because we've put the address of our reply port right in
  1754. the message.  When we wait for that reply, we are giving up control of the
  1755. microprocessor so NewClock can close things up.  Notice, however, that when
  1756. NewClock replies, if we are operating at a higher priority than it is, we
  1757. will regain control before NewClock is quite finished.  This gives me an
  1758. opportunity to demonstrate the use the AmigaDOS function Delay() to give up
  1759. control for a fraction of a second, long enough for NewClock to finish
  1760. before we unload the code of NewClock.  When the delay (about 20
  1761. milliseconds) expires, we assume NewClock is done and unload its code from
  1762. memory.
  1763.     Good programming practice requires somewhat more positive action than
  1764. this, but my aim is to show how some of the AmigaDOS functions work and to
  1765. give you an idea of the kind of thinking required for programming a
  1766. multitasking system: sometimes your program is running and sometimes it
  1767. isn't, and it's very important to keep the consequences of that in mind.
  1768.     Compile and link this with no special options and you'll be able to
  1769. close NewClock without touching the close gadget.
  1770.  
  1771. /***** DumpClock.c *******************************************************/
  1772.  
  1773. #include <exec/types.h>
  1774. #include <libraries/dosextens.h>
  1775.  
  1776. struct Task *FindTask();
  1777. struct MsgPort *CreatePort(), *FindPort(), *FindName();
  1778.  
  1779. void
  1780. main()
  1781. {
  1782.     BPTR segment;
  1783.     struct Task *T;
  1784.     struct MsgPort *P, *RP;
  1785.     struct Message m;
  1786.  
  1787.     T = FindTask("NewClockTask");
  1788.     if (T == NULL) {
  1789.         printf("NewClockTask not found\n");
  1790.         exit(0);
  1791.     }
  1792.  
  1793.     P = FindPort("IDCMP");
  1794.     do {
  1795.         if (P->mp_SigTask == T && P->mp_Flags == PA_SIGNAL) break;
  1796.         P = FindName(P, "IDCMP");
  1797.     } while (P != 0);
  1798.  
  1799.     if (P == 0) {
  1800.         printf("Port 'IDCMP' not found\n");
  1801.         exit(0);
  1802.     }
  1803.  
  1804.     segment = (BPTR) T->tc_UserData;
  1805.  
  1806.     RP = CreatePort("", 0);
  1807.     m.mn_ReplyPort = RP;
  1808.     PutMsg(P, &m);
  1809.     WaitPort(RP);
  1810.     DeletePort(RP);
  1811.     Delay(1);
  1812.     UnLoadSeg(segment);
  1813. }
  1814.  
  1815. /*************************************************************************/
  1816.  
  1817.     This example showed off task memory allocation and used the same source
  1818. code for the clock as we used before, but another way to close out this
  1819. kind of program is to use a FinalPC() routine.  In the case of NewClock.c
  1820. you would drop out of the program immediately upon receipt of the close
  1821. message, be it from an Intuition message or one from outside.  Since it is
  1822. running as a task, Exec will then call your FinalPC() function which would
  1823. abort the timer and close the window and libraries.  There is a problem in
  1824. replying to the message:  If it were from Intuition you must reply before
  1825. you close the window, but if it came from outside you would want to have
  1826. the window and libraries closed before you replied.  The simple way to
  1827. distinguish the two cases would be to use techniques we used before: check
  1828. the message->mn_ReplyPort->mp_SigTask to see if it agrees with the result
  1829. of FindTask(0).  If it does, the message came from within our task
  1830. (Intuition), and if it does not, the message came from outside and you
  1831. would ReplyMsg() after everything were closed.  It is also possible to use
  1832. Forbid() right before the ReplyMsg() call.  It stops multitasking, so
  1833. control cannot pass to the task receiving the reply (or any other task)
  1834. until the forbidden state is exited by the termination of the program.
  1835. This prevents DumpClock from ever unloading the NewClock program segment
  1836. until it has completed deallocating things.
  1837.     A Process is to AmigaDOS what a Task is to Exec.  A process permits
  1838. access to AmigaDOS functions that themselves multitask, such as Write() and
  1839. functions which use them such as printf().  The process structure is
  1840. defined in libraries/dosextens.h in the RKM.  It starts off as a Task
  1841. structure, has a message port, and continues with elements specific to a
  1842. process.  The discussion in the AmigaDOS Reference manual starts with the
  1843. extensions.  Communications between processes is by an extension to the
  1844. Exec message structure called a packet (See Amiga Transactor, volume 1,
  1845. issue 1, "The Packet Interface to AmigaDOS").  A process structure is
  1846. created by the DOS function CreateProc() just as CreateTask() creates a
  1847. task structure for Exec.  The result returned by CreateProc(), however, is
  1848. a c pointer (A real address, not a BPTR) to the message port within the
  1849. process structure.  To obtain the address of the task structure (which can
  1850. also be interpreted as the address of the process structure) you can
  1851. subtract the size of a Task structure from the address returned by
  1852. CreateProc().  You can also obtain that address by examining the mp_SigTask
  1853. field of the message port pointed to by the address CreateProc() returns,
  1854. or you can get it with FindTask(0).
  1855.     CreateProc() links the process structure into the Exec task list with
  1856. its type (pr_Task.tc_Node.ln_Type) set to 13 to indicate it is a Process (a
  1857. Task has a type of 1.  See exec/nodes.h).  If you wanted to find it later
  1858. you would use FindTask() and check the type.  A BPTR to the loaded segment
  1859. is the fourth element of the SegArray (Counting the first, or element[0],
  1860. as the SegArray size).  That is sort of pointed to by the element
  1861. pr_SegList, a BPTR.  The segment pointer itself is a BPTR, ready for use by
  1862. UnLoadSeg(). To find that pointer you would use code like this:
  1863.  
  1864.     #include <libraries/dosextens.h>
  1865.  
  1866.     struct Process *P;
  1867.     long *seglist;
  1868.     BPTR segment;
  1869.  
  1870.     P = (struct Process *) FindTask("process_name");
  1871.     seglist = (long *) (P->pr_SegList << 2);
  1872.     segment = seglist[3];
  1873.  
  1874. I've provided code in the example to print out some information on the
  1875. process it creates.
  1876.     Programs you write are usually prepended with startup code.  You don't
  1877. see this in the code you write, but it is the first code block your linker
  1878. works on (called c.o, AStartup.obj, LStartup.obj or the like) as it makes
  1879. you executable program.  From a cold start AmigaDOS has control of the
  1880. machine.  It loads Intuition (The set of all the fancy Amiga graphics
  1881. routines), opens a CLI window, and runs the commands in the file
  1882. S:Startup-Sequence.  If the program LoadWB is in the file then a program
  1883. called Workbench is installed as a process.  If not, the CLI is active,
  1884. accepting characters typed on the screen.  A carriage return typed in the
  1885. CLI causes AmigaDOS to find the program, attach it to the process already
  1886. existing for the CLI, then turns control over to your startup code.  If
  1887. LoadWB was executed in the startup sequence, a program called Workbench is
  1888. installed as a process.  It monitors clicks on icons among other things.
  1889. When you click on a program's icon, Workbench is activated, which finds the
  1890. associated program, gathers some data from the icon and other sources (more
  1891. on that in a bit), builds a process structure for it, and calls your startup
  1892. code.
  1893.     The startup code finds the address of the ExecBase structure (SysBase),
  1894. opens the DOS library and examines the process structure element pr_CLI.
  1895. That will be zero if your program was started from Workbench or some value
  1896. if started from AmigaDOS.  That value is a BPTR to a CommandLineInterface
  1897. structure which contains pertinent information for the CLI (see RKM
  1898. Libraries and Devices, libraries/dosextens.h).  In either case the startup
  1899. routine makes up a list of arguments on a stack with the number of
  1900. arguments as the first entry and passes this to the routine called _main().
  1901. That routine also makes a determination of the source of the startup, CLI
  1902. or Workbench, opens files for your program's input and output (stdio) and
  1903. passes pointers to the arguments to your program in the familiar form of
  1904. argc (an argument count) and argv (a pointer or list of pointers to the
  1905. arguments themselves).  Your program can determine who started it by
  1906. examining argc.  If it is not zero, startup was from the CLI, otherwise
  1907. startup was from Workbench.  When your program exits, if no exit routine
  1908. was called, _main() calls exit() for you, which completes any file writing
  1909. in progress and closes any open files.  The startup environment under
  1910. AmigaDOS and Workbench is so different I'll treat them separately.
  1911.     For a CLI startup, the arguments are the things you have typed on the
  1912. command line after the program name.  If the first character after the
  1913. command is a '<' or a '>' then DOS traps out the next following item and
  1914. tries to interpret it as a filename for redirecting standard input or
  1915. output.  Otherwise everything typed on the screen before a carriage return
  1916. is put in a string (starting with the command name).  AmigaDOS finds the
  1917. command (your program), loads the code into memory, attaches it to the
  1918. process structure already existing for the CLI, and passes the address of
  1919. the command string to your startup code.  The startup code sees you have
  1920. started from the CLI (pr_CLI != 0) and passes the address of the string to
  1921. _main().  That routine also sees you have started from the CLI, so it
  1922. separates the tokens (characters separated by spaces or enclosed in double
  1923. quotes) and builds an array of pointers (argv[]) to them.  It puts the
  1924. count of tokens in argc.  It then opens the standard IO files with calls to
  1925. Input() and Output() and obtains the FileHandle of the console (CLI) with a
  1926. call to Open() (All AmigaDOS functions).  It then calls your program with
  1927. the two function arguments (int) argc and (char *) argv[].  The first
  1928. argument of the array, argv[0], is the name of your program, so argc is
  1929. always at least 1.  It will be in argv[0], and any arguments typed after
  1930. the command will be in argv[1] through argv[argc].
  1931.     If you started by double clicking on an icon, Workbench obtains
  1932. information about your program from the .info file associated with it.
  1933. That file holds the graphics for your program's icon and information for
  1934. running your program like stack size.  The equivalent of a command line is
  1935. a list of icons you have selected.  The first one is your program and other
  1936. items are other icons you might have selected with extended selection.
  1937. Each item comes in the form of a struct WBArg, consisting of a BPTR to a
  1938. FileLock structure and a pointer to a string of characters that is the name
  1939. of the item selected.  Workbench gets your program, builds a process
  1940. structure for it with pr_CLI set to zero, and calls your program.  The
  1941. startup code sees that you started from Workbench so it waits at pr_MsgPort
  1942. for a startup message of the form struct WBStartup (refer to RKM Libraries
  1943. and Devices, workbench/startup.h).  That gives Workbench an opportunity to
  1944. make up the startup message.  When it arrives at the process port, the
  1945. startup code is activated and looks for an argument list in the message
  1946. (sm_ArgList, which points to a struct WBArg).  If there is one it assumes
  1947. the first argument structure contains a lock on a directory which it makes
  1948. the current directory.  It then looks for a filename in sm_ToolWindow.
  1949. This is expected to be the name of a CON: or RAW: window that has already
  1950. been opened.  If there is one the startup code opens that file and makes it
  1951. the standard I/O, putting the address of its process message port (fh_Type
  1952. in the FileHandle structure obtained from Open()) into this process's
  1953. structure element pr_ConsoleTask.  Instead of passing an address of a
  1954. string to _main() as above, it pushes the address of the startup message on
  1955. the stack and then a zero.  When _main() sees it has been called with a
  1956. zero it knows that the startup is from Workbench, so it sets argc to zero,
  1957. opens a small console window which it makes the standard IO (which could
  1958. conflict with the ToolWindow; more on that later),  sets the name of that
  1959. window to the name in the wa_Name field of the first argument (if one
  1960. exists) and calls your program.  The value of argv is the address of the
  1961. startup message.
  1962.     We can simulate the action of a Workbench startup with the following
  1963. program.  We first obtain a lock on the code we wish to load to prevent
  1964. another task from deleting or changing it before we're finished.  If it
  1965. exists we load it into memory and create a process structure for it much as
  1966. we did for the task demonstrations.  At this point CreateProc() asks Exec
  1967. to enter the process structure into the system task list and Exec calls
  1968. your code, but the startup code quickly reaches the point of waiting on the
  1969. process message port for the startup message.  Exec returns control to this
  1970. program as soon as the startup code executes WaitPort().  CreateProc()
  1971. returns a pointer to a message port (pr_MsgPort) from which we extract the
  1972. address of the newly created process structure.  CreateProc() puts the name
  1973. of the program you loaded into the process node.  I've put code in here to
  1974. print out that name and the node type to show how it is entered in the
  1975. system task list.  Next we make up an argument structure (there can be more
  1976. than one; make them an array: struct WBArg args[], for example), putting a
  1977. file lock structure into it so the current directory will become the
  1978. directory containing the loaded program, and also putting in the name of
  1979. the loaded program.  The startup message can be prepared now: Like all
  1980. messages there needs to be a reply port, and in this case the length field
  1981. needs to be filled in so the system won't lose track of any of it.  The
  1982. element sm_Process is set to the address of the message port of the process
  1983. structure (AmigaDOS refers to all process structures by their message ports
  1984. rather than the address of the node structure).  The BPTR to the loaded
  1985. code segment goes into sm_Segment.  There is only one argument here: its
  1986. address is in sm_ArgList.  I didn't set any ToolWindow filename because the
  1987. _main() the compiler uses opens a console window.  There is a section here
  1988. to print out the contents of the message on the CLI for reference.  (Soon
  1989. to come is a program that can be run from Workbench itself and use the
  1990. small window _main() makes to print out the contents of the received
  1991. startup message.)  We start the loaded program running by sending the
  1992. message.  We can tell when it has terminated by waiting for it to reply to
  1993. our reply port.  At that point we deallocate resources: delete the reply
  1994. port, unlock the file we loaded, and unload its segment.
  1995.     Compile and link this without any special options.
  1996.  
  1997. /***** DoProc.c **********************************************************/
  1998.  
  1999. #include <exec/types.h>
  2000. #include <libraries/dosextens.h>
  2001. #include <workbench/startup.h>
  2002.  
  2003. struct MsgPort *CreatePort(), *CreateProc();
  2004.  
  2005. void
  2006. main(argc, argv)
  2007.     int argc;
  2008.     char *argv[];
  2009. {
  2010.     struct WBStartup WBS;
  2011.     struct WBArg WBA;
  2012.     struct MsgPort *MP, *RP;
  2013.     struct Process *P;
  2014.     BPTR segment;
  2015.     BPTR FL;
  2016.  
  2017.     if (argc != 2) {
  2018.         printf("Please provide a filename\n");
  2019.         exit(0);
  2020.     }
  2021.  
  2022.     FL = Lock(argv[1], ACCESS_READ);
  2023.     if (FL == NULL) {
  2024.         printf("%s not found\n", argv[1]);
  2025.         exit(0);
  2026.     }
  2027.  
  2028.     segment = LoadSeg(argv[1]);
  2029.  
  2030.     MP = CreateProc(argv[1], 0, segment, 4096);
  2031.  
  2032.     P = (struct Process *) MP->mp_SigTask;
  2033.  
  2034.     printf("Process Name: '%s'\n", P->pr_Task.tc_Node.ln_Name);
  2035.     printf("Task Type:     %d\n",  P->pr_Task.tc_Node.ln_Type);
  2036.  
  2037.     WBA.wa_Lock = FL;
  2038.     WBA.wa_Name = argv[1];
  2039.  
  2040.     RP = CreatePort(NULL, 0);
  2041.     WBS.sm_Message.mn_ReplyPort = RP;
  2042.     WBS.sm_Message.mn_Length = sizeof(struct WBStartup);
  2043.     WBS.sm_Process = MP;
  2044.     WBS.sm_Segment = segment;
  2045.     WBS.sm_NumArgs = 1;
  2046.     WBS.sm_ToolWindow = NULL;
  2047.     WBS.sm_ArgList = &WBA;
  2048.  
  2049.     printf("sm_Message:    %6x\n", &WBS.sm_Message   );
  2050.     printf("sm_Process:    %6x\n",  WBS.sm_Process   );
  2051.     printf("sm_Segment:    %6x\n",  WBS.sm_Segment   );
  2052.     printf("sm_NumArgs:    %6x\n",  WBS.sm_NumArgs   );
  2053.     printf("sm_ToolWindow: '%s'\n", WBS.sm_ToolWindow);
  2054.     printf("sm_ArgList:    %6x\n",  WBS.sm_ArgList   );
  2055.  
  2056.     PutMsg(MP, &WBS);
  2057.     WaitPort(RP);
  2058.     GetMsg(RP);
  2059.  
  2060.     DeletePort(RP);
  2061.     UnLock(FL);
  2062.     UnLoadSeg(segment);
  2063. }
  2064.  
  2065. /*************************************************************************/
  2066.  
  2067.     You can run this on Clock that you made before, but not on NewClock.
  2068. The reason is that NewClock has no startup code, so the startup message
  2069. passed to it will never be replied.  When you run it on Clock you will see
  2070. the clock as before, but also a small CLI window called "Clock".  This is
  2071. the window _main() opens for a program started from Workbench.  Clock does
  2072. not use any DOS I/O, so the window stays empty.  I'll present a little
  2073. program to read and print out the startup message in that window before I
  2074. go on to showing how to avoid opening it.  You might wonder why the window
  2075. doesn't open when Clock is run directly.  When you run it directly, DOS
  2076. calls the program from the CLI, so _main() doesn't open the window.  When
  2077. you call it from DoProc, _main() thinks it started from Workbench, so opens
  2078. the window.  Since you are now proficient in reading c source, here is
  2079. Start.c without comment:
  2080.  
  2081. /***** Start.c ***********************************************************/
  2082.  
  2083. #include <libraries/dosextens.h>
  2084. #include <workbench/startup.h>
  2085. #include <stdio.h>
  2086.  
  2087. void
  2088. main(argc, argv)
  2089.     int argc;
  2090.     struct WBStartup *argv;
  2091. {
  2092.     struct WBStartup *WBS;
  2093.  
  2094.     if (argc != 0) exit(0);
  2095.  
  2096.     WBS = argv;
  2097.  
  2098.     printf("sm_Message:    %6x  ", &WBS->sm_Message   );
  2099.         printf("wa_Lock: %x\n", WBS->sm_ArgList->wa_Lock);
  2100.     printf("sm_Process:    %6x  ",  WBS->sm_Process   );
  2101.         printf("wa_Name: %s\n", WBS->sm_ArgList->wa_Name);
  2102.     printf("sm_Segment:    %6x\n",  WBS->sm_Segment   );
  2103.     printf("sm_NumArgs:    %6x\n",  WBS->sm_NumArgs   );
  2104.     printf("sm_ToolWindow: '%s'\n", WBS->sm_ToolWindow);
  2105.     printf("sm_ArgList:    %6x\n",  WBS->sm_ArgList   );
  2106.  
  2107.     printf("\nHit return when ready: ");
  2108.     getchar();
  2109. }
  2110.  
  2111. /*************************************************************************/
  2112.  
  2113.     Notice the declaration of WBS is redundant.  I put it in for a smidgin
  2114. of clarity.  You could try running Clock, NewClock and Start from
  2115. Workbench.  Copy some program .icon file to the same directory they are in,
  2116. copying to Clock.info, NewClock.info, and Start.info.  Now that they have
  2117. icons (one on top of the other; move them apart, click once on each one and
  2118. select the menu item Snapshot to fix them in place), you can click on them
  2119. to see the same kind of thing as you do when you run DoProc on Clock and
  2120. Start.  NewClock runs as soon as AmigaDOS calls CreateProc() for it,
  2121. ignoring the startup message.  When you click on its close gadget it
  2122. returns to DOS, never having replied to the message nor restoring the
  2123. machine's stack pointer, so DOS doesn't deallocate things properly.  All
  2124. these things are done in a few bytes of startup code.  NewClock is really
  2125. an example for setting up programs as tasks from within other programs.
  2126. While the technique is useful when your main program controls loading and
  2127. executing code, it isn't suitable for execution from the Workbench
  2128. environment.  Using the standard startup code and modifying _main.c is a
  2129. fruitful approach.
  2130.     If you are writing a program to run from Workbench and you don't intend
  2131. to use standard I/O (printf(), scanf(), and the like), you'll not want that
  2132. little CLI window.  In the files that came with your compiler there should
  2133. be a file called _main.c.  Using the Lattice compiler as an example, you
  2134. will see a few #ifndef TINY statements.  These enclose code which relates
  2135. to opening that console window.  Place the statement #define TINY with the
  2136. other #defines at the beginning of the source code and compile it.  Since
  2137. it is a proven program you can defeat stack checking when you compile to
  2138. make it even smaller.  Then link from your startup code, _main.o, and
  2139. Clock.o to LittleClock, in that order, with the debugging option off.  The
  2140. use of _main.o in the link line prevents the linker from looking into the
  2141. libraries for its usual version of _main.o.  Now run DoProc Clock and you
  2142. will not see the console window.  You'll notice the file is about 700 bytes
  2143. smaller than that of Clock, too.  If you recompiled Clock.c with stack
  2144. checking disabled, the final file will be about two thirds the size of the
  2145. original. NewClock doesn't have _main() in it because the linker pulls it
  2146. in only if it is referenced by the startup code.  If you want a Workbench
  2147. program to have a console window of a different size you can alter the
  2148. window definition in _main.c and recompile it.
  2149.     Now that you see how a startup message looks, you can run this next
  2150. program to show a Workbench style argument list.  It is obtained by holding
  2151. down a shift key, then clicking on the program's icon once.  While still
  2152. holding down the shift key, move to another icon and click on it once.  Do
  2153. a few this way, double clicking on the last one.  Workbench obtains file
  2154. locks on each one and gets their names, passing it all to you.
  2155.     Compile this without special options so you get the default console
  2156. window to print into.  Make an icon for this program or copy another one to
  2157. the name ShowNames.info so it will show on the screen.
  2158.  
  2159. /***** ShowNames.c ******************************************************/
  2160.  
  2161. #include <workbench/startup.h>
  2162. #include <stdio.h>
  2163.  
  2164. extern char *_ProgramName;
  2165. extern struct WBStartup *WBenchMsg;
  2166.  
  2167. void
  2168. main(argc, argv)
  2169.     int argc;
  2170.     char *argv;
  2171. {
  2172.     struct WBArg *A;
  2173.     int i;
  2174.  
  2175.     /* Don't allow a CLI start */
  2176.     if (argc != 0) exit(0);
  2177.  
  2178.     printf("%s: \n", _ProgramName);
  2179.     /* Print the argument list */
  2180.     A = WBenchMsg->sm_ArgList;
  2181.     for(i = 0; i < WBenchMsg->sm_NumArgs; i++) {
  2182.         printf("'%s'\n", (A + i)->wa_Name);
  2183.     }
  2184.  
  2185.     printf("Hit return to continue: ");
  2186.     getchar();
  2187.  
  2188. }
  2189.  
  2190. /************************************************************************/
  2191.  
  2192.     The reason for passing arguments like this is mainly for data files.
  2193. The icons you have been using are called Tool icons, meaning that when they
  2194. are activated, Workbench executes the associated program.  A data file that
  2195. you intend to read must have a different type of icon called a Project
  2196. icon.  Using extended selection, you'll have the name of the project to
  2197. work on and a file lock all ready so you can use the DOS functions Read()
  2198. and Write() to access it.  This next program is intended only to show you
  2199.  how the arguments are obtained.  Notice the use of the external variable
  2200. _ProgramName.  It comes from the startup code, and there are others of some
  2201. utility, the most useful being SysBase, the pointer to the ExecBase
  2202. structure that holds the fundamental Exec pointers, lists, and constants.
  2203.     Workbench provides more information than just a list of selected icons.
  2204. If you click once on an icon and then obtain the Workbench menu, under the
  2205. Workbench menu list you'll see an Info selection.  Selecting that gets you
  2206. a full sized window of icon information.  The comment string gadget
  2207. implements the same thing that the CLI command FileNote does.  It attaches
  2208. a short note to a file that you can see with the List command, or see in
  2209. the Info window whenever you select it.  The Tool Types box can be of great
  2210. help to a program.  You can enter anything you want here (There's 32k of
  2211. space available for these text strings), but by using a certain format you
  2212. can use Intuition functions to sort them out for you.  They are of no
  2213. importance to the system, just your program.  You enter them by clicking on
  2214. the ADD gadget, then in the string gadget, and start typing.  You can click
  2215. on the up and down arrows to enter or see other strings.  There is some
  2216. discussion in RKM, Libraries and Devices, in the Workbench section.  Here
  2217. is a program that will read strings in the recommended format.  It is a
  2218. name in capital letters followed by the equals sign and then the text of
  2219. interest to your program.  We'll just name them FIRST, SECOND, and THIRD.
  2220.  
  2221. /***** ShowTools.c ******************************************************/
  2222.  
  2223. #include <workbench/workbench.h>
  2224. #include <workbench/icon.h>
  2225. #include <stdio.h>
  2226.  
  2227. extern char  *_ProgramName;
  2228.  
  2229. long IconBase;
  2230.  
  2231. char *tools[]= { "FIRST", "SECOND", "THIRD" };
  2232.  
  2233. void
  2234. main()
  2235. {
  2236.     struct DiskObject *DO;
  2237.     char *text;
  2238.     int i;
  2239.  
  2240.     if (!(IconBase = OpenLibrary("icon.library", 0))) {
  2241.         /* Error reporting goes here */
  2242.         exit(0);
  2243.     }
  2244.     if (DO = GetDiskObject(_ProgramName)) {
  2245.         for (i = 0; i < sizeof(tools)/sizeof(char *); i++) {
  2246.             if (text = FindToolType(DO->do_ToolTypes, tools[i])) {
  2247.                 printf("%s\n", text);
  2248.             } else {
  2249.                 printf("%s not found\n", tools[i]);
  2250.             }
  2251.         }
  2252.         FreeDiskObject(DO);
  2253.     } else {
  2254.         /* Error reporting goes here */
  2255.     }
  2256.     CloseLibrary(IconBase);
  2257.     printf("Hit return to continue ");
  2258.     getchar();
  2259. }
  2260.  
  2261. /************************************************************************/
  2262.  
  2263.     Make a Tool icon for this program with the Workbench icon editor.  After
  2264. you have one, click on it once and select the Info menu item.  In the
  2265. Tool Types gadget select ADD and click on the string gadget.  Type in
  2266. "FIRST=" and some text.  The tool name has to be in upper case and the
  2267. equals sign must follow without a space in between.  Click on ADD again.
  2268. The text you just typed will disappear and the string gadget is ready to
  2269. accept another line.  Keep doing that until you have enough entries.  (You
  2270. might have noticed that the reading loop in the above program calculates
  2271. the size of the tools array, so if you want more, just put some more
  2272. strings in tools[]).  When you're finished, click on the SAVE gadget in the
  2273. lower left corner.  Now it's ready.  Click twice on the icon and watch the
  2274. show.  If you plan to have a program read another icon's tool types, get
  2275. the name of the other icon from the startup message argument list.  You'll
  2276. also have to use the lock in the argument list (wa_Lock) to make the
  2277. current directory the one that contains the selected icon (wa_Name).  To
  2278. use the pevious example, If "A" is your pointer to the argument list then
  2279. CurrentDir((A + i)->wa_Lock) will accomplish that.  At the end of the loop
  2280. set the current directory back by using your own directory with
  2281. CurrentDir(A->wa_Lock).
  2282.     A note about style before I go on:  This program is quite a simple
  2283. thing.  If you are reading several tool types and intend to take action on
  2284. them later, you will have to copy the string from the DiskObject structure
  2285. into a buffer.  FindToolType() returns only a pointer to a string which
  2286. will be invalid as soon as you look at another one or FreeDiskObject().
  2287. The other function for handling tool strings is MatchToolValue(), which
  2288. searches the string for a specific text and returns TRUE or FALSE.  Also it
  2289. would be wise to check the element do_Type to see that the icon you get is
  2290. the correct type.  For a project the type will be WBPROJECT.  See the
  2291. initial #defines in workbench/workbench.h.
  2292.     Notice the lines in the code above, "Error reporting goes here".  You
  2293. should provide a path for any faults so that the program can inform you of
  2294. them.  Under Workbench there is usually no window to print into with
  2295. printf() (although if you prevent _main() from opening one, you can open
  2296. one yourself later).  There is another way.  When you're using Workbench
  2297. the Workbench screen title bar is usually visible.  There is an Intuition
  2298. function which makes it easy to write into it.  To use it you must know the
  2299. address of the window structure of your window.  Refer to
  2300. intuition/intuitionbase.h.  You'll see the element ActiveWindow.  The
  2301. instant you click on an icon your window is the active one.  If you grab
  2302. that address right off you'll be ready to write to the screen's title bar
  2303. any time later.  The reason you get it right away is that another window
  2304. can become active while your program is running, and it could be in another
  2305. screen.  Here's the example:
  2306.  
  2307. /***** ShowTitle.c ******************************************************/
  2308.  
  2309. #include <intuition/intuition.h>
  2310. #include <intuition/intuitionbase.h>
  2311.  
  2312. struct IntuitionBase *IntuitionBase;
  2313.  
  2314. void
  2315. main()
  2316. {
  2317.     struct Window *W;
  2318.  
  2319.     IntuitionBase = (struct IntuitionBase *)
  2320.                                                 OpenLibrary("intuition.library", 0);
  2321.     if (! IntuitionBase) exit(0);
  2322.  
  2323.     W = IntuitionBase->ActiveWindow;
  2324.  
  2325.     SetWindowTitles(W, -1, "Hello There!");
  2326.     Delay(100);
  2327.  
  2328.     CloseLibrary(IntuitionBase);
  2329. }
  2330.  
  2331. /************************************************************************/
  2332.  
  2333.     Compile this, make an icon for it, and run it from Workbench.  An
  2334. important point for whoever uses your program is that way deep in it, some
  2335. screen other than Workbench may be active when you need to report an error.
  2336. You can choose to put the message on the screen that contains your icon, or
  2337. you can put it on the currently active screen by obtaining the window
  2338. address just before displaying the message.  By using the screen currently
  2339. active you alert the user immediately if titles are visible on the current
  2340. screen.  The second parameter, the -1 in the example, is a place for
  2341. putting text in the window title bar instead of the screen's.  What can
  2342. happen is an attempt to put more text into a window title bar than will fit
  2343. (the user might have resized the window).  Whatever method you use, it's a
  2344. good way to report things to the user.
  2345.     Besides specifically writing to the screen title bar, you can specify a
  2346. Screen Title to be displayed when the window is active by setting
  2347. char * Window->ScreenTitle.  Normally it holds the title from the Screen
  2348. structure.
  2349.     One of the things you will need to report is a failed result of a call
  2350. to an AmigaDOS function like Open().  AmigaDOS reports a failure of this
  2351. call by returning a zero.  AmigaDOS also puts the error number (as listed
  2352. in the AmigaDOS manual, "Error Codes and Messages") in your process's
  2353. element pr_Result2, and it is also available by a call to IoErr().  If you
  2354. want to handle reporting all errors yourself, then put a -1 in your
  2355. process's element pr_WindowPtr.  This causes AmigaDOS to report errors
  2356. quietly, as they say in the trade.  If you put a zero in pr_WindowPtr, (the
  2357. default value when your process is created) and the error is one that
  2358. requires user intervention like an attempted Open() on a non-existent disk,
  2359. then AmigaDOS will put up a requester on the Workbench screen asking that
  2360. the disk be inserted in a drive.  If the user corrects the situation and
  2361. clicks on the Retry box, then the call will return with a succesful
  2362. indication like the address of the file handle you wanted.  If the user
  2363. selects Cancel the call will return with an unsuccessful indication (zero
  2364. in the case of Open()) and DOS will put the error value in pr_Result2.
  2365. Perhaps you are operating your program in another screen than Workbench.
  2366. Then you can put the address of your window in pr_WindowPtr, and the same
  2367. action will occur, but in your screen instead of Workbench's.
  2368.   If you are operating from the CLI you can report errors as text in the
  2369. CLI window.  Put an error code from the list in the DOS manual into
  2370. pr_Result2 and call exit(-1) yourself, bypassing the call in _main().  You
  2371. might try this little program to illustrate:
  2372.  
  2373.     #include <libraries/dosextens.h>
  2374.  
  2375.     void
  2376.     main()
  2377.     {
  2378.         struct Process *FindTask();
  2379.  
  2380.         FindTask(0)->pr_Result2 = 226;
  2381.         exit(-1);
  2382.     }
  2383.  
  2384.     Did you know you could use a function call as a structure pointer like
  2385. that?  Anyway, you'll generally not set pr_Result2 yourself.
  2386.     Way back when, I said that tasks couldn't call functions which require
  2387. multitasking themselves, like printf().  You can, of course, have your task
  2388. report back to your main program, which is always installed as a process
  2389. and so can use such functions.  However, now that you have a handle on
  2390. error reporting (so these next experiments won't confuse the daylights out
  2391. of you) and some of the DOS functions, here's an example of AmigaDOS input
  2392. and output from a task.  Instead of using compiler library functions or
  2393. calling the DOS functions directly, we send DOS packets, or the message
  2394. form that DOS uses.  It's a peculiar adaptation of the Exec message system.
  2395. It has been discussed in Amiga Transactor, volume 1, issue 1, by Matthew
  2396. Dillon.  I also recommend a careful reading of the DOS manual.  That
  2397. reference speaks of various structures using different names for the
  2398. structure elements than the RKM, but study will clarify things.  At least
  2399. the structure names are pretty much the same.  They are, indeed, the same
  2400. structures listed in RKM, Libraries and Devices, Include Files,
  2401. libraries/dos.h and libraries/dosextens.h.  The main program simply makes a
  2402. task as we have done before and waits on a signal that it has completed.
  2403. The task builds a DOS packet first to write to the console window (twice)
  2404. then reads from it, using the same packet.  When it receives a carriage
  2405. return it cleans up and signals main().  Compile this with stack checking
  2406. disabled and register saving enabled and link it with the standard _main.o
  2407. so you'll have a console window to use when you run this from Workbench.
  2408.  
  2409. /***** DoPackets.c ******************************************************/
  2410.  
  2411. #include <libraries/dosextens.h>
  2412. #include <exec/memory.h>
  2413.  
  2414. #define BTOC(p) ((ULONG *) ((int) p << 2))
  2415. #define CTOB(p) ((BPTR) ((int) p >> 2))
  2416.  
  2417. struct Task *Mother, *FindTask();
  2418. struct FileHandle *FH;
  2419. LONGBITS ready;
  2420.  
  2421. void
  2422. Task()
  2423. {
  2424.     struct MsgPort *MP;
  2425.     struct StandardPacket *PKT;
  2426.  
  2427.     MP = (struct MsgPort *) CreatePort("", 0);
  2428.     PKT = (struct StandardPacket *)
  2429.         AllocMem(sizeof(struct StandardPacket), MEMF_CLEAR | MEMF_PUBLIC);
  2430.  
  2431.     PKT->sp_Msg.mn_Node.ln_Name = (char *) &PKT->sp_Pkt;
  2432.     PKT->sp_Pkt.dp_Link = &PKT->sp_Msg;
  2433.     PKT->sp_Pkt.dp_Port = MP;
  2434.     PKT->sp_Pkt.dp_Type = ACTION_WRITE;
  2435.     PKT->sp_Pkt.dp_Arg1 = (LONG) CTOB(FH->fh_Type);
  2436.     PKT->sp_Pkt.dp_Arg2 = (LONG) "Hello There!\n";
  2437.     PKT->sp_Pkt.dp_Arg3 = 13;
  2438.     PutMsg(FH->fh_Type, PKT);
  2439.     WaitPort(MP);
  2440.     GetMsg(MP);
  2441.  
  2442.     PKT->sp_Pkt.dp_Port = MP;
  2443.     PKT->sp_Pkt.dp_Arg2 = (LONG) "Hit return to continue ";
  2444.     PKT->sp_Pkt.dp_Arg3 = 23;
  2445.     PutMsg(FH->fh_Type, PKT);
  2446.     WaitPort(MP);
  2447.     GetMsg(MP);
  2448.  
  2449.     PKT->sp_Pkt.dp_Port = MP;
  2450.     PKT->sp_Pkt.dp_Type = ACTION_READ;
  2451.     PKT->sp_Pkt.dp_Arg2 = NULL;
  2452.     PKT->sp_Pkt.dp_Arg3 = 1;
  2453.     PutMsg(FH->fh_Type, PKT);
  2454.     WaitPort(MP);
  2455.     GetMsg(MP);
  2456.  
  2457.     DeletePort(MP);
  2458.     FreeMem(PKT, sizeof(struct StandardPacket));
  2459.     Signal(Mother, ready);
  2460.     Wait(0);
  2461. }
  2462.  
  2463. void
  2464. main()
  2465. {
  2466.     struct Task *T;
  2467.     BPTR FileHandle;
  2468.     int r;
  2469.  
  2470.     Mother = FindTask(0);
  2471.     r = AllocSignal(-1);
  2472.     ready = 1 << r;
  2473.     FileHandle = Open("*", MODE_OLDFILE);
  2474.     FH = (struct FileHandle *) BTOC(FileHandle);
  2475.     T = (struct Task *) CreateTask("T", 0, Task, 4000);
  2476.     Wait(ready);
  2477.     Close(FileHandle);
  2478.     DeleteTask(T);
  2479.     FreeSignal(r);
  2480. }
  2481.  
  2482. /************************************************************************/
  2483.  
  2484.     Notice the pointer to a FileHandle structure in main().  Open() returns
  2485. a BPTR to such a structure.  It has to be converted to a real pointer to
  2486. reference the element fh_Type which is a pointer to the message port where
  2487. the opened file expects to receive packets (the "*" in Open() is just a
  2488. reference to the existing console window, and is an easy way to obtain a
  2489. FileHandle).  In the task itself you see what makes a packet peculiar.  The
  2490. structure starts off with an Exec Message structure which isn't initialized
  2491. as you usually would.  The name field, however is expected to contain, not
  2492. a pointer to a name, but a pointer to a StandardPacket structure.  The first
  2493. field of that structure is a pointer to the Message structure.  Because of
  2494. that the two structures can be allocated separately, although here I
  2495. didn't.  The next element of the StandardPacket part is a pointer to a
  2496. message port to be used as a reply port.  This pointer gets overwritten by
  2497. DOS every time it receives a message, so that element has to be rewritten
  2498. by your program every time you reuse the packet.  The next element is the
  2499. action desired of DOS.  The various kinds are discussed in the DOS manual
  2500. and the ACTION_XX codes are in libraries/dosextens.h.  All the other
  2501. elements contain values particular to the action desired.  The packet is
  2502. put to the message port obtained by Open() and put in fh_Type as I
  2503. mentioned.  The task waits on the arrival of a reply and goes on from
  2504. there, rebuilding the message port element and putting in new values where
  2505. needed.  I put the Open() in main() to keep things simpler (you can't call
  2506. a DOS function directly from a task!).  The mechanism for doing it all from
  2507. within a task gets quite involved because some of the DOS functions aren't
  2508. available by packet passing.  The real importance of this demonstrator is
  2509. that the file opened doesn't have to be a console device.  It can be any
  2510. valid file in the system.  Since file handling is done from within a task
  2511. your main program doesn't have to stop everything to read or write.  This
  2512. can become very important in high speed serial data transfers.
  2513.     As usual with the Amiga, there's another way.  You don't necessarily
  2514. have to involve DOS at all!  If you have built a window yourself rather
  2515. than use the DOS CON: console device, you can have your tasks send messages
  2516. directly to your window's UserPort.  Windows are discussed fairly
  2517. thoroughly in RKM Intuition Reference Manual.  I recommend trying out
  2518. window and screen building and installing them as tasks.  Pretty soon you
  2519. get to the point that main() becomes not much more than a task monitoring
  2520. program.
  2521.     I hope you've come a long way from being mystified by the mass of
  2522. information available on the Amiga, and I hope you can use some of this to
  2523. utilize the great capabilities of the machine.
  2524.  
  2525.                                                                     Joseph M. Hinkle
  2526.                                                                     April 7, 1988
  2527.  
  2528.  
  2529.  
  2530. /*************************************************************************/
  2531. /*************************************************************************/
  2532.  
  2533.