ARTICLES: IPX/SPX for NetBIOS Developers

Original article: (c) Copyright Novell, 1994
                  Novell Professional Developer BULLETS
                  January 1994 (Volume 6, Number 1)
NwTP additions  : (between angular brackets/ minus signs [- ... -])

     NetBIOS is a popular peer-to-peer communication method that it is
     supported under NetWare through a NetBIOS emulator. However, even though
     NetBIOS is supported, there are definite advantages to using Novell's
     "native tongue" protocols, IPX (Internet Packet eXchange) and SPX
     (Sequenced Packet eXchange), when doing peer-to-peer communication.

     This article discusses the advantages of using IPX/SPX and provides an
     introduction to Novell's IPX and SPX protocols for developers who have a
     working familiarity with NetBIOS.

Why Use IPX/SPX?

     The most obvious reason to use IPX and SPX is to improve performance;
     since NetWare emulates NetBIOS, processing NetBIOS commands involves more
     overhead than processing IPX/SPX commands. NetWare encapsulates emulated
     NetBIOS packets within IPX packets before they go out on the wire, so
     moving to IPX/SPX allows you to "cut out the middleman."

     You lose no connectivity by switching protocols either, since the
     emulated NetBIOS layer cannot communicate with hardware NetBIOS systems.
     In fact, moving to IPX/SPX gives you a net gain in connectivity; NetWare
     has a 70% share of the network operating system market.

     Also, since the NetBIOS emulator adds an additional layer of complexity
     to packets being sent out, it is more difficult to troubleshoot problems.
     Emulating NetBIOS involves an extra driver and an extra set of potential
     incompatibilities. Generally speaking, since IPX and SPX are not
     dramatically different from NetBIOS, it makes your job easier to work
     with the protocols that NetWare is designed to support.

Datagram Services

     Novell's IPX protocol provides almost the same functionality as NetBIOS
     datagrams. Both specifications deliver packets on a best-effort basis,
     but with no guarantee of delivery or sequencing. Both IPX and NetBIOS
     also provide the capability to send packets either to a single node or to
     multiple nodes. NetBIOS supports the multicast, or the sending of a
     datagram to a selected group of nodes with the same group name. Since IPX
     is address-based instead of name-based, this capability is not directly
     supported; instead IPX must send an individual packet to each node.

     NetBIOS also supports the broadcast datagram, a datagram that is
     broadcast to the entire internetwork. IPX supports broadcasts, but only
     to one subnet at a time. Usually, this restriction poses no problem,
     since mechanisms such as the NetWare Service Advertising Protocol (SAP)
     overcome this limitation.

     The data portion of a NetBIOS datagram is limited in length to 512 bytes,
     whereas IPX packets allow 546 bytes of data on all networks and can
     sometimes be substantially larger than that depending on the maximum
     packet size supported by network routers. Some networks can handle packet
     sizes of 4096 bytes or more.

Session Services

     As in the relationship between IPX and NetBIOS datagrams, Novell's SPX
     protocol serves much the same function as the NetBIOS session. Both SPX
     and NetBIOS sessions provide guaranteed delivery and sequencing of
     packets, but at the cost of increased overhead.

     The primary difference between the two is the supported packet size.
     NetBIOS sessions support 64K packet sizes (128K with Chain Sends). SPX
     has the same 546-byte packet size limitation as IPX and, in fact, SPX
     allows slightly less data in a packet than IPX, since the SPX header
     requires an additional 12 bytes. SPX therefore supports 534 bytes of data
     on all networks with the potential for much larger packets if supported
     by the routers, although attaining a 64K packet size is unlikely.

     Probably the most noticeable difference between IPX/SPX and NetBIOS is
     how each addresses packets. IPX/SPX addresses packets using network,
     node, and socket numbers. NetBIOS uses unique names to address packets.
     Each workstation can be uniquely addressed using the network and node
     numbers.

     A workstation can then have as many open sockets as desired for receiving
     peer-to-peer data packets. Many methods exist for determining a
     workstation's network, node, and destination socket number, but for
     simplicity the example code in this article uses SAP to obtain this
     information.

