Novell is now a part of Micro Focus

Programming to NWSIPX- Novell's 32-bit IPX/SPX API

Articles and Tips: article

DAN STRATTON
Developer Support Engineer
Developer Support

01 Jul 1996


NWSIPX is Novell's new, 32-bit IPX/SPX API for 32-bit client operating systems. Available on Windows 95, OS/2, and soon to be available on Windows NT, NWSIPX provides common access, functionality, and use across these platforms. This article discusses the features of the API, usage, and porting issues from previous API sets.

Introduction

NWSIPX is Novell's new, 32-bit IPX/SPX API for 32-bit client operating systems. Available on Windows 95, OS/2, and soon to be available on Windows NT, NWSIPX provides common access, functionality, and use across these platforms. In this article, I will discuss the features of the API, usage, and porting issues from previous API sets.

General Architecture

NWSIPX has been designed to eliminate the need for the developer to understand the very low-level IPX and SPX design, which in the past has varied from platform to platform. When programming to NWSIPX, you can expect to communicate with a consistent control block structure and consistent APIs, regardless of protocol used. The following describe some of the features of the NWSIPX API:

  • A new interface control block, called the NetWare Transport Control Block (NWTCB), is used to transfer information between the client application and the API. All NWTCB management (allocating and freeing) is provided through the API. This has the added benefit of freeing the developer from worrying about locking the memory for ECBs, buffers, etc.

  • In the NWSIPX connection oriented service, the application can select between streaming and message mode when receiving data. In streaming mode, the client application receives message fragments as they arrive from the network andis responsible for putting them into a meaningful context. In message mode, only whole messages are received.

  • For both connection oriented and datagram services, the client application can select, on a per request basis, one of several methods for event notification. These methods include API managed events, multiplexed API managed events, callback functions, polling, blocking, and user managed events.

  • The NWSIPX API supports simultaneous attachment to multiple networks and/or multiple attachments to the same physical network using different data encapsulation methods, such as Ethernet802.2 and Ethernet II. While the client application is not required to select or be aware of which subnetwork is used for communicationwith a remote system, the NWSIPX API does allow the client application to specify the subnetwork to use.

The NWTCB is a new control block structure for use with NWSIPX. It is roughly equivalent to the ECB used in the legacy IPX API. Unlike the ECB, though, all fields in the TCB are meant to be used by the developer for retrieving or setting information. This differs from previous IPX/SPX implementations that contained fields that were not intended for manipulation by the developer. The following listing describes the NWTCB.

typedef struct TAG_NWTCB

{

TAG_NWTCB         *TCBNext ;             // Points to the next NWTCB in a list. Not used by the API.

TAG_NWTCB         *TCBPrevious ;         // Points to the previous NWTCB in a list.  Not used by the API.

SIPXSOCK_HANDLE   TCBSockHandle ;        //Specifies a socket handle.

SIPXCONN_HANDLE   TCBConnHandle ;        //Specifies the Connection-endpoint handle.

nptr              TCBClientContext ;     // Points to a field for use by the developer for context sensitive information.

nuint32           TCBTransportEvent ;    // Indicates the transport event to be registered for when calling

                                        //  NWSipxRegisterForTransportEvent

  

union

{

nuint32         TCBTimeout ;

nuint8          TCBEventSpace [32];

} TCBEvent;        //Specifies parameters associated with the transport event.

nuint32           TCBFinalStatus ;       //Indicates the final completion status of a request or event.

nuint32           TCBBytesTransferred ;  //Indicates the number of bytes sent or received on a data transfer operation.

nflag32           TCBFlags ;             //Common flag field of the TCB. Errors result when invalid flags are used.

NETADDR           TCBRemoteAddress ;     //Indicates the source or destination of a data transfer operation.

SIPXSUBNET_HANDLE TCBSubnetworkHandle ;  //Specifies the Subnetwork handle to be used for an operation.

nuint32           TCBMsgSequenceNumber ; //Connection services only. Indicates the message sequence number.

nuint8            TCBPacketType ;        //Datagram services only. Specifies the packet type.

nuint8            TCBDataStreamType ;    //Connection services only. Specifies the data stream type.

nuint8            TCBReserved [2];       // Currently unused but reserved for future definition.

nuint32           TCBFragmentCount ;     // Specifies the number of data fragments used to hold data.

PFRAGMENT         TCBFragmentList ;      // List of data fragments and their sizes.

} NWTCB, *PNWTCB, **PPNWTCB;

