home *** CD-ROM | disk | FTP | other *** search
/ Chip Special: Netware / Chip-Special_1996-08_NetWare_cd2.bin / content / support / token / 4 / windr2.exe / VXDS / VIPX.DOC < prev    next >
Encoding:
Text File  |  1994-10-01  |  42.7 KB  |  1,125 lines

  1. VIPX.386 Description, Limitations, Configuration and Programming Document
  2.  
  3. Current Version:    1.19
  4. Date:               940524
  5.  
  6. Table of Contents
  7. -----------------
  8. I.   Description
  9. II.  Limitations
  10.      A.  Windows 3.X Versions
  11.      B.  Version Compatibility with Dedicated IPX
  12.      C.  Packet Size Limitations
  13.      D.  Memory Manager Limitations
  14. III. System Settings in the SYSTEM.INI File
  15.      A.  [386Enh] Section
  16.           1.  TimerCriticalSection
  17.      B.  [VIPX] Section
  18.           1.  VipxMappingPages
  19.           2.  VipxFailOverSizedPackets
  20.           3.  VpicdFix
  21.           4.  AutoIrqVirtualize
  22.           5.  VirtualizeIrq[0-F]
  23.           6.  VipxErrorMessages
  24.           7.  VipxWarningMessages
  25.           8.  VipxOutDebugStrOnErrors
  26.           9.  VipxOutDebugStrOnWarnings
  27.           10. VipxBreakOnErrors
  28.           11. VipxBreakOnWarnings
  29. IV.  Programming to the VIPX Interface
  30.      A.  Getting the VIPX API Entry Point
  31.      B.  Memory Management Issues
  32.      C.  APIs Specific to VIPX.386
  33.      D.  Handling Reentrancy Issues
  34.      E.  Using NWIPXSPX.DLL
  35.      F.  Windows Application Resource Cleanup
  36.      G.  Using GlobalDOSAlloc()
  37. V.   Global DOS TSRs using IPX/SPX
  38.  
  39.  
  40. I.  Description
  41. ---------------
  42.  
  43. VIPX.386 is a Windows 3.X virtualization driver for IPXODI.COM
  44. driver.  It virtualizes requests to the globally loaded IPX driver. 
  45. When a request is made to IPX, VIPX will allocate a request buffer in
  46. the system's global memory, copy the original request to the global
  47. buffer and give the global request to IPX.  When the global request
  48. completes, IPX will call VIPX.  VIPX will then copy any results back
  49. to the original request buffer and call the application which
  50. submitted the request.
  51.  
  52.  
  53. II.  Limitations
  54. ----------------
  55.  
  56.  
  57. II.A.  Windows Versions
  58. -----------------------
  59.  
  60. VIPX supports Windows versions 3.0, 3.0a and 3.1.  However, there is
  61. some functionality which VIPX will only support in Windows 3.1. This
  62. is LAN IRQ virtualization for deadlock avoidance.  Under Windows 3.0
  63. and 3.0a, VIPX will not virtualize LAN IRQs.  See section III.B.5
  64. VirtualizeIrq[0-F].
  65.  
  66. The preferred version of Windows is a patched version of Windows 3.1 
  67. or the Windows 3.11 update which contains this fix. 
  68.  
  69. Microsoft found a bug in the their Virtual Timer Device (VTD) driver
  70. which would cause a deadlock with the NetWare shell (NETX).  This
  71. version of WIN386.EXE has a single byte patch which prevents the
  72. situation from occurring. 
  73.  
  74. The fix is in the Virtual Timer Device's control procedure. Without
  75. the patch (VTDA.386), a certain procedure is called during the Create_VM state. 
  76. With the patch, this procedure is now called during the
  77. VM_Critical_Init state.  The patch is applied at offset 441D8 in
  78. WIN386.EXE (544789 03-01-92 3:10a).  The value at that location is 07
  79. (Create_VM).  The value should be changed to 08 (VM_Critical_Init).
  80.  
  81. The deadlock occurs as a result of the shell waiting for the timer
  82. tick to advance.  When an NCP is sent, the shell will start it's
  83. retry time-out countdown.  This is essentially a loop waiting for the
  84. timer tick value to advance to a certain point.  The deadlock occurs
  85. when the tick value never increments and thus the shell is left in an
  86. infinite loop.  The reason the tick count is never incremented is
  87. that the timer interrupt is not allowed to be simulated into V86 mode
  88. under certain circumstances.  The patch to the VTD allows the timer
  89. interrupts to be simulated.
  90.  
  91.  
  92. II.B.  Version Compatibility with Dedicated IPX
  93. -----------------------------------------------
  94.  
  95. Novell officially ceased maintenance on the dedicate IPX driver
  96. (IPX.OBJ) version 3.10 dated 11-21-91.  The last version of VIPX to
  97. explicitly support that driver is 1.10.  The current version of VIPX
  98. supports IPXODI.COM only.  It is strongly recommended that you update
  99. your dedicated IPX driver with IPXODI.COM when using versions of VIPX
  100. later than 1.10.
  101.  
  102.  
  103. II.C.  Packet Size Limitations
  104. ------------------------------
  105.  
  106. VIPX.386 will only virtualize packets which are 8000 bytes (7.81K) or
  107. less.  Any DOS and Windows IPX or SPX applications which use networks
  108. with a physical packet size greater than 7.81K may not work with
  109. VIPX.386.  For example, IBM Token Ring can be configured to run with
  110. 16K packets.  A request by a DOS or Windows IPX application to send a
  111. 16K packet will be truncated to 7.81K.  On the other hand, any 16K
  112. packet that is received by the LAN driver will be dropped because
  113. VIPX cannot allocate a packet big enough to handle it.
  114.  
  115.  
  116. II.D.  Memory Manager Limitations
  117. ---------------------------------
  118.  
  119. When an request is passed up from IPX, VIPX will immediately test the
  120. request buffer to see if it is in global memory already.  If it is in
  121. global memory, the request will be passed back down to IPX without
  122. any virtualization.  For example, a TSR loaded before Windows is
  123. considered to be in global memory.  If that TSR calls IPX, VIPX will
  124. test the requests and pass them back down to IPX.  This is done
  125. because there is no need to virtualize requests which are already
  126. global.
  127.  
  128. The use of UMB memory complicates the test for global memory.  There
  129. are two basic scenarios.  Scenario One is when a TSR has been loaded
  130. high before Windows was loaded.  In this case, IPX requests will come
  131. from global UMB memory.  VIPX will simply pass these requests back
  132. down to IPX.  Scenario Two is when a TSR is loaded high in a Windows
  133. DOSBOX.  In this case, IPX requests will come from local UMB memory. 
  134. VIPX will virtualize these requests.
  135.  
  136. Using HIMEM.SYS and EMM386.SYS, all tests for global UMB memory will
  137. work properly under both of scenarios.
  138.  
  139. However, there are some memory managers that do not work properly
  140. under Windows.  With these drivers, all LOCAL DOSBOX UMB's look as if
  141. they are GLOBAL UMB's.  Under Scenario Two, when a TSR calls IPX, VIPX
  142. will test the request buffer and think that it is in global UMB
  143. memory when it is really in LOCAL UMB memory.  As a result, VIPX will
  144. pass a local ECB to IPX without virtualization.  The normal result of
  145. this is a hung machine or data corruption.
  146.  
  147. This problem has been reported with Compaq's EXMEM.SYS (version
  148. unknown)and QEMM using STEALTH mode.
  149.  
  150. If you are using one of these two memory managers, all TSRs USING THE IPX
  151. INTERFACE (INCLUDING IPX ITSELF) MUST BE LOADED IN CONVENTIONAL
  152. MEMORY.
  153.  
  154.  
  155. III.  System Settings in the SYSTEM.INI File
  156. --------------------------------------------
  157.  
  158. III.A.  [386Enh] Section
  159. ------------------------
  160.  
  161. III.A.1.  TimerCriticalSection
  162. ------------------------------
  163.  
  164. As of version 1.15 of VIPX, TimerCriticalSection is required to be set
  165. on.  The recommended setting is as follows:
  166.  
  167. [386Enh]
  168. TimerCriticalSection=10000
  169.  
  170. The reason for this parameter is to avoid a deadlock with the LAN IRQ
  171. Virtualization code.  See section III.B.5 VirtualizeIrq[0-F].
  172.  
  173.  
  174. III.B.  [VIPX] Section
  175. ------------------------
  176.  
  177. Under most circumstances, VIPX will work fine under the default
  178. configuration.  However, there may be some applications which require
  179. custom configuration of the driver.  This is a list of SYSTEM.INI
  180. parameters which can be used to configure VIPX:
  181.  
  182. [VIPX]
  183. VipxMappingPages         =[number of 4K pages]    (default = 16)
  184. VipxFailOverSizedPackets =[ON|OFF|TRUE|FALSE]     (default = OFF)
  185. VpicdFix            =[ON|OFF|TRUE|FALSE]     (default = ON)
  186. AutoIrqVirtualize        =[ON|OFF|TRUE|FALSE]     (default = ON)
  187. VirtualizeIrq[0-F]       =[ON|OFF|TRUE|FALSE]     (default = OFF)
  188. KeepLongLivedSocketsOpen =[ON|OFF|TRUE|FALSE]     (default = OFF)
  189.  
  190. (BELOW ARE CONFIGURATION PARAMETERS FOR BETA VERSIONS ONLY!)
  191. VipxErrorMessages        =[ON|OFF|TRUE|FALSE]     (default = OFF)
  192. VipxWarningMessages      =[ON|OFF|TRUE|FALSE]     (default = OFF)
  193. VipxBreakOnErrors        =[ON|OFF|TRUE|FALSE]     (default = OFF)
  194. VipxBreakOnWarnings      =[ON|OFF|TRUE|FALSE]     (default = OFF)
  195. VipxOutDebugStrOnErrors       =[ON|OFF|TRUE|FALSE]     (default = OFF)
  196. VipxOutDebugStrOnWarnings     =[ON|OFF|TRUE|FALSE]     (default = OFF)
  197.  
  198.  
  199. III.B.1.  VipxMappingPages
  200. --------------------------
  201.  
  202. This is the number of pages that VIPX can use to globalize requests
  203. to the global IPXODI.COM driver.  VIPX is not absolutely guaranteed
  204. to have all of these pages available at any one point, since this is
  205. the requested number of pages for SHARED global mapping which VIPX
  206. makes to the Windows VMM at initialization time.
  207.  
  208.  
  209. III.B.2.  VipxFailOverSizedPackets
  210. ----------------------------------
  211.  
  212. This parameter tells VIPX to fail any requests which require more
  213. than the maximum allowed globalization size.  The actual maximum will
  214. vary according to which media the user is on.  The absolute maximum
  215. is 8000 (decimal) bytes.  With media that have smaller packets than
  216. 8000 bytes, the maximum allowed size is the maximum packet size that
  217. can be put onto the media.
  218.  
  219.  
  220. III.B.3.  VpicdFix
  221. ------------------
  222. III.B.4.  AutoIrqVirtualize
  223. ---------------------------
  224.  
  225. VpicdFix and AutoIrqVirtualize are synonyms for the same option.
  226. The VPICD VXD has a problem of allowing an interrupt to be simulated
  227. to a VM which previously held the Windows Critical Section but currently
  228. does not.  (For more details, see III.B.5. VirtualizeIrq[0-F]).  This 
  229. causes a deadlock situation when LAN I/O is attempted in another VM.  
  230. To get around the problem, VIPX will take over the job of virtualizing 
  231. LAN IRQs and will simulate LAN interrupts only to the owner of the 
  232. Windows Critical Section.
  233.  
  234. This option is used to turn on or off the automatic detection and
  235. virtualization of LAN IRQs when ODI LAN drivers or dedicated LAN
  236. drivers are loaded.  The reason for this option is to allow OEMs to 
  237. virtualize their LAN drivers with their own VXDS.
  238.  
  239.  
  240.  
  241. III.B.5.  VirtualizeIrq[0-F]
  242. ----------------------------
  243.  
  244. This is option is automatically configured excepted on machines using
  245. the IBM LAN Support Program.  This parameter tells VIPX to virtualize
  246. the specified (hex) Interrupt request (IRQ) line.  This feature is
  247. mainly designed to assist the IBM LAN Support Program in avoiding the
  248. "Black Screen of Death".
  249.  
  250. Because of a design flaw in the VPICD.386 driver, network interrupts
  251. will sometimes deadlock a PC running Windows 3.X.  VIPX 1.15 attempts
  252. to avoid the deadlock by virtualizing the Network Interface Card's
  253. (NIC) IRQ(s).  With ODI and dedicated IPX (IPX.OBJ) drivers, VIPX
  254. will automatically read the configuration of the NIC from the driver
  255. and virtualize the selected IRQs.
  256.  
  257. The only time that VIPX cannot automatically detect the LAN IRQs is
  258. when using the IBM LAN Support Program with either SLANSUP.OBJ or 
  259. LANSUP.COM.  In this case, the LAN IRQ is not readable from the
  260. driver.  The reason (so I am told) is that IBM LAN Support does not
  261. have an API to read the IRQ setting on the NIC:  The only way to get
  262. this information is to read the NIC hardware itself.  The problem
  263. with doing this is that the hardware can be Token Ring, PCN2 or
  264. Ethernet.  This means that VIPX would have to be aware of all of the
  265. different configurations of  these types of hardware. Instead of this,
  266. VIPX requires the IBM LAN Support user to specify the NIC IRQ in the
  267. [VIPX] section of the SYSTEM.INI.  IRQs range from 0 to F (hex).  An
  268. example is listed below:
  269.  
  270. [VIPX]
  271. VirtualizeIrq2=TRUE
  272. VirtualizeIrq3=TRUE
  273.  
  274. In this example, VIPX will virtualize both IRQ 2 and IRQ 3.  VIPX can
  275. virtualize up to 4 different LAN IRQs.  The reason for virtualizing 
  276. multiple IRQs is to allow other LAN cards and protocols to be
  277. installed on the same PC and prevent them from deadlocking the
  278. machine.  For example, you may have IPX running through an NE2000
  279. card on IRQ 3 and TCP/IP running through to an IBM Token Ring card on
  280. IRQ 2.
  281.  
  282. One side note.  On an AT class PC, IRQ 2 is really IRQ 9 on the
  283. physical machine.  IRQ 2 is a special cascade interrupt used to
  284. access IRQs 9-F.  Whenever a NIC raises the IRQ 2 line, the hardware
  285. configuration on the PC's bus actually issues an IRQ 9.  Also, if a
  286. NIC is configured to IRQ 2, the LAN driver during its initialization
  287. procedure will detect that the PC is an AT class machine and will
  288. correctly configure its software to use IRQ 9.
  289.  
  290. Getting to the point, the Windows VPICD VxD will not allow IRQ 2 to
  291. be exclusively virtualized by any VxD.  However, when VIPX detects
  292. that IRQ 2 is to be virtualized, it changes the IRQ to 9.  So if you
  293. are using the IBM LAN Support Program on an IBM Token Ring NIC set to
  294. IRQ 2, you should have not trouble.  The following two configurations
  295. are equivalent:
  296.  
  297. [VIPX]
  298. VirtualizeIrq2=TRUE
  299.  
  300. or
  301.  
  302. [VIPX]
  303. VirtualizeIrq9=TRUE
  304.  
  305. One important note when using VIPX 1.15 for higher:  Set the
  306. TimerCriticalSection under the [386Enh] section in the SYSTEM.INI as
  307. follows:
  308.  
  309. [386Enh]
  310. TimerCriticalSection=10000
  311.  
  312. The reason for this is that the timer interrupt can cause a deadlock
  313. with the virtualization of the LAN interrupt.
  314.  
  315. You should note that the VirtualizeIrq[0-F] can be used to override
  316. any auto-virtualization of LAN IRQs.  For example, if your ODI LAN 
  317. driver is configured for IRQ 5, VIPX will automatically detect that
  318. IRQ 5 is to be virtualized.  However, if you have
  319.  
  320. [VIPX]
  321. VirtualizeIrq5=FALSE
  322.  
  323. in your SYSTEM.INI, VIPX will override the automatic detection of IRQ
  324. 5 and not virtualize the interrupt.
  325.  
  326. Another way to disable the auto-virtualization feature of VIPX is
  327. with the VpicdFix or AutoIrqVirtualize parameters (See III.B.3. 
  328. VpicdFix or III.B.4 AutoIrqVirtualize).
  329.  
  330.  
  331. III.B.6.  KeepLongLivedSocketsOpen
  332. ----------------------------------
  333.  
  334. This options prevents VIPX from closing long-lived sockets which
  335. where opened while Windows was loaded when exiting Windows.  This
  336. only applies to sockets which were opened from a DOS application or
  337. TSR:  Long-lived sockets opened from a Windows application will be
  338. closed when Windows exits.  The problem with keeping long-lived
  339. sockets opened is that some IPX applications leave their sockets open
  340. before exiting.  VIPX by default will close all sockets opened during
  341. Windows.  However, there are some globally loaded TSRs (i.e. loaded
  342. before Windows) which need to open and close sockets during a Windows
  343. session.  When exiting Windows, VIPX will close these sockets.  For
  344. TSRs which need to open sockets while Windows is loaded, use the
  345. following in the SYSTEM.INI:
  346.  
  347. [VIPX]
  348. KeepLongLivedSocketsOpen=TRUE
  349.  
  350.  
  351.  
  352. III.B.7.  VipxErrorMessages
  353. ---------------------------
  354. III.B.8.  VipxWarningMessages
  355. -----------------------------
  356. III.B.9.  VipxOutDebugStrOnErrors
  357. ---------------------------------
  358. III.B.10. VipxOutDebugStrOnWarnings
  359. -----------------------------------
  360. III.B.11. VipxBreakOnErrors
  361. ---------------------------
  362. III.B.12. VipxBreakOnWarnings
  363. -----------------------------
  364.  
  365. These parameters are only enabled in Beta versions of VIPX. With Beta
  366. software, the user can enable/disable these parameters to make VIPX
  367. print error/warning messages to the user screen and debugger screen.
  368. They can also instruct VIPX to execute a break point whenever an
  369. error/warning occurs so that debugging can be done on site.
  370.  
  371.  
  372. IV.  Programming to the VIPX Interface
  373. --------------------------------------
  374.  
  375. IV.A.  Getting the VIPX API Entry Point
  376. ---------------------------------------
  377.  
  378. VIPX supports API calls from 16-bit DOS and Windows applications. 
  379. This is the interface which NWIPXSPX.DLL uses to issue calls to VIPX.
  380.  
  381. To get the VIPX Device V86/PM API Entry Point, a 16-Bit DOS/Windows 
  382. application must do the following:
  383.  
  384.      DWORD VIPX_API_ADDR;
  385.      #define WIN_GET_DEV_API_ENTRY_PT 0x1684
  386.      #define VIPX_DEVICE_ID 0x0200
  387.      GetVipxApiAddr ()
  388.      {
  389.           _asm
  390.           {
  391.                mov  ax, WIN_GET_DEV_API_ENTRY_PT
  392.                mov  bx, VIPX_DEVICE_ID
  393.                int  2fh
  394.                mov  word ptr VIPX_API_ADDR+2, ES
  395.                mov  word ptr VIPX_API_ADDR, DI
  396.           }
  397.           if (!VIPX_API_ADDR)
  398.           {
  399.                print error - NO PM API! RIGHT VERS OF VIPX?
  400.                handle error
  401.           }
  402.      }
  403.  
  404. Once the API address is obtained, an application then can make a far
  405. call to the VIPX API Handler to do two primary functions:
  406.  
  407.      1.  Request an 16-Bit PM IPX/SPX operation (SendPacket, etc...).
  408.      2.  Request a device specific operation (GetVersion, GetDiagnotics).
  409.  
  410. The way that VIPX differentiates between the two types of APIs is by
  411. the value in the BH register.  If BH equals 00h, then VIPX will
  412. handle a 16-Bit Protected Mode IPX/SPX operation.  NOTE:  The V86
  413. IPX/SPX API interface (i.e. DOS application) is done through the
  414. globally loaded IPX TSR itself.  This way, DOS applications do not
  415. need to change the way they access the IPX/SPX APIs under Windows and
  416. DOS.
  417.  
  418. If BH equals the character 'V' (56h), then VIPX will perform a
  419. V86/PM Device Specific API.  These APIs are listed below (and will be
  420. presented in full specification later):
  421.  
  422.      GetVipxVersion
  423.      GetVipxDiagnostics
  424.  
  425. For IPX/SPX operations (BH = 00h), a 16-bit Windows program must
  426. follow the IPX/SPX API specification in the NetWare System Calls-DOS
  427. manual under the Communications chapter.  Coverage of the DOS IPX/SPX
  428. APIs is beyond the scope of this document.  However, an example is
  429. provided below for reference:
  430.  
  431.      DWORD VIPX_API_ADDR;
  432.      int ccode;
  433.      CallPmIpxSpxApi ()
  434.      {
  435.           _asm
  436.           {
  437.                mov  bx, (IPX operation number)
  438.                (set other registers as specified by the function)
  439.                call VIPX_API_ADDR
  440.                (save return registers as specified by the function)
  441.                ErrorCondition = (function error condition)
  442.           }
  443.           if (ErrorCondition)
  444.           {
  445.                print error - function failed!
  446.                handle error
  447.           }
  448.      }
  449.  
  450.  
  451. IV.B.  Memory Management Issues
  452. -------------------------------
  453.  
  454. One problem with using the IPX/SPX interface in Windows applications
  455. is that all memory used by VIPX must be fixed. Windows really has two levels of
  456. memory
  457. management:  high-level segment memory management used by Windows
  458. applications and  low-level kernel page memory management.  The
  459. high-level memory manager (referred in this document as GUI MM) allocates pages
  460. from the
  461. low-level memory manager (referred in this document as Page MM) and creates what
  462. is
  463. known as memory segments.  A segment can consist of several pages.
  464. The pages allocated by the GUI MM from the Page MM are totally under
  465. the control of GUI MM.  The GUI MM can decide if certain pages of
  466. segments are to be paged out, discarded, etc.  The Page MM, on the
  467. other hand, just carries out the orders of the GUI MM:  it can never
  468. tell the GUI MM what to do with pages which it owns.  For example, 
  469. the Page MM can never tell the GUI MM that it locked a page belonging 
  470. to a segment.
  471.  
  472. With this last example in mind, let's discuss VIPX.  Since VIPX is an
  473. asynchronous driver, all application ECBs, fragments and ESRs must be
  474. in locked memory to insure that they can be accessed during interrupt
  475. time.  If an ECB, fragment or ESR is in moveable memory, it could
  476. move when VIPX "owns" that memory.  For example, assume an ECB is
  477. located at 16-bit PM address 0A10:0000 in moveable memory.  Assume
  478. this address translates to 32-bit linear address 8140ACB0.  Assume
  479. also that the application gives the ECB to VIPX and VIPX commences to
  480. work on it.  Now if Windows does garbage collection via
  481. GlobalCompact(), the linear mapping for the 16-bit address 0A10:0000
  482. may change from 8140ABC0 to 81408100.  When VIPX tries to access the
  483. old linear address 8140ABC0, memory corruption will occur because
  484. VIPX is accessing the wrong memory.  VIPX does call the Page MM to
  485. lock all pages belonging to ECBs and fragments passed to it. 
  486. However, this in no way tells the GUI MM that pages were locked.  The
  487. GUI MM can simply do a GlobalCompact() and remap those pages to
  488. different linear addresses!  In order for the GUI MM to know that a
  489. GlobalCompact() is not supposed to remap a segment (and thus the
  490. pages in that segment) the Windows application or DLL must call the
  491. GUI MM's GlobalFix() function:  VxDs (such as VIPX) cannot call
  492. 16-bit GUI MM functions.
  493.  
  494. The reason why Windows applications must lock the memory
  495. instead of the NWIPXSPX.DLL was a decision based on performance.  The
  496. writer of the DLL felt that it was better for applications to lock
  497. and unlock their own memory because they have a better idea of when
  498. that memory will be used intensively for network traffic.  For
  499. example, if NWIPXSPX.DLL were to do a GlobalFix() on each ECB and
  500. fragments when it was submitted and then did a GlobalUnfix() when it
  501. was completed, there would be a lot of overhead when that memory used
  502. over and over again for network operations.  If the application were
  503. to lock the memory, do 1000s of network operations and then unlock
  504. the memory, there would be much less overhead.
  505.  
  506. To avoid memory corruption, follow the guidelines below:
  507.  
  508. 1.  Read Chapter 15 (Memory Management) and 16 (More Memory
  509. Management) of the MS Windows SDK Version 3.1.  These will explain
  510. many terms and concepts which will be discussed below.
  511.  
  512. 2.  Read Chapter 2 (Windows Memory Management) of Windows Internals
  513. by Matt Pietrek, ISBN 0-201-62217-3.
  514.  
  515. 3.  Define all ESRs, routines called by ESRs and data accessed by
  516. ESRs in one module.  In the .DEF file for your project, define the
  517. module's code and data segments as FIXED.
  518.  
  519. 4.  Use GlobalFix() on all dynamically allocated heap memory
  520. (via GlobalAlloc() or LocalAlloc()) which is to be used by VIPX or an
  521. ESR.  GlobalFix() informs the GUI kernel memory manager to fix
  522. the linear base of the selector until a GlobalUnfix() is done on
  523. the memory.
  524.  
  525. 5.  When using GlobalAlloc(), use the GMEM_DDESHARE and GMEM_FIXED
  526. options.  GMEM_DDESHARE will allocate the memory, according to sources, high
  527. up on the global heap.  This will avoid using conventional memory and
  528. will allow room for other Windows applications to load after your
  529. application has been loaded and initialized.  The GMEM_FIXED option
  530. tells the GUI kernel memory manager that the memory is FIXED.  This
  531. does not mean that the linear address will remain unchanged, but it
  532. does mean that the segmented address (selector:offset) will not
  533. change.  After the call to GlobalAlloc(), call GlobalLock() to
  534. dereference the global memory handle.  This provides a far pointer
  535. (selector:offset) to the memory which will not change.  Next call
  536. GlobalFix() to fix the linear base of the selector so that the
  537. 32-bit linear mapping of the selector:offset does not change.
  538.  
  539. 6.  If local moveable or discardable code or data segments are used
  540. for ECBs, fragments or ESRs, use GlobalFix() on those segments.
  541.  
  542. 7.  After you load your application and then you get a message from
  543. Windows that there are not enough resources to load another program,
  544. try changing the attributes of your segments. I believe this
  545. condition occurs because too much conventional memory has been
  546. allocated to fixed memory objects and Windows needs to allocate
  547. conventional memory to load a program.  One strategy may be to do a
  548. GlobalCompact() with a argument of -1 at initialization time.  This
  549. will move all moveable segments and discard all discardable
  550. segments.  Next call GlobalAlloc() for one large moveable global
  551. object which contains all of your ECBs, fragments and data which must
  552. be accessed from VIPX or your ESRs.  Next call GlobalLock() to
  553. dereference the global handle.  Last call GlobalFix() to fix the
  554. linear address of the object.  (*This last step is untested so there
  555. may be some problems with it).  Also make your ESR code as small as
  556. possible using PostMessage to indicate to your application that an
  557. ECB has completed.  This way, your FIXED code segment will have
  558. little impact on conventional memory.
  559.  
  560. 8.  Read Chapter 4 of The Windows Programmer's Guide to DLLs and
  561. Memory Management by Mike Klein, ISBN 0-672-30236-5.
  562.  
  563. Previously we had recommended that you use GlobalPageLock() and
  564. GlobalPageUnlock() on the ECBs and fragment you needed locked.  We
  565. now recommend GlobalFix() and GlobalUnfix() because these calls do
  566. not cause the memory to move to conventional memory, but still provides
  567. the functionality of page locking that is needed for VIPX to touch
  568. the memory in the context of other VMs. 
  569.  
  570.  
  571. IV.C.  APIs Specific to VIPX.386
  572. --------------------------------
  573.  
  574. There are two APIs via the VIPX API entry point which are specific
  575. to VIPX.386 only.  These are:
  576.  
  577.      GetVipxVersion
  578.      GetVipxDiagnostics
  579.  
  580. The device specific APIs are called in the same manner as the IPX/SPX
  581. APIs except that BH = 56h.  Some sample code is listed below:
  582.  
  583.      DWORD VIPX_API_ADDR;
  584.      #define VIPX_DEVICE_API_TAG 'V'
  585.      int ccode;
  586.      CallVipxDeviceSpecificApi ()
  587.      {
  588.           _asm
  589.           {
  590.                mov  bh, VIPX_DEVICE_API_TAG
  591.                mov  bl, api_function_number
  592.                (set other registers for API function)
  593.                call VIPX_API_ADDR
  594.                mov  ccode, ax
  595.           }
  596.           if (!ccode)
  597.           {
  598.                print error - api function failed!
  599.                handle error
  600.           }
  601.      }
  602.  
  603. The full API specification for GetVipxVersion is as follows:
  604.  
  605. GetVIPXVersion
  606.  
  607.      This procedure puts the VIPX major and minor version numbers into
  608.      the client AH and AL.
  609.  
  610.      To call this API a DOSBOX or Windows 3.0 application
  611.      must first get the VIPX Device API entry point (see
  612.      VIPX_V86_API_Handler or VIPX_PM_Handler).  Then
  613.      it must do the following:
  614.  
  615. Example:
  616.  
  617.      DWORD VIPX_API_ADDR;
  618.      BYTE VIPX_Major_Version;
  619.      BYTE VIPX_Minor_Version;
  620.      #define VIPX_DEVICE_API_TAG 'V'
  621.      #define VIPX_GET_VERSION 0x00
  622.      int ccode;
  623.      main ()
  624.      {
  625.           _asm
  626.           {
  627.                mov  bh, VIPX_DEVICE_API_TAG
  628.                mov  bl, VIPX_GET_VERSION
  629.                call VIPX_API_ADDR
  630.                mov  ccode, ax
  631.                mov  VIPX_Major_Version, bh
  632.                mov  VIPX_Minor_Version, bl
  633.           }
  634.           if (!ccode)
  635.           {
  636.                print error - api function failed!
  637.                handle error
  638.           }
  639.      }
  640.  
  641. Entry:
  642.      BH   056h - VIPX Device API
  643.      BL   0 - Get VIPX Version function
  644.  
  645. Exit:
  646.      BH   VIPX Major Version
  647.      BL   VIPX Minor Version
  648.      AX   Return code
  649.                0000h - success
  650.  
  651. Uses:
  652.      AX, BX, Flags
  653.  
  654. The full API specification for GetVIPXDiagnostics is as follows:
  655.  
  656. GetVIPXDiagnostics
  657.  
  658.      This procedure puts the diagnostics for VIPX into the
  659.      specified client diagnostic buffer.  The buffer is 112 bytes 
  660.      and has the following structure:
  661.  
  662.           VIPXDiagStruc  STRUC
  663.                MallocCount              dd   ?
  664.                MallocErrorCount         dd   ?
  665.                FreeCount           dd   ?
  666.                AllocPageCount           dd   ?
  667.                FreePageCount            dd   ?
  668.                PMRequestCount           dd   ?
  669.                TSRServiceCount               dd   ?
  670.                ServiceAESEventCount          dd   ?
  671.                ServiceIPXEventCount          dd   ?
  672.                PostEventCount           dd   ?
  673.                IPXGetECBCount           dd   ?
  674.                IPXGetECBBadPacketCount       dd   ?
  675.                IPXGetECBOutOfResource        dd   ?
  676.                IPXCountECBCount         dd   ?
  677.                IPXReturnECBCount        dd   ?
  678.                IPXRequestCount               dd   ?
  679.                FreeECBCount             dd   ?
  680.                AllocECBCount            dd   ?
  681.                LockECBCount             dd   ?
  682.                UnlockECBCount           dd   ?
  683.                OpenSocketCount               dd   ?
  684.                CloseSocketCount         dd   ?
  685.                SendPacketCount               dd   ?
  686.                ListenCount              dd   ?
  687.                IPXScheduleEventCount         dd   ?
  688.                AESScheduleEventCount         dd   ?
  689.                CancelECBCount           dd   ?
  690.                PMInt2FCount             dd   ?
  691.                AllocPageErrorCount      dd   ?
  692.                FreePageErrorCount       dd   ?
  693.                FreeECBErrorCount        dd   ?
  694.                MaxVipxAllocSize         dd   ?
  695.                MaxVipxFragsSize         dd   ?
  696.                TsrServiceReenterCnt          dd   ?
  697.                IPXGetEcbSktNotOpenCnt        dd   ?
  698.                IPXGetEcbNoEcbAvailCnt        dd   ?
  699.           ENDS
  700.  
  701.      NOTE:  Starting with VIPX 1.16, MaxVipxAllocSize, MaxVipxFragsSize, and
  702.      TsrServiceReenterCnt have been updated to DWORDs.  Also
  703. IPXGetEcbSktNotOpenCnt
  704.      and IPXGetEcbNoEcbAvailCnt have been added.
  705.  
  706.  
  707.      To call this API a DOSBOX or Windows 3.0 application
  708.      must first get the VIPX Device API entry point (see
  709.      VIPX_V86_API_Handler or VIPX_PM_Handler).  Then
  710.      it must do the following:
  711.  
  712. Example:
  713.  
  714.      DWORD VIPX_API_ADDR;
  715.      VIPXDiagStruc Diag;
  716.      #define VIPX_DEVICE_API_TAG 'V'
  717.      #define VIPX_GET_DIAGNOSTIC 0x01
  718.      int ccode;
  719.      main ()
  720.      {
  721.           _asm
  722.           {
  723.                mov  bh, VIPX_DEVICE_API_TAG
  724.                mov  bl, VIPX_GET_DIAGNOSTIC
  725.                mov  ax,ds
  726.                mov  es,ax
  727.                mov  di, offset Diag
  728.                mov  cx, size VIPXDiagStruc
  729.                call VIPX_API_ADDR
  730.                mov  ccode, ax
  731.           }
  732.           if (!ccode)
  733.           {
  734.                print error - api function failed!
  735.                handle error
  736.           }
  737.      }
  738.  
  739. Entry:
  740.      BH   056h - VIPX Device API
  741.      BL   1 - Get VIPX Diagnostics
  742.      ES:SI     ptr to client VIPX Diagnostics buffer.
  743.      CX   Length of diagnostics buffer in bytes
  744.  
  745. Exit:
  746.      ES:SI     client's buffer now has VIPX diags.
  747.      AX   Return code
  748.                0000h - success
  749.                0FFFEh - diagnostics buffer too short
  750.  
  751. Uses:
  752.      AX, SI, ES, Flags
  753.  
  754.  
  755. IV.D.  Handling Reentrancy Issues
  756. ---------------------------------
  757.  
  758. A Windows application which uses the IPX/SPX APIs via VIPX must
  759. handle reentrancy issues.  Since the IPX/SPX API is interrupt driven,
  760. any code running while interrupts are enabled can (and will) be
  761. interrupted at any time by IPX/SPX.  When an IPX event completes, an
  762. ESR for that event is called.  The ESR is entered with interrupts
  763. disabled.  If that ESR reenables interrupts, then it can be
  764. reentered.  This is usually a problem if:
  765.  
  766. 1.  The application has limited stack space
  767. 2.  The application's ESR uses global variables
  768.  
  769. An ESR must protect itself from these two reentrancy issues if it
  770. reenables interrupts.  Interrupts can be reenabled by several
  771. different places.  One of the most notorious is the PostMessage()
  772. Windows call.  Any ESR using PostMessage() must be reentrant. 
  773. Another notorious enabler of interrupts is IPXSendPacket. 
  774. IPXSendPacket will call the LSL to transmit a packet.  The LSL,
  775. however,  will reenable interrupts during transmission of a packet. 
  776. This can cause reentrancy into any ESR that calls IPXSendPacket.
  777.  
  778. An ESR can handle reentrancy issues by
  779.  
  780. 1.  Switching to a stack exclusively for the ESR to use.
  781. 2.  Provide a reentrancy queue.
  782.  
  783. The stack switching is usually done in assembly.  It is by far the
  784. easiest and most efficient way to protect your stack.  The code for
  785. stack switching is listed below:
  786.  
  787. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  788. ~~~
  789. memL equ 1 ; large model
  790. ?PLM = 1 ; pascal conventions
  791. ?WIN = 1 ; epilogue/prolog code
  792.  
  793.  
  794. INCLUDE cmacros.inc
  795.  
  796. sBegin    DATA
  797.      assumes   DS, DATA
  798.  
  799. ReenterCnt     db   0
  800.  
  801. OldSs          dw   0
  802. OldSp          dw   0
  803.  
  804. EsrStack  db   4096 dup (0)
  805.  
  806. EndEsrStack label byte
  807.  
  808. sEnd DATA
  809.  
  810. sBegin    CODE
  811.      assumes   CS, CODE
  812. ;    
  813. ;    This is the front-end procedure to the C function "ReceiveESR"
  814. ;
  815.  
  816. externFP cESR
  817. PUBLIC aESR
  818. aESR proc FAR
  819.  
  820.      nop
  821.      nop
  822.      nop
  823.      nop
  824.  
  825.      ; Don't know why Windows needs to inc bp
  826.      inc  bp
  827.      push bp
  828.      mov  bp, sp
  829.  
  830.      ; Save data segment
  831.      push ds
  832.  
  833.      ; Fix to our data segmented
  834.      mov  ax, DGROUP
  835.      mov  ds, ax
  836.  
  837.      ; Update our reenter cnt
  838.      inc  ReenterCnt
  839.  
  840.      ; Do we need to switch stack to ESR stack?
  841.      cmp  ReenterCnt, 2       ;are we reentering?
  842.      jae  CallEsr             ;y: call esr
  843.      mov  ax, ss
  844.      cmp  ax, DGROUP          ;same stack seg?
  845.      jne  SwitchStack         ;n: switch
  846.      lea  ax, EsrStack        ;ax=offset of EsrStack
  847.      cmp  sp, ax              ;below the esr stack?
  848.      jb   SwitchStack         ;y: switch
  849.      lea  ax, EndEsrStack          ;ax=offset of end of EsrStack
  850.      cmp  sp, ax              ;above the esr stack
  851.      jae  SwitchStack         ;y: switch
  852.  
  853.      ; Call the esr
  854. CallEsr:
  855.      push es
  856.      push si
  857.      cCall     cESR
  858.      lea  sp, [bp-2]
  859.  
  860.      ; Update reenter cnt
  861.      dec  ReenterCnt
  862.  
  863.      ; Switch stacks back?
  864.      jz   SwitchStackBack
  865.  
  866.      ; return to IPX
  867. EsrExit:
  868.  
  869.      ; restore ds and bp
  870.      pop  ds
  871.      pop  bp
  872.      dec  bp
  873.  
  874.      ret
  875.  
  876. SwitchStack:
  877.      mov  ax, sp
  878.      mov  OldSp, ax
  879.      mov  ax, ss
  880.      mov  OldSs, ax
  881.      lea  ax, EndEsrStack
  882.      mov  sp, ax
  883.      mov  ax, ds
  884.      mov  ss, ax
  885.      jmp  CallEsr
  886.  
  887. SwitchStackBack:
  888.      mov  ax, OldSp
  889.      mov  sp, ax
  890.      mov  ax, OldSs
  891.      mov  ss, ax
  892.      jmp  EsrExit
  893.  
  894. aESR endp
  895.  
  896. sEnd CODE
  897.      end
  898.  
  899. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  900. ~~~
  901.  
  902. The above code switched to a 4K stack before calling the main ESR
  903. code.  Notice that there is a reentrancy count to determine if a stack
  904. switch should occur.  This is used to prevent switching the stack
  905. again if the ESR is reentered.
  906.  
  907. The main ESR code is written in C.  An example is listed below:
  908.  
  909. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  910. ~~~
  911. void far pascal cESR(ECB far *esr_ecb)
  912. {
  913.      static ECB FAR * EcbQueue;
  914.      static ECB FAR * EcbQueueTail = (ECB FAR *)&EcbQueue;
  915.      static WORD ReenterFlag = 0;
  916.  
  917.      // Check for reentrancy
  918.      if (ReenterFlag)
  919.      {
  920.           // Enqueue the ECB
  921.           EcbQueueTail->Link = (void far *)esr_ecb;
  922.           EcbQueueTail = (ECB far *)esr_ecb;
  923.           esr_ecb->Link = NULL;
  924.           return;
  925.      }
  926.  
  927.      // Set the reentrant flag to indicate that we can't allow 
  928.      // another thread through the ESR.
  929.      ReenterFlag++;
  930.  
  931.      // Process the current ECB
  932. NextEcb:
  933.  
  934.      // Do some work here if need be -- BUT BE QUICK!
  935.      ...
  936.  
  937.      // Post a message to the Windows App that the event happened
  938.      PostMessage (
  939.           hMainWnd, 
  940.           USER_DEFINED_IPX_EVENT_HAPPPENED, 
  941.           0, 
  942.           (DWORD) esr_ecb);
  943.  
  944.      // Check for anything on the queue
  945. CheckQueue:
  946.      if (EcbQueue)
  947.      {
  948.           esr_ecb = EcbQueue;
  949.           EcbQueue = (ECB FAR *)(EcbQueue->Link);
  950.           if (!EcbQueue)
  951.                EcbQueueTail = (ECB FAR *)&EcbQueue;
  952.           goto NextEcb;
  953.      }
  954.  
  955.      // Reset the reentrancy flag to indicate that the 
  956.      // first thread is done
  957.      ReenterFlag--;
  958. }
  959.  
  960. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  961. ~~~
  962.  
  963. One side note about the code above.  PostMessage() will queue a
  964. message onto an applications message queue.  This queue is limited in
  965. size.  If the application is fairly LAN I/O intensive, the message
  966. queue can be overrun.  In this case, PostMessage() will simply drop 
  967. the message.  If this happens, your application will never get
  968. notified of the event.  To insure that messages are never dropped,
  969. you may want to increase the size of your message queue.  Another
  970. alternative is to have the message indicate that an event happened
  971. and the message loop will then poll all ECBs to determine which ones
  972. completed.  This way, if a message is dropped, it won't matter:  the
  973. main message loop will still detect that the ECB completed and will
  974. be able to reuse the ECB.
  975.  
  976.  
  977.  
  978. IV.E.  Using NWIPXSPX.DLL
  979. -------------------------
  980.  
  981. Most Windows applications will use the IPX/SPX APIs via the
  982. NWIPXSPX.DLL.  The reason is that NWIPXSPX.DLL address the issues for
  983. both Standard and Enhanced mode Windows.  Under Standard mode,
  984. NWIPXSPX will use TBMI2.COM to virtualize the local ECBs passed to
  985. it.  Under Enhanced mode, NWIPXSPX.DLL will use VIPX.386 to
  986. virtualize ECBs.  If you Windows application is targeted toward
  987. Enhanced mode only, then you may find it more convenient to program
  988. directly to the VIPX interface in assembly.  However, if you are
  989. interested in using an established C interface for IPX/SPX, then
  990. NWIPXSPX.DLL is the way to go.  There is example code using NWIPXSPX.DLL 
  991. on the CompuServe NOVDEV forum.
  992.  
  993.  
  994.  
  995. IV.F.  Windows Application Resource Cleanup
  996. -------------------------------------------
  997.  
  998. Under DOS, many IPX applications took advantage of the so called
  999. "short-lived" or "lazy-man's" sockets.  These are sockets which 
  1000. automatically closed when the DOS application terminates.  These
  1001. sockets were associated with a specific PSP or DOS task.  When
  1002. a short-lived socket was opened, the current DOS task was recorded for
  1003. that socket.  When a DOS Terminate system call was done, IPX would
  1004. match the PSP of the terminating application with the sockets which
  1005. are associated with that PSP and close them.  This all worked great
  1006. under DOS.
  1007.  
  1008. However, Enhanced Windows 3.X came along and totally changed the
  1009. rules.  DOS applications still run the same way they used to, except
  1010. now they run in separate VMs.  IPX and VIPX have no trouble keeping
  1011. track of them.  With Windows applications, this is a different story
  1012. all together.  Windows applications are different tasks that run in
  1013. VM1 (the system VM).  Windows applications use 16-bit selectors which
  1014. define dynamically paged segments in memory.  When a segment is
  1015. allocated to a Windows application, it can be paged, moved and
  1016. discarded, depending on the attributes and state of the arena that
  1017. defines the segment.  When a Windows application terminates, the
  1018. segment selectors allocated to the application are freed.
  1019.  
  1020. The problem is that by the time VIPX is able to get an indication
  1021. that the task is terminating, the selectors have been freed. 
  1022. Touching the memory that used to belong to those selectors could
  1023. cause a GPI in ring-0.  This is what happens:
  1024.  
  1025. When an ECB is submitted to VIPX, it is in 16-bit selector:offset
  1026. format.  VIPX immediately converts that to a 32-bit flat, linear
  1027. address.  It then links the ECB onto a list using the Next field in
  1028. the ECB.  Let's say, for example, we have 3 ECBs: ECB1, ECB2, ECB3. 
  1029. ECB1 and ECB2 are submitted and are queued onto some sort of list. 
  1030. The list would look like this:
  1031.  
  1032.      ListHeadPtr -> ECB1 -> ECB2 <- ListTailPtr
  1033.  
  1034. Now an Windows application submits ECB3.  VIPX converts the
  1035. 16-bit selector:offset pointer to ECB3 into a linear address, let's
  1036. call it LINEAR3.  Now VIPX puts it on the list with the following
  1037. operations:
  1038.  
  1039.      ; ASSUME EAX has the value of LINEAR3
  1040.      mov  esi, ListTailPtr    ; get -> to last ECB on list (in this case ECB2)
  1041.      mov  [esi].Next, eax          ; ECB2.Next = LINEAR3
  1042.      mov  [eax].Next, 0       ; ECB3.Next = NULL
  1043.      mov  ListTailPtr, eax    ; Tail = LINEAR3
  1044.  
  1045. and our list look like:
  1046.  
  1047.      ListHeadPtr -> ECB1 -> ECB2 -> ECB3 <- ListTailPtr
  1048.  
  1049. Now if the application goes away, the selector have just
  1050. become invalid and thus LINEAR3 is bogus.  So when VIPX traverses the
  1051. list, it will hit the invalid linear address.  For example:
  1052.  
  1053.      mov  esi, ListHeadPtr    ; esi -> ECB1
  1054.      mov  esi, [esi].Next          ; esi -> ECB2
  1055.      mov  esi, [esi].Next          ; esi = LINEAR3 = BOGUS 
  1056.                          ; (no GPI yet--just reading a 
  1057.                          ; value from memory and placing 
  1058.                          ; it into a register)
  1059.      mov  esi, [esi].Next          ; GPI!!!!!! Black screen with
  1060.                          ; cursor at upper left-hand corner.
  1061.  
  1062. The point is:  There is absolutely no way for VIPX to avoid this. 
  1063. VIPX (a VxD) does not get informed when a task in VM1 (i.e. a Windows
  1064. application) terminates:  VxDs are only notified when a VM
  1065. terminates.  (NOTE:  There are the Begin_PM_App and End_PM_App
  1066. callbacks to VxDs, but they are not for Windows applications.  They
  1067. indicate when the protected mode kernel load and unload--i.e.  when
  1068. Windows begin and exits).  If there was an terminate task indication 
  1069. to VxDs, VIPX could clean up resources for that task. But Windows system level APIs
  1070. requires the application to worry about cleaning up after itself.  Therefore, the
  1071. application must clean 
  1072. up by closing the socket.  If resources are left dangling while VIPX owns them, then a
  1073. crash will occur.
  1074.  
  1075. One suggestion is for VIPX to use the Intel 80386 lsl instruction to check if a segment
  1076. is present in memory before touching it.  The problem is that this won't work with the
  1077. above described architecture
  1078. of VIPX.  VIPX translates the selector:offset to a linear address
  1079. when the ECB is submitted.  The ECB is put on the list and the
  1080. linear address is used to fetch it from the list.  VIPX will never
  1081. access the ECB via the 16-bit selector:offset address provided by the
  1082. Windows application.
  1083.  
  1084. The suggestion is that Windows applications close all sockets belonging to
  1085. them before exiting.  In addition, Novell suggest that all listens posted on
  1086. the sockets be canceled before the socket is closed.
  1087.  
  1088. IV.G.  Using GlobalDOSAlloc()
  1089. -----------------------------
  1090.  
  1091. Interesting news about GlobalDOSAlloc() in Windows found in chapter 2
  1092. of Windows Internals by Matt Pietrek (ISBN 0-201-62217-3).  On page
  1093. 153, GlobalDOSAlloc() is described as a wrapper routine around
  1094. GlobalAlloc().  However, the memory is allocated via the GA_ALLOC_DOS
  1095. flag.  This looks like it allocated FIXED memory from the global
  1096. heap.  A routine called GSearch() will eventually get called and
  1097. tests if that FIXED memory is below 1 MB.  If not, it fails,
  1098. otherwise it succeeds.  There appears to be no GlobalFix()
  1099. associated with that memory, so if you do a GlobalFix()
  1100. on all memory allocated via GlobalDOSAlloc().  They reason is that
  1101. the page(s) for that memory may be switched to a different linear
  1102. address during garbage collection.  If so, then a GPI will occur--if
  1103. not right away, then eventually.
  1104.  
  1105.  
  1106.  
  1107. V.  Global DOS TSRs using IPX/SPX
  1108. ---------------------------------
  1109.  
  1110. TSRs loaded before Windows can call IPX without the intervention of
  1111. VIPX.  This is done by ORing the BX register with 8000h (i.e. setting
  1112. the high-bit of the function number).  This is a previously
  1113. undocumented feature in IPX to support the globally loaded NETX
  1114. driver.  When IPX sees that a request has the high-bit set, it will
  1115. assume that the request came from a globally loaded TSR and will not
  1116. forward the request to VIPX.
  1117.  
  1118. The only problem with the high-bit is that SPX does not recognize
  1119. it.  When SPX gets a request with a high-bit, SPX ignores it and
  1120. reposts the ECB to IPX without the preserving the high-bit.  VIPX
  1121. will then get the IPX request and then determine if the ECB is really
  1122. loaded in global memory.  If so, then it will simply pass the global
  1123. request back down to IPX with the high-bit set.
  1124.  
  1125.