A Netware Chat Utility

This utility lets you chat interactively with other users across Novell networks. You can call other users, accept or reject incoming calls, and even set up a conference chat via individual-user viewports.


November 01, 1993
URL:http://www.drdobbs.com/web-development/a-netware-chat-utility/184409111

Figure 1


Copyright © 1993, Dr. Dobb's Journal

Figure 2


Copyright © 1993, Dr. Dobb's Journal

Figure 2


Copyright © 1993, Dr. Dobb's Journal

Figure 1


Copyright © 1993, Dr. Dobb's Journal

Figure 2


Copyright © 1993, Dr. Dobb's Journal

NOV93: A Netware Chat Utility

A Netware Chat Utility

Understanding IPX programming

Eduardo M. Serrat

Eduardo is a systems analyst with Galmes y Casale s.r.l. and can be contacted at Libertad 38, (5000) Cordoba, Argentina


Multiuser operating systems like OpenVms on VAXs or TSX-32 on 386/486-based PCs provide a "phone" utility that let users chat interactively or across the network. Functionally, this utility resembles a real phone--you can call a user, accept or reject incoming calls, even set up a conference chat. The utility creates one viewport on the screen for each user engaged in conversation.

Unfortunately, Netware doesn't provide an interactive chat utility. Sure, network users can communicate via the Send command to transmit messages up to 45 characters long. But the message displays only on the last line of the target workstation, locks up the keyboard, and halts any running programs until the target user presses Ctrl-Enter. Alternatively, there are commercial applications like Direct Micro's LAN Intercom and Dave Frailey's shareware Chat utility. Like the Send command, however, LAN Intercom only displays messages a single line at a time (although it does have other add-on features) and you don't get the source code with either Frailey's Chat or LAN Intercom.

Because a Netware chat utility is indeed useful, I wrote a phone-like utility similar to those available on other operating systems--by using IPX programming and Netware services calls. With this utility, which I call "Phone," you can carry on interactive dialogs across the network. Remote users can access the network via Novell Remote Access Server facilities, then use the phone utility. (Where I work, we use Phone for customer software support, coordinating maintenance activities on the fly.)

In the process of presenting the utility, I'll examine two Netware APIs--IPX services and INT 21h extensions. For IPX background information, see "IPX: The Great Communicator" by Rahner James (DDJ, May 1992).

IPX, Addresses, and Sockets

Netware IPX (Internetwork Packet Exchange), is a high-performance connectionless, datagram-oriented protocol. Because it has its roots in XNS (Xerox Network Standard protocol), IPX features a big-endian byte ordering instead of the low-endian approach used by Intel CPUs. To deal with this "inverted" byte order, you must swap byte values when filling in numeric values in the packet. Since IPX packets can arrive at their destination out of sequence, the application using IPX as its transport is responsible for ensuring correct packet delivery. (Netware Core Protocol, NCP, deals with this.) Figure 1 and Table 1 show the format of an IPX packet and a description of the fields contained on its header.

Each workstation connected to a Novell network is uniquely identified by an internetwork address consisting of a 4-byte network number, and a 6-byte node number related to the physical address of the network adapter. Figure 2 shows internetwork addresses. Most of the work needed to transmit packets over the network involves determining internetwork addresses of the source and destination, and filling the IPX header correspondingly.

Sockets let several applications running on the same workstation distinguish the packets destined for them. Before a program can send or receive packets over the network, it must create a socket.

Netware reserves socket 0000h to 3FFFh and assigns socket numbers from 8000h to FFFFh to third-party developers. General-purpose sockets are from 4000h to 7FFFh. You can use these for your network programs. Alternately, you can simplify programming by choosing sockets 4141h, 4242h, 4443h, and so on to avoid swapping byte values when filling in sockets numbers in the packet. "Temporary" sockets remain active until deleted, or until the program finishes, whichever happens first. "Permanent" sockets (used by TSRs) remain active until explicitly deleted. Sockets must be unique in a single workstation, but the same socket number can be used by another workstation in the network.

Novell Netware APIs

Netware provides you with two APIs. The first provides IPX services (INT 7Ah) for sending and receiving packets from the network. The second API provides Netware services as an extension of the INT 21h (DOS interrupt). These are implemented by Netware's Workstation Shell that's normally made up of IPX.COM and NETX.COM (the redirector).

