Developer Links
SUPPORT FORUMS  
SAMPLE CODE & TIDS  
TEST/DEVELOPMENT KIT INFO
 
DEVELOPER LABS  
NOVELL SUPERLAB  
SUPPORT DOCUMENTS  
STRATEGIC/EXECUTIVE ISSUE TRACKING  
SYSOP PROGRAM  
     
Related Links
 
 
 
 
 
Related Links  
 

 
DEVELOPER SUPPORT HOME  

AUGUST 1993 VOLUME 5 NUMBER 8


INDEX


MAD'S COLUMN

Hello and welcome to the August 1993 issue of Bullets!

On NetWire, the Novell Compuserve forum, there are several sections where you, a Novell Developer, can post your technical questions on developer issues. The NOVC forum offers sections 4, 6 and 13. NOVDEV includes sections 6, 11, 12 and 15. If you have questions on communications products, NOVA section 2 is your starting point. It may surprise you to know that this somewhat haphazard layout is not a result of design, but of years of refining and changing the developer product line and releasing new tools.

It has taken us several months to assess and devise one complete all-encompassing forum where any Novell developer can post messages on any developer kit, look up product information, or even sing a song. Over the coming months, this forum (soon with a utility similar to DEVCIM) will contain most of the development-related information you need for your developing projects.

Very shortly, you will see the removal of the NOVA, NOVB and NOVC NetWire sections. These placeholder names will be replaced by restructuring forum content and forum nomenclature. Look for changes in the OS sections as well.

Next month's issue of Bullets will include a complete layout of this new forum. Please feel free to start using the developer support section of this forum, GO NDEVSUP, immediately.

Happy_Programming!

Mad Poarch
Director
Developer Support/Service


ARTICLES:

Building a TLI Application with the NetWare Client SDK

The Transport Layer Interface (TLI), originally developed for the UNIX platform, provides standardized, direct access to transport services. TLI allows the same basic code paths to be used for different transports, like IPX, SPX, and TCP/IP. The TLI implementation in the NetWare Client SDK, currently only provides IPX and SPX protocol support, on DOS, Microsoft Windows and OS/2.

This article provides an overview of Novell's TLI implementation, compares blocking mode with non-blocking mode, and describes the basic steps for building simple TLI applications with the SPX transport using the NetWare Client SDK. This discussion focuses primarily on TLI applications on the DOS and Microsoft Windows platforms, but also briefly discusses issues specific to OS/2.

The concepts in this article apply only to connection-mode TLI applications. Connection-mode implementations provide guaranteed delivery of messages and feedback on whether connections are solid and continually provide a path between multiple nodes. Connectionless implementations provide a basic method of sending or receiving messages, but do not guarantee delivery (on sends) and do not provide any feedback when destination nodes are not in receiving mode.

TLI applications written using the Client SDK can communicate not only with each other, but also with other NetWare Loadable Modules (NLMs) on the server.

Novell's TLI Implementation

Novell implements TLI as an API library that makes calls to the IPX/SPX protocol drivers. The session-based support provided by SPX transports is built directly into the current TLI libraries. The underlying SPX support is provided by SPXII, a windowing protocol. Details of the SPXII windowing protocol are outside the scope of this article and are not discussed.

TLI uses file handles that identify the communication paths or endpoints that the application opens. TLI could be described as a "state" machine; events occur based on the current state of individual file handles. The state of each file handle denotes what operations may be carried out. To understand TLI, you must understand the flow of events that are based on the state of file handles.

The NetWare Client Transport Protocol API for C and the NetWare Programmer's Guide for C provide additional information on TLI.

Blocking & Non-Blocking Modes

Most TLI functions can be executed in either blocking (synchronous) or non-blocking (asynchronous) mode depending on the degree of control required. Generally, non-blocking mode is more flexible since it permits you to check the endpoint at your earliest convenience to monitor whether a particular event has occurred that requires handling. Blocking mode is less flexible since it waits until a function has completed before returning control to the application.

Novell's implementation of TLI allows you to select either blocking or non-blocking mode for each function. If you want to switch modes before a TLI function, call t_blocking() or t_nonblocking(). The ability to set the mode for each function can provide additional control depending on the particular operation. For example, as the sample Microsoft Windows TLI application discussed in this article shows, nearly all calls are carried out in non-blocking mode. The functions t_snddis() and t_rcvdis() are called in blocking mode to reduce the need for state checking. In the sample DOS TLI application presented in this article, the server application runs entirely in non-blocking mode (as specified by the O_NDELAY option in the t_open() function), while the client application runs wholly in blocking mode.

When you select blocking mode, functions run to their completion before relinquishing control back to your application. Although this approach works well in many programming situations, it provides little control over the endpoint when connections are terminated incorrectly or when communication with the other end of the connection is intermittent. For example, a blocking t_rcv() call will wait indefinitely for data to be received - as long as it thinks the other side is active. For obvious reasons, this scenario is inappropriate for multiple-connection applications. Non-blocking mode is used most frequently since most TLI servers are multiple-connection applications.

Sample DOS TLI Application

The sample DOS TLI application is a basic set of client and server applications using the SPX endpoints. For demonstration purposes, the sample application uses a multiple-connection server application. Due to the space limitations of this publication, the entire DOS TLI example application cannot be presented here. Please see the end of this article for information on where to obtain the complete source code.

The Client Application

