AUGUST 1993 VOLUME 5 NUMBER 8
INDEX
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
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:
- 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.
- 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.
- 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).
- 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
- 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
- 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.
- 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
- 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
- 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.
- 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).
- 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.
- 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.
- 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
- 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
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.
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:
- A very large transaction (e.g., thousands of records inserted),
- 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
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:
- Create a temporary sub-directory (for example, \TEMPDIR).
- Copy BTR61.EXE to \TEMPDIR and type BTR61.EXE -d. (This step creates two
subdirectories, \SYSTEM and \PUBLIC and copies appropriate files into each.)
- Back up all files on the server where Btrieve will be installed.
- Copy the files from \TEMPDIR\SYSTEM into \SYSTEM on the server.
- Copy all the files from \TEMPDIR \PUBLIC to \PUBLIC on the server.
- At the server console prompt, type "BSTOP" to unload the existing versions of
BTRIEVE.NLM and BSPXCOM.NLM.
- At the server console prompt, type "UNLOAD CLIB".
- At the server console prompt, type "LOAD CLIB" to load the latest version of CLIB.
- At the server console prompt, type "AFTER311".
- 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).
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.
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.
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.
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.
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.
|