IPX services can be invoked by a FAR CALL or using INT 7Ah. When programming in a high-level language, calling IPX using FAR CALL requires some inline assembly code to save registers, execute a FAR CALL instruction, and restore registers.

Using INT 7Ah is preferred because you don't need to deal with inline assembly code. Furthermore, almost every high-level language provides procedures for generating interrupts. Before using IPX services, you must make sure IPX is loaded at your workstation. To do that, call the multiplexer to determine the presence of IPX. If you use the FAR CALL method of invocation, this call also provides you with the segmented address required.

To call the multiplexer, load register AX with 7A00h and issue INT 2Fh. Upon returning from this interrupt, register AL contains this status information: 00h, IPX not loaded (address not available); FFh, IPX loaded. In addition, register ES contains the segment of IPX services address; and register DI contains its offset.

If you issue an INT 7Ah and IPX isn't loaded, you'll crash the system, so it's important to detect the presence of IPX before proceeding with IPX requests; see Example 1. Before you can request IPX to send or receive packets, always create a socket for this purpose.

Event-control Blocks

Event-control blocks (ECBs) are a fundamental structure for send and receive packet requests; see Table 2. When receiving packets, IPX uses a "no wait" approach; that is, after submitting such a request, control returns to our program, and the corresponding ECB is queued as a outstanding request until a packet for the specified socket arrives. If a packet arrives and there aren't outstanding requests, the packet is dropped. You can then place any number of receive packet requests to avoid this situation. IPX doesn't guarantee that outstanding ECBs will be used in the order they were submitted, so you must check this in your program.

By filling in the event-service routine field of the ECB with the address of a FAR routine, you can specify that you want a routine to be called asynchronously upon completion of the ECB. Alternatively, you can zero this field and poll the status flag in the ECB to see if the request completed (see Example 2). The latter method is used when sending packets. In this case, you need to poll the status flag of the submitted ECB until IPX zeroes it, signaling that the request finished successfully. Tables 3 and 4 show the status flag values and the completion codes. When writing event-service routine conventions, all interrupt-services routine conventions must be considered. For example, to avoid DOS reentrancy problems, you can't call DOS services. When IPX transfers control to the event-service routine, ES:SI points to the address of the serviced ECB, other registers contents are undetermined. To let the ESR code address data, you need to copy ES into DS using inline assembly code. Finally, you must declare ESR as a far procedure to return properly with a FAR RETURN instruction. The ESR routine in Example 3 shows how the contents of register ES are copied into DS to establish addressability for global variable readflg. (The Turbo Pascal directive {$F+} is used to define the ESR as a far procedure.)

When filling in the ECB, you must supply an immediate address value which IPX uses to fill the packet's DESTINATION field. This address can be the same target node address, or the address of a routing node in an internetwork. This address is obtained using the IPX's GetLocalTarget function (see Listing One, page 100).

Suppose, for example, your program runs on workstation #1 and intends to send packets to workstation #2 located on another segment and connected to yours via a router. Here, GetLocalTarget returns address 00001b401564 as the immediate address. On the other hand, if you're sending a packet to server #1, then the immediate address returned by this function would be the same as server #1--00001b307035 as in Figure 2.

You can divide the packet for transmission or reception using the ECB's fragment-count and fragment-descriptor fields. This provides an automatic way to split the packet into meaningful parts. The phone utility, for example, uses two fragments--the first contains the IPX header, the second the packet's data. You fill the fragment count with a value between 1 and 42, and the corresponding fragments addresses and lengths.

Netware services, which are invoked as extensions of the INT 21h (DOS interrupt), consist of functions for determining connection numbers, internet addresses, user information, message transmission, and so on. The necessary function codes and structures are in Listing One.

Implementing and Using the Phone Utility

I wrote the phone utility in Turbo Pascal 5.0. The IPX and Netware services routines are contained in the Turbo Pascal unit IPXUNIT.PAS (Listing One), and the main program in PHONE.PAS (available electronically; see "Availability," page 3.) I recommend copying the PHONE.EXE executable to the PUBLIC directory so the utility is accessible network-wide.

When invoking the phone utility in dialing mode, you must provide in the command line the user name you're calling and (optionally) the connection number of this user. Netware's Userlist command can be used to view connected users and connection numbers.

In answer mode, the phone utility is invoked without parameters. The phone utility notifies the user being called, sending a message as does Send command. This message contains the name of the user generating the call, and its starting time. This notification is repeated at 30 second intervals, until the called user answers the call.