API Features and Usage

The NWSIPX API set is broken up into seven categories: NWSIPX API Management, Control Block Management, Event Notification Management, Socket Management, Connection Oriented Services (SPX), Datagram Services (IPX), and the NWSIPX API Toolbox.

All of the management sections deal with getting information from, or putting information into, the API. For example, NWSipxGetInformationand NWSipxSetInformation belong to the API management section. These API calls can be used to find out what subnets are active on a workstation, what socket is being used for a connection, to set the streaming mode or message mode for SPX services, etc.

NWSIPX differs from past IPX/SPX API sets in that all control block allocation and deallocation is done by the API. The Control Block Management section of the API specifies function calls that manage these NWTCBs. NWSipxAllocControlBlock and NWSipxChangeControlBlock are examples of this.

Note: Allowing the API to allocate and deallocate control structures has a number of benefits. Possibly the most important side effect for the developer is that, when developing in Windows, you no longer have to worry about locking all of your memory for ECBs, data buffers, ESRs, IPX Headers, etc. The need to do this previously has been a large source of bugs in developers' code.

NWSIPX also provides multiple methods of event notification. DOS and Windows IPX programmers are familiar with the ESR (event service routine) interface. While effective, the ESR had to be written in assembly and executed at interrupt time, which meant that all processing had to be accomplished very quickly. Using this interface under Windows compounded certain issues, making development even more challenging.

Under OS/2, functions were either blocking or semaphore driven (depending on whether IPX or SPX was being used), which typically required extensive use of threads. While using multiple threads is generally good programming practice, it locked the developer into certain design decisions that might not be appropriate for the application.

NWSIPX provides support for blocking, polling, callback functions, API managed events, multiplexed API managed events, and user managed events. Callback functions most closely resemble the old ESR interface, without requiring you to write assembly code. Also, the callback function does not run at interrupt time, allowing you to do whatever processing of the TCB is needed from within the callback function.

Blocking and polling resemble the way the legacy OS/2 API functioned, as mentioned previously. Blocking is useful when you need to accomplish a task before you proceed (for example, making an SPX connection to a database server). Polling is useful when you want to periodically check on the status of an operation.

API managed events, multiplexed API managed events, and user managed events, however, are new to NWSIPX. API managed events, in some ways, act like polling. The program posts a send or receive, which returns immediately. Then, at some point in the future, the program issues an NWSipxWaitForSingleEvent. This is a blocking function call, which will return when the send or receive has completed (either successfully or with a failure).

NWSipxWaitForSingleEvent, like all of these functions, can be called from the main program or from a thread. A typical implementation would be to build a thread that does nothing but wait for some event or group of events. Multiplexed API managed events are a continuation of this idea. With multiplexed events, the program can issue a single wait, which will wait until any one of a user-defined group of events (usually sends or receives) occurs.

For example, your program may post five receives on a particular socket and you want to be informed when any of those occurs. In the past this was more complex. With DOS or Windows, you would build the ESR handler in assembly code, then point your ESRAddress field to the ESR. You had to be careful not to do anything terribly meaningful since this executed at interrupt time. Consequently, the standard method of dealing with these packets would be to move the contents of the packet to some holding area for processing outside of the interrupt handler. NWSIPX makes things quite a bit easier.

With multiple event notification, the program calls NWSipxWaitForMultipleEvents, which blocks until any one of the TCBs assigned to a specific group complete. When that happens, the program is given the TCB that completed and processing of that TCB can occur. In the example of a receive, if the program takes a while to call another wait, that's okay because the packet will be kept around until it's retrieved by the program (this is true, although any packets received beyond the number of NWTCBs currently posted, and the number of system-wide packet buffers will be lost).

The following code segment demonstrates handling multiple IPX receives in a separate receive thread. Notice that this code (from an OS/2 sample in this case) does not perform the initial NWSipxReceiveDatagram, but does repost the TCB after processing has completed.

void receiveThread(void *eventInfo)