The Waiting Game

     With NetBIOS, you can choose to allow most NetBIOS commands to complete
     before returning control to the application, but most IPX/SPX commands
     return control immediately. In other words, most IPX/SPX commands are
     "no-wait" commands; there is no IPX/SPX "wait" counterpart.

     Since most NetBIOS developers use the "no-wait" variants, this difference
     should not pose a problem, but if you need to use a "wait," you can code
     it very simply by issuing the command and then looping on the in use
     field.

Asynchronous Events

     IPX/SPX also has a feature that is not used with NetBIOS: the
     asynchronous event. An asynchronous event can be initiated at any time
     and, as the name implies, can be set to occur independent of an
     application's execution path. An event could be set up, for example, to
     automatically broadcast an IPX packet every 45 seconds. The application
     initiating this event could then continue processing and leave the timing
     and broadcasting of packets to the IPX event handler.

The Network Control Block & the Event Control Block

     From a developer's perspective, the "core" of NetBIOS is the Network
     Control Block (NCB). IPX and SPX are based on an Event Control Block
     (ECB) and an IPX/SPX header. Figure 1 describes the fields in the ECB.

     *********************************************************
     Figure 1: The IPX/SPX Event Control Block  [- C structure -]

     void far *linkAddress              Set by IPX
     void (far *ESRAddress)()           Equivalent to NetBIOS POST routine
     BYTE inUseFlag                     Set when the ECB is in use, zero
                                        when it is available
     BYTE completionCode                Equivalent to NetBIOS Command
                                        Completion
     WORD socketNumber                  Socket number associated with ECB
     BYTE IPXWorkspace[4]               Set by IPX
     BYTE driverWorkspace[12]           Set by IPX
     BYTE immediateAddress[6]           Node address of next "hop"
     WORD fragmentCount                 Number of buffer fragments in packet
     ECBFragment fragmentDescriptor[2]  Address and size of fragment(s)

     END of FIGURE 1
     *********************************************************

 [-  *********************************************************
     Figure 1a: The IPX/SPX Event Control Block (Pascal syntax)

     linkAddress     :Pointer                   Set by IPX
     ESRAddress      :Pointer                   Equivalent to NetBIOS
                                                POST routine
     InUseFlag       :Byte;                     Set when the ECB is in use,
                                                zero when it is available
     CompletionCode  :Byte;                     Equivalent to NetBIOS Command
                                                Completion
     SocketNumber    :Word;                     Socket number associated
                                                with ECB
     IPXWorkspace    :array[1..4] of byte;      Set by IPX
     DriverWorkspace :array[1..12] of byte;     Set by IPX
     ImmediateAddress:array[1..6] of byte;      (Tnodeaddress)
                                                Node address of next "hop"
     FragmentCount   :word;                     Number of buffer fragments
                                                in packet
     Fragment        :array[1.. ] of Tfragment  Address and size of
                                                fragment(s)

     (Note: this structure is declared as the Tecb type in the nwIPX unit)

     END of FIGURE 1a
     ********************************************************* -]

     Note that the ECB contains a field that has no equivalent in the NCB
     called the immediate address field. This field should be populated with
     the node address of the first "hop" on the way to the packet's ultimate
     destination. Novell provides an API call to populate this field, the
     IPXGetLocalTarget() API available in the NetWare Client SDK.