The sequence of procedures the phone utility uses to set up a dialogue is:

  1. User #1 invokes a phone utility in dialing mode providing a Username.
  2. Phone utility calls GetConnection to obtain a connection number for this user. If more than one connection exists for this user, the program exits, and asks for a connection number to be specified.
  3. With this connection number, a program calls GetInternetAddress to get the internetwork address of the target.
  4. Program gets the immediate address to send a packet calling the GetLocalTarget procedure.
  5. Program creates socket for transmission and reception.
  6. Program requests IPX, a receive packet service for this socket, and specifies an ESR to be called upon completion.
  7. The program Sends message notifying the target that a call is in progress, and includes a packet which contains the connection number of the user originating the call.
  8. Repeats step #7 until the service requested in step #6 is completed or the call is canceled by the originating user. If the user is satisfied, processing resumes at step #13.
  9. User #2 invokes the utility in answering mode.
  10. The phone utility creates a socket for transmission and reception, and requests IPX for a receive packet service. The packet sent in step #7 is received.
  11. The connection number obtained from the received packet, is used as described in step #3 and #4 to obtain internetwork and immediate addresses.
  12. The procedure sends a packet that is received by the outstanding request of the originating workstation generated in step #6.
  13. The screen at both workstations present two viewports corresponding to the users involved in the conversation. Characters typed at one workstation, are echoed at the remote viewport and vice versa, until one of the users presses Esc to terminate the conversation.

Some Special Considerations

The message sent by Phone to notify a user that a call is in progress produces the same effect as the Send command. Therefore, if you're running an application that can't be interrupted, you'll need to use the Castoff command to reject a phone's notification messages. (Alternatively, use Caston to enable the messages.)

Users connected to the network and running graphical applications won't receive the utility's notification messages when being called. Consequently, the phone utility can't be used to establish a dialogue with these users. However, anyone using Windows 3 can use Phone without any problem because Windows supports Novell Netware's broadcast messages. These users can create a PIF for the phone utility, and include it in their preferred applications group.

Figure 1: IPX packet fomat.

Table 1:IPX header format (* indicates filling is required).

Name              Type               Description
Checksum          Word               Obsolete field inherited from XNS           
                                       protocol. Always contains FFFFh.
Length            Word               Contains length of IPX header + data in packet.
Transport-        Byte               Initial value of 00h and is incremented as the
                                       Control packet passes one router. When packet 
                                       reaches its destination, this field counts number of routers
                                       packet passed through.
Packet Type*      Byte               Describes packet type. For example 04h is used for
                                       an IPX packet and 05h for a SPX packet.
Destination Net*  Array [1..4] Byte  Destination network.
Destination       Array [1..6] Byte  48-bit address of destination node.
DSocket*          Word               Destination socket.
Source Net*       Array [1..4] Byte  Source network.
Source Node*      Array              48-bit address of source node.
          
SSocket*          Word               Source socket.

Table 2:Event Control Block (* indicates filling is required).

Field Name              Type               Description
Link Address            Pointer            Used by IPX when queuing ECB.
Event Service Routine*  Pointer            Address of routine to be called when a packet arrives.
Status Flag             Byte               Provides status for ECB for each processing stage 
                                             (see Table 3).
Completion Code         Byte               Completion status code.  
Socket*                 Word               Socket for send/receive.
WorkSpace               Array [1..4] Byte  Reserved for IPX use.
Drive Work Space        Array [1..12] Byte Reserved for IPX use.
Immediate Address*      Array [1..6] Byte  Packet destination address. (It can be a routing node address.)
Fragment Count*         Word               Number of fragments composing packet.
Fragment Address*       Pointer            Address and Length of fragment.
Fragment Length*        Word
.
.
.
Fragment Address
Fragment Length

Table 3:ECB's status flag values.

Value   Description
00h     IPX finished processing ECB.
FAh     ECB being processed by IPX.
F8h     ECB is being queued by IPX.
FBh     Send/receive being processed into ECB.
FEh     Waiting for Incoming packets.
FFh     ECB is in use for sending a packet.

Table 4:ECB completion codes.

Value   Description
00h     Success.
FCh     Error, ECB was canceled.
FDh     Error, packet overflow.
FFh     Error, invalid socket in ECB.

