VIPX.386 Description, Limitations and Configuration Document Current Version: 1.17 Date: 931118 Table of Contents ----------------- I. Description II. Limitations A. Windows 3.X Versions B. Version Compatibility with Dedicated IPX C. Packet Size Limitations D. Memory Manager Limitations III. System Settings in the SYSTEM.INI File A. [386Enh] Section 1. TimerCriticalSection B. [VIPX] Section 1. VipxMappingPages 2. VipxFailOverSizedPackets 3. VpicdFix 4. AutoIrqVirtualize 5. VirtualizeIrq[0-F] 6. VipxErrorMessages 7. VipxWarningMessages 8. VipxOutDebugStrOnErrors 9. VipxOutDebugStrOnWarnings 10. VipxBreakOnErrors 11. VipxBreakOnWarnings IV. Programming to the VIPX Interface A. Getting the VIPX API Entry Point B. Memory Management Issues C. APIs Specific to VIPX.386 D. Handling Reentrancy Issues E. Using NWIPXSPX.DLL V. Global DOS TSRs using IPX/SPX I. Description --------------- VIPX.386 is a Windows 3.X virutalization driver for IPXODI.COM driver. It virtualizes requests to the globally loaded IPX driver. When a request is made to IPX, VIPX will allocate a request buffer in the system's global memory, copy the original request to the global buffer and give the global request to IPX. When the global request completes, IPX will call VIPX. VIPX will then copy any results back to the original request buffer and call the application which submitted the request. II. Limitations ---------------- II.A. Windows Versions ----------------------- VIPX supports Windows versions 3.0, 3.0a and 3.1. However, there is some functionality which VIPX will only support in Windows 3.1. This is LAN IRQ virtualization for avoiding deadlocks. Under WIndows 3.0 and 3.0a, VIPX will not virtualize LAN IRQs. See section III.B.5 VirtualizeIrq[0-F]. A patch has been developed by Microsoft for Windows 3.1. The patch is VTDA.386 found in WW0863.EXE on MicroSoft's BBS. The BBS phone number is (206)936-6735. Microsoft found a bug in the their Virtual Timer Device (VTD) driver which may cause a deadlock with the NetWare shell (NETX). The deadlock occurs as a result of the shell waiting for the timer tick to advance. When an NCP is sent, the shell will start it's retry timeout countdown. This is essentially a loop waiting for the timer tick value to advance to a certain point. The deadlock occurs when the tick value never increments and thus the shell is left in an infinite loop. The reason the tick count is never incremented is that the timer interrupt is not allowed to be simulated into V86 mode under certain circumstances. The patch to the VTD allows the timer interrupts to be simulated. II.B. Version Compatibility with Dedicated IPX ----------------------------------------------- Novell offically ceased maintenence on the dedicate IPX driver (IPX.OBJ) verion 3.10 dated 11-21-91. The last version of VIPX to explicitly support that driver is 1.10. The current version of VIPX supports IPXODI.COM only. It is strongly recommended that you update your dedicated IPX driver with IPXODI.COM when using versions of VIPX later than 1.10. II.C. Packet Size Limitations ------------------------------ VIPX.386 will only virtualize packets which are 8000 (decimal) bytes or less. Any DOS and Windows IPX or SPX applications which use networks with a physical packet size greater than 8000 bytes may not work with VIPX.386. For example, IBM Token Ring can be configured to run with 16K packets. A request by a DOS or Windows IPX application to send a 16K packet will be truncated to 8000 bytes. On the other hand, any 16K packet that is received by the LAN driver will be dropped because VIPX cannot allocate a packet big enough to handle it. II.D. Memory Manager Limitations --------------------------------- When an request is passed up from IPX, VIPX will immediately test the request buffer to see if it is in global memory already. If it is in global memory, the request will be passed back down to IPX without any virtualization. For example, a TSR loaded before Windows is considered to be in global memory. If that TSR calls IPX, VIPX will test the requests and pass them back down to IPX. This is done because there is no need to virtualize requests which are already global. The use of UMB memory complicates the test for global memory. There are two basic senarios. Senario One is when a TSR has been loaded high before Windows was loaded. In this case, IPX requests will come from global UMB memory. VIPX will simply pass these requests back down to IPX. Senario Two is when a TSR is loaded high in a Windows DOSBOX. In this case, IPX requests will come from local UMB memory. VIPX will virtualize these requests. Using HIMEM.SYS and EMM386.SYS, all tests for global UMB memory will work properly under both of senarios. However, there are some memory managers that do not work properly under Windows. With these drivers, all LOCAL DOSBOX UMBs look as if they are GLOBAL UMBs. Under Senario Two, when a TSR calls IPX, VIPX will test the request buffer and think that it is in global UMB memory when it is really in LOCAL UMB memory. As a result, VIPX will pass a local ECB to IPX without virtualization. The normal result of this is a hung machine or data corruption. III. System Settings in the SYSTEM.INI File -------------------------------------------- III.A. [386Enh] Section ------------------------ III.A.1. TimerCriticalSection ------------------------------ As of version 1.15 of VIPX, TimerCriticalSection is required to be set on. The recommended setting is as follows: [386Enh] TimerCriticalSection=10000 The reason for this parameter is to avoid a deadlock with the LAN IRQ Virtualization code. See section III.B.5 VirtualizeIrq[0-F]. III.B. [VIPX] Section ------------------------ Under most circumstances, VIPX will work fine under the default configuration. However, there may be some applications which require custom configuration of the driver. This is a list of SYSTEM.INI parameters which can be used to configure VIPX: [VIPX] VipxMappingPages =[number of 4K pages] (default = 16) VipxFailOverSizedPackets =[ON|OFF|TRUE|FALSE] (default = OFF) VpicdFix =[ON|OFF|TRUE|FALSE] (default = ON) AutoIrqVirtualize =[ON|OFF|TRUE|FALSE] (default = ON) VirtualizeIrq[0-F] =[ON|OFF|TRUE|FALSE] (default = OFF) (BELOW ARE CONFIGURATION PARAMETERS FOR BETA VERSIONS ONLY!) VipxErrorMessages =[ON|OFF|TRUE|FALSE] (default = OFF) VipxWarningMessages =[ON|OFF|TRUE|FALSE] (default = OFF) VipxBreakOnErrors =[ON|OFF|TRUE|FALSE] (default = OFF) VipxBreakOnWarnings =[ON|OFF|TRUE|FALSE] (default = OFF) VipxOutDebugStrOnErrors =[ON|OFF|TRUE|FALSE] (default = OFF) VipxOutDebugStrOnWarnings =[ON|OFF|TRUE|FALSE] (default = OFF) III.B.1. VipxMappingPages -------------------------- This is the number of pages that VIPX can use to globalize requests to the global IPXODI.COM driver. VIPX is not absolutely guaranteed to have all of these pages available at any one point, since this is the requested number of pages for SHARED global mapping which VIPX makes to the Windows VMM at initialization time. III.B.2. VipxFailOverSizedPackets ---------------------------------- This parameter tells VIPX to fail any requests which require more than the maximum allowed globalization size. The actual maximum will vary according to which media the user is on. The absolute maximum is 8000 (decimal) bytes. With media that have smaller packets than 8000 bytes, the maximum allowed size is the maximum packet size that can be put onto the media. III.B.3. VpicdFix ------------------ III.B.4. AutoIrqVirtualize --------------------------- VpicdFix and AutoIrqVirtualize are synonyms for the same option. The VPICD VxD has a problem of allowing an interrupt to be simulated to a VM which previously held the Windows Critical Section but currently does not. (For more details, see III.B.5. VirtualizeIrq[0-F]). This causes a deadlock situation when LAN I/O is attemped in another VM. To get around the problem, VIPX will take over the job of virtualizing LAN IRQs and will simulate LAN interrupts only to the owner of the Windows Critical Section. This option is used to turn on or off the automatic detection and virtualization of LAN IRQs when ODI LAN drivers or dedicated LAN drivers are loaded. The reason for this option is to allow OEMs to virtualize their LAN drivers with their own VxDs. III.B.5. VirtualizeIrq[0-F] ---------------------------- This is option is automatically configured excepted on machines using the IBM LAN Support Program. This parameter tells VIPX to virtualize the specified (hex) Interrupt ReQuest (IRQ) line. This feature is mainly designed to assist the IBM LAN Support Program in avoiding the "Black Screen of Death". Because of a design flaw in the VPICD.386 driver, network interrupts will sometimes deadlock a PC running Windows 3.X. VIPX 1.15 and later attempts to avoid the deadlock by virtualizing the Network Interface Card's (NIC) IRQ(s). With ODI and dedicated IPX (IPX.OBJ) drivers, VIPX will automatically read the configuration of the NIC from the driver and virtualize the selected IRQs. The only time that VIPX cannot automatically detect the LAN IRQs is when using the IBM LAN Support Program with either SLANSUP.OBJ or LANSUP.COM. In this case, the LAN IRQ is not readable from the driver. The reason (so I am told) is that IBM LAN Support does not have an API to read the IRQ setting on the NIC: The only way to get this information is to read the NIC hardware itself. The problem with doing this is that the hardware can be Token Ring, PCN2 or Ethernet. This means that VIPX would have to be aware of all of the different configurations of all of these hardwares. Instead of this, VIPX requires the IBM LAN Support user to specify the NIC IRQ in the [VIPX] section of the SYSTEM.INI. IRQs range from 0 to F (hex). An example is listed below: [VIPX] VirtualizeIrq2=TRUE VirtualizeIrq3=TRUE In this example, VIPX will virtualize both IRQ 2 and IRQ 3. VIPX can virtualize upto 4 different LAN IRQs. The reason for virtualizing multiple IRQs is to allow other LAN cards and protocols to be installed on the same PC and prevent them from deadlocking the machine. For example, you may have IPX running through an NE2000 card on IRQ 3 and TCP/IP running through to an IBM Token Ring card on IRQ 2. One side note. On an AT class PC, IRQ 2 is is really IRQ 9 on the physical machine. IRQ 2 is a special cascade interrupt used to access IRQs 9-F. Whenever a NIC raises the IRQ 2 line, the hardware configuration on the PC's bus actually issues an IRQ 9. Also, if a NIC is configured to IRQ 2, the LAN driver during its initialization procedure will detect that the PC is an AT class machine and will correctly configure its software to use IRQ 9. Getting to the point, the Windows VPICD VxD will not allow IRQ 2 to be exclusively virtualized by any VxD. However, when VIPX detects that IRQ 2 is to be virtualized, it changes the IRQ to 9. So if you are using the IBM LAN Support Program on an IBM Token Ring NIC set to IRQ 2, you should have not trouble. The following two configurations are equivalent: [VIPX] VirtualizeIrq2=TRUE or [VIPX] VirtualizeIrq9=TRUE One important note when using VIPX 1.15 or higher: Set the TimerCriticalSection under the [386Enh] section in the SYSTEM.INI as follows: [386Enh] TimerCriticalSection=10000 The reason for this is that the timer interrupt can cause a deadlock with the virtualization of the the LAN interrupt. You should note that the VirtualizeIrq[0-F] can be used to override any auto-virtualization of LAN IRQs. For example, if your ODI LAN driver is configured for IRQ 5, VIPX will automatically detect that IRQ 5 is to be virtualized. However, if you have [VIPX] VirtualizeIrq5=FALSE in your SYSTEM.INI, VIPX will override the automatic detection of IRQ 5 and not virtualize the interrupt. Another way to disable the auto-virtualization feature of VIPX is with the VpicdFix or AutoIrqVirtualize parameters (See III.B.3. VpicdFix or III.B.4 AutoIrqVirtualize). III.B.6. VipxErrorMessages --------------------------- III.B.7. VipxWarningMessages ----------------------------- III.B.8. VipxOutDebugStrOnErrors --------------------------------- III.B.9. VipxOutDebugStrOnWarnings ----------------------------------- III.B.10. VipxBreakOnErrors --------------------------- III.B.11. VipxBreakOnWarnings ----------------------------- These parameters are only enabled in Beta versions of VIPX. With Beta software, the user can enable/disable these parameters to make VIPX print error/warning messages to the user screen and debugger screen. They can also instruct VIPX to execute a break point whenever an error/warning occurs so that debugging can be done on site. IV. Programming to the VIPX Interface -------------------------------------- IV.A. Getting the VIPX API Entry Point --------------------------------------- VIPX supports API calls from 16-bit DOS and Windows applications. This is the interface which NWIPXSPX.DLL uses to issue calls to VIPX. To get the VIPX Device V86/PM API Entry Point, a 16-Bit DOS/Windows application must do the following: DWORD VIPX_API_ADDR; #define WIN_GET_DEV_API_ENTRY_PT 0x1684 #define VIPX_DEVICE_ID 0x0200 GetVipxApiAddr () { _asm { mov ax, WIN_GET_DEV_API_ENTRY_PT mov bx, VIPX_DEVICE_ID int 2fh mov word ptr VIPX_API_ADDR+2, ES mov word ptr VIPX_API_ADDR, DI } if (!VIPX_API_ADDR) { print error - NO PM API! RIGHT VERS OF VIPX? handle error } } Once the API address is obtained, an application then can make a far call to the VIPX API Handler to do two primary functions: 1. Request an 16-Bit PM IPX/SPX operation (SendPacket, etc...). 2. Request a device specific operation (GetVersion, GetDiagnotics). The way that VIPX differentiates between the two types of APIs is by the value in the BH register. If BH equals 00h, then VIPX will handle a 16-Bit Protected Mode IPX/SPX operation. NOTE: The V86 IPX/SPX API interface (i.e. DOS application) is done through the globally loaded IPX TSR itself. This way, DOS applications do not need to change the way they access the IPX/SPX APIs under Windows and DOS. If BH equals the character 'V' (56h), then VIPX will will perform a V86/PM Device Specific API. These APIs are listed below (and will be presented in full specification later): GetVipxVersion GetVipxDiagnostics For IPX/SPX operations (BH = 00h), a 16-bit Windows program must follow the IPX/SPX API specification in the NetWare System Calls-DOS manual under the Communications chapter. Coverage of the DOS IPX/SPX APIs is beyond the scope of this document. However, an example is provided below for reference: DWORD VIPX_API_ADDR; int ccode; CallPmIpxSpxApi () { _asm { mov bx, (IPX operation number) (set other registers as specified by the function) call VIPX_API_ADDR (save return registers as specified by the function) ErrorCondition = (function error condition) } if (ErrorCondition) { print error - function failed! handle error } } IV.B. Memory Management Issues ------------------------------- One problem with using the IPX/SPX interface in Windows applications is that all memory used by VIPX must be page-locked. Since VIPX is an asynchronous driver, all application ECBs, fragments and ESRs must be in locked memory to insure that they can be accessed during interrupt time. If an ECB, fragment or ESR is in moveable memory, it could move when VIPX "owns" that memory. For example, assume an ECB is located at 16-bit PM address 0A10:0000 in moveable memory. Assume this address tranlates to 32-bit linear address 8140ACB0. Assume also that the application gives the ECB to VIPX and VIPX commences to work on it. Now if Windows does garbage collection, the linear mapping for the 16-bit address 0A10:0000 may change from 8140ABC0 to 81408100. When VIPX tries to access the old linear address 8140ABC0, memory corruption will occur because VIPX is accessing the wrong memory. To avoid memory corruption, follow the guidelines below (your comments on the effectiveness of these guidelines are appreciated): 1. Read Chapter 15 (Memory Management) and 16 (More Memory Management) of the MS Windows SDK Version 3.1. These will explain many terms and concepts which will be discussed below. 2. Define all ESRs, routines called by ESRs and data accessed by ESRs in one module. In the .DEF file for your project, define the module's code and data segments as FIXED. 3. Use GlobalPageLock() on all dynamically allocated heap memory (via GlobalAlloc() or LocalAlloc()) which is to be used by VIPX or an ESR. GlobalPageLock() informs the GUI kernel memory manager to fix the linear base of the selector until a GlobalPageUnlock() is done on the memory. 4. When using GlobalAlloc(), use the GMEM_DDESHARE and GMEM_FIXED options. GMEM_DDESHARE will allocate the memory (so I am told) high up on the global heap. This will avoid using conventional memory and will allow room for other Windows applications to load after your application has been loaded and initialized. The GMEM_FIXED option tells the GUI kernel memory manager that the memory is FIXED. This does not mean that the linear address will remain unchanged, but it does mean that the segmented address (selector:offset) will not change. After the call to GlobalAlloc(), call GlobalLock() to derefernce the global memory handle. This provides a far pointer (selector:offset) to the memory which will not change. Next call GlobalPageLock() to fix the linear base of the selector so that the 32-bit linear mapping of the selector:offset does not change. 5. If local moveable or discardable code or data segments are used for ECBs, fragments or ESRs, use GlobalPageLock() on those segments. 6. After you load your application and then you get a message from Windows that there are not enough resources to load another program, try changing the attributes of your segments. I believe this condition occurs because too much conventional memory has been allocated to fixed memory objects and Windows needs to allocate conventional memory to load a program. One strategy may be to do a GlobalCompact() with a argument of -1 at initialization time. This will move all moveable segments and discard all discardable segments. Next call GlobalAlloc() for one large moveable global object which contains all of your ECBs, fragments and data which must be accessed from VIPX or your ESRs. Next call GlobalLock() to dereference the global handle. Last call GlobalPageLock() to fix the linear address of the object. (I have not tried this yet, so there may be some problems with it). Also make your ESR code as small as possible using PostMessage to indicate to your application that an ECB has completed. This way, your FIXED code segment will have little impact on conventional memory. 7. Read Chapter 4 of The Windows Programmer's Guide to DLLs and Memory Management by Mike Klein. IV.C. APIs Specific to VIPX.386 -------------------------------- There are two APIs via the VIPX API entry point which are specific to VIPX.386 only. These are: GetVipxVersion GetVipxDiagnostics The device specific APIs are called in the same manner as the IPX/SPX APIs except that BH = 56h. Some sample code is listed below: DWORD VIPX_API_ADDR; #define VIPX_DEVICE_API_TAG 'V' int ccode; CallVipxDeviceSpecificApi () { _asm { mov bh, VIPX_DEVICE_API_TAG mov bl, api_function_number (set other registers for API function) call VIPX_API_ADDR mov ccode, ax } if (!ccode) { print error - api function failed! handle error } } The full API specification for GetVipxVersion is as follows: GetVIPXVersion This procedure puts the VIPX major and minor version numbers into the client AH and AL. To call this API a DOSBOX or Windows 3.0 application must first get the VIPX Device API entry point (see VIPX_V86_API_Handler or VIPX_PM_Handler). Then it must do the following: Example: DWORD VIPX_API_ADDR; BYTE VIPX_Major_Version; BYTE VIPX_Minor_Version; #define VIPX_DEVICE_API_TAG 'V' #define VIPX_GET_VERSION 0x00 int ccode; main () { _asm { mov bh, VIPX_DEVICE_API_TAG mov bl, VIPX_GET_VERSION call VIPX_API_ADDR mov ccode, ax mov VIPX_Major_Version, bh mov VIPX_Minor_Version, bl } if (!ccode) { print error - api function failed! handle error } } Entry: BH 056h - VIPX Device API BL 0 - Get VIPX Version function Exit: BH VIPX Major Version BL VIPX Minor Version AX Return code 0000h - success Uses: AX, BX, Flags The full API specification for GetVIPXDiagnostics is as follows: GetVIPXDiagnostics This procedure puts the diagnostics for VIPX into the specified client diagnostic buffer. The buffer is 112 bytes and has the following structure: VIPXDiagStruc STRUC MallocCount dd ? MallocErrorCount dd ? FreeCount dd ? AllocPageCount dd ? FreePageCount dd ? PMRequestCount dd ? TSRServiceCount dd ? ServiceAESEventCount dd ? ServiceIPXEventCount dd ? PostEventCount dd ? IPXGetECBCount dd ? IPXGetECBBadPacketCount dd ? IPXGetECBOutOfResource dd ? IPXCountECBCount dd ? IPXReturnECBCount dd ? IPXRequestCount dd ? FreeECBCount dd ? AllocECBCount dd ? LockECBCount dd ? UnlockECBCount dd ? OpenSocketCount dd ? CloseSocketCount dd ? SendPacketCount dd ? ListenCount dd ? IPXScheduleEventCount dd ? AESScheduleEventCount dd ? CancelECBCount dd ? PMInt2FCount dd ? AllocPageErrorCount dd ? FreePageErrorCount dd ? FreeECBErrorCount dd ? MaxVipxAllocSize dd ? MaxVipxFragsSize dd ? TsrServiceReenterCnt dd ? IPXGetEcbSktNotOpenCnt dd ? IPXGetEcbNoEcbAvailCnt dd ? ENDS NOTE: Starting with VIPX 1.16, MaxVipxAllocSize, MaxVipxFragsSize, and TsrServiceReenterCnt have been updated to DWORDs. Also IPXGetEcbSktNotOpenCnt and IPXGetEcbNoEcbAvailCnt have been added. To call this API a DOSBOX or Windows 3.0 application must first get the VIPX Device API entry point (see VIPX_V86_API_Handler or VIPX_PM_Handler). Then it must do the following: Example: DWORD VIPX_API_ADDR; VIPXDiagStruc Diag; #define VIPX_DEVICE_API_TAG 'V' #define VIPX_GET_DIAGNOSTIC 0x01 int ccode; main () { _asm { mov bh, VIPX_DEVICE_API_TAG mov bl, VIPX_GET_DIAGNOSTIC mov ax,ds mov es,ax mov di, offset Diag mov cx, size VIPXDiagStruc call VIPX_API_ADDR mov ccode, ax } if (!ccode) { print error - api function failed! handle error } } Entry: BH 056h - VIPX Device API BL 1 - Get VIPX Diagnostics ES:SI ptr to client VIPX Diagnostics buffer. CX Length of diagnostics buffer in bytes Exit: ES:SI client's buffer now has VIPX diags. AX Return code 0000h - success 0FFFEh - diagnostics buffer too short Uses: AX, SI, ES, Flags IV.D. Handling Reentrancy Issues --------------------------------- A Windows application which uses the IPX/SPX APIs via VIPX must handle reentrancy issues. Since the IPX/SPX API is interrupt driven, any code running while interrupts are enabled can (and will) be interrupted at any time by IPX/SPX. When an IPX event completes, an ESR for that event is called. The ESR is entered with interrupts disabled. If that ESR reenables interrupts, then it can be reentered. This is usually a problem if: 1. The application has limited stack space 2. The application's ESR uses global variables An ESR must protect itself from these two reentrancy issues if it reenables interrupts. Interrupts can be reenabled by several different places. One of the most notorious is the PostMessage() Windows call. Any ESR using PostMessage() must be reentrant. Another notorious enabler of interrupts is IPXSendPacket. IPXSendPacket will call the LSL to transmit a packet. The LSL, however, will reenable interrupts during transmission of a packet. This can cause reentrancy into any ESR that calls IPXSendPacket. An ESR can handle reentrancy issues by 1. Switching to a stack exclusively for the ESR to use. 2. Provide a reetrancy queue. The stack switching is usually done in assembly. It is by far the easiest and most efficient way to protect your stack. The code for stack switching is listed below: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ memL equ 1 ; large model ?PLM = 1 ; pascal conventions ?WIN = 1 ; epilogue/prolog code INCLUDE cmacros.inc sBegin DATA assumes DS, DATA ReenterCnt db 0 OldSs dw 0 OldSp dw 0 EsrStack db 4096 dup (0) EndEsrStack label byte sEnd DATA sBegin CODE assumes CS, CODE ; ; This is the front-end procedure to the C function "ReceiveESR" ; externFP cESR PUBLIC aESR aESR proc FAR nop nop nop nop ; Don't know why Windows needs to inc bp inc bp push bp mov bp, sp ; Save data segment push ds ; Fix to our data segmented mov ax, DGROUP mov ds, ax ; Update our reenter cnt inc ReenterCnt ; Do we need to switch stack to ESR stack? cmp ReenterCnt, 2 ;are we reentering? jae CallEsr ;y: call esr mov ax, ss cmp ax, DGROUP ;same stack seg? jne SwitchStack ;n: switch lea ax, EsrStack ;ax=offset of EsrStack cmp sp, ax ;below the esr stack? jb SwitchStack ;y: switch lea ax, EndEsrStack ;ax=offset of end of EsrStack cmp sp, ax ;above the esr stack jae SwitchStack ;y: switch ; Call the esr CallEsr: push es push si cCall cESR lea sp, [bp-2] ; Update reenter cnt dec ReenterCnt ; Switch stacks back? jz SwitchStackBack ; return to IPX EsrExit: ; restore ds and bp pop ds pop bp dec bp ret SwitchStack: mov ax, sp mov OldSp, ax mov ax, ss mov OldSs, ax lea ax, EndEsrStack mov sp, ax mov ax, ds mov ss, ax jmp CallEsr SwitchStackBack: mov ax, OldSp mov sp, ax mov ax, OldSs mov ss, ax jmp EsrExit aESR endp sEnd CODE end ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The above code switched to a 4K stack before calling the main ESR code. Notice that there is a reentrancy count to determine if a stack switch should occur. This is used to prevent switching the stack again if the ESR is reentered. The main ESR code is written in C. An example is listed below: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void far pascal cESR(ECB far *esr_ecb) { static ECB FAR * EcbQueue; static ECB FAR * EcbQueueTail = (ECB FAR *)&EcbQueue; static WORD ReenterFlag = 0; // Check for reentrancy if (ReenterFlag) { // Enqueue the ECB EcbQueueTail->Link = (void far *)esr_ecb; EcbQueueTail = (ECB far *)esr_ecb; esr_ecb->Link = NULL; return; } // Set the reentrany flag to indicate that we can't allow // another thread through the ESR. ReenterFlag++; // Process the current ECB NextEcb: // Do some work here if need be -- BUT BE QUICK! ... // Post a message to the Windows App that the event happened PostMessage ( hMainWnd, USER_DEFINED_IPX_EVENT_HAPPPENED, 0, (DWORD) esr_ecb); // Check for anything on the queue CheckQueue: if (EcbQueue) { esr_ecb = EcbQueue; EcbQueue = (ECB FAR *)(EcbQueue->Link); if (!EcbQueue) EcbQueueTail = (ECB FAR *)&EcbQueue; goto NextEcb; } // Reset the reentrancy flag to indicate that the // first thread is done ReenterFlag--; } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ One side note about the code above. PostMessage() will queue a message onto an applications message queue. This queue is limited in size. If the application is fairly LAN I/O intensive, the message queue can be overrun. In this case, PostMessage() will simply drop the message. If this happens, your application will never get notified of the event. To insure that messages are never dropped, you may want to increase the size of your message queue. Another alternative is to have the message indicate that an event happened and the message loop will then poll all ECBs to determine which ones completed. This way, if a message is dropped, it won't matter: the main message loop will still detect that the ECB completed and will be able to reuse the ECB. IV.E. Using NWIPXSPX.DLL ------------------------- Most Windows applications will use the IPX/SPX APIs via the NWIPXSPX.DLL. The reason is that NWIPXSPX.DLL address the issues for both Standard and Enhanced mode Windows. Under Standard mode, NWIPXSPX will use TBMI2.COM to virtualize the local ECBs passed to it. Under Enhanced mode, NWIPXSPX.DLL will use VIPX.386 to virtualize ECBs. If your Windows application is targeted toward Enhanced mode only, then you may find it more convenient to program directly to the VIPX interface in assembly. However, if you are interested in using an established C interface for IPX/SPX, then NWIPXSPX.DLL is the way to go. There is example code using NWIPXSPX.DLL on the Compuserve NOVDEV forum. V. Global DOS TSRs using IPX/SPX --------------------------------- TSRs loaded before Windows can call IPX without the intervention of VIPX. This is done by ORing the BX register with 8000h (i.e. setting the high-bit of the function number). This is a previously undocumented feature in IPX to support the globally loaded NETX driver. When IPX sees that a request has the high-bit set, it will assume that the request came from a globally loaded TSR and will not forward the request to VIPX. The only problem with the high-bit is that SPX does not recognize it. When SPX gets a request with a high-bit, SPX ignores it and reposts the ECB to IPX without the preserving the high-bit. VIPX will then get the IPX request and then determine if the ECB is really loaded in global memory. If so, then it will simply pass the global request back down to IPX with the high-bit set.