Path: utzoo!utgpu!water!watmath!clyde!att!osu-cis!tut.cis.ohio-state.edu!mailrus!ulowell!page From: page@swan.ulowell.edu (Bob Page) Newsgroups: comp.sources.amiga Subject: v02i015: dres - an object-oriented resource library (docs) Message-ID: <9820@swan.ulowell.edu> Date: 25 Oct 88 00:08:17 GMT Organization: University of Lowell, Computer Science Dept. Lines: 1588 Approved: page@swan.ulowell.edu Submitted-by: dillon@cory.berkeley.edu (Matt Dillon) Posting-number: Volume 2, Issue 15 Archive-name: util/dres.docs # This is a shell archive. Remove anything before this line # then unpack it by saving it in a file and typing "sh file" # (Files unpacked will be owned by you and have default permissions). # This archive contains the following files: # ipc.doc # lists.doc # memory.doc # misc.doc # qints.doc # res.doc # runlib.doc # timedate.doc # if `test ! -s ipc.doc` then echo "writing ipc.doc" cat > ipc.doc << '\Rogue\Monster\' IPC.DOC (DRES.LIBRARY) Matthew Dillon 25 September 1988 Special note. I've tried my best, but this document is somewhat lacking in the exact mechanics and implementation of IPC. Please refer to the source module to resolve such questions. The IPC section of the DRES.LIBRARY pertains to the sending of commands and retrieval of results between arbitrary processes without conflict. Processes which use IPC come in three flavors: (1) A process which is able to accept and process IPC commands (2) A process which might send IPC commands to other processes (3) A process which does both. The IPC mechanism implemented by this library is inherently asychronous in nature (messages and ports), but also provides a synchronous call specially tailored for cases (2) and (3). Doing IPC commands synchronously has certain advantages, the greatest of which is that the implementation by application software is greatly simplified. The synchronous call gets around possible deadlocks in case (3) by interrupting itself and calling a handler if incomming messages occur while it is waiting for the sent message to complete. The greatest feature of this IPC mechanism is a great flexibility in who allocates and deallocates the message buffers. Both the source and destination processes have a wide range of options concerning message buffers. Unless otherwise specified, an allocated copy of the message buffer is automatically made and used which allows the send or reply message buffer used by the process to be immediately discarded. Additionaly, a destination IPCPort can be flagged to force incomming messages to be allocated no matter what the sender requests. NOTE: The term 'allocation of messages' inherently means that if the message is allocated, the user of the message can munge it however he likes (exception: IF_GLOBAL messages that are propogated through all IPC ports). Allocated messages are automatically FreeMem()d by the IPC subsystem or by the receiver. Also, WHEN DUPLICATION OF MESSAGES OCCUR, THE ALLOCATED MEMORY IS OF THE SAME TYPE AS THE ORIGINAL BUFFER. NOTE: Do not get confused by the many different ways messages buffers can be allocated. A message buffer will never be allocated-duplicate more than once. For example, if the source specifies that it is passing an allocated buffer, the further duplication is done by the IPC subsystem. -The sender can allocate the message buffer himself and pass that, pass a temporary buffer which the IPC subsystem duplicates, or pass a static buffer to the destination without the overhead of allocation and duplication. -The receiver of a particular message can determine whether that message was allocated and FreeMem() it himself, can allow the IPC subsystem to free allocated messages, and can even force all incomming messages on his IPC port to be allocated no matter what the sender specifies. Forcing the IPC subsystem to allocate otherwise static messages is a very useful ability to have. For instance, the parser employed by the receiver of a message might destroy or modify the message buffer during parser, while the original sender of the message might have used a static string or otherwise expected the buffer not to be screwed up. By the receiver being able to specify that he WANTS the buffer to be an allocated duplicate because he intends to modify it without permission is a good thing. Even though the sender passes a static buffer to the IPC subsystem, that subsystem will allocate and duplicate it for the receiver. -In his reply to the sender, the receiver has the same sort of control as sender in terms of how the reply message buffer is allocated. After getting the reply back, the original sender can determine whether the reply buffer was allocated and FreeMem() it himself, or allow the IPC subsystem to free it when he is through with the reply. -Finally, the receiver of the original message can even free his own reply buffer when the original sender is through with it! So in general, one can use either the IPC subsystem's automatic buffer copying feature (the default), use no buffer copying at all, or use one's own scheme. MOST OF THESE ALLOCATION FEATURES ARE IMPLEMENTED THROUGH FLAGS. READ THE DOCS BELOW ON OPENIPC() and SENDIPC(). APPLICATION NAME DOMAINS An application for cases (1) or (3) must create a receiver port for IPC messages with the OpenIPC() call. OpenIPC() takes a name and flags argument. The Flags argument is discussed in OpenIPC() below. You do not need a port to send messages, only to receive them. The name of the port determines the type of messages it will receive. The format is: . , for example: "dmouse.CMD" Currently, only the .CMD domain has a defined standard. It does not matter whether multiple copies of the application are running, they can share the same port name. You could have, say, two invocations of your favorite editor each with five active projects. Support for this is discussed in the ReplyIPC() call below. While this port exists, other tasks may send IPC messages to it. You must extract the messages (GetMsg()), process it, and then ReplyIPC() the message to return it to the original sender. The CloseIPC() call will shutdown an IPC port that you had previously openning and return any pending messages by trying to pass them to the next application of the same name (if none, an error is eventually returned to the sender). .CMD ports Messages sent over .CMD ports are formatted as null-terminated ascii-text. The send buffer (msg->TBuf) is formatted as follows: project\0command\0 (\0 = NUL = ascii 0) The \0 MUST be included as part of the buffer and buffer length. The project name allows the application to identify which of its possibly multiple projects is to be affected by the command. a NULL project name (\0command\0) is allowed and refers to the 'active' project in the application. The project name is ignored by applications which do not support the notion of projects. The format of the command depends on the application, but must be in ascii and terminate with a \0 (ascii 0). The logical extension for the specification of multiple project names is to allow comma delimited names and wildcards (* and ?). For example, "*\0command\0" would refer to all projects, "a.c,b.c\0command\0" would refer to two specific projects. This might not be supported by all applications. The format of the reply buffer depends on the success of the operation. If the command was successful, the reply buffer will either be NULL or contain some ascii/text response ending in \0. If the command was not successful, IF_ERROR will be set in msg->RFlags and the reply buffer will be either NULL or contain some ascii/text error message ending in \0. The msg->Error integer error code will be non-zero if IF_ERROR was set, possibly non-zero if IF_ERROR was not set to indicate a warning (IF_ERROR always means Total-Failure). If msg->Error is not set by the application which returns the error condition, it will be set by the IPC subsystem. ParseCmd ParseCmd argc = ParseCmd(buf, &argv, varget, varfree, &error, NULL); int argc; char **argv; (note, address passed) char *((*varget)()); void (*varfree)(); long error; (note, address passed) (note: Last NULL argument (0L) is required for future compatibility). THIS COMMAND CAN EASILY BE USED WITH NON-IPC RELATED ROUTINES! Do DME-type parsing on a string buffer. The buffer is terminated with a \0 but may contain a \n at the end (which is ignored) to be compatible with fgets() as well as gets(). NOTE that if the string is terminated with a \ i.e. "\\n\0", the backslash will override the standard ignorance of the \n and parse it into the result. DME-type parsing delimites fields by spaces except those enclosed in matching parenthesis. Multiple levels of parenthesis is tracked and the OUTERMOST SET REMOVED. Other special characters include shift-6 (^), backslash (\), and dollar ($): form examples function ^ ^c ^d ^L embed a control character, case is ignored you may not embed ^@ (0) \ \\ \( \^ override the meaning of the next character, allows embedding special characters. $(varname) inline string variable, var name can be anything except ')'. $varname inline string variable, var name restricted to alphanumeric and '_'. Example: "echo (this (is a) test) $file/x a mess ^g \(hi! \)ug\\" Result: argv[0] = "echo" argv[1] = "this (is a) test" argv[2] = "dnet:foo/x" (assumes $file == 'dnet:foo') argv[3] = "a" argv[4] = "mess" argv[5] = "" (i.e. ascii code 7) argv[6] = "(hi!" argv[7] = ")ug\" argv[8] = NULL Note: offset -1 of each argument contains a status byte. Currently, bit 0 is defined to indicate whether the argument was paren'd or not. This cannot otherwise be told as the highest level of parenthesis are stripped. (argv[0][-1] & 1) == 0, (argv[1][-1] & 1) == 1. -------- buf -\0 or \n\0 terminated buffer holding the command to parse. The buffer is not modified and may be discarded after the call returns. &argv -address of a variable which will be initialized to point to an allocated array of pointers to strings exactly the way the main() argv works. varget -Pointer to a subroutine which when given a \0 terminated variable name will return a \0 terminated string or NULL if that variable does not exist. varget(varname) char *varname; This argument may be NULL indicating string variables are not supported. The routine may return non-allocated strings (i.e. static storage or string constants) in which case there is no need for a varfree() function. varfree -Pointer to a subroutine which when given the string pointer returned by varget() will free it. This call is made immediately after varget() is called and returns nothing. You may pass NULL for this argument if no free function exists (varget was NULL or returned static strings). &error -You must supply a pointer to a longword which will be returned holding the error, if any, and the index where the error occured, if any. The index is stored in the lower 16 bits and the error # stored in the upper 16 bits: 1 << 16 = NO MEMORY 2 << 16 = STRING VARIABLE NOT FOUND (varget() returned NULL), the lower 16 bits contain the index into buf. NULL -This argument is reserved for future expansion and must be NULL (0L). NOTE: The returned arg list may be munged however you wish, including the pointers themselves, just as long as you pass the original argv pointer (i.e. the entries may be munged) to FreeParseCmd(). 0 is always returned on error, and error will be set properly. Otherwise, argc is returned and error will hold 0. The original buffer is not modified in any way. NOTE: Since the outer set of parenthesis () are stripped, offset -1 of each arguments provides information as to whether the argument was quoted or not. If bit 0 is set, the argument was quoted, I.E.: (a) b c (av[0][-1] & 1) = 1 av[0] = "a" (av[1][-1] & 1) = 0 av[1] = "b" (av[2][-1] & 1) = 0 av[2] = "c" FreeParseCmd FreeParseCmd (void) FreeParseCmd(argv) This routine frees the storage referenced by the argv... the argv array and the original strings are all freed. It is ok if Individual entries in the argv array are munged. OpenIPC OpenIPC port = OpenIPC(name, flags) PORT *port; char *name; long flags; Create an IPC port of the specified name suitable for receiving IPC commands on. If the port could not be created due to lack of memory, NULL is returned, else an EXEC port is returned. The port is setup as PA_SIGNAL and takes up a signal bit in your signal set. If the application does not wish a signal-port, it may FreeSignal() the signal and change the flags to something other than PA_SIGNAL. Due to the fact that the signal was allocated for the calling task, the port is normally usable only from the calling task. One can change the ownership and port characterstics but always remember that CloseIPC() can only be called from the owning task. IF_ALLOC If IF_ALLOC is specified, *ALL* incomming message buffers will be forced to be allocated even if the sender of the message specifies IF_NOCOPY. I.E. you, as the receiver of IPC messages on this port can specify this flag to guarentee the msg->TBuf is allocated and thus modify the contents of the buffer itself without confusing the original sender (who may have specified a static string) Note: However, if this message is to be passed on via IF_GLOBAL or IF_NOTFND, you probably do not want to munge the buffer. IF_GLOBAL If IF_GLOBAL is specified, this port will accept global messages. Global messages are always of the .CMD form though the sender could easily put stuff beyond the second \0 in the message buffer destined for specialized routines. If not specified, IF_GLOBAL messages will NEVER propogate through this port. CloseIPC CloseIPC (void) CloseIPC(port) PORT *port; Delete an IPC port previously obtained by OpenIPC(). The owning task must make this call since it will FreeSignal() the allocated signal. Note that if the port flags are set to something other than PA_SIGNAL, no attempt will be made to free the possibly non-existant signal bit. This call will pass any pending messages on to the next application of the same name, and if no other exists, will return the messages with an error. Pending messages with IF_GLOBAL set continue their journey through the IPC ports flagged to accept IF_GLOBAL messages. SendIPC SendIPC msg = SendIPC(appname, buf, len, flags) IPCMSG *msg; char *appname; APTR buf; long len, flags; This routine starts an asynchronous IPC request. It allocates and initializes an IPCMSG and reply port and sends the message to the destination application (appname). The IPCMSG is returned and it's msg->RFlags holds the IF_ALLOCMSG internal flag to cause the IPCMSG and replyport to be deallocated when you are through with the reply. NOTE! The returned IPCMSG has already been sent to the destination and is not owned by you. The idea is to then CheckMsg() and/or WaitMsg() on it. The ANode filed of the IPCMSG, however, *IS* owned by you and you may use it however you wish. The buffer and buffer length you pass SendIPC() is used to allocate a copy of the buffer (which is deallocated automatically when the destination ReplyIPC()s). This means that you can discard/reuse your buffer as soon as the call returns. However, if you specify the IF_NOCOPY flag SendIPC() will use your buffer pointer and you must be sure not to modify its contents until the message is returned to you. If you specify IF_ALLOC it is assumed your buffer pointer and length were AllocMem()d and the system will FreeMem() them when the destination is through processing your message and calls ReplyIPC(). ** See SendIPC2() for info on how to handle the message when it comes back. * If the 'name' is NULL, the first application in the application list is called. This is usually used in conjunction with IF_GLOBAL to indicate that the message should propogate to all IPC ports in the system. SendIPC2 SendIPC2 msg = SendIPC2(name, msg) This routine is called by SendIPC(). This assumes you already have an IPC message structure that you wish to use. The msg argument is returned. The reply fields, including msg->Error and msg->Confirm, are zerod. The IPCMSG must be initialized as follows: msg->Msg.mn_ReplyPort -must hold reply port for the message msg->Msg.mn_Length -must hold length of IPCMSG structure ONLY if you had specified the IF_ALLOCMSG in msg->TFlags msg->Msg.TBuf -holds the buffer pointer to the message or NULL msg->Msg.TLen -holds the length of the message msg->Msg.TFlags -holds flags associated with the message: IF_ALLOC * automatic deallocation on reply. This flag is set automatically if you do not specify IF_NOCOPY. If NEITHER IF_NOCOPY or IF_ALLOC are specified, the IPC subsystem will allocate a duplicate of your buffer, place it in TBuf, and set this flag for you. IF_GLOBAL * force propogate to all applications in the system before returning to the sender. IF_NOCOPY * force IPC system to use the buffer you placed in TBuf. Otherwise, the IPC system will allocate and copy your buffer into its own and place that in TBuf. IF_ALLOCMSG * IPC system will deallocate the IPCMSG itself when FreeIPC() is called. This flag is set automatically when the IPC system has allocate the IPCMSG in the first place. WARNING! Upon return, msg->TBuf will not necessarily point to your buffer, even if you specified IF_NOCOPY or IF_ALLOC. Handling the message when it comes back After you make the call, you must wait for the message to complete via WaitMsg and/or CheckMsg(). Note that WaitMsg() will remove the message from its reply port. If you do not use WaitMsg(), you must remember to remove the message before continuing. Then, process the reply (msg->RBuf, msg->RLen, and msg->RFlags). The msg->RBuf IS ALLOWED TO BE NULL. msg->RFlags will indicate any errors: IF_ERROR|IF_NOTFND|IF_NOAPP: no application of that name exists IF_ERROR|IF_NOTFND : application exists, but the requested project does not exist. OR the command does not exist for this application. IF_ERROR : The application exists and can handle the project & command, but an error occured during interpretation of the command. IF_ALLOC : The reply buffer was allocated. You have the option of FreeMem()ing it yourself or allowing the system to do so. If you do it, you must set RBuf = NULL. NOTE: msg->Error does not need to be set by the receiver. If msg->Error is 0 and IF_ERROR is set, msg->Error will be set to 20 by the IPC subsystem. NOTE: In the case of IF_ERROR, the reply buffer might still be non-NULL and contain an error message. When through dealing with the reply, you MUST call FreeIPC(msg). This routine will deallocate the reply buffer if msg->RFlags had IF_ALLOC set by the destination and also free the message it self if msg->TFlags contained IF_ALLOCMSG set by SendIPC() or the user before a SendIPC2(). Additionaly, if msg->Confirm was set by the destination, this function vector will be called with the message pointer as an argument to allow the destination to deallocate its reply. NOTE!!! If the message has IF_GLOBAL set in msg->TFlags, msg->Confirm should NOT be touched by receiving applications. DoIPC2 DoIPC2 (void) DoIPC2(name, msg, collfunc, collport) long flags char *name; IPCMSG *msg; void (*collfunc)(); PORT *collport; This is the hall mark of the IPC library and allows one to send SEMI-SYNCHRONOUS messages while still retaining the ability to process incomming messages on your own IPC port (case #3). The IPC message msg must be setup as in SendIPC2(). The collision function and collision port may be NULL if you wish. This command sends the message and then waits for the reply. If while waiting for the reply a message comes in on the collision port (usually your IPC port), the collision vector will be called with the collision port as an argument, allowing you to process incomming messages while waiting for one you sent. By properly designing your software, you can allow multiply stacked DoIPC2() calls. When the message finally comes back, this call returns. The application must still deal with its reply buffer and call IPCFree() when through. NOTE: The collision function is called as a subroutine of this routine and thus if it is a general command-handling routine remember that your command interpreter might have to be synchronously reentrant. DoIPC2() is certainly reentrant. A4 and A5 are not screwed up by DoIPC2() so you do not have to reload your data segment base register from the collision vector routine if using the small data model. (Compatible with both Aztec and Lattice C). ReplyIPC ReplyIPC (void) ReplyIPC(msg, buf, len, flags) IPCMSG *msg; APTR buf; (may be NULL & len would then be 0) long len; long flags; You must reply to all IPC messages you receive on your IPC port by calling ReplyIPC(). ReplyIPC() works almost exactly like IPCSend(), but deals with the RBuf, RFlags, and RLen fields of the IPC structure. Normally, the IPC system will allocate a duplicate of your reply buffer and stick that in msg->RBuf which allows you to throw away your buffer after calling ReplyIPC(). Various flags override this feature: IF_ALLOC - The buffer is automatically deallocated when the source calls FreeIPC(). If this flag exists the IPC system will not allocate a duplicate buffer. IF_NOCOPY - The IPC system will not allocate a duplicate buffer. Normally, (1) neither of these flags is specified, or (2) IF_ALLOC is specified. Specifying IF_ALLOC alone means that the IPC system assumes you have AllocMem()d the buffer yourself and want it automatically freed when the source (original sender) calls FreeIPC(). Specifying both IF_ALLOC and IF_NOCOPY is equivalent to specifying just IF_ALLOC. Specifying just IF_NOCOPY means that the IPC system will use your buffer pointer and will NOT attempt to deallocate it. In this case, you are responsible for keeping the buffer intact until the source calls FreeIPC(). This is possible through the use of the msg->Confirm vector. ** you cannot use msg->Confirm for IF_GLOBAL packets! Because there is only one vector entry and possibly multiple users of the message. NOTE: The buffer pointer placed in msg->RBuf will either be yours or the allocated duplicate. msg->RBuf will be set to NULL when (if) the system deallocates it. If you specify IF_NOTFND, your buffer and length are completely ignored and the message is passed on to the next application program with the same name. IF THE ORIGINATOR SPECIFIED IF_GLOBAL WHEN HE SENT THE MESSAGE, AND YOU DID NOT SPECIFY IF_NOTFND, the message will be passed on to the next application anyway and your reply will be passed on along with it. In the same way, if you were not the first application to get this message (it was passed to you), the msg->RBuf/RLen/RFlags fields may already have something in them. If your reply buffer pointer is not the same address as the one already in msg->RBuf and msg->RFlags held IF_ALLOC, the old buffer in msg->RBuf will be deallocated before processing continues with your ReplyIPC(). ** Warning, msg->RBuf will be replaced in the same manner msg->TBuf is by an allocated duplicate unless you specify otherwise. FreeIPC FreeIPC (void) FreeIPC(msg) IPCMSG *msg; This routine must be called when the original sender of an IPC message has sent the message, waited for the reply, removed the message from the reply port, and processed the reply buffer. This serves to free the message and any associated buffers as specified below: If msg->RFlags has IF_ALLOC set the reply buffer is immediately deallocated and msg->RBuf set to NULL. If msg->Confirm is non-NULL the vector will be called with the IPCMSG as an argument. If msg->TFlags holds IF_ALLOCMSG, the msg will be FreeMem()d with the length msg->Msg.mn_Length. IF_ALLOCMSG is not usually set by the user... it is set by SendIPC() which allocates a message/replyport and wants both to be deallocated on completion. \Rogue\Monster\ else echo "will not over write ipc.doc" fi if [ `wc -c ipc.doc | awk '{printf $1}'` -ne 24710 ] then echo `wc -c ipc.doc | awk '{print "Got " $1 ", Expected " 24710}'` fi if `test ! -s lists.doc` then echo "writing lists.doc" cat > lists.doc << '\Rogue\Monster\' LISTS.DOC These are general list handling routines that should have been provided in EXEC. In addition to basic additions, I provide support for structures whos Nodes are not at the base of the structure. These are the infamous 'sptr's below. Such routines take a pointer to the structure, add an offset to it to get to the Node, do the specified operation, then subtract the offset to return to the 'sptr's actual base. In otherwords, it allows you to deal with such structures without having to do these hacks in your source. GetHead GetHead GetSucc GetSucc node = GetHead(list) node = GetSucc(node) (physically the same routine as GetHead()). This routine returns the successor of a node in the list. If a list is specified, the first node in the list is returned. NULL is returned if the list is empty or one has reached the end of the list. GetTail GetTail node = GetTail(list) This routine returns the last node in the list, or NULL if the list is empty. GetPred GetPred node = GetPred(node) This routine returns the previous node from the given node, or NULL if we are at the head of the list. GetHeadOff GetHeadOff sptr = GetHeadOff(list, offset) This routine retrieves the first node in the list and subtracts the specified offset to get to the base of the structure (where 'offset' is the offset into the structure where the Node has been placed rather than placing it as the first object in the structure). NULL is returned is the list is empty. NOTE! Unlike GetHead(), only a valid list pointer may be specified here. GetTailOff GetTailOff sptr = GetTailOff(list, offset) This routine works the same as GetHeadOff() but works on the last node in the list. GetSuccOff GetSuccOff sptr = GetSuccOff(sptr, offset) Given a structure pointer where the Node is offset from the structure base, add the offset to the pointer, determine the successor if any, then subtract the offset from the successor to get back to the structure base for the successor. NULL is returned if there is no successor. GetPredOff GetPredOff sptr = GetPredOff(sptr, offset) This routine works the same as GetSuccOff() but works on the previous node rather than the next. NULL is returned if we are at the head of the list; EnqueueLong EnqueueLong (void) EnqueueLong(list, startnode, node, valoff) This routine queues a node into a list beginning its search at the startnode (first element if startnode == NULL). The list is assumed to be sorted by the longword valoff from the node structure. I.E. If you had a graphic structure with a Node at its base and where coordinates were stored in longwords, you insert nodes sorted by one of the coordinates into a list. valoff is the offset from the node base where the longword value the list is sorted by exists. EnqueueOffLong EnqueueOffLong (void) EnqueueOffLong(list, startnode, sptr, off, valoff) This works just like EnqueueLong() but an extra parameter, 'off', is provided to inform the routine where in the structure the Node is. Does that make sense? SearchFwdNode SearchFwdNode retval = SearchFwdNode(node, function, arg) MINNODE *node; long (*function)(); long arg; This routine searches a list in the forward direction beginning at the specified node, calling the function vector for every node. The function is called C-fashion (A4 and A5 are intact to support the small code model and D2/D3 may be destroyed in addition to standard scratch variables to support Aztec C). The function is given two arguments: the node, and the arg passed to SearchFwdNode(): (*function)(current_srch_node, arg) The search is terminated if the function vector returns a non-zero value. If the search reaches the end of the list, NULL is returned. NOTE: you may pass a NULL as the node to SearchFwdNode() which results in a NULL being immediately returned. SearchRvsNode SearchRvsNode retval = SearchFwdNode(node, function, arg) This function works exactly the same as SearchFwdNode() but works in reverse begining at the specified node. SearchFwdList SearchFwdList retval = SearchFwdList(list, function, arg) This function works exactly the same as SearchFwdNode() but you give it a pointer to a list rather than to a node. A valid list pointer must be specified, but the list may be empty. SearchRvsList SearchRvsList retval = SearchRvsList(list, function, arg) This function works exactly the same as SearchRvsNode() but you give it a pointer to a list rather than to a node. A valid list pointer must be specified, but the list may be empty. SearchFwdNodeOff SearchFwdNodeOff SearchRvsNodeOff SearchRvsNodeOff SearchFwdListOff SearchFwdListOff SearchRvsListOff SearchRvsListOff rval = SearchFwdNodeOff(sptr, function, off, arg) rval = SearchRvsNodeOff(sptr, function, off, arg) rval = SearchFwdListOff(list, function, off, arg) rval = SearchRvsListOff(list, function, off, arg) These routines work like those shown above, but an additional argument, an offset, is supplied. This is the offset into the structure pointer where the Node structure is embedded. In otherwords, the other routines were simply special cases of this one with off == 0. Note that for *NodeOff() routines a pointer to a structure containing the Node structure in it somewhere is supplied, whereas in the *ListOff() routines a pointer to the list base is given (this hasn't changed). \Rogue\Monster\ else echo "will not over write lists.doc" fi if [ `wc -c lists.doc | awk '{printf $1}'` -ne 5861 ] then echo `wc -c lists.doc | awk '{print "Got " $1 ", Expected " 5861}'` fi if `test ! -s memory.doc` then echo "writing memory.doc" cat > memory.doc << '\Rogue\Monster\' MEMORY.DOC These are general memory handling routines which will perform operations as quickly as possible. They are super-optimized to use long word and multiple-register moves whenever possible. BZero BZero (void) = BZero(buf, bytes) APTR buf; long bytes; Zero out an area of memory. That was simple! This is simply a special case of BSet() BSet BSet (void) = BSet(buf, bytes, value) APTR buf; long bytes; long value; (only lower 8 bits used) Jam a byte value into an area of memory. BMov BMov (void) = BMov(src, dest, bytes) APTR src, dest; long bytes; Move a block of memory from the source to the destination. An ascending or decending copy is used depending on how the source and destination overlap, if at all. Both are optimized to use multiple- register moves when possible, longword moves, or byte moves if the two blocks are hopelessly out of alignment. BCmp BCmp BOOL = BCmp(src, dest, bytes) APTR src, dest; long bytes; Compare two blocks of memory. (1) is returned on success, (0) on failure. Currently, this routine is not optimized at all. \Rogue\Monster\ else echo "will not over write memory.doc" fi if [ `wc -c memory.doc | awk '{printf $1}'` -ne 1234 ] then echo `wc -c memory.doc | awk '{print "Got " $1 ", Expected " 1234}'` fi if `test ! -s misc.doc` then echo "writing misc.doc" cat > misc.doc << '\Rogue\Monster\' MISC.DOC These are miscellanious useful routines for your enjoyment. WildCmp WildCmp CBOOL = WildCmp(wildcard, filename) char *wildcard; char *filename; The wildcard characters supported are * and ?. The wildcard is compared against the filename and (1) returned on success, (0) on failure. WaitMsg WaitMsg msg = WaitMsg(msg) EXECMSG *msg; This routines Waits for a message to be returned just like WaitIO() waits for an io request to complete. The message is REMOVED from the reply port after it has returned. the message must have a valid mn_ReplyPort() and must have been queued with PutMsg(), which sets the ln_Type to NT_MESSAGE, and replied with ReplyMsg(), which sets the ln_Type to NT_REPLYMSG. CheckMsg CheckMsg msg/NULL = CheckMsg(msg) EXECMSG *msg; This routine checks to see if the message has been returned. The same restrictions apply as for WaitMsg(). the message is NOT removed. The message is returned if it has been returned (not removed), or NULL otherwise. CheckPort CheckPort msg/NULL = CheckPort(port) EXECMSG *msg; PORT *port; This routine works like WaitPort(), but (1) does not block, and (2) does not remove the message. If the port is not empty the first message is returned (not removed), else NULL is returned if the port is empty. LockAddr LockAddr (void) LockAddr(&lock) long lock[2]; The lock structure (8 bytes) MUST initially be 0. This routine obtains an exclusive lock on the structure and blocks until that lock can be obtained. The structure must be word-aligned. Unless it is forced to block, this subroutine is extremely fast. This routine does not allocate any signals, but uses the reserved EXEC semaphore signal. LockAddrB LockAddrB (void) LockAddr(bitno, &lock) long bitno; long lock[2]; The lock structure actually supports up to 8 independant exclusive locks. LockAddr() is a special case which uses bit # 0. This call works as in LockAddr() but you may specify the bit you wish to lock (0 to 7). If you specify 0, this call is equivalent to LockAddr(). UnLockAddr UnLockAddr (void) UnLockAddr(&lock) long lock[2]; Remove an exclusive lock you had previously obtained. If other tasks are waiting for this lock, ALL are awakened even though only one will get the lock next. This ensures that the highest priority task will get the lock next. This routine is extremely fast if nobody else is waiting for the lock, else it has to Signal() them. You MUST have previously obtained the lock. UnLockAddrB UnLockAddrB (void) UnLockAddrB(bitno, &lock) long bitno; long lock[2]; Again, this routine works the same as UnLockAddr() with the exception that you may specify one of the 8 bits to unlock (0 to 7). DoSyncMsg DoSyncMsg (void) DoSyncMsg(port, msg) PORT *port; EXECMSG *msg; This routine PutMsg()s a message and waits for it to be returned. This routine creates its own reply port on the stack and stuffs it into mn_ReplyPort for you. Thus, virtually no setup is required to use this routine. FindName2 FindName2 node/NULL = FindName2(list, name) This routine is identical to the EXEC FindName() call with the exception that it ignores nodes whos ln_Name fields are NULL. ln_Name fields in the list nodes must contain either NULL or a valid string pointer. GetTaskData GetTaskData ptr = GetTaskData(name, bytes) APTR ptr; char *name; long bytes; This routine retrieves/allocates task-private named storage. For a specific name, the first GetTaskData() call will allocate the specified # of bytes and zero them. Space to hold the name itself is also allocated (i.e. you can use a temporary buffer to hold 'name' when you make this call). Future calls return the pointer to the already allocated storage without modifying it. The storage is automatically freed if the TASK is removed... note that the task is not normally removed when a C program exits back into a CLI enviroment... it uses the CLI's task to run the program. The task's memory list is used to implement this function. FreeTaskData FreeTaskData (void) FreeTaskData(name) char *name; If the task-private name exists, the storage associated with it is freed. This works with memlist entries allocated with GetTaskData() or by the user, assuming the ln_Name field points to a valid string. (note: FreeEntry() is used after the associated MemList structure is unlinked frlom the list. ln_Name is not specifically freed but the way GetTaskData() works, the second entry is actually the storage associated with the ln_Name) \Rogue\Monster\ else echo "will not over write misc.doc" fi if [ `wc -c misc.doc | awk '{printf $1}'` -ne 4956 ] then echo `wc -c misc.doc | awk '{print "Got " $1 ", Expected " 4956}'` fi if `test ! -s qints.doc` then echo "writing qints.doc" cat > qints.doc << '\Rogue\Monster\' QINTS.DOC I call them Q-Interrupts to tell them apart from other types of interrupts that exist on the Amiga. QInts provide a general purpose priority enhanced local task-interrupt system based on exceptions. Essentially, one can associate a Q interrupt with any set of EXEC signals. When an associated signal comes in, a Q interrupt occurs. The interrupt is taken immediately if the current task Q-priority is lower than the Q-priority of the interrupt, else it is queued until the priority becomes lower. When a Q-interrupt is taken, the task's Q-priority is raised to that of the Q-interrupt while the interrupt is being processed. Additionaly, the application software may set the task's Q-priority at any time, usually to prevent interrupts from occuring in critical sections. OpenQInts OpenQInts handle = OpenQInts() long handle; This routine initializes a handle for the Q interrupt system. All 32 EXEC signals may be controlled through this one handle. NULL is returned on error, else a non-zero handle is returned. CloseQInts CloseQInts (void) = CloseQInts(handle) long handle; This routine shuts down the handle, including the removal of any active Q Interrupt vectors. SetQPri SetQPri (void) = SetQPri(handle, pri) long handle; long pri; This routine sets the current Q-interrupt priority for the task... actually all interrupts associated with the handle but this is usually all the interrupts period. Any Q-interrupts with higher priority can occur, while interrupts with lower or equal priority will be queued until you SetQPri() to a lower value. 'pri' has a range -127 to 127 SetQVector SetQVector oldvec = SetQvector(handle, vector, signo, arg, pri) void (*oldvec)(); void (*vector)(); long handle; long signo; long arg; long pri; This routine applied or removes or changes the priority of a Q interrupt. If vector is NULL, the interrupt is removed for the specified signo. If vector is not NULL the interrupt is set for the specified signo, replacing any previous interrupt vector and priority that existed. The interrupt has a priority of 'pri', (-127 to 127). The vector may be a C routine. The OpenQInts() routine saved A4/A5 and these registers are automatically re-loaded before the vector is called to support the small-data model. Additionaly, D2 and D3 Parameters are also scratch to support Aztec C. Both Aztec and Lattice C are thus supported. Parameters to the service routine are passed on the stack: (*vector)(arg) Specifically, the argument you gave SetQVector(). NOTE: SetQVector() may be called from a Q-interrupt. \Rogue\Monster\ else echo "will not over write qints.doc" fi if [ `wc -c qints.doc | awk '{printf $1}'` -ne 2789 ] then echo `wc -c qints.doc | awk '{print "Got " $1 ", Expected " 2789}'` fi if `test ! -s res.doc` then echo "writing res.doc" cat > res.doc << '\Rogue\Monster\' RESOURCE.DOC AMIGA RESOURCES AS IMPLEMENTED BY DRES.LIBRARY The first order of business is to avoid some confusion. The Amiga already has resources .... EXEC resources, which are used mainly to arbitrate low level hardware. The resources I am talking about is an object oriented system and has nothing to do with EXEC resources. All references to 'resources' in this document refer to this object oriented system. What is a resources? A resource is a means of accessing one or more objects through a generalized interface with as much flexibility as possible. A specific object in this system has certain capabilities and is referenced by a pointer to something. For instance, an intuition Window could be a resource. Each and every resource in the system may be flagged as follows (any combination of flags): SHARABLE - Multiple references to the resource are allowed. If not sharable, duplication of a resource results in two distinct resources with different data areas rather than two pointers to a shared data area. Duplication of non-sharable resources via DupRes() results in a copy of that resource's data including any changes made to that data. GetRes()ing it results in a pristine copy. Duplication of a shareable resource returns the common data area whether you DupRes() it or GetRes() it. GLOBAL - A global resource can be accessed by any task in the system. NOTE: Private resources can still be sharable, but only by a DupRes() call or a GetRes() call from a task's private resource list. Also, any resources tagged as GLOBAL in a task's private resource list are immediately accessable to other tasks via GetRes(). Searching other task's private lists for global resources is always done last. VIRTUAL - A virtual resource is one that has been algorithmically generated. Most resources are virtual resources. For example, an intuition Window would be a virtual resource. Non virtual resources are the 'raw' resources on disk. For instance, a "_List" resource would be a raw resource but when you GetRes(name, "List") note that obviously some conversion code is required to go from "_List" to "List", in this case the code initializes the list pointers. Most of the time, the existance of one or more algorithms (subroutines) to generate a resource is completely transparent to the routine requesting the resource. Using the above example, the task requesting the resource simply asks for a "List" and is oblivious to what actually must be performed to give him that List. SWAPABLE - While not being referenced (all references fall to 0), rather than delete the resource, it should be swapped to disk (read: any filesystem device) and then swapped back in when accessed. This does not mean a resource is automatically swapped when references fall to 0, just that it *might* be swapped. This only applies to SHARABLE resources, as you cannot DupRes() a non-shared resource when the references fall to 0 (nothing to dup), and GetRes() returns a pristine copy. SHARABLE resources on the otherhand are assumed to be of a more permanent nature. As in the LOCKED flag below, this flag guarentees the resource will not become pristine on the next reference after previous usage. LOCKED - While not being referenced, rather than delete or swap the resource, it should stay in memory. This only applies to SHARABLE resources. RESOURCE NAMES AND ALGORITHMIC GENERATION The identification of a resource consists of two parts. (1) The name of the resource, and (2) the structure of the resource. The name of the resource is arbitrary while the structure of the resource is some agreed upon standard. The two parts are stuck together with an intervening period like so: CHArlIE.Window For the above example, we have a resource named 'CHArlIE' of structure 'Window'... an intuition window pointer would be the result of accessing this particular resource. However, it is not possible to store a Window structure on disk verbatim since there are lots of pointers and other dependancies with intuition. On disk, the resource might actually be: CHArlIE.NewWindow But we want a Window structure... if the program requests CHArlIE.Window there must be some means of algorithmically translating a NewWindow to a Window. These algorithms are provided by yet a third resource named: NewWindow.Window.CODE The name is 'NewWindow.Window' and the type is 'CODE'. This resource is a procedure which translates a NewWindow to a Window by calling OpenWindow(). The translation procedure is recursive, thus allowing any manner of 'patching' for reasons given above, for maintaining compatibility with older structures, and for convenience. Finally, a resource is owned by a specific task. One can pass resources around but it must be done with cooperation from the library. CODE resources are discussed later on. Now that you have an idea how resources work, we shall discuss the library calls currently available: RESOURCE LIBRARY CALLS Beginning with the easiest of the calls and ending with the more difficult. NOTE: Resource names are limited to 31 characters, Resource types are limited to 31 characters. resptr= GetRes(resnametype) char *resnametype; This function retrieves the requested resource, doing any translations required to get the resource into the requested type. NULL is returned if the resource could not be found or could not be translated to the requested type. In-Memory private resources are searched first, then the private resource files for the task, then In-Memory system resources, then the global resource files for the system. Openning an already-open resource causes one of two actions depending on whether the resource is shared or not. If shared, the reference count is simply incremented, otherwise a private copy of the resource is made. Example: Win = GetRes("Charlie.Window"); resptr2 = DupRes(resptr1) APTR resptr1, resptr2; This call duplicates a resource. If the resource is shared resptr2 will be the same as resptr1 and the reference count will be bumped. Otherwise, a new resource data area is allocated and the old copied to the new. Things like fixing pointers and such within a resource that is physically duplicated are handled by the interface code (since raw resources do not contain pointers, only VIRTUAL resources will contain pointers and all VIRTUAL resources have some interface code). error = FreeRes(resptr) Free a resource that you retrieved via GetRes(). Only the task that owns the resource may free it (though several tasks may own a resource through duplication and ownership changes). 1 is returned on success, 0 on error. That is, if task #1 GetRes()'s the resource and duplicates it once, then task #2 duplicates it once, task #1 must call FreeRes() twice and task #2 must call FreeRes() once. Ownership is strictly tracked. numres= FreeAllRes(task) Free all resources associated with the specified task (NULL for self). error = ChownRes(resptr, fromtask, totask) Change ownership of a resource from the source task to the destination task. The resource must be owned by the source task or an error will occur (0 return value). 1 is returned on success. NULL may be specified for either or both tasks and means the calling task. handle= UnLinkAllRes(task) Unlink all resources associated with the specified task (NULL for self) and return a handle representing those resources. This is useful for shells and such to keep their resources from getting removed by commands they run. Combined with the NUNLINK flag one can pass resources to a Command and keep the rest out of reach. (void) ReLinkAllRes(handle, task) Link all the resources represented by the handle to the specified task (NULL for self). oldfl = SetResFlags(resptr, newflags, flagsmask) Modify the flags associated with a resource. NOTE: If multiple references to a shared resource exist, all are modified. Some modifications may be disallowed by the system. This call is normally used to modify the LOCKED and SWAPABLE flags (note that the SWAPABLE flag can be changed back and forth only for resource which support it). error = AddRes(resnametype, flags, ptr, ctlcode); char *resnametype; long flags; APTR ptr; long (*ctlcode)(); Add a resource to the system. The resource is placed either in the task's privately accessable in-memory resource list or in the system global accessable in-memory resource list depending on the specified flags. One may now GetRes() the resource. ONLY VIRTUAL RESOURCES MAY BE ADDED IN THIS WAY. The VIRTUAL and LOCKED flags are automatically set. If flags specifies a VIRTUAL resource error = RemRes(resname) Remove the specified resource. An error will occur and the resource will not be removed (1) if it does not exist in memory, or (2) it is currently being referenced. (Note: the resource is removed even if it is swapped or locked). error = GetResInfo(resname, &flags, &bytes) Get information on a resource. Information may not exist for a resource if it is not currently in memory and would have to be translated to get to the right type. error = GetResList(wildcard, from, &av, &ac); from: mask, bit 0 search private list 1 search system list 2 3 search in-memory private list 4 search in-memory global list Return an ARGC/ARGV list of resource names. Note that some names might be duplicated if searching multiple lists. Restricting the search to in-memory lists give resources which are already in memory (but might be swapped out or removed at any time for those with no references) RESOURCE FILES error = GetFileList(wildcard, from, &av, &ac); from: mask, bit 0 search private list 1 search system list 2 search swap list Return an ARGC/ARGV list of the files which match the specified wildcard from the private list, system list, or system swap dir list (in which case you get directory names). This list has been allocated, and can be freed as follows: Loop through all entries for (i = 0; i < ac; ++i) FreeMem(av[i], strlen(av[i])+1); Free the array itself: FreeMem(av, sizeof(char *) * (ac+1)); num = AddPrivResFile(filename, pri) Add a file name to the list of resource files for this task. These are scanned before global files when a resource is requested. All files in the list are write protected (i.e. a shared lock is kept for each file). Thus, such files cannot be updated until removed from the list. Can also be used to modify the priority of an existing file num = RemPrivResFiles(wildcard) Remove zero or more file names from the list of resource files for this task. A wildcard pattern (* and ?) is accepted. Note for command processors: commands you run might execute this command for *. num = AddGlobResFile(filename, pri) Same as AddPrivResFile() but applies to the system list, which is searched last and by any requesting task. Wildcard file names are NOT accepted. The file need not exist at this time, and references to unmounted volumes are allowed. Can also be used to modify the priority of an existing entry num = RemGlobResFiles(wildcard) Remove zero or more resource files from the global list. Again, a wildcard filename is accepted. num = AddResSwapDir(dirname, pri, maxkbytes) char *dirname; char pri; long maxkbytes Add a directory to the list of directories the resource system can swap to. The maximum number of KBytes of material allowed in the directory should be specified. You can also use this call to modify the priority and maxkbytes for an entry. The highest priority directories are used before lower priority directories. Not all directories need be mounted, but if a swapin occurs from an unmounted directory a requester will appear. A lock is kept on each specified directory. num = RemResSwapDirs(wildcard) Remove directories associated with the resource swap areas. RESOURCE FILE IFF FORMAT \Rogue\Monster\ else echo "will not over write res.doc" fi if [ `wc -c res.doc | awk '{printf $1}'` -ne 12714 ] then echo `wc -c res.doc | awk '{print "Got " $1 ", Expected " 12714}'` fi if `test ! -s runlib.doc` then echo "writing runlib.doc" cat > runlib.doc << '\Rogue\Monster\' DRES.LIBRARY OVERVIEW OF FUNCTIONS This document is meant to give a simple overview of library functions, not to provide complete explanations. Please refer to the separate document files for various command groups for more detailed information. * All function arguments are 32 bits unless otherwise specified * Any Address register may be used to hold the library base pointer when calling library routines from assembly. IPC ipcport = OpenIPC(name, 0) (void) = CloseIPC(ipcport) ipcmsg = SendIPC(name, buf, len, flags) ipcmsg = SendIPC2(name, ipcmsg) (void) = ReplyIPC(msg, buf, len, flags) (void) = FreeIPC(msg) (void) = DoIPC2(name, msg, handler, ipcport) argc = ParseCmd(str, &argv, varget, varfree, &error, NULL) FreeParseCmd(argv) QINTS handle = OpenQInts() (void) = CloseQInts(handle) (void) = SetQPri(handle, pri) oldvect = SetQVector(handle, vector, signo, arg, pri) MISC BOOL = WildCmp(wildcard, filename) msg = WaitMsg(msg) msg/NULL= CheckMsg(msg) msg/NULL= CheckPort(port) (void) = LockAddr(&varlock) (long varlock[2] = { 0,0 }) (void) = LockAddrB(bitno, &varlock) (void) = UnLockAddr(&varlock) (void) = UnLockAddrB(bitno, &varlock) (void) = DoSyncMsg(port, msg) (do not setup reply port for message) node/NULL= FindName2(list, name) (ignores NULL ln_Name fields) ptr = GetTaskData(name, bytes) (void) = FreeTaskData(name) LISTS node = GetHead(list) node = GetTail(list) node = GetSucc(list/node) node = GetPred(node) sptr = GetHeadOff(list, offset) sptr = GetTailOff(list, offset) sptr = GetSuccOff(sptr, offset) sptr = GetPredOff(sptr, offset) (void) = EnqueueLong(list, start, node, valoff) (void) = EnqueueOffLong(list, start, sptr, off, valoff) rval = SearchFwdNode(node, function, arg) rval = SearchRvsNode(node, function, arg) rval = SearchFwdList(list, function, arg) rval = SearchRvsList(list, function, arg) rval = SearchFwdNodeOff(sptr, function, off, arg) rval = SearchRvsNodeOff(sptr, function, off, arg) rval = SearchFwdListOff(list, function, off, arg) rval = SearchRvsListOff(list, function, off, arg) >> function(sptr:4(sp)orA2, arg:8(sp)orD4) MEMORY (void) = BZero(buf, bytes) (void) = BSet(buf, bytes, char) (void) = BMov(src, dest, bytes) BOOL = BCmp(src, dest, bytes) 0=failed, 1=success TIMEDATE DOSBOOL = SetFileDate(file, date) 0=failed, -1=success char * = DateToS(datestamp, buf, format) RESOURCES (NOT IMPLEMENTED YET) \Rogue\Monster\ else echo "will not over write runlib.doc" fi if [ `wc -c runlib.doc | awk '{printf $1}'` -ne 2799 ] then echo `wc -c runlib.doc | awk '{print "Got " $1 ", Expected " 2799}'` fi if `test ! -s timedate.doc` then echo "writing timedate.doc" cat > timedate.doc << '\Rogue\Monster\' TIMEDATE.DOC Useful routines... SetFileDate SetFileDate DOSBOOL = SetFileDate(file, date) char *file; DATESTAMP *date; This routine sets the datestamp for a file by implementing the new ACTION_SETDATE Dos packet. 0 is returned on failure, non-zero (-1) on success. NOTE: The routine will fail if the file has been locked, shared or otherwise. DateToS DateToS buf = DateToS(date, buf, format) DATESTAMP *date; char *buf; char *format; This routine calculates the ascii time and date from the datestamp and jams it into the output buffer (which is returned) according to the specified format string. If the format string is NULL, "D M Y h:m:s" is used. Essentially, the characters D,M,Y,h,m, and s are replaced by their ascii date equivalents (day, month, year, hours, minutes, seconds) in the output buffer, with all other characters copied verbatim. \Rogue\Monster\ else echo "will not over write timedate.doc" fi if [ `wc -c timedate.doc | awk '{printf $1}'` -ne 968 ] then echo `wc -c timedate.doc | awk '{print "Got " $1 ", Expected " 968}'` fi echo "Finished archive 1 of 1" # if you want to concatenate archives, remove anything after this line exit -- Bob Page, U of Lowell CS Dept. page@swan.ulowell.edu ulowell!page Have five nice days.