Example 1: Detecting IPX presence.

function IpxPresent;
const
     MULTIPLEXER  = $2F;
     IPXINSTALLED = $FF;
begin
     regs.ax:=$7A00;
     intr(MULTIPLEXER,regs);
     if (regs.al = IPXINSTALLED) then IpxPresent:=TRUE
                                 else IpxPresent:=FALSE;
end;

Example 2: Polling ECB's status flag in send packet requests.

Procedure IpxSendPacket;
const
     IPX_SendPacket = $03;
begin
     regs.bx:=IPX_SendPacket;
     regs.es:=Seg(SendEcb);
     regs.si:=Ofs(SendEcb);
     IpxServicesCall;
     while (SendEcb.StatusFlag <> 0) do ;
end;

Example 3: ESR used by the Phone Utility

{$F+$S-}                   {Far Proc, No Stack Check }
Procedure EsrHandler;
begin
     inline($06            { push  es                }
           /$1f            { pop   ds                }
           );
     readflg:=true;
end;

Figure 2: Internetwork addresses



[LISTING ONE]


_A NETWARE CHAT UTILITY_
by Eduardo M. Serrat

Unit IpxUnit;

Interface

uses dos;
const
     IPX_PACKET_TYPE    =   4;
type
    NetWrkAdr = record
                  NetworkNumber      : array [1..4] of byte;
                  NodeAddress        : array [1..6]    of byte;
                end;

    IpxHeader = record
                  CheckSum           : word;
                  Len                : word;
                  TransportControl   : byte;
                  PacketType         : byte;
                  Destination        : NetWrkAdr;
                  DestinationSocket  : word;
                  Source             : NetWrkAdr;
                  SourceSocket       : word;
                end;
    ConNbrArr = record
                  Len                : word;
                  Count              : byte;
                  Connections        : array [1..250] of byte;
                end;
    ftype     = record
                  Adr                : pointer;
                  Len                : word;
                end;
    Ecb       = record
                  LinkAddress        : pointer;
                  EventServiceRoutine: pointer;
                  StatusFlag         : byte;
                  CompletionCode     : byte;
                  SocketNumber       : word;
                  WorkSpace          : array [1..4]  of byte;
                  DriverWorkSpace    : array [1..12] of byte;
                  ImmediateAddress   : array [1..6]   of byte;
                  FragmentCount      : word;
                  FragmentDescriptor : array [1..2] of ftype;
                end;
    ConnInfo  = record
                  Len                : word;
                  ObjectID           : array [1..4]  of byte;
                  ObjectType         : word;
                  ObjectName         : array [1..48] of byte;
                  LoginTime          : array [1..7]  of byte;
                  Reserved           : word;
                end;
    NetType   = array [1..4] of byte;
    NodType   = array [1..6]    of byte;
var
   regs           : registers;
   ipxrutofs,
   ipxrutseg      : word;
{--------------------------------------------------------------------------}
function LeadingZero(w:word) : String;
function Time : String;
procedure WriteHexByte(b : byte);

function  IpxPresent : boolean;
procedure IpxServicesCall;
function  IpxCreateSocket (Socket : word) : boolean;
function  LocalConnectionNumber : byte;
procedure IpxDeleteSocket (Socket : word);
procedure GetInternetAddress (ConnectionNbr : byte; var NetNod : NetWrkAdr);
procedure UserInfo (ConnectionNumber: byte; var ConnInfoRec : ConnInfo);
procedure GetConnections (UserName: string; var ConNbrRec : ConNbrArr);
procedure GetLocalTarget(DestNet : NetWrkAdr;
                         DestSock : word; var LocalTarget : NodType );
procedure SendMessage(ConnectionNumber : byte; Message : String);
Procedure IpxSendPacket(var SendEcb : Ecb);
Procedure IpxReadPacket(var ReadEcb : Ecb);

Implementation

{--------------------------------------------------------------------------}
function LeadingZero;
var
   s : String;
begin
     Str(w:0,s);
     if Length(s) = 1 then
        s := '0' + s;
        LeadingZero := s;
end;
{--------------------------------------------------------------------------}
function Time;
var
    h, m, s, hund : Word;
begin
     GetTime(h,m,s,hund);
     Time:=LeadingZero(h)+':'+LeadingZero(m)+':'+LeadingZero(s);