{

 PNWTCB   tcbReturned;

 ULONG    termSemPostCount=0;

 char     tempBuf[]={'.','Q','U','I','T',0x0d,'\0'};

 nuint32  ccode=0;

   do{

     ccode = NWSipxWaitForMultipleEvents(*((SIPXMUXGRP_HANDLE *)eventInfo), RCV_TIME_OUT, &tcbReturned);&
     if(ccode==SIPX_SUCCESSFUL)

     {

  

     if( stricmp((const char *)tcbReturned  TCBFragmentList  FAddress, tempBuf)==0) 
        {

           DosPostEventSem(termSemHandle);

           printf("The other workstation has terminated this session\n");"
        }

        else

        {

           displayPacket(tcbReturned); /* we got a packet */

           tcbReturned  TCBFlags = 0; 
           ccode = NWSipxReceiveDatagram(tcbReturned);

           if(!SIPX_SUCCESS(ccode))

           {

             sipxError(ccode, "NWSipxReceiveDatagram Thread");"
             DosPostEventSem(termSemHandle);

           }

        }

     }

     else if(ccode != SIPX_PENDING) /* if we don't hit this, we timed out, 

                                       and we'll wait again at the top of the loop */

     {

        sipxError(ccode, "NWSipxWaitForMultipleEvents"); /* we got an error */"
        tcbReturned  TCBFlags = 0;   /* It's a good idea to put some code in to  
                                                check what kind of error you got */

        ccode = NWSipxReceiveDatagram(tcbReturned);

        if(!SIPX_SUCCESS(ccode))

        {

           DosPostEventSem(termSemHandle);

           sipxError(ccode, "NWSipxReceiveDatagram");"
        }

     }

     ccode=DosQueryEventSem(termSemHandle, &termSemPostCount);&
   }while(termSemPostCount==0);

}

User managed events are intended to allow the developer to use a signaling mechanism other than those described above. For instance, the program may interface with a program that has already been implemented. In general, the developer will pass a handle to a user managed event (i.e., event semaphores, mutex semaphores, etc.). The language used here varies from platform to platform. For example, in OS/2, these are event or mutex semaphores, while in Windows they are known as events, mutexes, or semaphores. In Windows, these API calls would be CreateEvent, CreateMutex, and CreateSemaphore, respectively.

Socket management in NWSIPX functions similarly to that in previous API sets. NWSipxOpenSocket opens a socket, NWSipxCloseSocket closes one. There are a couple of additional features to this API however. NWSipxOpenSocket allows the developer to specify an options flag (which currently can be used to turn packet checksum on and off), and a subnetwork handle. The subnetwork handle is used to specify that communication will occur over a subnetwork other than the default subnetwork.

Using this functionality, you could, for example, establish a connection to a database over Ethernet_802.3 although your NetWare file server communications was done over Ethernet_802.2. Alternately, you could carry out communications to a peer over a second NIC (network interface card). This is new functionality to this API set and has not been previously available.

Connection services and Datagram Services are the heart of NWSIPX, although I will talk the least about this group of API calls. These calls are used for all sending and receiving of IPX or SPX packets. Their structure is fairly intuitive and similar to calls available on Novell's older API sets. There are a few notable differences from the legacy API sets though.

First, it is no longer necessary for the program to call IPXGetLocalTarget, and maintain that information. That information is now monitored by the API set. NWSIPX also provides an API call, NWSipxRegisterForTransportEvent, that allows the program to be notified when certain things occur.

The following are valid transport events:

  • SIPX_LISTEN_FOR_DISCONNECT

  • SIPX_SCHEDULE_TIMER_EVENT

  • SIPX_SUBNET_STATUS_CHANGE

The SIPX_SCHEDULE_TIMER_EVENT, for instance, is used to set timers for delayed execution of a functions, much like IPXScheduleIPXEventin the DOS and Windows API.

The NWSIPX "Toolbox" APIs are miscellaneous functions that do not cleanly fit into other categories. For example, NWSipxAdvertiseService, NWSipxCancelAdvertiseService, and NWSipxQueryService are used for registering, deregistering, and querying SAP objects (the interface was intentionally left generic so that other naming schemes, i.e. NDS, could be put there later and not require program modifications). The Toolbox also has API calls for canceling pending requests, getting the workstation's internet address, and registering for transport events.

Porting Legacy Applications

The complexity involved in porting your application from a current Windows or OS/2 application to NWSIPX depends on whether you decide to maintain the same logical flow of your program. The new notification methods provided in NWSIPX allow you, in many cases, to write your program to be more efficient than was previously possible. If you choose to modify the logical flow of the program, porting the application may be fairly time consuming (this would be much like writing the network components from scratch, which is essentially what you are doing). Porting the application and maintaining the programs original logical flow, however, is fairly easy.

The following function was originally contained in a piece of sample code. This function was written in TLI and demonstrated making an SPX connection to another machine. The second piece of sample code is the same function, modified for NWSIPX. I have removed parts of each function to save on space, but I think I've left enough to give a general idea of the differences.