IPX Send Example

     The sample code in this article includes simple examples written under
     DOS with the NetWare Client SDK. Figure 2 shows a routine sending an IPX
     packet.

     *********************************************************
     Figure 2: IPX Send [- C example -]

     /* Send "Hello!" to the station at network 0x11111111, node
        0x222222222222, socket 0x3333 using IPX */

     void IPXSayHello()
     {
        char buffer[] = "Hello!";
        ECB ecb;
        IPXHeader header;
        int transTime;
        header.packetType = 4;
        memset(header.destination.network, 0x11, 4);
        memset(header.destination.node,    0x22, 6);
        memset(header.destination.socket,  0x33, 2);

        ecb.ESRAddress     = NULL;
        ecb.socketNumber   = 0x4444;
        IPXGetLocalTarget(header.destination,
                          ecb.immediateAddress, &transTime);
        ecb.fragmentCount = 2;
        ecb.fragmentDescriptor[0].address = &header;
        ecb.fragmentDescriptor[0].size    = sizeof(IPXHeader);
        ecb.fragmentDescriptor[1].address = buffer;
        ecb.fragmentDescriptor[1].size    = strlen(buffer) + 1;
        IPXSendPacket(&ecb);
     }

     END of FIGURE 2
     *********************************************************

 [-  *********************************************************
     Figure 2a: IPX Send (Pascal example)

     { Send "Hello!" to the station at network $11111111, node
       $222222222222, socket $3333 using IPX }

     Procedure IPXSayHello;
     Var buffer:string;
         ecb:Tecb;
         header:TipxHeader;
         transTime:Word;
     begin
      Buffer:="Hello!";
      header.packetType := 4;
      FillChar(header.destination.network,4,$11);
      FillChar(header.destination.node,   6,$22);
      FillChar(header.destination.socket, 2,$33);

      ecb.ESRAddress:=NIL;
      ecb.socketNumber:=$4444;
      IPXGetLocalTarget(header.destination,
                        ecb.immediateAddress, transTime);
      ecb.fragmentCount:=2;
      ecb.fragment[1].address:= @header;
      ecb.fragment[1].size   := SizeOf(TIPXHeader);
      ecb.fragment[2].address:= @buffer[1];
      ecb.fragment[2].size:= ord(buffer[0]);
      IPXSendPacket(ecb);
     end;

     END of FIGURE 2a
     ********************************************************* -]


     The first apparent difference between IPX and NetBIOS is that IPX uses
     two buffers where NetBIOS would use one. The first buffer is the IPX
     Header containing the source and destination addresses, the packet type,
     and several "housekeeping" fields. Refer to Figure 3 for a description of
     the IPX header.

     *********************************************************
     Figure 3: IPX Header

     WORD       checkSum           Included to conform to Xerox IDP standard
                                   Set to FFFF by IPX
     WORD       length             Length of entire IPX packet including
                                   header
                                   Set by IPX
     BYTE       transportControl   Hop count - Set to zero by IPX
     BYTE       packetType         IPX packet type is 4
     IPXAddress destination        Address the packet is sent to
                                   [- Pascal: of type TInternetworkAddress -]
     IPXAddress source             Address of node sending packet set by IPX
                                   [- Pascal: of type TinternetworkAddress -]

     END of FIGURE 3
     *********************************************************

     The second buffer is the data to be sent. Two fields in the IPX header
     must be set for an IPX send: the packet type and the destination address.
     IPX packets are type 4, SPX packets are type 5. [- Note that according
     to the original xerox definitions this statement is not correct. Type 4
     packets are reserved for the PEP protocol. Use type 0 (undefined) when
     transmitting standard IPX packets-] The destination address consists
     of a four-byte network number, a six-byte node number, and a two-byte
     socket number.

     If these examples used an Event Service Routine (ESR), the ESR address
     would be filled with the address of a procedure to be run when the send
     completes, but since NULL is specified, this routine will not be run. The
     ESR is equivalent to the NetBIOS POST routine. When the IPX send
     executes, the rest of the fields in the IPX header are filled in
     automatically, including the source address. You must specify the socket
     number to be included in the source address, but the socket need not be
     open to send a packet. For this example, socket number 0x4444 was
     arbitrarily chosen.

     The immediate address field described above must be filled in as well,
     and the IPXGetLocalTarget() API call fills in this field with the
     appropriate value. It is passed the final destination of the packet and
     it calculates the address of the "first hop" on the way to the final
     destination. Note that if the target workstation is on the same subnet as
     the sending workstation the immediate address will be the same as the
     final destination. Otherwise, it will be a bridge or router on the
     subnet.

     Each of the buffers sent in the IPX packet is considered to be a
     fragment. Since there are two buffers (the IPX header and the data), the
     fragment count is equal to two. The address and size of the fragments are
     then entered, starting with the IPX header. As soon as all of the
     relevant fields are filled, the example calls IPXSendPacket() and passes
     it the address of the ECB.

     Receiving an IPX packet is much like sending one from a programming
     standpoint, except that you do not need to set the IPX header fields. In
     the ECB, you should set the ESR address, socket number, immediate
     address, and fragment descriptors.

     Note about socket numbers: the socket number specified for an IPX send
     does not need to be open, but for an IPX receive the socket must be open.
     The API call to receive an IPX packet is IPXListenForPacket().