end;
{--------------------------------------------------------------------------}
procedure WriteHexByte;
const
     hexChars : array [0..$F] of Char =
                '0123456789ABCDEF';
begin
     Write(hexChars[b shr 4],
           hexChars[b and $F]);
end;

{--------------------------------------------------------------------------}
function IpxPresent;
const
     MULTIPLEXER  = $2F;
     IPXINSTALLED = $FF;
begin
     regs.ax:=$7A00;
     intr(MULTIPLEXER,regs);
     if (regs.al = IPXINSTALLED) then IpxPresent:=TRUE
                                 else IpxPresent:=FALSE;
end;
{--------------------------------------------------------------------------}
procedure IpxServicesCall;
begin
     intr($7a,regs);
end;
{--------------------------------------------------------------------------}
function IpxCreateSocket;
const
     IPX_CreateSocket = $00;
     PermanentSocket  = $FF;
     TemporarySocket  = $00;
var
   SwapSocket    : word;
begin
     SwapSocket:=swap(Socket);
     regs.al:=TemporarySocket;
     regs.bx:=IPX_CreateSocket;
     regs.dx:=SwapSocket;
     IpxServicesCall;
     if (regs.al = $00) then IpxCreateSocket:=TRUE
                        else IpxCreateSocket:=FALSE;
                        {0FEh Full Socket Table
                         0FFh Socket Already Opened}
end;
{--------------------------------------------------------------------------}
procedure IpxDeleteSocket;
const
     IPX_DeleteSocket             = $01;
var
     SwapSocket    : word;
begin
     SwapSocket:=swap(Socket);
     regs.bx:=IPX_DeleteSocket;
     regs.dx:=SwapSocket;
     IpxServicesCall;
end;
{--------------------------------------------------------------------------}
function LocalConnectionNumber;
const
     GET_CONNECTION_NUMBER = $DC;
begin
     regs.ah:=GET_CONNECTION_NUMBER;
     regs.al:=$00;
     msdos(regs);
     LocalConnectionNumber:=regs.al;
end;
{--------------------------------------------------------------------------}
procedure  GetInternetAddress;
const
     GET_INTERNET_ADDRESS = $13;
     NETWARE_SERVICE_E3   = $E3;

var
   ReqBlk   : record
                Len       :  word;
                ReqType   :  byte;
                ConnNbr   :  byte;
              end;
   ResBlk   : record
                Len       :  word;
                NetNod    :  NetWrkAdr;
                SrvSocket :  word;
              end;
begin
     with ReqBlk do
       begin
            Len:=sizeof(ReqBlk) - sizeof(Len);
            ReqType:=GET_INTERNET_ADDRESS;
            ConnNbr:=ConnectionNbr;
       end;

     with ResBlk do Len:=sizeof(ResBlk) - sizeof(Len);

     regs.ah:=NETWARE_SERVICE_E3;
     regs.ds:=seg(ReqBlk); regs.si:=ofs(ReqBlk);
     regs.es:=seg(ResBlk); regs.di:=ofs(ResBlk);
     msdos(regs);
     if regs.al <> $00 then writeln('Error GETINTERNETADDRESS...')
     else
       begin
            NetNod.NetworkNumber:=ResBlk.NetNod.NetworkNumber;
            NetNod.NodeAddress:=  ResBlk.NetNod.NodeAddress;
       end;
end;
{--------------------------------------------------------------------------}
procedure UserInfo;
const
     GET_CONNECTION_INFORMATION   = $16;
     NETWARE_SERVICE_E3           = $E3;
var
   ReqBlk   :  record
                Len    :  word;
                ReqType   :  byte;
                ConnNbr   :  byte;
               end;
begin
     with ReqBlk do
       begin
            Len :=sizeof(ReqBlk) - sizeof(Len);
            ReqType:=GET_CONNECTION_INFORMATION;
            ConnNbr:=ConnectionNumber;
       end;
     with ConnInfoRec do Len:=sizeof(ConnInfoRec) - sizeof(Len);
     regs.ah:=NETWARE_SERVICE_E3;
     regs.ds:=seg(ReqBlk);       regs.si:=ofs(ReqBlk);
     regs.es:=seg(ConnInfoRec);  regs.di:=ofs(ConnInfoRec);
     msdos(regs);