Building the client application of a DOS TLI application requires six basic steps:
  1. The function t_open()opens the endpoint in blocking mode for SPX. Whether you specify the "/dev/nspx" or the "nSPXII" transport, the SPX version will default to SPXII. Novell's TLI API libraries are designed for SPXII.
  2. The t_bind() function binds the endpoint. Note: as an option, we have returned the endpoint information in the third parameter. This allows us to obtain the node address, including the socket number that is bound on the node. The bind operation is responsible for allocating the resources that are to be used for the specified handle.
  3. The client application initializes the t_call structure, tcall. This structure in turn sets up the socket by setting the socket field, SPX address structure, spx_addr.ipxa_socket. (The server and client must synchronize on the same socket number.) The client then sets pointers to the appropriate address and option specifiers for the endpoint (the spx_addr and spx_options structures).
  4. The client application calls t_connect() in blocking mode. Although an unsuccessful t_connect() will time-out in either blocking or non-blocking mode, you need to check the state for non-blocking mode to handle the time-out. Figure 1 contains code illustrating steps 1 - 4. FIGURE 1: Initializing and attempting to connect
    // initialize structures
    
    memset(&tcall, 0 , sizeof(tcall);
    memset(&spx_options, 0, sizeof(spx_options));
    memset(&spx_addr, 0, sizeof(spx_addr);
    
    if ((fd = t_open("/dev/nspx", O_RDWR,
                     (struct t_info *)0)) == -1) {
       t_error("\nopen of /dev/nspx failed");
       exit(2);
       }
    
    tbind.addr.len    = sizeof(spx_addr2);
    tbind.addr.maxlen = sizeof(spx_addr2);
    tbind.addr.buf    = (char *)&spx_addr2;
    
    if (t_bind(fd, (struct t_bind *)0,
               (struct t_bind *)&tbind) == -1) {
       t_error("\nbind failed");
       exit(2);
       }
    
    *(WORD *)&spx_addr.ipxa_socket[ 0 ] =  SPX_SOCKET;
    
    tcall.addr.buf                      = (char *)&spx_addr;
    tcall.addr.len                      = sizeof(spx_addr);
    tcall.addr.maxlen                   = sizeof(spx_addr);
    tcall.opt.buf                       = (char *)&spx_options;
    tcall.opt.len                       = sizeof(spx_options);
    tcall.opt.maxlen                    = sizeof(spx_options);
    if (t_connect(fd, &tcall, &tcall) == -1) {
       t_error("\nt_connect failed");
       if (t_errno == TLOOK && t_look(fd) == T_DISCONNECT)
           SPXDisconReason(fd);
       exit(2);
          }
       printf("\nt_connect successful, beginning send
    loop...\n");
    
    END of FIGURE 1
    
  5. Once t_connect() is successful, the client application is ready to send and receive messages, and enters a send/receive loop. Since the client application is in blocking mode, every TLI function call must complete before control is returned to the application. Figure 2 shows the simple send/receive loop.

    FIGURE 2: Simple send/receive loop

    while (!kbhit()) {
        if (t_snd(fd, buf, strlen(buf)+1, 0) == -1) {
           t_error("\nt_snd failed");
           exit(2);
           }
        flags = 0;
        if (t_rcv(fd, buf2, sizeof(buf2), &flags) == -1) {
           t_error("\nt_rcv failed");
           if (t_errno == TLOOK && t_look(fd) == T_DISCONNECT)
              SPXDisconReason(fd);
           exit(2);
           }
    }
    
    END of FIGURE 2
    
  6. After the send/receive loop has terminated, the client application disconnects and closes the connection. The function t_snddis() carries out the orderly disconnection from the server application and t_close() closes the endpoint.

The Server Application

As stated previously, the server application of the example DOS TLI application is a multiple-connection implementation. Handling multiple connections in the DOS environment presents a few complications. First, the DOS environment lacks a "polling" function. As a result, you must check each file handle for events to determine if an event needs to be handled. Although this process uses some CPU cycles, it is still a viable design. The event checking and handling could be improved; in fact, refining this part of the DOS TLI application would be a good learning exercise.

The basic steps for building the server application are described below.

  1. The server application calls the t_open() function to open the endpoint in non-blocking mode, specified by O_NDELAY. In this mode, you can check multiple file handles for events without waiting on any particular function to complete (Figure 3). However, once an event occurs, you must handle it as soon as possible.

    FIGURE 3: Non-blocking t_open call

    if ((fd = t_open("/dev/nspx", O_RDWR|O_NDELAY,(struct t_info
    *)0)) == -1) {
       t_error("\nopen of /dev/nspx failed");
       exit(1);
       }
    
    END of FIGURE 3
    
  2. The server application uses one endpoint to listen for connection requests from the client side. When a request arrives, the server application opens a new file handle for each connection requested. The initialization steps are very similar to those for the client side (see "The Client Application," steps 1 - 3) but the server application also initializes the t_bind structure, tbind.

    This step is required in order to force the use of a particular socket with which the client application will try to connect. Figure 4 shows t_bind structure initialization. By default, if the tbind structure is not initialized, the client application will connect with a dynamic socket. The dynamic socket is automatically specified when the t_bind structure does not explicitly specify a socket number in the "spx_addr" field.

    FIGURE 4: t_bind structure initialization

    *(WORD *)&spx_addr.ipxa_socket[ 0 ] = SPX_SOCKET;
    tbind.addr.len                      = sizeof(spx_addr);
    tbind.addr.maxlen                   = sizeof(spx_addr);
    tbind.addr.buf                      = (char *) &spx_addr;
    tbind.qlen                          = 5; // outstanding
    events on endpoint
    if (t_bind(fd, &tbind, &tbind) == -1) {
       t_error("\nbind failed");
       exit(1);
       }
    
    END of FIGURE 4
    
  3. Since the server application must deal with more than one file handle, it will need a basic control structure in which to store them. The array "nfd" is used to store file handles:

    int nfd[MAX_CLIENTS];

    Initially, the unused slots in the array are represented by the value -1 to allow you to recycle entries for new connections as sessions are terminated. When new connections are requested, the server application uses the first available slot for the new file handle.

  4. The server application calls t_listen(), which allows it to accept connection requests from the client side. The t_listen() function uses the t_call structure, tcall, which is initialized just like it was for the client application (refer to Figure 1).
  5. The server application continually checks the state of the opened endpoint for any events; it loops, testing the state of the original file handle, "fd," for events that need attention. For simplicity, the server application of the example DOS TLI application only checks the T_LISTEN state, and the default condition may end up doing nothing (continuing through the next iteration), depending on whether any sessions are currently active. (See Figure 6 below). Refer to the NetWare Client Transport API for C for more information on states.

    The next step is split into two sub-steps: Step 6A describes the acceptance of connection requests, and Step 6B describes the processing of existing sessions.

    1. New connections are accepted once a T_LISTEN state occurs on the original endpoint. Before accepting a new connection, the server application must obtain the first available slot in the "nfd" array that will store a new file handle. To obtain this slot, it calls getAvailablefh(), as shown in Figure 5 and Figure 6.

      FIGURE 5: Helper Functions

      void closeConnections()
      {
         t_close(fd);           // listening endpoint
         maxOpenedCount--;
         for (; maxOpenedCount > -1; maxOpenedCount--)
             t_close(nfd[maxOpenedCount]);
      }  // closeConnections
      
      //--------------------------------------------------------
      
      void disconnect( int fdIndex )
      {
         struct   t_discon discon;
         char   *msg;
         if (t_rcvdis(nfd[fdIndex], &discon) == -1) {
            t_error( "t_rcvdis failed" );
            closeConnections();
            exit( 2 );
            }
         switch( discon.reason) {
            case TLI_SPX_CONNECTION_FAILED:
                     msg = "Connection failed"; break;
            case TLI_SPX_CONNECTION_TERMINATED:
                     msg = "Connection terminated by client";  break;
            case TLI_SPX_MALFORMED_PACKET:
                     msg = "Internal SPX interface error -- malformed packet";
      break;
            default:
                     msg = "Unknown termination reason";
            } // switch
         t_unbind(nfd[fdIndex]);
         nfd[fdIndex] = -1;                     // mark as unused
         printf("\n\tDisconnected: %s\n", msg );
      } // Disconnect
      
      //--------------------------------------------------------
      
      int getAvailablefh()
      {
         int I, openfd;
         for (I = 0, openfd = -1; I < MAX_CLIENTS; I++) {
             if (nfd[I] == -1)
                return(I);
             } // for
         return(-1);
      } // getOpenSlot
      
      END of FIGURE 5
      
      FIGURE 6: Accepting a client connection request
      t_res = t_look(fd);
      switch (t_res) {
      case T_LISTEN:
         if ((openIndex = getAvailablefh()) < 0)  break;
         if ((nfd[openIndex] = t_open("/dev/nspx", O_RDWR|O_NDELAY,
                                          (struct t_info *)0)) == -1) {
            printf("\n\t[%d] - Client - t_open failed", openIndex);
            t_error("\n\tClient - t_open");
            closeConnections();
            exit(1);
            }
         if (t_bind(nfd[openIndex], (struct t_bind *)0,
             (struct t_bind *)0) == -1) {
            printf("\n\t[%d] - t_bind", openIndex);
            t_error("\n\tt_bind");
            closeConnections();
            exit(1);
            }
         listenResult = t_listen(fd, &tcall);
         if (t_accept(fd, nfd[openIndex], &tcall) == -1) {
            printf("\n\t[%d] - t_accept", openIndex);
            t_error("\n\tt_accept");
            closeConnections();
            exit(1);
            }
         acceptedCount++;
         printf("\n----- Total Accepted %d Connections\nAccepting 'index'
                [%d]-----", acceptedCount, openIndex);
         if (maxOpenedCount < MAX_CLIENTS - 1) maxOpenedCount++;
         newConnection = TRUE;
         defaultOn = FALSE;
      
      //--------------------------------------------------------
      // fall thru to "default" check on in coming data from
         any connection
      //--------------------------------------------------------
      
      default:
         :
      
      END of FIGURE 6
      
      After obtaining the first available slot, the server application opens a new endpoint using t_open() (in non-blocking mode), binds it to take care of the T_LISTEN state on the original file handle using t_listen(), and then finally accepts the connection with the t_accept() call. The original file handle and the new file handle are both parameters of this API, as shown in Figure 6.
    2. To process existing sessions, the server application checks each file handle for pending events as quickly as possible. When all events have been handled, the inner loop completes and the server application again checks for new connection requests before returning to this step again. The server application spends most of its time processing events, attempting to process them as quickly as possible.

      Although you could write a more efficient algorithm to handle this functionality, the process illustrated in Figure 7 presents the main issues. This test just echoes back the same message received.

      FIGURE 7: Client-Server Connection processing

         for (I = 0; I < maxOpenedCount; I++) {
            if (nfd[I] == -1) break;          // skip over unused slot
            t_res = t_look(nfd[I]);
            switch (t_res) {
               case T_DATA:
                  if ((len = t_rcv(nfd[I],  buf, sizeof(buf),&flags)) != -1) {
                     printf("\n[%d] Received: - '%s'",I,buf);
                     if (t_snd(nfd[I], buf, len, 0) == -1) {
                        printf("\n\t[%d]- t_snd", I);
                        t_error("\n\tt_snd");
                        closeConnections();
                        exit(1);
                        } // if
                     } // if
                  defaultOn = FALSE;
                  break;
      
               case T_DISCONNECT:
                  printf("\n[%d] Disconnection Requested...",I);
                  disconnect(I);         // cleanup local resources
                  defaultOn = FALSE;
                  break;
      
               default:
                  printf("\r[%d] state: %d", I, t_res);
                  break;
               } // switch
      
      END of FIGURE 7
      
  6. The final step is closing the connections, including the original endpoint and all client connections. The T_DISCONNECT state (shown in Figure 7), handles the condition when a client session is terminated in an orderly fashion by the client side. In this case, the server application calls disconnect(), a local function (Figure 5), with the file handle at which the event occurred. To handle a T_DISCONNECT state, the server application must call t_rcvdis(), and then call t_unbind() and t_close() to close the file handle. Finally, the server application resets the "nfd" array to free the current slot for new connection requests.

To terminate the server application, the original endpoint must be shut down first by calling t_unbind(), t_close(), and then looping through each active connection and closing it. To keep the example application simple, the server application does not carry out a t_snddis() procedure; it simply closes the connections and exits, forcing any active connections to time-out on the client side. The time-out length depends on SPX parameters (primarily "SPX ABORT TIMEOUT") in the NET.CFG workstation configuration file, or the values in the SPX_OPT and SPXII_OPTIONS structures defined in TIIPXSPX.H. (Refer to the structures in this header file that name the specific fields that manage the time-outs values).

Example Microsoft Windows TLI Application

To illustrate how to write a TLI application in Microsoft Windows, the client and server applications of the DOS TLI example have been converted to run in Microsoft Windows. One application contains both pieces. Due to the space limitations of Bullets, the entire Microsoft Windows TLI example application cannot be presented here. The Microsoft Windows TLI application code excerpts included in this article deal only with TLI-related functions. Please see the end of this article for information on where to obtain the complete source code.

Three Microsoft Windows timers provide a means to implement non-blocking mode. These timers are used to delay for a very short interval; once they are triggered, the application sends an appropriate message that controls checking for a particular state on each file handle. The receive timer delays while checking the state on an active session. The connect timer delays while checking the state following a t_connect() attempt, waiting for a T_CONNECT state. Finally, the listen timer delays while checking the state following a t_listen(), waiting on a pending T_LISTEN state.

The original endpoint is initially opened in blocking mode. The Microsoft Windows TLI application calls t_blocking() after the t_bind() call returns successfully. Figure 8 contains a short code excerpt showing the timer and the condition that waits for a successful connection request.

FIGURE 8: Timers and connection request handling (Windows)

#define  CONNECT_TIMER_DELAY    1000
:
      case WAIT_FOR_CONNECT: /* wait for a connect - t_connect side */
         PrintString (hSendWnd,"--- Waiting for connection ---\r\n");
         t_res = t_connect(htli, (struct t_call FAR *)tconcall,
                           (struct t_call FAR *)NULL);
         if (t_res == -1 && t_errno != TNODATA) {
            PostMessage (parentWnd, TERMINATE, NULL, (DWORD)NULL);
            break;
            }
         connectTimer = SetTimer (hWnd, WAIT_T_CONNECT,
                                  CONNECT_TIMER_DELAY, NULL);
         if (!connectTimer) {
            PostMessage (parentWnd, TERMINATE, NULL, (DWORD)NULL);
            break;
            }
         t_tres = -99;
         /----------------------------------------------------/
         /------ falling through - loop until connected ------/
         /----------------------------------------------------/
      case WAIT_T_CONNECT:
         t_res = t_look(htli);
         switch (t_res) {
            case T_CONNECT:
               t_res = t_rcvconnect(htli, (struct t_bind FAR *)NULL);
               if (t_res == -1) {
                  break;
                  }
               // kill timer that was waiting to establish connection
               if (connectTimer) {
                  connectTimer = 0;
                  KillTimer(hWnd,WAIT_T_CONNECT);
                  }
               connected = 1;
               hclient = htli;     // client side only needs one handle
               nbytes = 1;      /* number of bytes to recieve or send */
               receiveTimer = SetTimer (hWnd, TLI_RECEIVE,
                                        RECEIVE_TIMER_DELAY, NULL);
               if ( !receiveTimer) {
                  PostMessage (parentWnd, TERMINATE, NULL, (DWORD)NULL);
                  }
               break;

           default:
               if (RETRY_MAX < retryCount++) {
                    // kill timer that was waiting on establishing
                    if (connectTimer) {
                       connectTimer = 0;
                       KillTimer(hWnd,WAIT_T_CONNECT);
                       }
                    PostMessage(hWnd, TERMINATE, NULL, (DWORD)NULL);
                    }
                 break;
            } /* switch */
         break;
:

END of FIGURE 8
The functions t_block() and t_nonblock() bracket the t_snddis() and t_rcvdis() functions and force them to complete. Figure 9 shows t_snddis() in blocking mode. Although t_snddis() is not required to be in blocking mode, this mode reduces the number of states that must be checked prior to a successful disconnect. Blocking mode was selected for these particular functions to stop the program flow from advancing too far and attempting to deinitialize too early.

FIGURE 9: Termination and closing handles

case TERMINATE:                // clean-up and exit application
    :
   if (terminated) {
      PostQuitMessage (0);
      break;
      }
   if (receiveTimer)  KillTimer (hWnd, TLI_RECEIVE);
   if (listenTimer)   KillTimer (hWnd, WAIT_T_LISTEN);
   if (connectTimer)  KillTimer (hWnd, WAIT_T_CONNECT);
   if (htli != -1) {
      // make it blocking - client side htli & hclient are same
      if (hclient != -1) {
         t_res = t_blocking(hclient);
         t_res = t_snddis(hclient, (struct t_call FAR *)NULL);
         t_res = t_nonblocking(hclient);
        :

END of FIGURE 9

OS/2 TLI Applications

The TLI implementation for OS/2 is currently more limited than those for the DOS and Microsoft Windows platforms, primarily because the TLI library's implementation of SPXII does not make use of the SPX driver for OS/2. The platform independence and simplicity of the OS/2 TLI sample application allows you to modify the source for TLIMCLNT.C and TLIMSRV.C as follows:
#include <OS2.H>
#define NWOS2
The delay() function used in TLIMCLNT.C also must be replaced with the dosSleep() function. With these modifications, the application will run successfully.

Since configuration issues are an obstacle in OS/2, the NET.CFG file for Ethernet support requires that "Buffers" be set at a minimum of 1514 bytes, and the 802.3 frame type must be specified at the first frame type if more than one are used.

The TLI interface offers a method of accessing transport protocols. Although TLI client applications are currently limited to using IPX and SPX, TCP/IP support may be added in the future. TLI is easier to manage than the lower-level IPX/SPX protocols, and provides the added benefit of portability. Another benefit of using TLI is that it is consistent from platform to platform, and this consistency streamlines the development process.

The sample applications discussed in this article are available in their entirety on Novell's NDEVREL forum on CompuServe (Library 6, TLIAPPX.ZIP). For more information on the concepts discussed in this article, or to order the NetWare Client SDK, contact Novell at 1-800-NETWARE (1-800-638-9273) or 1-801-429-5588.

An Introduction to AIO

AIO or Asynchronous I/O is a component of Network C for NLMs v2.0 and the NLM SDK v3.0. AIO provides NLMs with a high- level mechanism for addressing asynchronous communications services. AIO is most commonly used to access the server's local serial ports.

The AIO interface functions communicate with the AIO.NLM program which provides the services. AIO.NLM then works through a hardware driver written to AIO specifications. The driver communicates directly with the hardware.

This article provides an overview of basic AIO features and how to program for them. Structurally, this article parallels the example program, AIOSAM.NLM (see Figure 10 below), and can be used as extended documentation for it. AIOSAM.NLM demonstrates functions needed to acquire, configure, and communicate using an AIO port. AIOSAM.NLM can be downloaded in electronic format from NetWire (Library 7, AIOSAM.EXE). For more information on AIO, please refer to the AIO Services section of Optimizing NLM Performance or the NLM Library Reference Vol. 1 for additional details.

Serial Ports

DOS programmers are generally used to dealing with serial ports as interrupt-driven devices. They require the programmer to implement interrupt handlers and buffering schemes. NetWare servers provide the low-level functionality as a part of the operating system, through the AIO interface.

Acquisition

Before an application can use a port it must ask AIO to assign one to it by calling AIOAcquirePort(). A port may be specified in terms of hardware type, board number, and port number. Any or all of those parameters may be left as wild cards, which will assign the first available port that meets any non-wild-carded requirements.

Application can scan the pool of known ports using AIOGetFirstPortInfo() and AIOGetNextPortInfo(). These functions return basic information about a port, including availability, without acquiring it for use. They do not provide any information about the devices that are attached to the ports.

Capabilities

Once a port has been acquired an application can find out about the port by calling AIOGetPortCapability(). This will normally return information about things like baud rates, buffer sizes, and flow control. However, not all AIO drivers must support all options. The notSupportedMask field should be checked to make sure that any values being used are valid.

Configuration

Before using a port an application will normally need to configure characteristics like baud rate, data bits, parity, and flow control. To configure these characteristics, the application calls AIOConfigurePort(). AIOGetPortCapability() can be used to ensure that the desired configuration is within the capabilities of the acquired port.

Control

It is sometimes necessary to change the states of the various RS-232C control lines. For example, most modems require that DTR be on before they will accept control sequences and dropping DTR is the most common way to force a modem to hang- up.

AIOSetExternalControl() provides direct control over the DTR and RTS lines. It can also generate a break signal and to alter the flow control settings.

Writing

AIOWriteData() allows the application to write data to the port. More accurately, it puts data into the output queue from which it is sent to the port at whatever rate the port can accept it.

AIOWriteData() may "succeed" but return a value of 0 (zero) bytes written if the buffer is full. The application program should note how many bytes are actually written and be prepared to resend any data that was not accepted into the output buffer.

Reading

Incoming data is automatically received and buffered by AIO. When an application is ready to look at incoming data it should call AIOReadStatus() to find out if any data is waiting to be read. If data is waiting it can be read from the buffer using AIOReadData(). The maximum number of bytes to be read is specified by the application. AIO provides the number of bytes actually read.

Status

The status of the port can be determined at any time by calling AIOGetPortStatus(). This function returns information about the buffers and the port configuration. It could be used in place of AIOReadStatus() above.

When communicating with an RS-232C device (such as a standard IBM-compatible serial port) it is often necessary to determine the status of non-data lines such as RI, DCD, DSR, and CTS. AIOGetPortStatus() returns that information through the extStatus bit mask. It also provides chgdExtStatus bit masks to indicate which status bits have changed.

Release

When an application no longer needs to communicate with a port it should call AIOReleasePort(). Doing so allows other applications to acquire and use the port.

This article and the accompanying example program, AIOSAM.NLM are intended to demonstrate commonly used features of the AIO interface. If your application is modem control, you may be able to use parts of the example program with little or no modification.

FIGURE 10: AIOSAM.NLM example Asynchronous I/O (AIO) program

#include <stdio.h>
#include <stdlib.h>
#include <process.h>
#include <string.h>
#include <aio.h>

#define BUFFER_SIZE     128

int main(void)
{
   int                 rcode;
   int                 portHandle;
   LONG                count;
   WORD                state;
   char                buffer[BUFFER_SIZE];
   AIOPORTCAPABILITIES portCaps;

    /* Acquire available ports from COMx driver only */
   int       hardwareType  = AIO_COMX_TYPE;
   int       boardNumber   = AIO_BOARD_NUMBER_WILDCARD;
   int       portNumber    = AIO_PORT_NUMBER_WILDCARD;

   if ((rcode = AIOAcquirePort( &hardwareType,
      &boardNumber, &portNumber, &portHandle)) !=
      AIO_SUCCESS)
   {
      printf("AIOAcquirePort failed, rcode=%d.\n", rcode );
      exit(1);
   }

   printf("Acquired port for driver %d, board %d,
         port %d.\n", hardwareType, boardNumber,
         portNumber );

   /*  Get the high-level capabilities of the acquired
       port, not driver caps  */
   if ((rcode = AIOGetPortCapability(portHandle,
      &portCaps, NULL)) != AIO_SUCCESS)
   {
      printf("AIOGetPortCapability failed,
            rcode=%d.\n", rcode );
      exit(1);
   }

   /*  Verify that structures are current or newer  */
   if (portCaps.returnLength < sizeof(portCaps))
   {
      printf("Obsolete structures in use!\n");
      exit(1);
   }

   /*  Verify that the port can handle 2400 baud  */
   if ((portCaps.minBitRate > AIO_BAUD_2400)  ||
      (portCaps.maxBitRate < AIO_BAUD_2400))
   {
      printf("Port cannot be configured for 2400bps
            rate requirement.\n");
      exit(1);
   }

   /*  Configure the port for use  */
   if ((rcode = AIOConfigurePort(portHandle,
      AIO_BAUD_2400, AIO_DATA_BITS_8,
      AIO_STOP_BITS_1, AIO_PARITY_NONE,
      AIO_SOFTWARE_FLOW_CONTROL_ON)) != AIO_SUCCESS)
   {
      printf("AIOConfigurePort failed, rcode=%d.\n", rcode );
      exit(1);
   }

   /*  Set DTR and RTS on  */
   if ((rcode = AIOSetExternalControl(portHandle,
      AIO_EXTERNAL_CONTROL,
      AIO_EXTCTRL_DTR | AIO_EXTCTRL_RTS)) !=
      AIO_SUCCESS)
   {
      printf("AIOSetExternalControl failed,
            rcode=%d.\n", rcode );
      exit(1);
   }

   /*  Send an AT attention string to the modem  */
   strcpy(buffer, "AT\r");
   if ((rcode = AIOWriteData(portHandle, buffer,
      strlen(buffer), &count)) != AIO_SUCCESS)
   {
      printf("AIOWriteData failed, rcode=%d.\n", rcode );
      exit(1);
   }

   if (count < strlen(buffer))
      printf("AIOWriteData: incomplete write, only %d
            bytes written.\n", count );

   /*  Poll until at least 3 characters to be read  */
   do
   {
      if ((rcode = AIOReadStatus(portHandle, &count,
         &state)) != AIO_SUCCESS)
       {
         printf("AIOReadStatus failed, rcode=%d.\n", rcode );
         exit(1);
       }
     ThreadSwitch();
   } while (count < 3);

   /*  Read the input buffer  */
   if ((rcode = AIOReadData(portHandle, buffer,
      BUFFER_SIZE, &count)) != AIO_SUCCESS)
   {
      printf("AIOReadData failed, rcode=%d.\n", rcode );
      exit(1);
   }

   /*  Verify attention string echoed back by modem  */
   if (strncmp(buffer, "AT\r", 3) != 0)
      {
      printf("Modem did not echo, saw \"%c%c%c\".\n",
            buffer[0], buffer[1], buffer[2] );
      }
   else
      printf("Modem echo confirmed.\n");

   /*  Release the port  */
   if ((rcode = AIOReleasePort(portHandle)) !=
      AIO_SUCCESS)
   {
      printf("AIOReleasePort failed, rcode=%d.\n", rcode );
      exit(1);
   }

   return 0;
}

END of FIGURE 10

DEVELOPER NEWS:

The AppWare Foundation: In Depth

The AppWare Foundation is the base component of the Novell AppWare development architecture and is designed for developers who want to code directly to the core component of the AppWare development environment. Other AppWare products include Visual AppBuilder, the AppWare Bus and core AppWare Loadable Modules (ALMs), as well as developer construction kits that allow you to create your own ALMs or 4GL development tools designed for the AppWare evironment.

The AppWare Foundation is composed of programming libraries and a cross-platform API for accessing operating system, user interface, and connectivity services with traditional 3GL programming tools. The AppWare Foundation is divided into different service groups that share many characteristics with traditional libraries, but there are important differences.

Similar to a library, these service groups are a collection of utility routines that are reusable within and among applications. Services encapsulate their routines with associated data elements, and some services rely on and inherit capabilities from other components. These services are implemented as modules using C and as classes using C++.

The AppWare Foundation collects the various types of services and designates them as three separate series:

  • The Foundation Series
  • The User Interface Series
  • The Connectivity Series
These series include over 35 component families such as Memory, File, Button, Window, Item, and Graph (see Figure 11). A component family consists of core functionality and extensions to the core.

FIGURE 11: AppWare Foundation Series

     FOUNDATION          CONNECTIVITY        USER INTERFACE
SERIES:             SERIES:             SERIES:

Character           Action              Application
Data                Clipboard           Box
Error               Link                Button
File                Pipe                Dialog
Font                Task                Display
Graphics                                Edit Text
Instance                                Help
Key                                     Item
Memory                                  List
Message                                 Menu
Module                                  Slider
Pointer                                 Standard
Preferences                                  Table
Print                                   Void
Resources                               Windows
System

END of FIGURE 11
The core component contains the most efficient implementation of a component family's most basic features. For example, the core component of the Edit Text Family supports the following features:
  • Multiple lines of text
  • Scrollable viewing
  • Clipboard cut, copy, and paste
  • Single font in a view
  • Maximum 32,767 characters
  • Left, center, and right justification
Extensions to the core component are additional implementations of a family beyond the core functions. For example, the Multifont Extension of Edit Text supports multiple fonts in a view. Some AppWare Foundation families like Graph provide a great number of extensions; other families, such as Font, currently have no extensions. Extensions allow you to scale the size of the AppWare Foundation to provide only necessary functionality, thus improving performance.

The Foundation Series

The Foundation Series provides developers with operating system services like memory management, data management, file management, font selection, application internationalization, and device management (see Figure 12).

FIGURE 12: Foundation Series Components

Character -    (Chararacter handling) Allows developer to deal with internationalization issues
relating to character encoding and types.

Data -    (Data conversion & storage) Provides functionality similar to atoi, itoa, sprintf & sccanf.
Also supports buffers, lists, and trees.

Error -   (Error handling) Handles errors such as device failures, media failures, and media
overflows. Callbacks used for error control.

File -    (File I/O & system management) Supports file I/O, file/directory path manipulation,
directory traversal, copying files & aliases.

Font -    (Font control) Allows platform-independent font selection & sharing operations. Can query
AppWare Foundation for font info.

Graphics -     (Graphics package) Provides a wealth of capacities incl. lines, polygons,
rectangles, text, patterns, icons, bitmaps, palettes, etc.

Instance -     (Autonomous instance management) Provides shared functionality for all AppWare
Found. instances, incl. User Interface Series instcs.

Key -     (Keyboard status & management) Provides a universal keycode interface allowing
programmers to code for various keyboards.

Memory -  (Memory management) Provides routines for allocating, locking, unlocking,
resizing, and deallocating memory from the heap.

Message - (Message passing system) Used for communication with AppWare Foundation
instances. Implemented on top of native messaging system.

Module -  (Module management) Provides routines for creating shared libraries. Supports
static, dynamic & runtime linking; & multitasking.

Pointer - (Pointer/cursor manipulation) Allows you to get and set the pointer/cursor position,
as well as constrain the cursor position.

Preferences    (User preference management) Provides the ability to access & modify preference
settings in application-specific files.

Print -   (Printing support) Provides functions to open and close print devices, setup page & print
options, & control documents & pagination.

Resources -    (Resource management) Enables loading of user-defined resources and standard
system resources.

System -  (Operating system attributes) Allows querying of platform-specific attributes,
including number of screens, and workstation info.

END of FIGURE 12
For memory management, the AppWare Foundation Series provides routines to get around memory limitations. The Memory Component provides routines for allocating, locking, unlocking, resizing, and deallocating memory from the heap.

For file management, the Foundation Series provides standard I/O functions, file and directory management, resource management facilities, and a way to deal with user preferences in a platform-independent manner.

To support the developer with flow of control issues, the AppWare Foundation Series provides an error handling facility and incorporates its own polymorphic message-passing system. The AppWare Foundation message system is implemented as a hand-crafted layer on top of the native message system. This design allows the developer to gain access to the native message system, if necessary.

The Font Component Family provides support for font selection and management that is platform-independent. To support application internationalization, the Character Component provides support for processing character data or text, independently of language and character encoding (Unicode, WorldScript, ASCII, DBCS, DOS-OEM). The Foundation Series families also support device management, including support for printers, keyboard control, system control, and graphics output to non-screen devices.

The User Interface Series

The user interface (UI) is a crucial part of most applications because:
  • Users base their impressions of an application upon the UI, with respect to how the application looks and how easy it is to use.
  • The UI typically consumes a large percentage of the engineering resources required for application development.
Because of the critical nature of user interfaces, much effort has been devoted to developing sophisticated UI facilities. The AppWare Foundation User Interface Series (Figure 13) provides complete GUI support and manages a superset of operating system interface objects such as windows, lists, and buttons and boxes (momentary-on or push, click-on or redo, binary check, and tri-state). Its capabilities include a fully nestable window manger, virtual viewports, multi-font text, embedded graphics for all objects, universal "edit-in-place," and a context-sensitive help manager.

FIGURE 13: User Interface Series Components

Application    (Application instance support) Manages global interface facilities, including
system pallets, system notification & appl. menus.

Box -     (Box instance support) Supports an interface instance with a border, which is used as a
container for buttons and other objects.

Button -  (Button instance support) Supports single-, double-, and tri-state selection (button)
mechanisms.

Dialog -  (Dialog control) Provides functions, messages and data structures used to create a
variety of dialogs.

Display - (Display instance support) Supports an interface used to display static information
to the user.

Edit Text -    (Edit Text instance support) Supports an interface object used as an input device
for entering and editing text.

Help -    (Help instance support) Provides centralized and automated help processing for both
continuous & immediate help.

Item -    (Interface data management) Provides facilities for manipulating many data types in
AppWare Foundation instances.

List -    (List instance support) Supports an interface instance used for both data display and as a
single-dimension selection device.

Menu -    (Menu instance support) Supports menu bars pop-up menus, cascade menus, option
menus, and button menus.

Slider -  (Slider instance support) Supports scroll bars as well as input devices for selecting
a value within a fixed, programmer-defined range.

Standard -     (Standardized interface dialog support) Supplies several dialogs for selecting files,
fonts, colors, printers, etc.

Table -   (Table instance support) Provides an inter- face for displaying, selecting and
editing data from a two-dimensional grid.

Void -    (Void instance support) Provides an interface instance, whose behavior can be defined by
the developer.

Window -  (Window instance support) Provides support for modal, modeless, and floating
windows.

END of FIGURE 13
The User Interface Series currently supports the following GUI standards:
  • For Microsoft Windows - supports Microsoft Windows GUI standards
  • For Macintosh - supports Apple Macintosh GUI standards
  • For UNIX - supports Motif GUI standards
  • OS/2 - support for OS/2 GUI standard is pending
To provide developers with flexibility when designing user interfaces, the AppWare Foundation provides two layers of UI facilities:
  • For situations in which only simple dialogs are required, the Dialog Component provides a set of functions, messages, and data structures that support a variety of dialogs. The Dialog Component supports the design and development of most of the modal dialogs found in applications. A special function is provided to easily display message boxes (also called "alerts").
  • For more complex interfaces, built-in controls that can be nested are available. These controls include windows, voids, and boxes (which usually contain buttons, boxes, edit text areas, display areas, lists, sliders, tables, etc.). A large variety of menu types are also available. To provide even more flexibility, a sophisticated GUI data management facility allows the developer to associate any combination of text and graphics inside the controls.
In addition, the Standard Package Component provides a platform-independent library family of functions that support standardized dialogs for selecting files, fonts, colors, printers, and so on. Each of these selection dialogs and its associated functions is packaged in a separate extension, with its own initialization and termination function. Each package uses native dialog capabilities, when available, and maintains their native look and feel.

Connectivity Series

The Connectivity Series (Figure 14) provides inter- and intra-application connectivity and communication facilities for standalone or mixed environments.

FIGURE 14: Connectivity Series Components

Action -  (Event management) Provides access to high-level application messages about
events.

Clipboard -    (Data transfer) Allows transfer of data within and between applications in support
of the "cut-copy-paste" model.

Link -    (Object linking) Provides support for object linking and embedding using the OLE 2.0
standards.

Pipe -    (Pipe and socket management) Supports creation, querying, and closing of pipes for
streamed communication between tasks.

Task -    (Task manipulation) Supports launching, executing, and enumerating tasks in a multitasking
environment.

END of FIGURE 14
The AppWare Foundation supports creating, opening, querying, and closing of named pipes. Pipes provide a file-like interface for streamed communication between tasks. Pipes are created much like files; after a pipe is created, it may be opened by another task to exchange data with the pipe's creator.

Named pipes can be used in a client-server environment. In this situation, a server will create a named pipe as a published service; clients can then access the pipe by name. With the Pipe Network Extension, named pipes can be accessed by remote clients. The Connectivity Series also provides a portable mode to define objects so that scripting languages can be used to drive your application.

AppWare Foundation Availability

The AppWare Foundation SDK will be available for developers in the fourth quarter of 1993 through the Novell Professional Developers' Program.

To request white papers on AppWare, call 1-800-554-4446 or 1-512-345-1145.

The AppWare Foundation: International Support

Specific international support capabilities provided by the AppWare Foundation Font and Character families include:
  • Superset classification of locale identifiers--script, language, region, version
  • Transparent compatibility with all mainstream character encodings
  • Support for character-by-character processing (no performance penalties)
  • Encoding sensitive newline handling, including support for Unicode line and paragraph separators
  • Language-sensitive collation, comparison, and case transformations
  • Locale-sensitive formatting and scanning of numbers, dates, times, currency
  • Extended character type (ctype) information, including Unicode types
  • Transparent keyboard input handling for multibyte characters
  • Association between fonts and scripts
  • Full locale information: script and language attributes, number, date/time, and currency formats
  • Enumeration of installed locales and selection of default locale
  • Design that allows for future multi-language support
There is also support by International Extensions in several other Software Component Families that support text processing. Examples of these are the Edit Text Family, the Data Family, the Graphics Family, and the Item Family. The International Extension of each component family enables special processing of text for certain locales and for multibyte encodings.

Character-Encoding Support

The AppWare Foundation supports a variety of character encodings needed to support the international market including:
  • Unicode - standard Unicode double-byte encoding (Windows NT)
  • WorldScript - WorldScript I (single-byte) and WorldScript II (multibyte) encodings (Macintosh)
  • Standard ASCII single-byte encoding (UNIX)
  • DBCS - Double Byte Character Set multi-byte encoding (Windows)
  • DOS-OEM (Microsoft Windows, OS/2, and DOS)
Unicode will be supported for Macintosh and UNIX as soon as it is available. The AppWare Foundation does not support "shift state" or other stateful multibyte encodings.

Special Features Support Unicode

The AppWare Foundation provides the following special features to help support development involving Unicode:
  • Compile-time switches allow optimizing for Unicode and transparent use of wide (two-byte) characters and strings
  • Transparent use of PARAGRAPH SEPARATOR and LINE SEPARATOR characters
  • Extended character type information, including non-spacing and directional types
  • Non-spacing mark handling for partitioning and truncating text
  • Mapping to composite or precomposed character forms
  • Mapping of compatibility zone characters to their canonical form
  • For platforms supporting Unicode, conversion between Unicode and other native encodings

Character Encoding Without Performance Penalties

By changing a preprocessor flag, you can compile versions of your application to support one or more of the following encoding groups: single-byte, multi-byte, and Unicode. The functions and macros used to manipulate character data are optimized when compiling for Unicode or single-byte encodings to take advantage of the fixed-size characters of these encodings. Since these three encoding groups represent distinct market areas, creating separate versions of application for each group is appropriate.

In addition, the AppWare Foundation itself can be configured to support only single-byte or Unicode encodings, if desirable. This provides improved performance for applications that will not be used in a multibyte environment.

Depending on whether an application is being compiled for Unicode or not, the basic AppWare Foundation character data types are redefined to be either one or two bytes in size, and strings are defined similarly. This allows character data to be treated independently of size, and allows some simple tasks, such as string termination, to be performed without the use of special macros. The detailed rules for ensuring that processing of character data is performed independently of character encoding are described in the AppWare Foundation technical documentation.

Locale Definition

A locale ID is defined as a script-language-region descriptor, along with the related information necessary for text processing. The language, with the region in which the language is used, is the minimum information needed to determine text processing behavior. Locale information is typically installed and/or configured by the end user. All locale information can be queried by an application program.

For systems that support multiple installed locales, access to the list of locales and selection of a default locale is provided via the Character Software Component Multilocale Extension. True multilingual support (concurrent use of multiple locales for different languages) is not currently available, pending the availability of related OS features. However in preparation for multilingual support, all AppWare Foundation functions and macros that perform text processing accept locale indentifiers as parameters.

OEM Codepage & Microsoft Windows

For historical reasons, the Microsoft Windows DOS-based platform supports two encoding classifications: ANSI and OEM. Some fonts in Microsoft Windows are identified as OEM fonts, meaning that their characters are encoded for the current DOS OEM character set, rather than for the Microsoft Windows ANSI character set. Examples of OEM fonts are Terminal and OEM Fixed. Such fonts are used for DOS compatibility by some applications.

Applications developed with the AppWare Foundation may choose to disallow selection of OEM fonts using the Font Software component family. Other applications may allow selection of both ANSI and OEM fonts, in which case the script identifier associated with a font can be used to distinguish between them. A similar situation occurs in the use of the Symbol fonts on all platforms, and is treated in the same manner.

Since the DOS file system uses the OEM character set for all file and path names, file and path names used in a Microsoft Windows application must be representable in the OEM character set. This is easily accomplished using the AppWare Foundation since on the Microsoft Windows platform all Foundation routines that interact with the file system translate to and from OEM when necessary.

Data entry of files names limited to the OEM character set is supported via the Edit Text and List software component families. Utilities are also provided for validating arbitrary text to be used as file or path names. To support data exchange with native DOS applications, utilities are provided for explicitly translating between OEM and non-OEM text.

Multiple Script Systems & Macintosh

Although true multilingual support is not yet available, Macintosh users may implicitly use more than one script when selecting fonts. Since the AppWare Foundation provides a script identifier for each font, applications may choose to record the script for a font selected by the user. The Foundation's multifont text editing interface supports data entry using fonts in multiple scripts when such fonts are installed.

Currently, no standard mechanism or data structure exists for storing the association between text and language (or script). Although it is possible to embed the language or script information in the text itself, no standard way of doing this exists and this technique is generally discouraged. The recommended approach is to store the language or script identifier separately from the text itself, along with other "fancy text" information such as font and color.

Novell Announces GNU Tools

Building upon the recently announced AppWare development strategy, Novell recently announced that it has entered into an agreement with Cygnus Support to develop a version of the GNU UNIX-based development tools for NetWare. The tools let developers deliver network services in the form of NLMs that are independent of hardware platform. The server-based network services will be accessible to developers of desktop applications through the AppWare Foundation and AppWare Loadable Modules (ALMs).

The GNU development tools are a set of freely redistributable tools and utilities that are widely used and supported by almost all major UNIX environments. The development tools consist of a C compiler called gcc, a C++ compiler called g++, an assembler called Gas, and a debugger called gdb. Other utilities support the development environment including binary utilities, and editor, document formatting tools, a documentation browser, and a patch installer.

With GNU tools for NetWare, developers can use UnixWare to build single-source NLMs that can be deployed on all server platforms, supported by PIN (Processor Independent NetWare). Network applications and services such as messaging, database, telephony or imaging will be delivered on Intel- and RISC-based NetWare platforms including Digital's Alpha AXP, Hewlett Packard's PA-RISC and Sun Microsystem's SPARC. Digital Equipment, Hewlett Packard, and Sun Microsystems, all of whom have announced versions of NetWare for their RISC platforms, are working with Cygnus and Novell to enhance the GNU tools for their platforms as well.

The GNU tools will be included in the AppWare developer offerings including the NLM SDK. The product will be offered through Cygnus Support and will be available Q1 '94. Pricing of support and SDKs will be announced when the product is available.

Get Involved in the Compass Program

As Novell announces more operating systems and development tools, it becomes imperative that we seek direction from those most involved in using and developing to the new technology. In order to work more cooperatively with software developers and hardware vendors, Novell has created the Compass program.

The Compass program provides a channel through which application developers, including Independent Software Vendors (ISVs), consultants, and in-house application developers, can work directly with Novell to:

  • Communicate their needs to the Novell Engineering and Product Marketing
  • Provide feedback on Novell APIs and future development product directions.

Novell offers the Compass program to members of Novell's Developer Programs members and the UnixWare Developer Program. The Compass program includes the Novell Advisory Council and Special Interest Groups (SIGs).

The Novell Advisory Council

The Novell Advisory Council reviews the directions and product offerings that support the development of distributed applications. The Council meets at least four times each year.

Special Interest Groups (SIGs)

SIGs consist of developers who focus on a specific technology, such as document management, telephony, imaging, security, etc. There are actually three phases in the SIG development process:
  • Phase 1: Technology Preview - during the development of a new technology, Novell will hold a Technology Preview to consult with developers on the proposed product direction and whether it will meet developers' needs.
  • Phase 2: Specification Review - after a product specification has been prepared for the new technology, a Specification Review will be held to gather feedback on proposed APIs and to determine if these APIs will meet developers' needs.
  • Phase 3: SIG Workshop - after the new technology has been released as a development product to developers, a SIG Workshop will be held to discuss development issues to the new technology and development concerns.

Developers may join existing SIGs or propose new ones. Each SIG will have one representative on the Novell Advisory Council to ensure that the needs of all developers are represented in the overall Novell application framework architecture. In addition to exchanging information by e-mail and phone, SIGs will meet two to four times each year.

For more details about the Compass program, please call 1-800-NETWARE (1-800-638-9273) or 1-801-429-5588 in the U.S and Canada. In all other locations, call your local Novell sales office and ask for the information on the Novell Professional Developers' Program.

You can also fax inquiries to 1-512-794-1773 or send an e-mail message to one of the following addresses:

  • MHS library @ Novell
  • CompuServe:>MHS:library@Novell
Specify "PDP-Compass" on the subject line.

See the Calendar of Upcoming Events below for SIGs on Security, Document Management, and Telephony to be held in the coming months.

NetWare Application Notes Back Issues

Due to a shortage of printed copies of the June, July, and August issues of NetWare Application Notes, Novell Professional Developers' Program members will not automatically receive their copies. To obtain an order form for these three issues, call (from a touch tone phone) the Developer Relations Auto-mated Fax System (AFS) at 1-800-RED-WORD or 1-512-794-1796, select the AFS option, and request document #7250.

TECHNICAL INSIGHTS:

Using Directory Handles (NetWare Client SDK v1.0d)

When using directory handles in APIs, have the application allocate the handles instead of using drive handles that are mapped to drives. This technique protects existing handles on which the drive mappings rely.

To illustrate, the drive handle returned from NWParseNetWarePath() corresponds to a mapped drive and should not be used directly. Instead, allocate a drive handle using NWAllocTemporaryDirectoryHandle() or NWAllocPermanentDirectoryHandle(); the drive handle returned by these functions can be used for any purpose by your application.

APIs that scan or gather information on directory or file entries cannot use the directory handle from NWParseNetWarePath() directly. APIs like NWScanFileInformation2() and NWScanDirEntryInfo() require that the directory handles to be set to the parent directories that contain the item to scan. The item itself is the file or directory entry supplied to the API via the "searchPattern"/pathname parameter.

For example:

  VOL1:\DIR1\DIR2\ENTRY

  • Directory handle is set to VOL1:\DIR1\DIR2.
  • ENTRY can be a directory or a file.

Btrieve v5.x Status 109 (Transaction Too Complex) (NetWare Btrieve (NLM) v5.15)

NetWare Btrieve (NLM) v5.15 (patches applied only through #93) may return a status 18 (Disk Full) within an explicit Begin Transaction loop if the preimage file grows larger than 32 MB. When the preimage file exceeds 32MB, the Btrieve file is corrupted.

The latest set of patches for NetWare Btrieve (NLM) v5.15 introduces a new status code, status 109 (Transaction Too Complex) added by patch #95. This status code was created to compensate for applications inserting large numbers of records into their files while inside an explicit Begin Transaction loop and receiving status 18s.

The status 18 is returned because NetWare Btrieve uses a two-byte or 16-bit counter for pages in the preimage file. This counter has now been changed to allow a preimage file of 64 MB to be created before the new error 109 is returned. When the error occurs, the application may either end or abort the transaction without file corruption. Operations on other Btrieve files (not the file whose preimage file has exceeded 64 MB) can still be performed successfully.

New Btrieve Status 130 (Ran Out Of System Locks) (NetWare Btrieve (NLM) v6.x)

NetWare Btrieve (NLM) v6.x may abend or otherwise malfunction if it runs out of system locks. Beginning with patch release v6.10b (scheduled for release September '93), NetWare Btrieve will return a new status code, 130 (Ran Out of System Locks).

With any version of Btrieve before v6.10b, the abend may occur when NetWare Btrieve runs out of system locks and either the system is under heavy use, or a certain combination of start-up parameters are used. The September patch release will ensure that NetWare Btrieve monitors its supply of system locks and returns a status 130 to applications trying to perform operations requiring system locks if none are available.

NetWare Btrieve uses system locks to lock pages in v6.x files that are changed during Insert, Update and Delete operations. These locks are held for the duration of the operation or until the transaction, if any, ends or aborts.

The number of system locks Btrieve can use depends on two of its start-up parameters: LocksPerClient ("Number of Locks" in BSETUP, "l" on the BTRIEVE.NLM command line) and MaxClients (not directly set by BSETUP, "s" on the BTRIEVE.NLM command line -not the BSPXCOM.NLM command line). The maximum number of system locks Btrieve can allocate is:

#systemLocks = 65,536 - ( l * s ).
The higher the values for "s" and "l," the fewer system locks are available. This pool of system locks is shared by all users. Two factors cause heavy usage of system locks and increase the likelihood of generating a status 130:
  1. A very large transaction (e.g., thousands of records inserted),
  2. Many users performing large transactions concurrently.
Under these conditions, any user requesting an Update, Insert, or Delete may get a status 130, whether or not the user is in a transaction.

If you are in a transaction and receive a status 130, you should end or abort the transaction. You could then retry the operation; other users may have exhausted NetWare Btrieve's system locks and may have ended or aborted their transactions.

WLINK Error 2067 (Network C for NLMs v2.0(e))

While linking an NLM, WLINK may return an error 2067, "Cannot relocate between code and data in Novell formats," if one of the variables and a function in the NLM had exactly the same name but were different cases. To avoid this situation, use the option "caseexact" in the NLM's link file. The "caseexact" option tells the WATCOM linker to respect case when resolving references to global symbols. By default, the WATCOM linker is case-insensitive.

Unresolved External Error with Microsoft C (NetWare Client SDK v1.0d)

When using Microsoft C, linking the NWCALLS.LIB (51712 bytes) import library with a Microsoft Windows application can result in unresolved external errors on certain function calls (NWGetObjectID() in particular).

This situation occurs because the import library, NWCALLS.LIB, was created with Borland utilities. In most cases, import libraries created with the Borland IMPLIB.EXE utility are compatible with Microsoft, but NWCALLS.LIB is not. To resolve this situation, either create a new import library with Microsoft's IMPLIB.EXE utility or specify each function in the IMPORTS section of the application.

NetWare Btrieve v5.x Step Operations (NetWare Btrieve (NLM) v5.x)

With NetWare Btrieve v5.x, the Step Next and Step Previous operations can skip over records if all the bytes in the record except the first four are set to binary zero, and the first four bytes are either all binary zero or -1 (0xff). Other patterns may also be found in the first four bytes. Btrieve identifies records with these patterns as deleted records and skips them.

This situation only occurs if Btrieve 6.x or 5.x engines are used with files in 5.x format; it does not occur with NetWare Btrieve v6.x files.

Converting the NetWare Btrieve files from v5.x to v6.x format will resolve the situation. If, however, you wish to leave the files in NetWare Btrieve v5.x format, there are three possible workarounds:

  • Alter your program to devote at least one byte (beyond the fourth byte) in the record to hold a non-zero value.
  • Create the Btrieve file using the compression option.
  • Create the Btrieve file using the variable-length record option.

Status 330 on a WHERE Clause (NetWare SQL v3.x)

Under NetWare SQL v3.x, when you try to execute the statement,
SELECT * FROM <table> WHERE <logical type> = 'T'
NetWare SQL returns a status 330 (Data is not formatted according to its defined mask). The values for a logical field must be the default mask ("True" or "False").

This status code is not the result of a problem in NetWare SQL v3.0. Although NetWare SQL v2.11 allows the above statement, it should not.

Drive Number Count & NetWare Client SDK (NetWare Client SDK v1.0d)

When using NWGetDriveInformation() in the NetWare Client SDK v1.0d, the drive number is counted from 1 (A=1, B=2,...). However, in the NetWare C Interface for Windows v1.3, GetDrive-Information() uses drive numbers starting from 0 (A=0, B=1, ...).

OS/2 Requester v2.01 Traps NWCALLS.DLL (NetWare OS/2 SDK v2.01)

With the NetWare OS/2 requester v2.0, the API NWGetEffectiveRights() used a effective rights mask parameter of type "BYTE." With NWCALLS.DLL v2.01, the parameter has been changed to a "WORD." This change will cause all applications aligned on even boundaries to trap NWCALLS.DLL. All applications are assumed to be byte-aligned.

While there is no solution at this time, this situation will be resolved in the next patch for the OS/2 requester.

Status 4 Occurs with Different Alternate Collating Sequences (NetWare Btrieve (NLM) v5.x)

If two separate files have different alternate collating sequences (ACSs) but the two sequences have the same name, status 4 may be returned when you attempt an UPDATE or DELETE operation. Btrieve will not read an alternate collating sequence into memory if one with the same name is already in memory. Thus, if a user is working with one file and then switches to work with the other file, the ACS associated with the latter file will not load, resulting in an improper sorting of the keys.

When using different ACSs for separate files, make sure that the names for each ACS are different.

NWATTRIBUTES Not Pre-Defined (NetWare Client SDK v1.00)

NWATTRIBUTES is not pre-defined in the header files included with the NetWare Client SDK. This type is used to define the variable "attributes" in the structure NWENTRY_INFO used in the API NWScanDirEntryInfo() and in NWDELETED_INFO for the API NWScanForDeletedFiles().

In place of NWATTRIBUTES, define a variable "attributes" of type DWORD.

LogLogicalRecord() & Exclusive Mode (NLM SDK for NetWare 4.0 v1.0)

If a logical record is already locked in an exclusive mode by an NLM or a workstation program, LogLogicalRecord() called from a different NLM successfully returns for locking that same record.

This situation has been reported to Novell Engineering. For now, use semaphores instead of the logical records.

Status 36 on Begin Transaction (NetWare Btrieve (NLM) v6.10)

If a workstation running a Btrieve application call Begin Transaction (19) with BREQUEST.EXE loaded, Btrieve may return a status 36 (Transaction Error). To prevent the status 36, all servers to which the workstation is attached and that are running Btrieve must have the Btrieve NLM configured for transactions.

In addition, if the workstation is configured for local and requester access, a Begin Transaction operation will also fail with a status 36 if the client (local) copy of Btrieve is not configured for transactions as well. This is true even if no local data file has been or will be accessed.

To prevent the status 36 from being returned on a workstation configured for both client and requester access, also configure the local engine for transactions. To accomplish this in the DOS environment, load BTRIEVE.EXE with /t:<file>.In the Microsoft Windows environment, specify options= /t:<file> in the WIN.INI file's [btrieve] paragraph.

If no local data files will be accessed, you can also resolve the situation in the DOS environment by not loading BTRIEVE.EXE or in the Microsoft Windows environment specifying local=no in the [brequestDPMI] paragraph of NOVDB.INI.

NET.CFG For TLI Under OS/2 (NetWare Client SDK v1.0d)

The t_connect() function fails with TLI on OS/2 v2.x and an NE2000 LAN driver when the "Buffers" option in NET.CFG is set to lower than 1514. t_connect() fails with the SPX timeout error 237. Connections to any type of node (DOS, Microsoft Windows, etc.) may be established.

To prevent t_connect() from failing, set the following in the Link Support section of the NET.CFG:

Link Support
  Buffers x 1514
where x is the number of buffers and 1514 is the buffer length. (See "Building a TLI Application with the NetWare Client SDK" in this issue for more information on TLI and NetWare application development.)

Using BASIC FIELD statements with Btrieve for Windows (Btrieve for Windows v5.10)

FIELD statements may be used with Microsoft QuickBasic to initialize the random file buffer area for subsequent Btrieve calls. However when assigning values to these data areas with the LET assignment statement, the record's data is not inserted on the Btrieve Insert call. Instead, garbage characters are inserted.

Do not use LET or INPUT statements to assign values to FIELD variables. Instead, use the LSET or RSET operator. Once a variable name is fielded, it points to the correct place in the random file buffer. If a subsequent INPUT or LET statement with that variable name is executed, the variable's pointer is moved to string space, therefore no longer pointing to the random file buffer.

LSET left justifies the string and pads with blanks to the indicated FIELD length. RSET right justifies the string and pads with blanks to the indicated FIELD length. FIELD statements work only with string variables.

Changing Btrieve 5.10a Interrupt (Btrieve for DOS v5.10a)

When you attempt to change the 7b interrupt which is used by Btrieve, you will discover more than one occurrence of it. For this reason, you must be careful which address you change: the first address should not be edited, while the third address should. The "25h-7bh" found in the first address are just bytes in the offset to another call, and only coincidentally contain these values.

Under Btrieve for DOS v5.10x, change the code as shown in Figure 15.

FIGURE 15: Changing Btrieve v5.10a interrupt

ren btrieve.exe btrieve <cr>
debug btrieve <cr>
-r <cr>                 ;get the value of the cx register.
-s 0L[cx] 7b 25 <cr>    ;search for "set interrupt" where [cx] is value
                        ;of cx register obtained above.
                        ;
                        ;the search returns THREE addresses
  ssss:zzzz             ;DO NOT EDIT THE FIRST ADDRESS !!!
  ssss:xxxx             ;
  ssss:yyyy             ;
                        ;
-e ssss:xxxx            ;edit first address to change interrupt
   ssss:xxxx 7b.        ;address one is returned showing value of 7b.
                        ;Prompt is waiting for the new interrupt value.
               __ <cr>  ;Enter the value of the new interrupt.
-e ssss:yyyy            ;edit second address to change interrupt
   ssss:yyyy 7b.        ;address two is returned showing value of 7b.
                        ;Prompt is waiting for the new interrupt value.
               __ <cr>  ;Enter the value of the new interrupt.
-s 0L[cx] 7b 35 <cr>    ;search for "check interrupt" where [cx] is value
                        ;of cx register obtained above.
   ssss:xxxx            ;the search returns ONE address
-e ssss:xxxx            ;edit that address to change interrupt
   ssss:xxxx 7b.        ;that address is returned showing value of 7b.
                        ;Prompt is waiting for the new interrupt value.
               __ <cr>  ;Enter the value of the new interrupt.
-w <cr>                 ;write changes to disk
-q <cr>                 ;quit debug
ren btrieve btrieve.exe

END of FIGURE 15

NIBBLES AND BITS

Fast Answers to Common Questions

NetWire

Tip On August 17th, Novell began a new developer support forum on NetWire called NDEVSUP in which developers may post technical support questions. Currently, NDEVSUP is divided into the following sections:
  • General
  • Btrieve
  • NetWare SQL
  • Client SDK
  • NLM SDK
  • Mac Products
  • Communication Products
  • Imagery
  • DPMS
Support is being phased out on both NOVC and NOVDEV, but these NetWire forums will still be monitored for the next several weeks.

Tip When you try to unzip a self-extractable patch/NLM file you have downloaded from NetWire, always use the "-d" option to rebuild the original directory structure.

NetWare System Calls for DOS

Tip A new set of F2 System Calls is now available in Novell's NetWire forum on CompuServe (NOVLIB Library 7 SC3X04.EXE). The new release will contain all of the old NetWare 3.x calls:
  • Add Trustees
  • Get Connection's Open Files
  • Get Connections Using File
  • Get File Server Info
  • Get Object Disk Restrictions
  • Obtain File or Subdirectory Information
  • Scan Bindery Object Trustee Paths
  • Scan Entry for Trustees
  • Get Semaphore Information.
Plus, the following new calls will be included in this release:
  • Clear Connection Number
  • Get Connection Information
  • Get Physical Record Locks By File
  • Get Network Serial Number
  • Set Directory Disk Space Restriction
  • Add User Disk Space Restriction

Network C for NLMs

Q - When LoginToFileServer is called, it sometimes returns an error code 5. What is error code 5?

A - Error 5 is an NCP error code indicating a broadcast error. The most likely reason for LoginToFileServer to return this code is if the "Reply To Get Nearest Server" parameter is set to "OFF" for the server. It will take the server longer to locate other servers on the network with this parameter set to OFF, and if an attempt is made to log in to a remote server that is not known the login may time out before the remote server's destination has been found.

This error is generally returned only when the server has just been started or when RESET ROUTER has just been run on the console. It does not seem to occur if "Reply to Get Nearest Server" is set to "ON."

NetWare Btrieve (NLM) v6.10

Q - After installing NetWare Btrieve (NLM) v6.10, existing applications trying to access BTRIEVE.NLM receive the message "Public Symbol not Found." What should I do?

A - This message results from improper installation of the Btrieve NLM. To install the NLM properly, obtain BTR61.EXE and perform the following ten steps:

  1. Create a temporary sub-directory (for example, \TEMPDIR).
  2. Copy BTR61.EXE to \TEMPDIR and type BTR61.EXE -d. (This step creates two subdirectories, \SYSTEM and \PUBLIC and copies appropriate files into each.)
  3. Back up all files on the server where Btrieve will be installed.
  4. Copy the files from \TEMPDIR\SYSTEM into \SYSTEM on the server.
  5. Copy all the files from \TEMPDIR \PUBLIC to \PUBLIC on the server.
  6. At the server console prompt, type "BSTOP" to unload the existing versions of BTRIEVE.NLM and BSPXCOM.NLM.
  7. At the server console prompt, type "UNLOAD CLIB".
  8. At the server console prompt, type "LOAD CLIB" to load the latest version of CLIB.
  9. At the server console prompt, type "AFTER311".
  10. At the server console prompt, type "BSTART" to load BTRIEVE and BSPXCOM with default values.
Tip A number of Btrieve interfaces were changed to support 119 keys for Btrieve 6.x. These interfaces can be downloaded from Novell's NDEVREL CompuServe forum (Library 2, BTRINT.ZIP). The interfaces included are for BASIC and COBOL. They are:
  • BC7XBTRV.ASM
  • BC7RBTRV.OBJ
  • BC7PBTRV.OBJ
  • COBPBTRV.OBJ
  • MFXBTRV.OBJ
  • COBRBTRV.OBJ
  • REALBTRV.OBJ
  • MFXBTRV.BIN
  • MF2BTRV.BIN
The interfaces are only available on NDEVREL for those customers that have purchased the Btrieve v6.x SDK Supplement.

NetWare for SAA Tools SDK

Q - What products are bundled in the new NetWare for SAA Tools SDK? What has happened to the NetWare 3270 Tools product?

A - The NetWare for SAA Tools SDK is now bundled with NetWare LU6.2 Tools v1.4, NetWare 3270 Tools v2.0 and the Netware Open NetView SDK v1.0. The NetWare 3270 LAN Workstation software for DOS and Microsoft Windows are now being sold and supported by AttachMate. To contact AttachMate, call 1-800-426-6283. NetWare LU6.2 and NetWare 3270 Tools are now supported on CompuServe in Novell's NDEVSUP forum (Library 7).


DEVELOPER EDUCATION:

Available Novell Developer Education Courses

Novell Developer Education offers several courses for developers who use Novell's development & database tools, including NetWare SQL, Btrieve, Xtrieve PLUS, and the NetWare Client APIs.
904 - Btrieve: An Overview
905 - Programming with Btrieve
907 - Xtrieve PLUS
911 - NetWare Database Administrator
912 - Programming with NetWare SQL
930 - Developing NetWare Loadable Modules (NLMs)
940 - NetWare Programming: Basic Services
945 - NetWare Programming: Protocol Support
To obtain information on pricing, location, scheduling, & course content for classes held in the US, call 1-800-233- 3382, 1-801-429-5508 or 1-800-NETWARE. For information on classes outside of the US, contact your local Novell office.

CURRENT PATCHES FOR NOVELL DEVELOPMENT TOOLS

The latest NetWare drivers, example code for NetWare API development tools, OS/2 requester patches, and patches for Novell's database products are available on Novell's NetWire forum on CompuServe. Back issues of Bullets and developer FYIs are also available (NOVLIB, library 11). New information is first stored in NOVLIB library 1, and moved to library 7 after a period of 30 days. If you do not have access to CompuServe, request these files from Novell's Developer Support Group at 1-800-NETWARE (1-800-638-9273) or 1-801-429-5588.

When calling, be ready to provide a shipping address, disk preference (5.25", 3.5", HD, or DD) and, if you prefer overnight delivery, your company's Federal Express account number. If you do not provide a Fed Ex account number, the patch disk will be sent to you via regular mail.

Updated NetWare API SDK components can be obtained from Novell's NDEVREL forum on CompuServe. For more information on Novell developer programs contact Novell at 1-800-NETWARE (1-800-638-9273) or 1-801-429-5588.

Novell's Developer Relations Automated Fax System

A document describing available patches and other files and their location on CompuServe is available through Novell Developer Relations Automated Fax System (AFS). This system can provide you other useful information as well. To use the AFS, call 1-800-RED-WORD (1-800-733-9673) or 512-794-1796 from a touchtone phone. Then, choose the option for the Automated Fax System, select the documents you wish to receive, and supply your fax number (the fax number to which you want the document(s) sent). Document #7805 describes the patches. Document #1 lists all other documents available through the Automated Fax System. Up to five documents can be requested per call.

CONTACTING NOVELL

Developer Support

Developers in the U.S. and Canada may contact Novell Developer Support via telephone, fax, BBS or electronic mail via CompuServe, MHS or the Internet.

Voice

For both presales and postsales support, call Novell Developer Support between 8:00 a.m. and 5:00 p.m. Mountain Time at 1-801-429-5588. Many calls to Novell Developer Support are passed to a software support engineer immediately. Calls to Novell Developer Support generally will be acknowledged or answered within four hours.

Fax

If you prefer, you may contact Novell Developer Support via fax at 1-801-429-2990. Faxed questions are acknowledged or answered within 24 hours.

Developer BBS

If you do not have access to either CompuServe or the Internet, send test cases to our BBS. Set your communication software to N-8-1 and call 1-801-429-5836.

E-mail: CompuServe

Post your messages in the appropriate section addressed to Novell at 76701,171. These messages will receive a response within 24 hours.

E-mail: MHS

You may direct your questions and comments to Novell Developer Support via Novell's Message Handling Service E-mail facility. Address your messages to devsup@novell. These messages will receive a response within 24 hours.

E-mail: Internet (SMTP)

An alternative method of contacting Novell Developer Support is through the Internet E-mail system. Send your messages to devsup@novell.com. These messages will receive a response within 24 hours.

NetWire on CompuServe

Novell's NetWire forum is open to all registered CompuServe subscribers. Through the NDEVSUP forum, professional developers writing applications with Novell development tools can gain access to information specific to Novell development. Support, patches, periodicals and product information, as well as information on all of the programs and services provided for Novell developers are accessible through this forum. Post your messages in the appropriate section addressed to Novell at 76701,171. Technical questions will be acknowledged or answered by Novell Developer Support within 24 hours.

Novell Products and SDKs

Novell products on the "Currently Shipping Developer Products" list (see page 15 of this issue), including the Red Box Products, are available to Novell Professional Developer's Program members. Call 1-800-RED-WORD (1-800-733-9673) or 1-303-894-4135 to order these products or to receive additional information. To order other Red Box Products not listed, contact your local Novell office or Novell Authorized Reseller.

DeveloperNet Labs

For information on DeveloperNet Labs development tools, education classes and product certification, in the U.S. and Canada call 1-800-453-1267 ext. 5544 or 1-801-429-5544, or call your local Novell office (see back cover of this issue).

Novell Developer Relations

Novell Professional Developers or those wishing to become members may contact Novell Developer Relations via telephone, fax, or electronic mail via Compuserve, MHS or the Internet.

Voice

For general information or questions on Novell Developer Relations programs, in the U.S. or Canada call 1-800-RED-WORD (1-800-733-9673). All others call 1-801-429-5281.

Fax

If you prefer, you may contact Novell Developer Relations via fax at 1-801-429-7207.

NetWire on CompuServe

Novell's NetWire forum is open to all registered Compuserve subscribers. Through the NDEVREL forum, Novell Professional Developer's Program-related issues or general questions may be posted. Through the NDEVINFO forum, customers who do not have products may post pre-sales product information questions on all Novell SDKs.

E-mail: MHS or Internet (SMTP)

You may direct your questions and comments to Novell Developer Relations via Novell's Message Handling Service E-mail facility. Address your messages to devprog@novell.com. Use the same address when going through the Internet E-mail system.

ERRATA

In the July issue of Bullets, the table, "New Releases of Novell Development Tools," announced that, "If you purchased [any of the tools replaced by the NetWare Client SDK] before Sept. 12, 1993, you can purchase [the NetWare Client SDK] at a discount with proof of purchase. If you purchased products after 9/12/93, new product is free until 8/30/93."

The dates should have read, "Sept. 12, 1992," and "9/12/92." We apologize for any confusion caused by this error.


ACKNOWLEDGMENTS

Publisher: Mad Poarch
Editor: Kirk R. Humphries
Design: Creative Services, Provo
Articles: Vitek Boruvka, Bob Quinlan, Holly Roff
Contributors: Vitek Boruvka, Neda Eslami, Kumar Gaddam, Raj Perubhatla, Matt Pinsonneault, Michael A. Spano, Glenn Stephens, John E. Stewart, Aslam Tejani, Howard C. Thamm, and Brenda Wallace

Special thanks to the Developer Support, Development, Developer Relations, Product Marketing, and Marketing Communications staff who contributed time and valuable input.

(c) 1993 Novell, Inc. All rights reserved.

Novell, the N design, NetWare, Btrieve, XQL, LAN WorkPlace, NetWare Name Services and LANalyzer are registered trademarks; NetWare Loadable Module, NLM, Global MHS, NetWare MHS, NetWare System Calls for DOS, NetWare Runtime, NetWare SQL, NetWare Btrieve, NetWare C Interface for DOS, NetWare RPC, NetWare RPC 386, NetWare LU6.2, Report Executive, NetWare Asynchronous Communication Services (NACS), NetWare Asynchronous Services Interface (NASI), NetWare Management System, NetWare 3270 LAN Workstation, Xtrieve PLUS, DeveloperNet Labs, IPX, and MacIPX are trademarks; and NetWire and Professional Developers' Program are service marks of Novell, Inc. IBM and OS/2 are registered trademarks, and NetBIOS and SAA are trademarks of International Business Machines Corporation. Microsoft is a registered trademark, and Windows, Windows NT and Microsoft QuickBasic are trademarks of Microsoft Corporation. Apple and Macintosh are registered trademarks of Apple Computer, Inc. CompuServe is a registered trademark of CompuServe Corporation. Sun Microsystems and NFS are registered trademarks; and TI-RPC is a trademark of Sun Microsystems, Inc. UNIX is a trademark of UNIX Systems Laboratories, Inc. in the USA and other countries. UNIX Systems Laboratories is a wholly-owned subsidiary of Novell, Inc. Univel and UnixWare are trademarks of Univel, Inc. WATCOM and WATCOM C are registered trademarks of WATCOM Systems, Inc.