BOOL InitConnection(IPX_ADDR *spx_addr, 

       SPX_OPTS *spx_options,

       t_call *tcall, 

       int *initComplete, 

       int *fd, HWND hwnd)

{

  static FARPROC lpfnGetNetAddressDlgProc;

  char  tempString[22];

  int  I, strCount=0;

  int  rval;

  if(*(initComplete))

     Terminate(fd);

     ..........

     ..........

     ..........

        /*                                                                    
        ** Connection is opened in non blocking mode by using 0_NDELAY 
        */

        if ( ( *(fd) = t_open( "/dev/nspx", O_RDWR | O_NDELAY, ( struct t_info * )0 ) ) == "1 ) "
        {  t_error( "Open of /dev/nspx failed" );"
           return FALSE;

        }

        if ( t_bind( *(fd), ( struct t_bind * )0, ( struct t_bind * )0  ) ==  1 )  
        {  t_error( "Bind failed!" );"
        return FALSE;

        }

        spx_addr  ipxa_socket[ 0 ]  = 0;   // Step 2     
        spx_addr  ipxa_socket[ 1 ]  = SPX_SOCKET; 
        tcall  addr.buf   = ( char * ) spx_addr;   // Step 3 
        tcall  addr.maxlen = sizeof( *(spx_addr) ); 
        tcall  addr.len   = sizeof( *(spx_addr) ); 
        spx_options  spx_connectionID[ 0 ] = 0; 
        spx_options  spx_connectionID[ 1 ] = 0; 
        spx_options  spx_allocationNumber[ 0 ] = 0; 
        spx_options  spx_allocationNumber[ 1 ] = 0; 
        tcall  opt.buf  = ( char * )spx_options; 
        tcall  opt.len    = sizeof( *(spx_options) ); 
        tcall  opt.maxlen = sizeof( *(spx_options) ); 
        tcall  udata.buf  = ( char * )0; 
        tcall  udata.maxlen = 0; 
        tcall  udata.len  = 0; 
        /*                                                                    
        ** The call to t_connect completes immediately in non blocking mode  
        ** and will typically return TNODATA. You will note that upon a 

        ** timer message I check to see if init is complete, if it is then

        ** I call t_rcvconnect to complete the establishment of the connection

        ** with the server. You must call t_rcvconnect in non blocking mode 
        ** to complete the establishment of the connection with the server.

        */

        rval = t_connect( *(fd), ( struct t_call  far * )tcall, (  struct t_call  far *  )tcall );

        if ( (rval ==  1)    (t_errno != TNODATA))       
            { 

             t_error( "t_connect failed" );"
             if ( t_errno == TLOOK && t_look( *(fd) ) == T_DISCONNECT ) SPXDisconReason( *(fd), hwnd );&
                    return FALSE;

        }

        *(initComplete) = 1;

  return TRUE;

}

BOOL InitConnection(NETADDR *pNetAddress, 

        HWND hwnd, 

        PSIPXCONN_HANDLE sipxHandle,

        pnuint8 initCompleteFlag)

{

  static FARPROC lpfnGetNetAddressDlgProc;

  SIPXSUBNET_HANDLE hSubnetworkHandle = 0;

  PNWTCB pEstabNWTCB;

  FRAGMENT estabFrag;

  char  tempString[25];

  int  I, strCount=0;

  int  rval;

  nuint32 sipxStatus;

  nuint16 sipxSocket = 0;

  IPXADDRESS ipxAddr;

  char  displayMessage[1024];

  ..........

  ..........

  ..........

  pNetAddress  NAType = TA_IPX_SPX; 
  pNetAddress  NALength = sizeof(IPXADDR); 
  memcpy(&pNetAddress&&NAAddress, &ipxAddr, sizeof(NETADDR));&
  sipxStatus = NWSipxAllocControlBlock(

             /* Sync Type         */ SIPX_API_EVENT,

             /* pEventInfo  */ NULL,

             /* Pnt NWTCB     */ &pEstabNWTCB&
             );

  if (SIPX_ERROR(sipxStatus))

      {

      wsprintf(displayMessage, "NWSipxAllocControlBlock() returned %08X", sipxStatus);"
      MessageBox(NULL, displayMessage,"Function Failed", MB_OK);"
      return FALSE;

      }

  sipxStatus = NWSipxOpenConnectionEndpoint(

                    /* sub network handle    */ hSubnetworkHandle,

                    /* socket to be opened   */ &sipxSocket,&
                    /* connection handle     */ sipxHandle

                    );

  if (SIPX_ERROR(sipxStatus))

      {

      wsprintf(displayMessage, "NWSipxOpenConnectionEndpoint() returned %08X", sipxStatus);"
      MessageBox(NULL, displayMessage,"Function Failed", MB_OK);"
      goto _FreeNWTCB;

      }

  /*                                                                     
  ** Set up NWTCB fields for send.

  */

  pEstabNWTCB  TCBConnHandle = *(sipxHandle); 
  pEstabNWTCB  TCBFragmentList =  estabFrag; 
  pEstabNWTCB  TCBFragmentCount = 1; 
  memcpy(&pEstabNWTCB&&TCBRemoteAddress,&
  pNetAddress, sizeof(NETADDR));

  tsduSize =  NWSipxGetMaxTsduSize(*(sipxHandle));

  estabFrag.FLength = tsduSize;

  estabFrag.FAddress = GlobalAlloc(GPTR,

  estabFrag.FLength);

  sipxStatus = NWSipxEstablishConnection(

                /* ptr to NWTCB       */ pEstabNWTCB

                );

  if (SIPX_ERROR(sipxStatus))

      {

      wsprintf(displayMessage, "NWSipxEstablishConnection() returned %08X", sipxStatus);"
      MessageBox(NULL, displayMessage,"Function Failed", MB_OK);"
      goto _FreeFrag;

      }

  sipxStatus = NWSipxWaitForSingleEvent(

                /* ptr NWTCB          */ pEstabNWTCB,

                /* time out interval  */ SIPX_INFINITE_WAIT

                );

  if (SIPX_ERROR(sipxStatus))

      {

      wsprintf(displayMessage, "NWSipxWaitForSingleEvent() returned %08X", sipxStatus);"
      MessageBox(NULL, displayMessage,"Function"
      Failed", MB_OK);"
      goto _FreeFrag;

      }

  if(pEstabNWTCB  TCBFinalStatus == SIPX_SUCCESSFUL) 
      {

      *initCompleteFlag = 1;

      memcpy(pNetAddress, &pEstabNWTCB&&TCBRemoteAddress, sizeof(NETADDR));&
      }

  else

      {

      wsprintf(displayMessage, "Attempt to Establish Connection Failed! Return code: %08X",  pEstabNWTCB""TCBFinalStatus);"
      MessageBox(NULL, displayMessage,"Establish Connection Failed!", MB_OK);"
      }

_FreeFrag:

  GlobalFree(estabFrag.FAddress);

_FreeNWTCB:

  NWSipxFreeControlBlock(pEstabNWTCB);

if(*initCompleteFlag)

  return TRUE;

else

  return FALSE;

}

As you've probably noticed, the two functions above are not identical in function. The TLI code returns immediately upon calling t_connect, which requires you to call t_rcvconnect later (found outside this function). Calling t_rcvconnect allows you to decide whether the t_connect has succeeded yet or not. It would have been possible to duplicate this functionality in NWSIPX by setting the syncType field in the NWSipxEstablishConnection call to SIPX_POLLING, then calling NWSipxPoll occasionally (as was done in the WM_TIMER event in the TLI example). This is one case where changing the function of the code slightly made sense for our application.

Wrapping It Up

NWSIPX provides a wide variety of options in program design for developers writing to IPX or SPX. Its many notification methods allow you to write code that conforms to your design, instead of changing your design to conform to your code. The new NWTCB structure adds many new features, control options, and reporting mechanisms, while removing internally manipulated fields that have historically been a source of developer confusion and bugs. The fact that the NWTCB is now allocated by the API will also save developers from having to worry about memory locking, etc. in the Windows environment.

Currently, there are a number of pieces of sample code available for NWSIPX. These examples are available on Novell's DeveloperNet Web site (developer.novell.com), Compuserve (go ndevsup), FTP (ftp.novell.com pub/netwire/ndevsup), and BBS (801 861-5836). I would recommend that you look at these, as well as the online documentation for NWSIPX provided with your quarterly SDK CD.

* Originally published in Novell AppNotes


Disclaimer

The origin of this information may be internal or external to Novell. While Novell makes all reasonable efforts to verify this information, Novell does not make explicit or implied claims to its validity.

© Copyright Micro Focus or one of its affiliates