end;
{--------------------------------------------------------------------------}
procedure GetConnections;
const
     GET_OBJECT_CONNECTION_NUMBERS= $15;
     USER_BINDERY_OBJECT_TYPE     = $0001;
     NETWARE_SERVICE_E3           = $E3;
var
   ReqBlk    :  record
                  Len          : word;
                  RequestType     : byte;
                  ObjectType      : word;
                  NameLength      : byte;
                  Name            : array [1..48] of byte;
                end;
   swapbind  :  word;
   i         :  integer;
begin
     swapbind:=swap(USER_BINDERY_OBJECT_TYPE);
     with ReqBlk do
       begin
            Len:=sizeof(ReqBlk) - sizeof(Len);
            RequestType:=GET_OBJECT_CONNECTION_NUMBERS;
            ObjectType:=SwapBind;
       end;
     ReqBlk.NameLength:=Length(UserName);
     for i:=1 to ReqBlk.NameLength do ReqBlk.Name[i]:=ord(UserName[i]);

     with ConNbrRec do Len:=sizeof(ConNbrRec) - sizeof(Len);
     regs.ah:=NETWARE_SERVICE_E3;
     regs.ds:=seg(ReqBlk);    regs.si:=ofs(ReqBlk);
     regs.es:=seg(ConNbrRec); regs.di:=ofs(ConNbrRec);
     msdos(regs);
     if regs.al <> 0 then ConNbrRec.Count:=0;
end;
{--------------------------------------------------------------------------}
procedure GetLocalTarget;
const
     IPX_GetLocalTarget           = $02;
var
   ReqBlk     :  record
                  Dnetwork  : NetWrkAdr;
                  DSocket   : word;
                 end;
   ResBlk     :  record
                   Ltarget  : NodType;
                 end;
   swapsocket :  word;
begin
     swapsocket:=swap(DestSock);
     ReqBlk.Dnetwork:=DestNet;
     ReqBlk.DSocket :=swapsocket;

     regs.bx:=IPX_GetLocalTarget;
     regs.es:=seg(ReqBlk);
     regs.si:=ofs(ReqBlk);
     regs.di:=ofs(ResBlk);

     IpxServicesCall;

     if regs.al = $00 then LocalTarget:=ResBlk.Ltarget;
                  {0FAh No path to Destination}
end;
{--------------------------------------------------------------------------}
procedure SendMessage;
const
     USER_BINDERY_OBJECT_TYPE     = $0001;
     NETWARE_SERVICE_E1           = $E1;
var
   ReqBlk     :  record
                  Len       : word;
                  Bindery   : word;
                  ConnNbr   : byte;
                  Mlen      : byte;
                  Mens      : array [1..45] of byte;
                 end;
   ResBlk     :  record
                   Len      : word;
                   Filler   : array [1..100] of byte;
                 end;
   i          :  integer;
begin
     with ReqBlk do
        begin
          Bindery:=swap(USER_BINDERY_OBJECT_TYPE);
          ConnNbr:=ConnectionNumber;
          Mlen:=Length(Message);
          Len:=Mlen + 4;
          for i:=1 to Mlen do mens[i]:=ord(message[i]);
        end;

     ResBlk.Len:=$6400;

     regs.ah:=NETWARE_SERVICE_E1;
     regs.ds:=seg(ReqBlk);  regs.si:=ofs(ReqBlk);
     regs.es:=seg(ResBlk);  regs.di:=ofs(ResBlk);
     msdos(regs);
end;

{--------------------------------------------------------------------------}
Procedure IpxSendPacket;
const
     IPX_SendPacket               = $03;
begin
     regs.bx:=IPX_SendPacket;
     regs.es:=Seg(SendEcb);
     regs.si:=Ofs(SendEcb);
     IpxServicesCall;

     while (SendEcb.StatusFlag <> 0) do ;
end;
{--------------------------------------------------------------------------}
Procedure IpxReadPacket;
const
     IPX_ReceivePacket               = $04;
begin
     regs.bx:=IPX_ReceivePacket;
     regs.es:=Seg(ReadEcb);
     regs.si:=Ofs(ReadEcb);
     IpxServicesCall;
     if regs.al <> $00 then
       begin
          writeln('Error Read Packet ');
          WriteHexByte(Regs.al);
       end;
                  {0ffh NonExistant socket}
end;
{--------------------------------------------------------------------------}
{--------------------------------------------------------------------------}
begin
end.





Copyright © 1993, Dr. Dobb's Journal

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.