SPX Connection Example

     Figure 4 contains a code sample that establishes an SPX connection.
     Before the request for an SPX connection is submitted, several ECBs are
     already listening for data (this is important). SPX temporarily "steals"
     two ECBs from the available and waiting ones for connection maintenance,
     and then it puts the stolen ECBs back in the pool when finished. If there
     are no pending ECBs for SPX to use, it cannot send an acknowledgement to
     the remote site and the connection will stall and time out.

     *********************************************************
     Figure 4: Establishing an SPX Connection [- C code example -]

     /* Start an SPX connection with the station at network
        0x11111111, node 0x222222222222, socket 0x3333, use
        local socket 0x4444 */

     #define NUM_BUFFS 5

     void call()
     {
       ECB send, receive[NUM_BUFFS], connect, term;
       SPXHeader sendHdr, rcvHdr[NUM_BUFFS], connHdr;
       char buffer[NUM_BUFFS][80], sendbuf[] = "Hello!";
       int i, ccode, packetsReceived;
       WORD spxConnectionID;

       for (i = 0; i < NUM_BUFFS; i++) {
           receive[i].ESRAddress = NULL;
           receive[i].socketNumber = 0x4444;
           receive[i].fragmentCount = 2;
           receive[i].fragmentDescriptor[0].address
             = &(rcvHdr[i]);
           receive[i].fragmentDescriptor[0].size
             = sizeof(SPXHeader);
           receive[i].fragmentDescriptor[1].address
             = &(buffer[i]);
           receive[i].fragmentDescriptor[1].size = 80;
           SPXListenForSequencedPacket(receive[i]);
       }

       connect.ESRAddress = NULL;
       connect.socketNumber = 0x4444;
       connect.fragmentCount = 1;
       connect.fragmentDescriptor[0].address = &connHdr;
       connect.fragmentDescriptor[0].size
         = sizeof(SPXHeader);

       memset(connHdr.destination.network, 0x11, 4);
       memset(connHdr.destination.node,    0x22, 6);
       memset(connHdr.destination.socket,  0x33, 2);

       ccode = SPXEstablishConnection(0, 0,
                                      &spxConnectionID,
                                      &connect);
       printf("SPXEstablishConnection return code
              = 0x%x\n", ccode);
       if (ccode != 0)
           return;
       while (connect.inUseFlag != 0)
           IPXRelinquishControl();
       if (connect.completionCode != 0)
           return;
       send.ESRAddress = NULL;
       send.fragmentCount = 2;
       send.fragmentDescriptor[0].address = &sendHdr;
       send.fragmentDescriptor[0].size = sizeof(SPXHeader);
       send.fragmentDescriptor[1].address = sendbuf;
       send.fragmentDescriptor[1].size = 7;
       SPXSendSequencedPacket(spxConnectionID, &send);

       packetsReceived = 0;
       while (packetsReceived < 10) {
           for (i = 0; i < NUM_BUFFS; i++) {
                if (receive[i].inUseFlag != 0) {
                    if (receive[i].completionCode != 0) {
                        packetsReceived = 10;
                    /* If we get an error, terminate */
                        break;
                    }
                    printf("Received: %s\n", buffer[i]);
                    packetsReceived++;
                }
                SPXListenForSequencedPacket(receive[i]);
           }
           IPXRelinquishControl();
       }

       term.ESRAddress = NULL;
       term.fragmentCount = 1;
       term.fragmentDescriptor[0].address = &connHdr;
       term.fragmentDescriptor[0].size = sizeof(SPXHeader);
       SPXTerminateConnection(spxConnectionID, &term);
       while (term.inUseFlag != 0)
           IPXRelinquishControl();
       for (i = 0; i < NUM_BUFFS; i++)
           IPXCancelEvent(receive[i]);
     }
     END of FIGURE 4
     *********************************************************

 [-  *********************************************************
     Figure 4a: Establishing an SPX Connection (Pascal example)

     { Start an SPX connection with the station at network
       $11111111, node $222222222222, socket $3333, use
       local socket $4444 }

     CONST NUM_BUFFS=5;

     Procedure call;
     Var send,connect,term:Tecb;
         receive          :array[1..NUM_BUFFS] of Tecb;
         sendHdr, connHdr : TspxHeader;
         rcvHdr           :array[1..NUM_BUFFS] of TspxHeader;
         buffer           :array[1..NUM_BUFFS] of string[80];
         sendBuf          :string;
         i,packetsReceived:Integer;
         spxConnectionId  :word;
     begin;
       sendbuf:="Hello!";

       for i:= 1 to NUM_BUFFS
        do begin
           receive[i].ESRAddress := NIL;
           receive[i].socketNumber := $4444;
           receive[i].fragmentCount = 2;
           receive[i].fragment[1].address := @rcvHdr[i];
           receive[i].fragment[1].size := sizeof(TSPXHeader);
           receive[i].fragment[2].address := @buffer[i];
           receive[i].fragment[2].size := 80;
           SPXListenForSequencedPacket(receive[i]);
           end;

       connect.ESRAddress := NIL;
       connect.socketNumber := $4444;
       connect.fragmentCount := 1;
       connect.fragment[1].address := @connHdr;
       connect.fragment[1].size := sizeof(TSPXHeader);

       FillChar(connHdr.destination.network, 4, $11);
       FillChar(connHdr.destination.node,    6, $22);
       FillChar(connHdr.destination.socket,  2, $33);

       IF NOT SPXEstablishConnection(0, 0,
                                    spxConnectionID,
                                    connect)
         then begin
              writeln('SPXEstablishConnection return code',
                      HexStr(nwSpx.result,2));
              exit;
              end;

       while (connect.inUseFlag <> 0)
        do IPXRelinquishControl();
       if (connect.completionCode <> 0)
        then exit;

       send.ESRAddress := NIL;
       send.fragmentCount := 2;
       send.fragment[1].address = @sendHdr;
       send.fragment[1].size := sizeof(TSPXHeader);
       send.fragment[2].address := @sendbuf[0];
       send.fragment[2].size := ord(sendBuf[0])+1;
       SPXSendSequencedPacket(spxConnectionID, send);

       packetsReceived := 0;
       while (packetsReceived < 10)
        do begin
           for i :=1 to NUM_BUFFS
            do begin
               if (receive[i].inUseFlag <> 0)
                  and (receive[i].completionCode <> 0)
                then begin
                     packetsReceived := 10;
                     exit;
                     { If we get an error, terminate }
                     end;
               writeln('Received: ", buffer[i]);
               inc(packetsReceived);
               SPXListenForSequencedPacket(receive[i]);
               end;
           IPXRelinquishControl;
           end;

       term.ESRAddress := NIL;
       term.fragmentCount := 1;
       term.fragment[1].address := @connHdr;
       term.fragment[1].size := sizeof(TSPXHeader);
       SPXTerminateConnection(spxConnectionID, term);
       while (term.inUseFlag <> 0)
        do IPXRelinquishControl;
       for i:=1 to NUM_BUFFS
        do IPXCancelEvent(receive[i]);
     end;

     END of FIGURE 4a
     ********************************************************* -]

     This process may sound complicated, but everything happens transparently.
     As long as there are extra ECBs available, the application never knows
     they have been borrowed, since SPX puts them back in the exact same state
     they were in when they were pressed into service.

     If the connection is established with the SPX watchdog enabled, the
     watchdog monitors the connection and notifies the application if the
     connection fails, even if the application is not currently sending data
     over the connection. This feature is useful for applications that start
     SPX connections, but use them infrequently. For simplicity, however, the
     example does not use the SPX watchdog.

     After the listen ECBs have been posted, the connection ECB is then set up
     in much the same way the IPX send ECB was, except that this ECB has only
     one fragment: the SPX header. The destination network, node, and socket
     also are set the same way they were in the previous example.

     SPXEstablishConnection() is passed a retry count of zero, indicating that
     you should use the default value for number of retries. This value is set
     in the workstation's NET.CFG file using the IPX RETRY COUNT parameter,
     which defaults to 20. The last zero passed in SPXEstablishConnection()
     indicates not to use the SPX watchdog. The SPX connection ID is returned
     as the third parameter. The SPX connection ID can be considered
     equivalent to the NetBIOS local session number.

     Next, the sample code attempts to establish a connection. It polls the
     ECB's in use flag waiting for the event to complete. The
     IPXRelinquishControl() call is very important at this stage. If the code
     did nothing but sit in a tight loop, IPX and SPX would never get the
     chance to do any processing. IPXRelinquishControl() allows the IPX/SPX
     layer to get some work done.

     Once the in use flag is set to zero, the example checks the return code
     to see if the attempt to establish a connection was successful. The code
     does not illustrate how to handle the various failure cases, but the most
     likely cause of a failure would be that the other side is not yet
     listening for a connection, just like in NetBIOS. After establishing the
     connection, packets can be sent to the remote station.

     The SPXSendSequencedPacket() call requires much less information than its
     IPX counterpart. Since the connection is already established, all
     SPXSendSequencedPacket() needs is the SPX connection ID, an ESR address,
     and the fragment information.

     After sending a packet, the example program waits for ten packets to
     arrive. When an ECB comes back, the example displays the data and then
     re-submits the ECB so that it can be used to receive a packet again.
     After receiving ten packets, it issues an SPXTerminateConnection() call
     to notify the other side that it is done.

     The call to terminate the connection takes almost the same parameters
     that the establish connection call does, except that there is no need to
     fill out any information in the SPX header. Once the connection has been
     terminated, the pending listen ECBs must be cancelled. To do so, the
     example calls IPXCancelEvent(). Unlike most other ECB-related calls,
     IPXCancelEvent() does not return until the ECB has been cancelled so
     there is no need to poll the in use flag.

Event Service Routines

     Event Service Routines (ESRs) serve the same purpose as the NetBIOS POST
     routines, but require a little more setup than the standard POST routine.
     Most ESRs are written in Assembly, although some call C functions.

     Figure 5 shows a generic ESR that calls a C function after allocating its
     own stack. This is very important since the amount of free stack space
     (if any) at interrupt time is unknown, and any attempt by a C function to
     use the stack could result in memory corruption if the stack is
     overflowed. The only way to guarantee that this will not occur is to
     allocate sufficient stack space in the ESR.

     *********************************************************
     Figure 5: Example Event Service Routine (ESR) [- C/ASM code -]

       .MODEL LARGE

       public       _ReceiveESRHandler
       extrn        _ProcessReceiveData:PROC

       .DATA

     ; The stack segment and pointer must be saved so that you can set up
     ; your own stack.

       stk_seg      dw 0                ; variable to store old stack segment
       stk_ptr      dw 0                ; variable to store old stack pointer
       stk_stk      dw 512 dup (0)      ; new stack of 1024 bytes in length
       stk_end      dw 0                ; the end of the stack

       .CODE

     ; @datasize is TRUE if the model is MEDIUM or LARGE and FALSE if the
     ; model is SMALL or COMPACT. Just modify the .MODEL ???? above for the
     ; model you want. ES/SI holds the seg/offset of the currently used ECB
     ; that ProcessReceivedData needs to process.

     _ReceiveESRHandler PROC far
         mov        ax,DGroup
         mov        ds,ax
         mov        stk_seg,ss          ; Save the stack segment
         mov        stk_ptr,sp          ; Save the stack pointer
         mov        ss,ax               ; move the segment of new_stk into ss
         mov        sp,offset stk_end   ; move offset of new_stk to sp
     IF  @datasize
         push       es                  ; push es if mem. model medium/large
     ENDIF
         push       si
         call       _ProcessReceivedData
         mov        ss,stk_seg          ; Restore old stack segment
         mov        sp,stk_ptr          ; Restore old stack pointer
         retf
     _ReceiveESRHandler ENDP

         END END of FIGURE 5
     *********************************************************


 [-  *********************************************************
     Figure 5a: Example Event Service Routine (ESR) (BASM/Pascal)

     { The stack segment and pointer must be saved so that you can set up
       your own stack. }

      Var stk_stk:array[1..512] of word; { new stack of 1024 bytes in length }
          stk_end:word;                  { the end of the stack              }

     {$F+}
     Procedure ESRhandler(Var p:Tpecb);  { * Type TPecb=^Tecb }
     begin
     .
     .
     end;
     {$F-}

     {$F+}
     Procedure ListenESR; assembler;
     asm { ES:SI are the only valid registers when entering this procedure ! }
         mov dx, seg stk_stk { = seg @DATA }
         mov ds, dx

         mov dx,ss  { setup of a new local stack }
         mov bx,sp  { ss:sp copied to dx:bx}
         mov ax,ds
         mov ss,ax
         mov sp,offset stk_end
         push dx    { push old ss:sp on new stack }
         push bx
         push es    { * push es:si on stack as local vars }
         push si    { * }
         mov  di,sp { * }
         push ss    { * push address of local ptr on stack }
         push di    { * }

         CALL EsrHandler

         add sp,4   { skip stack ptr-copy }
         pop bx     { restore ss:sp from new stack }
         pop dx
         mov sp,bx
         mov ss,dx
     end;
     {$F-}

     Note that a local stack of 1024 bytes (512 words) may not be large
     enough for some applications calling other functions within the
     ESRhandler. Increase the stacksize by 1024 bytes at a time to
     determine the stack requirement.


         END END of FIGURE 5a
     *********************************************************  -]

     Figure 6 contains a code fragment demonstrating the use of an ESR. It
     receives ten SPX packets just like the example in Figure 3 does, but it
     uses an ESR instead of polling the in use flag. The assembly language
     routine from Figure 4 is declared as the ESR, and it in turn calls the
     C [-/Pascal-] function ProcessReceivedData().

     *********************************************************
     Figure 6: Using an Event Service Routine (ESR) [- C Code -]

     int packetCount = 0;

     void ProcessReceivedData(ECB *ecb)
     {
         packetCount++;
         printf("%s\n", ecb->fragmentDescriptor[1].address);
         SPXListenForSequencedPacket(ecb);   /* Re-issue the listen */
     }

     main()
     {
         .
         . /* This code is identical to SPX setup code in Fig. 4, except */
         . /* for receive[i].ESRAddress line, which will be as follows: */

             receive[i].ESRAddress = (void (far *) () ) ReceiveESRHandler;

         .
         . /* The send ECB does not normally use an ESR. */
         .

         while (packetCount < 10)
             IPXRelinquishControl();
         .
         . /* Shut down connection, cancel ECBs */
         .
     }

     END of FIGURE 6
     *********************************************************

 [-  *********************************************************
     Figure 6a: Using an Event Service Routine (ESR) (Pascal)

     Var PacketCount;

     Procedure ProcessReceivedData(Var ECB:Tecb)
     begin
         inc(packetCount);
         writeln(string(ecb^.fragment[2].address^));
         SPXListenForSequencedPacket(ecb);   { Re-issue the listen }
     end;

     begin { main body }
     PacketCount:=0;

         .
         . { This code is identical to SPX setup code in Fig. 4a, except }
         . { for receive[i].ESRAddress line, which will be as follows: }

             receive[i].ESRAddress := @ReceiveESRHandler;
         .
         . { The send ECB does not normally use an ESR. }
         .

         while (packetCount < 10)
            do IPXRelinquishControl;
         .
         . { Shut down connection, cancel ECBs }
         .
     end;

     END of FIGURE 6a
     ********************************************************* -]

     IPX and SPX may look a little more complicated than NetBIOS at first, but
     as soon as you begin using these protocols, you see how similar they
     really are. Using IPX/SPX requires slightly more effort, but the
     performance and compatibility gains when running under NetWare more than
     compensate. If you are thinking about becoming more familiar with IPX and
     SPX development, feel free to contact Novell's Developer Support group at
     1-800-NETWARE (1-800-638-9273) or 1-801-429-5588.
