home *** CD-ROM | disk | FTP | other *** search
/ Power-Programmierung / CD1.mdf / pascal / library / dos / t_power / chain.doc < prev    next >
Text File  |  1988-10-31  |  21KB  |  451 lines

  1.                       Chain Facility for Turbo Pascal
  2.                                Version 5.1
  3.                                Kim Kokkonen
  4.  
  5. Overview
  6. ------------------------------------------------------------------------------
  7. Turbo Pascal 4.0 and 5.0 no longer support two features that many
  8. programmers have come to depend upon: Chain and Execute. This Chain facility
  9. provides a reasonable facsimile of the Turbo 3 Chain and Execute commands.
  10. Turbo's smart linker raises some new issues, though: see the Restrictions and
  11. Limitations section below before getting your hopes too high.
  12.  
  13. The Chain facility is implemented in a small unit that you USE in your
  14. Turbo Pascal 4.0 or 5.0 program. The unit exports a function called Chain4
  15. which you call to chain to a new program. You can chain to any other EXE or
  16. COM file. Unlike Turbo 3, there is no restriction that the chained program be
  17. another Turbo Pascal program. The new program overwrites the current program
  18. in memory. Control will not return to the original program unless you chain
  19. back to it.
  20.  
  21. This version of CHAIN has been updated to work with either Turbo Pascal 4.0
  22. or 5.0. When you compile CHAIN.PAS with either version, it will configure
  23. itself appropriately.
  24.  
  25.  
  26. Using CHAIN
  27. ------------------------------------------------------------------------------
  28. The source code for a unit named CHAIN is provided. Add this unit near the
  29. beginning of your USES statement. CHAIN depends on no other units, and uses
  30. about 700 bytes of code space. The first time you compile it, you'll need
  31. CHAIN.PAS, CHAIN.OBJ, and GETMEM.OBJ. Thereafter, you'll just need to have
  32. CHAIN.TPU to link into your program.
  33.  
  34. CHAIN's central function is Chain4. It is declared as follows:
  35.  
  36.   function Chain4(Path, CmdLine : string) : word;
  37.  
  38. The Path parameter to Chain4 specifies the name of the new program to execute.
  39. Path must be a complete program name, including the extension, and a drive or
  40. directory name if the file is not in the current directory. CmdLine is the
  41. equivalent of a DOS command line to pass to the new program. Due to the way
  42. CHAIN works, the command line is limited to 82 characters maximum. Longer
  43. command lines will be truncated to 82 characters.
  44.  
  45. If chaining occurs successfully, the function will not return. If an error
  46. occurs, Chain4 returns a DOS error code. The following error codes are those
  47. most likely to occur:
  48.  
  49.    2  File not found
  50.    4  Too many open files
  51.    8  Insufficient memory
  52.   30  Read fault
  53.  152  Drive not ready (mapped by Turbo's critical error handler)
  54.  
  55. Beyond a certain point, Chain4 is committed to chaining and cannot return to
  56. the calling program even if an error occurs. In this case, it will simply halt
  57. and return control to DOS. The only known case is when a read error occurs
  58. while the new executable file is being loaded.
  59.  
  60. Here are some example calls to Chain4:
  61.  
  62.   Status := Chain4('MENU.EXE', '');
  63.  
  64. chains to MENU.EXE in the current directory, passing it an empty command line.
  65. Error information, if any, is returned in the word variable Status.
  66.  
  67.   Status := Chain4('C:\BIN\TPC.EXE', 'MYPROG /M /Q /$T+');
  68.  
  69. chains to the command line Turbo compiler, telling it to compile the program
  70. MYPROG.PAS with various options.
  71.  
  72.  
  73. Restrictions and Limitations
  74. ------------------------------------------------------------------------------
  75. CHAIN works by using DOS function 4B, subfunction 03 (load overlay) to
  76. overwrite the current code in memory and transfer control to the new program.
  77. While performing the load overlay call, CHAIN executes a number of steps:
  78.  
  79.   o The new file is opened to check its type and/or size. If the file isn't
  80.     found, Chain4 returns with error 2. If the first 28 bytes of the file
  81.     cannot be read, Chain4 returns with error 30.
  82.   o Available memory is compared to the amount needed by the program. If
  83.     sufficient memory is not available, Chain4 returns with error 8.
  84.   o All available memory is allocated to the process.
  85.   o By default, all file handles except StdIn, StdOut, StdErr, and StdPrn are
  86.     closed. You can stop Chain4 from closing files by setting the typed
  87.     constant CloseFilesBeforeChaining to False. You shouldn't set it to False
  88.     unless you enable data sharing between the original and chained programs
  89.     as described below.
  90.   o Interrupt vectors taken over by the Turbo SYSTEM unit are restored to
  91.     their previous values. Which vectors are restored depends on the compiler
  92.     version used to compile CHAIN.
  93.   o The new command line is put in place within the program segment prefix.
  94.   o FCB's normally initialized by the DOS loader are initialized using the new
  95.     command line.
  96.   o The machine stack is temporarily moved to the top of available memory, a
  97.     step required for the load overlay call to work reliably. The newly loaded
  98.     program will move the stack back to wherever it is normally located.
  99.   o The DOS load overlay call is made to overwrite the original code with the
  100.     new.
  101.   o The registers DS, ES, SS, and SP are initialized the same way that the DOS
  102.     loader normally would and control is transferred to the entry point of the
  103.     program.
  104.  
  105. CHAIN _cannot_ handle items on the following list. It is your responsibility
  106. to take any needed action (although some helpful procedures for dealing with
  107. these items are described later).
  108.  
  109.   o CHAIN does not close or reset the DOS standard file handles. If standard
  110.     input or output is redirected, this will be passed on to the new program.
  111.     This may or may not be desirable -- if not, the original program should
  112.     reassign these handles before chaining.
  113.   o Interrupt vectors grabbed by units other than SYSTEM must be restored. In
  114.     particular, the CRT unit in Turbo Pascal 4.0 takes over interrupt 1Bh. You
  115.     can restore it by using the DOS unit in your program, and executing the
  116.     following statement prior to chaining: SetIntVec($1B, SaveInt1B). (Note
  117.     that in Turbo Pascal 5.0, interrupt $1B is managed by the SYSTEM unit and
  118.     no additional steps are required on your program's part.)
  119.   o Memory allocation changes for the chained-to program (normally handled by
  120.     the DOS EXE loader in accordance with the $M directive for the new
  121.     program) are not performed. The new process will have all available memory
  122.     when it starts up, overriding any {$M } heap settings that you may have
  123.     specified. You can call the supplied SetMaxHeap procedure to adjust the
  124.     maximum heap when another Turbo Pascal program is started.
  125.   o Neither CHAIN nor the operating system can tell that the file you chain to
  126.     is a valid executable file. If you chain to a text file or some other
  127.     inappropriately formatted file, the system will crash.
  128.  
  129. The DOS function call used by CHAIN is not used very often, and it appears
  130. that Microsoft or IBM didn't test it well in the days of DOS 2.X. As a result,
  131. CHAIN as supplied doesn't work with PC-DOS 2.0 and 2.1, or with some OEM
  132. versions of MS-DOS 2.x.
  133.  
  134. To work around this problem, you must reassemble CHAIN.ASM after first editing
  135. it to modify an assembly directive. Change the line "DOS2X = 0" near the top
  136. of CHAIN.ASM to say
  137.  
  138.   DOS2X = 1
  139.  
  140. You can reassemble CHAIN.ASM using Microsoft's MASM (version 4.0 or later)
  141. with the following DOS command line:
  142.  
  143.   MASM CHAIN;
  144.  
  145. Or you can reassemble CHAIN.ASM using Borland's TASM with the following:
  146.  
  147.   TASM CHAIN
  148.  
  149. Our testing indicates that this modified version of the Chain unit also runs
  150. fine under DOS 3.x. Even so, we have mixed feelings about using it -- the way
  151. memory allocation is handled in this version is different than the DOS
  152. documentation says it should be, but that's the only way it seems to work. If
  153. your chaining applications must run under DOS 2.x, set the DOS2X symbol to 1.
  154. Otherwise, don't.
  155.  
  156. Many authors of large programs are using the public domain Extend facility to
  157. increase the maximum number of open files. If you are doing so, be warned that
  158. Chain4's automatic file closing feature does not work properly with Extend. In
  159. this case, your program must explicitly close any open files and call
  160. "UnExtend" before chaining.
  161.  
  162. CHAIN provides a procedure to help solve the problem mentioned in the second
  163. bullet above, that of dangling interrupt vectors and other uncompleted exit
  164. processing.
  165.  
  166.   procedure ChainHalt(Path, CmdLine : string);
  167.     {-Execute all exit handlers after the CHAIN unit, then chain as specified}
  168.  
  169. If you call ChainHalt instead of Chain, all program exit handlers from units
  170. following CHAIN in the program USES list will be executed prior to chaining.
  171. ChainHalt does this by storing Path and CmdLine in global variables, halting
  172. the program, and then actually chaining when its exit handler is reached. For
  173. this technique to be effective, CHAIN must be first, or near the start, of the
  174. program's USES list.
  175.  
  176. If a chaining error occurs while using ChainHalt, it simply halts the program
  177. with an exit code given by the chain status values described above.
  178.  
  179. The CHAIN unit interfaces another routine, SetMaxHeap, that allows you to
  180. modify the heap setting of an executing program, and thereby work around the
  181. limitation described in the third bullet above. Here is the declaration for
  182. the routine:
  183.  
  184.   procedure SetMaxHeap(Bytes : LongInt);
  185.     {-Set maximum heap and adjust DOS memory allocation block}
  186.  
  187. If you need this routine (perhaps because the chained-to program must make an
  188. EXEC call), then you should call it immediately after the chained-to program
  189. starts, prior to any heap allocation. The parameter you specify to SetMaxHeap
  190. is the same as the number you would specify as the third parameter in a {$M }
  191. compiler directive. If there isn't sufficient memory to provide the requested
  192. number of bytes, SetMaxHeap doesn't do anything, since Chain4 will have
  193. already allocated all available memory to the process.
  194.  
  195.  
  196. Sharing Data
  197. ------------------------------------------------------------------------------
  198. Since the Turbo 4/5 runtime library is not completely incorporated into
  199. programs as it was in Turbo 3, it is not so easy to share data between chained
  200. programs. In fact, it is very likely that the new program will overwrite the
  201. data segment of the old. Without playing further tricks, about the only
  202. information that can be passed from the original program to the new program is
  203. the DOS command line. There is no way to directly share a data segment between
  204. two chaining programs, as there was in Turbo 3.
  205.  
  206. By taking advantage of DOS memory allocation functions, however, there is a
  207. way to accomplish the same goal. The CHAIN unit exports three additional
  208. routines to make this task easy. Here are their declarations:
  209.  
  210. procedure GetMemDos(var P : Pointer; Bytes : LongInt);
  211.   {-Allocate memory from DOS, returning a pointer to the new block.
  212.     Shrink Turbo allocation and relocate free list if forced to.
  213.     Returns P = nil if unable to allocate space}
  214.  
  215. function Pointer2String(P : Pointer) : string;
  216.   {-Convert a pointer to a string suitable for passing on command line}
  217.  
  218. function String2Pointer(S : string) : Pointer;
  219.   {-Convert a string formatted by Pointer2String to a pointer
  220.     Returns nil if S is an invalid string}
  221.  
  222. We'll show how to use these routines after first describing what they do.
  223.  
  224. GetMemDos works in a manner analogous to Turbo's own GetMem procedure,
  225. returning a pointer to a region of memory of size Bytes. Unlike GetMem,
  226. GetMemDos uses DOS allocation services rather than Turbo's own heap. By using
  227. DOS services, GetMemDos allocates a region of memory that cannot be
  228. overwritten during chaining. Also note that you can allocate regions larger
  229. than 64K bytes if desired.
  230.  
  231. GetMemDos first tries to allocate space from DOS's own free area. If your
  232. program has freed memory previously, perhaps by setting the maximum heap size
  233. at compile time, DOS will immediately return a pointer to the free area. If
  234. DOS doesn't succeed, GetMemDos tries again a different way by checking the
  235. free space on Turbo's own heap. If sufficient heap space exists to meet the
  236. request, GetMemDos moves Turbo's free list down in memory, shrinks the current
  237. allocation of the process, and then allocates the freed up space as a separate
  238. DOS block. In either of these cases, GetMemDos returns a pointer to the base
  239. of the allocated block. If neither approach succeeds, GetMemDos returns a nil
  240. pointer.
  241.  
  242. The Pointer2String and String2Pointer functions are used to pass the shared
  243. data area pointer between chaining programs. Pointer2String converts a pointer
  244. into an 8 character ASCII string suitable for passing on the command line to
  245. the chained program. String2Pointer performs the reverse operation. Note that
  246. the intermediate string format is not intended for printing.
  247.  
  248. The best way to show these routines in action is with an example. Let's assume
  249. a simple two program system: MEM1 starts up the action and chains to MEM2,
  250. which can then chain back to MEM1. Here are the programs:
  251.  
  252. SHARE.INC - holds common data declarations
  253. ------------------------------------------
  254. type
  255.   ShareRec =
  256.   record
  257.     {Any shared data declared here as a record field}
  258.     Counter : Integer;
  259.   end;
  260.   SharePtr = ^ShareRec;
  261. var
  262.   ShareData : SharePtr;
  263.  
  264.  
  265. MEM1.PAS - the first program
  266. ------------------------------------------
  267. program Mem1;
  268. uses
  269.   Chain;
  270. {$I SHARE.INC}
  271. var
  272.   Status : Word;
  273. begin
  274.   if ParamCount = 0 then begin
  275.     {First time in, allocate shared memory space}
  276.     GetMemDos(pointer(ShareData), sizeof(ShareRec));
  277.     {See if we could allocate}
  278.     if ShareData = nil then begin
  279.       WriteLn('error allocating shared data area');
  280.       Halt;
  281.     end;
  282.     {Initialize data}
  283.     with ShareData^ do begin
  284.       Counter := 0;
  285.       {Initialize any other fields}
  286.     end;
  287.   end else begin
  288.     {Get sharing pointer}
  289.     ShareData := String2Pointer(ParamStr(1));
  290.     {Do something with the data}
  291.     with ShareData^ do
  292.       Inc(Counter);
  293.   end;
  294.  
  295.   WriteLn('in mem1 with counter=', ShareData^.Counter);
  296.  
  297.   {Chain to other program}
  298.   Status := Chain4('mem2.exe', Pointer2String(ShareData));
  299.   {Check for chaining error}
  300. end.
  301.  
  302. MEM2.PAS - the second program
  303. ------------------------------------------
  304. program Mem2;
  305. uses
  306.   Chain;
  307. {$I SHARE.INC}
  308. var
  309.   Status : Word;
  310. begin
  311.   {Get sharing pointer}
  312.   ShareData := String2Pointer(ParamStr(1));
  313.  
  314.   {Check if it's ok}
  315.   if ShareData = nil then begin
  316.     WriteLn('program must be chained to');
  317.     Halt;
  318.   end;
  319.  
  320.   {Do something with the data}
  321.   with ShareData^ do begin
  322.     Inc(Counter);
  323.     WriteLn('in mem2 with counter=', Counter);
  324.   end;
  325.  
  326.   {Chain back}
  327.   Status := Chain4('mem1.exe', ParamStr(1));
  328.   {Check for error}
  329. end.
  330.  
  331. Let's go through this step by step. The SHARE.INC file holds data declarations
  332. to be shared by all programs. The ShareRec record can hold up to 64K worth of
  333. shared data. Turbo limits any given record to 64K bytes, so if more than that
  334. is needed, additional records should be defined. The ShareData variable is
  335. simply a pointer used to access this record type. It is very important that
  336. both MEM1.PAS and MEM2.PAS include the same declaration file to guarantee that
  337. the data is identical.
  338.  
  339. MEM1.PAS is the program to call first, from the DOS prompt. It uses a simple
  340. scheme to determine whether it has been called directly from DOS or by
  341. chaining from another program. In this example, we assume that no parameters
  342. on the DOS command line means that the program is being called for the first
  343. time. You could use more bullet-proof techniques to make the same decision --
  344. one way would be to have programs chaining to this one send it a special
  345. password as the first parameter on the command line.
  346.  
  347. In any case, the first time MEM1 starts, it calls the GetMemDos procedure to
  348. allocate a shared data area. Here, GetMemDos allocates the number of bytes
  349. required to hold ShareRec, the shared data declaration. GetMemDos returns a
  350. pointer to this region. Note that GetMemDos returns an untyped pointer.
  351. Because of Pascal's strict data typing, we must _typecast_ the ShareData typed
  352. pointer as an untyped one. If GetMemDos can't allocate the required space, it
  353. returns a nil pointer, which we check for here.
  354.  
  355. Next, MEM1 initializes the shared data area. Within the statement With
  356. ShareData^ Do Begin, we can refer to any of the shared data fields by name.
  357. Any other programs we chain to will be able to do the same thing.
  358.  
  359. Let's follow this through assuming that MEM1 chains to MEM2. In MEM1's call to
  360. Chain4, it specifies the executable file name MEM2.EXE, and passes the
  361. ShareData pointer in ASCII format on the DOS command line. If Chain4 is
  362. successful it won't return, but we should check for errors by polling the
  363. Status variable after the call to Chain4.
  364.  
  365. The next statement executed will be in the main block of MEM2.PAS. (Actually,
  366. any initialization blocks in units of MEM2 would be executed earlier if there
  367. were any. As it stands, these initialization blocks cannot refer to the shared
  368. data area.) MEM2 immediately takes the first command line parameter and
  369. converts it back to a pointer. If there is no command line, or if the first
  370. parameter isn't formatted like Pointer2String does it, String2Pointer will
  371. return nil. MEM2 checks for this and halts if there was an error.
  372.  
  373. Again, this validation process could be bulletproofed. MEM1 could always pass
  374. MEM2 a password as the first command line parameter. Perhaps better yet, the
  375. MEM2.EXE file could be renamed to a non-executable DOS filename (like
  376. MEM2.CHN) so that it could never be executed unless chained to. The Chain4
  377. routine doesn't care what extension a file has.
  378.  
  379. MEM2 can then refer to and modify any of the shared data inside of a With
  380. ShareData^ Do Begin statement. Then MEM2 chains back to MEM1, passing the same
  381. ShareData pointer back. MEM1 determines that it is being chained to, and does
  382. _not_ again allocate a shared data area.
  383.  
  384. In the example, this cycle never stops. Obviously, in a real program certain
  385. actions would stop the program, returning control to DOS. No matter which
  386. program is active, DOS automatically deallocates not only the main memory
  387. block allocated to the program, but also the shared data block allocated
  388. separately.
  389.  
  390. The easiest way to make programs share more than 64K of data is to call
  391. GetMemDos more than once, each time returning a pointer to a sub-64K region.
  392. Two or more pointers can be passed on the command line to other programs.
  393.  
  394. If your program calls GetMemDos, Pointer2String, or String2Pointer, Turbo's
  395. smart linker will pull in about 300 bytes more code from these routines.
  396.  
  397.  
  398. Revision History
  399. ------------------------------------------------------------------------------
  400. Version 1.0 11/17/87
  401.   First release.
  402. Version 1.1 11/18/87
  403.   Check for sufficient memory before chaining.
  404.   Add CloseFilesBeforeChaining constant to control file closing.
  405. Version 1.2 11/22/87
  406.   Add routines for data sharing.
  407. Version 5.0  9/29/88
  408.   Modified to work with either Turbo 4 or 5.
  409. Version 5.1 10/31/88
  410.   Add ChainHalt routine
  411.  
  412.  
  413. Copyright and Acknowledgement
  414. ------------------------------------------------------------------------------
  415. The Chain Facility for Turbo Pascal is copyright (c) 1987 by TurboPower
  416. Software. All rights reserved.
  417.  
  418. TurboPower Software hereby grants a limited license to use this software as
  419. follows:
  420.  
  421.   o The CHAIN unit and its source code may be distributed freely as long as
  422.     there is no charge, with the exception of a handling fee not to exceed $10.
  423.   o Programs using the CHAIN unit may be distributed without restriction,
  424.     commercially or otherwise.
  425.   o TurboPower Software's copyright notices must remain in the source code and
  426.     documentation.
  427.  
  428. TurboPower Software accepts no liability for the use of this software.
  429.  
  430. Thanks to Ray Lambert, who showed that this function could be done in Turbo
  431. 3.0, which buoyed the morale during a few days of machine crashes while trying
  432. to make this one work.
  433.  
  434. Contact Kim Kokkonen at Compuserve account 72457,2131 with comments regarding
  435. the Chain Facility.
  436.  
  437.  
  438. Advertisement
  439. ------------------------------------------------------------------------------
  440. TurboPower Software is in the business of providing powerful tools for the
  441. Turbo Pascal programmer.
  442.  
  443. Our products for Turbo Pascal 4 and 5 include Turbo Professional, a library of
  444. more than 500 routines for screen management, TSRs, mouse support, huge
  445. arrays, and much more. And Turbo Analyst, a collection of programmer's
  446. utilities: pretty printer, cross-referencer, program lister, 3 execution
  447. profilers, and an integrated environment to make them easy to use.
  448.  
  449. Call TurboPower Software at 408-438-8608 for more information.
  450.  
  451.