home *** CD-ROM | disk | FTP | other *** search
/ Power-Programmierung / CD1.mdf / magazine / wint1_92 / dpmi / dpmi.txt next >
Text File  |  1991-12-23  |  14KB  |  267 lines

  1. The code presented here is implemented in Turbo Pascal For Windows. The
  2. concepts illustrated apply to any Windows development environment. This code
  3. is adapted from code I wrote for TurboPower Software's NETBIOS unit included
  4. with B-Tree Filer network version. It is reproduced here with TurboPower's
  5. permission.
  6.  
  7. The code is provided in five source files. They are each described below:
  8.  
  9. WINDPMI.PAS
  10. This is a unit that implements the the core DPMI routines. This unit is a
  11. trimed down version of the unit by the same name distributed by TurboPower
  12. Software.
  13.  
  14. TNETBIOS.PAS
  15. This unit defines the various NetBIOS constants and types used by the other
  16. source files.
  17.  
  18. WNETBIOS
  19. This file implements the NetBIOS calls in a DLL.
  20.  
  21. UNETBIOS
  22. This unit defines the interface for the routine in WNETBIOS.DLL.
  23.  
  24. NBCHAT
  25. A simple NetBIOS chat program that runs under Windows. NBCHAT is a primitive
  26. Windows application in that it uses Borland's WinCrt unit. WinCrt is a unit
  27. that makes normal Turbo Pascal WriteLn and ReadLn statements work within a
  28. Windows application. Using WinCrt greatly reduces the size of the NBCHAT
  29. source, an important consideration in a magazine article!
  30.  
  31. The WinDPMI.PAS file contains the types and routines needed to access DPMI
  32. services. It also interfaces a useful routine called InRealMode. All of the
  33. code presented in this article assumes Windows is operating in protected mode,
  34. so if InRealMode returns true our demo program simply displays a message and
  35. halts.
  36.  
  37. The source to the WinDPMI unit is written using Turbo Pascal's Inline
  38. Assembler. The code looks deceivingly simple. Making the DPMI calls is the easy
  39. part, figuring out what to do with them is where the challenge lies!
  40.  
  41. The formal DPMI Specification is available free of charge from Intel. It
  42. explains each of the DPMI services provided here in detail. If you plan on
  43. doing any serious work using DPMI, I'd highly recommend getting the API
  44. specification from Intel.
  45.  
  46. The next source file, TNETBIOS.PAS, is straitforward. It defines the NetBIOS
  47. Network Control Block structure. Probably the most interesting record is the
  48. WindowsPostType. When writing this code, the part that gave me the most trouble
  49. was attempting to figure out how to deal with NetBIOS Post Routines. The
  50. WindowsPostType is a key component of my solution (more on this later).
  51.  
  52. WNETBIOS.PAS implements the meat of the routines. It is here that DPMI services
  53. are used to access the NetBIOS API. The first two routines in the source are
  54. routines that act as a shell around the Windows GlobalDosAlloc and
  55. GlobalDosFree routines. The TNETBIOS unit defines a WinNCB as follows:
  56.  
  57.   WinNCB =
  58.     record
  59.       NR, NP : NCBPtr;
  60.     end;
  61.  
  62. WinNCB is a record holding two pointers to NCBs. NR is a pointer is real mode
  63. format (segment:offset), and NP is a pointer in protected mode format
  64. (selector:offset). Keep in mind, these two pointers both point to the same
  65. block of memory. AllocateWinNCB allocates the real mode memory and sets up
  66. these two pointers, and FreeWinNCB returns the real mode memory to Windows.
  67.  
  68. Next come two low level routines for making the NetBIOS calls, ClearNCB and
  69. NetBIOSRequest. NetBIOSRequest makes the call to the NetBIOS API by
  70. initializing the important fields of a DPMIRegisters variable, then calling the
  71. DPMI Simulate Real Mode Interrupt service. Note that the DPMIRegisters variable
  72. is filled with zeros before the other fields are initialized. This is
  73. important, since invalid or uninitialized values in segment registers or SS:SP
  74. will cause problems for DPMI.
  75.  
  76. The NetBIOSRequest procedure takes a single parameter of type NCBPtr. It is the
  77. real mode pointer that is passed to this routine. Care is taken in the body of
  78. NetBIOSRequest to ensure that Turbo Pascal doesn't generate code to load that
  79. pointer into a register pair. Why, you ask? Because if Turbo Pascal generated
  80. its usual code:
  81.  
  82.   LES DI, SomePtr
  83.  
  84. the CPU would generate an exception error since you would be attempting to load
  85. an invalid selector into the ES segment register! Whenever a segment register
  86. is loaded in protected mode, the CPU will check to see if it is a valid
  87. selector, and if not, a protection fault is triggered. The typecasts are used
  88. to cause Turbo Pascal to treat the segment and offset components of the pointer
  89. as words, not pointers, alleviating the concern that an LES DI statement may be
  90. used.
  91.  
  92. The next 5 routines make various NetBIOS API calls. They construct an NCB
  93. (accessing the NP protected mode pointer of the WinNCB type), then call
  94. NetBIOSRequest (passing the NR real mode pointer of the WinNCB type). Notice
  95. how the NCB field PostRoutine is initialized in SendDatagram and
  96. ReceiveDatagram. It is passed the field Callback from the WindowsPostType. This
  97. is the value returned by the DPMI service Allocate Real Mode Callback. More on
  98. this when we reach this file's most intriquing routine, CallbackShell.
  99.  
  100. The routine CancelRequest, NetBIOSAddName, and NetBIOSDeleteName all allocate,
  101. use, and dispose of their own internal WinNCB variables. They can do this
  102. because the routines in question don't return until NetBIOS has completed the
  103. request. Therefore there is no need for the caller to assure the NCBs remain
  104. static, so to simplify things the NCBs are dealt with internally. Note that
  105. CancelRequest also takes a WinNCB as a parameter. That parameter is the WinNCB
  106. for the NetBIOS command to be cancelled. The CancelRequest routine still needs
  107. its own internal WinNCB to issue the cancel command itself.
  108.  
  109. The next three routines deal with determining NetBIOS' presence. We resort to
  110. using another DPMI routine to determine the current value of the real mode
  111. interrupt 5Ch vector. If the vector is NIL or pointing to the BIOS, then we
  112. know NetBIOS isn't installed. Otherwise, we issue an invalid NetBIOS command
  113. and see if the interrupt 5Ch handler installed returns the NetBIOS invalid
  114. command error. If so, NetBIOS is installed, if not, something else is
  115. processing interrupt 5Ch and NetBIOS is not present. It is interesting that IBM
  116.  forgot to include a NetBIOS Present function call when they designed NetBIOS!
  117.  
  118. Now comes the real hairy part. It is no trivial matter to deal with NetBIOS
  119. Post Routines! The last three routines in WNETBIOS deal with this tricky issue.
  120. Before we get to the code, lets examine the data structures involved more
  121. closely:
  122.  
  123.   RealModeCallbackProc = Pointer;
  124.   NetBiosPostRoutine = procedure(LastError : Byte; N : WinNCBPtr);
  125.   WindowsPostType =
  126.     record
  127.       Regs     : DPMIRegisters;
  128.       Post     : NetBiosPostRoutine;
  129.       CallBack : RealModeCallbackProc;
  130.       DataSegm : Word;
  131.       NCBs     : WinNCBPtr;
  132.     end;
  133.  
  134. The DPMI AllocateRealModeCallbackAddr procedure requires a pointer to a
  135. protected mode procedure to call, a variable of type DPMIRegisters to pass data
  136. in, and a pointer to return the real mode callback address. In our
  137. WindowsPostType, Regs is the DPMIRegisters, and Callback is the real mode
  138. callback address returned by DPMI. Post is a routine of type NetBIOSPostRoutine
  139. which will ultimately be called, and NCBs is a pointer to the WinNCB associated
  140. with this post routine. The DataSegm field is the data segment of the program
  141. using the callback. The WindowsPostType contains all the data necessary to pull
  142. off the tough task of allowing our protected mode application to take advantage
  143. of NetBIOS Post Routines.
  144.  
  145. WNETBIOS defines a routine called GetWindowsPostRoutine to fill in the
  146. WindowsPostType variable. GetWindowsPostRoutine is passed the
  147. NetBIOSPostRoutine to be called, the data segment of the caller, and a variable
  148. of type WindowsPostType. This routine calls the DPMI Allocate Real Mode
  149. Callback Address service to obtain a pointer to a procedure that can be called
  150. in real mode. This real mode callback will in turn call a specified protected
  151. mode procedure. If you look at the source to GetWindowsPostRoutine, you'll see
  152. the protected mode procedure we ask the real mode callback to call is not the
  153. NetBIOSPostRoutine, but rather a routine called CallbackShell.
  154.  
  155. CallbackShell is where we separate the men from the boys. This is a routine
  156. that knows the context of the system at the time the real mode callback calls
  157. the protected mode routine. CallbackShell then gets the information it needs to
  158. safely call the NetBIOSPostRoutine that had been specified in the call to
  159. GetWindowsPostRoutine.
  160.  
  161. DPMI doesn't go out of its way to make this easy for you. When the protected
  162. mode routine is called by the callback, you must grab the return address off of
  163. the real mode stack, place it into the drCS and drIP fields of the
  164. DPMIRegisters variable, and issue an IRET instruction. On entry into the
  165. protected mode routine, DPMI's real mode callback places a pointer to the real
  166. mode stack in DS:SI (DS contains a selector), and a pointer to the
  167. DPMIRegisters type in ES:DI. Our CallbackShell routine has the task of calling
  168. the NetBIOSPostRoutine. CallbackShell takes the following action:
  169.  
  170.   1) save all the registers we'll be modifying
  171.   2) grab the return address off the real mode stack
  172.   3) put the return address in the drCS:drIP fields of the DPMIRegisters
  173.   4) push the NetBIOS return code
  174.   5) push a pointer to the WinNCB associated with this event
  175.   6) setup the client program's DATA segment
  176.   7) call the user specified NetBIOSPostRoutine
  177.   8) restore the registers
  178.   9) issue an IRET
  179.  
  180. The need for callback routines is not limited to NetBIOS. Similar steps must be
  181. taken for Event Service Routines under NetWare's IPX and SPX protocols.
  182.  
  183. The UNETBIOS.PAS unit defines the interface to the WNETBIOS DLL. 
  184.  
  185. The final source file is the NBCHAT.PAS program. This is a quick and dirty chat
  186. program designed to test the routines presented here. The NBCHAT program first
  187. confirms that Windows is operating in Standard or 386 Enhanced mode. It then
  188. checks to see if NetBIOS is loaded. If so, it prompts the user for a NetBIOS
  189. name to give this workstation, and a name for the chat partner. It then adds
  190. the NetBIOS name for this workstation, allocates necessary resources, and
  191. enters the message loop.
  192.  
  193. The program sets up an ExitProc to handle program termination. Global typed
  194. constants (in Turbo Pascal, typed constants are simply initialized variables)
  195. are defined to track the state of various variables. The ExitProc uses these
  196. booleans to safely shut down any pending NetBIOS events and to release all
  197. allocated resources.
  198.  
  199. The main program logic is implemented in two routines: MessageLoop and
  200. PostRoutine. Here's how MessageLoop is implemented:
  201.  
  202.   procedure MessageLoop;
  203.   var
  204.     C : Char;
  205.   begin
  206.     WriteLn('Press space bar to enter message, ESC to quit');
  207.     ReceiveDatagram(RecN, NBNameNo, False, Post, SizeOf(String), SR);
  208.     repeat
  209.       while not KeyPressed do begin
  210.         if Pending then
  211.           ShowIncoming;
  212.       end;
  213.       C := ReadKey;
  214.       if C <> ^[ then
  215.         SendOutgoing;
  216.     until C = ^[;
  217.   end;
  218.  
  219. MessageLoop posts the first ReceiveDatagram call. It then sits in a loop until
  220. the escape key is pressed. If a key other than escape is pressed, then the user
  221. is prompted for a message to send, and the message is sent to the chat partner.
  222. While not processing messages to send, the loop constantly checks the status of
  223. the global boolean Pending. When Pending becomes True, it means an incoming
  224. message is waiting to be processed.
  225.  
  226. When the first ReceiveDatagram call is made by MessageLoop, it associates the
  227. call with a NetBIOS Post Routine appropriately called PostRoutine. When a
  228. datagram is received, NetBIOS (and our DPMI callback logic) will automatically
  229. call the Post Routine. Our Post Routine looks like this:
  230.  
  231.   procedure PostRoutine(LastError : Byte; N : WinNCBPtr); Far;
  232.   begin
  233.     if Exiting then Exit;
  234.     Pending := True;
  235.     if LastError = 0 then
  236.       Msg := SP^
  237.     else
  238.       Msg := 'NetBIOS error = ' + Num2Str(LastError);
  239.     ReceiveDatagram(N^, NBNameNo, False, Post, SizeOf(String), SR);
  240.   end;
  241.  
  242. It first checks to see if the program is shutting down in the ExitProc. If so,
  243. it exits without doing anything else. This prevents the PostRoutine from
  244. reissuing the ReceiveDatagram call. If the program isn't exiting, the global
  245. boolean Pending is set to True. If NetBIOS encountered an error, the global
  246. variable Msg is set to an error message, otherwise it is given the incoming
  247. string. Then the ReceiveDatagram call is reissued so NetBIOS can receive the
  248. next incoming datagram. Note that a Post Routine executes in an interrupts off
  249. state, much as if it was a interrupt service routine. You must be very careful
  250. what you do in post routines. This post routine simply sets some variables
  251. indicating a message has arrived and resubmits the WinNCB to listen for the
  252. next datagram. You cannot call DOS services from a Post Routine, but you may
  253. call most NetBIOS API calls.
  254.  
  255. The global variables SR and SP are pointers to strings. SP is a protected mode
  256. pointer, and SR is a real mode pointer. They are allocated through a call to
  257. GlobalDosAlloc. When accessing the string in our application, we must use SP.
  258. However, when we pass the string to NetBIOS, we must use SR, the real pointer.
  259. This is the same issue as discussed earlier when talking about the WinNCB
  260. record.
  261.  
  262. This is an overly simplistic program. In the real world, a program would
  263. probably post multiple ReceiveDatagram calls using a pool of WinNCBs to make
  264. sure rapidly incoming events were processed properly. The goal here wasn't to
  265. write the ultimate NetBIOS chat program, but to show how to use real mode API
  266. and callback services within a protected mode application.
  267.