home *** CD-ROM | disk | FTP | other *** search
- Chain Facility for Turbo Pascal
- Version 5.1
- Kim Kokkonen
-
- Overview
- ------------------------------------------------------------------------------
- Turbo Pascal 4.0 and 5.0 no longer support two features that many
- programmers have come to depend upon: Chain and Execute. This Chain facility
- provides a reasonable facsimile of the Turbo 3 Chain and Execute commands.
- Turbo's smart linker raises some new issues, though: see the Restrictions and
- Limitations section below before getting your hopes too high.
-
- The Chain facility is implemented in a small unit that you USE in your
- Turbo Pascal 4.0 or 5.0 program. The unit exports a function called Chain4
- which you call to chain to a new program. You can chain to any other EXE or
- COM file. Unlike Turbo 3, there is no restriction that the chained program be
- another Turbo Pascal program. The new program overwrites the current program
- in memory. Control will not return to the original program unless you chain
- back to it.
-
- This version of CHAIN has been updated to work with either Turbo Pascal 4.0
- or 5.0. When you compile CHAIN.PAS with either version, it will configure
- itself appropriately.
-
-
- Using CHAIN
- ------------------------------------------------------------------------------
- The source code for a unit named CHAIN is provided. Add this unit near the
- beginning of your USES statement. CHAIN depends on no other units, and uses
- about 700 bytes of code space. The first time you compile it, you'll need
- CHAIN.PAS, CHAIN.OBJ, and GETMEM.OBJ. Thereafter, you'll just need to have
- CHAIN.TPU to link into your program.
-
- CHAIN's central function is Chain4. It is declared as follows:
-
- function Chain4(Path, CmdLine : string) : word;
-
- The Path parameter to Chain4 specifies the name of the new program to execute.
- Path must be a complete program name, including the extension, and a drive or
- directory name if the file is not in the current directory. CmdLine is the
- equivalent of a DOS command line to pass to the new program. Due to the way
- CHAIN works, the command line is limited to 82 characters maximum. Longer
- command lines will be truncated to 82 characters.
-
- If chaining occurs successfully, the function will not return. If an error
- occurs, Chain4 returns a DOS error code. The following error codes are those
- most likely to occur:
-
- 2 File not found
- 4 Too many open files
- 8 Insufficient memory
- 30 Read fault
- 152 Drive not ready (mapped by Turbo's critical error handler)
-
- Beyond a certain point, Chain4 is committed to chaining and cannot return to
- the calling program even if an error occurs. In this case, it will simply halt
- and return control to DOS. The only known case is when a read error occurs
- while the new executable file is being loaded.
-
- Here are some example calls to Chain4:
-
- Status := Chain4('MENU.EXE', '');
-
- chains to MENU.EXE in the current directory, passing it an empty command line.
- Error information, if any, is returned in the word variable Status.
-
- Status := Chain4('C:\BIN\TPC.EXE', 'MYPROG /M /Q /$T+');
-
- chains to the command line Turbo compiler, telling it to compile the program
- MYPROG.PAS with various options.
-
-
- Restrictions and Limitations
- ------------------------------------------------------------------------------
- CHAIN works by using DOS function 4B, subfunction 03 (load overlay) to
- overwrite the current code in memory and transfer control to the new program.
- While performing the load overlay call, CHAIN executes a number of steps:
-
- o The new file is opened to check its type and/or size. If the file isn't
- found, Chain4 returns with error 2. If the first 28 bytes of the file
- cannot be read, Chain4 returns with error 30.
- o Available memory is compared to the amount needed by the program. If
- sufficient memory is not available, Chain4 returns with error 8.
- o All available memory is allocated to the process.
- o By default, all file handles except StdIn, StdOut, StdErr, and StdPrn are
- closed. You can stop Chain4 from closing files by setting the typed
- constant CloseFilesBeforeChaining to False. You shouldn't set it to False
- unless you enable data sharing between the original and chained programs
- as described below.
- o Interrupt vectors taken over by the Turbo SYSTEM unit are restored to
- their previous values. Which vectors are restored depends on the compiler
- version used to compile CHAIN.
- o The new command line is put in place within the program segment prefix.
- o FCB's normally initialized by the DOS loader are initialized using the new
- command line.
- o The machine stack is temporarily moved to the top of available memory, a
- step required for the load overlay call to work reliably. The newly loaded
- program will move the stack back to wherever it is normally located.
- o The DOS load overlay call is made to overwrite the original code with the
- new.
- o The registers DS, ES, SS, and SP are initialized the same way that the DOS
- loader normally would and control is transferred to the entry point of the
- program.
-
- CHAIN _cannot_ handle items on the following list. It is your responsibility
- to take any needed action (although some helpful procedures for dealing with
- these items are described later).
-
- o CHAIN does not close or reset the DOS standard file handles. If standard
- input or output is redirected, this will be passed on to the new program.
- This may or may not be desirable -- if not, the original program should
- reassign these handles before chaining.
- o Interrupt vectors grabbed by units other than SYSTEM must be restored. In
- particular, the CRT unit in Turbo Pascal 4.0 takes over interrupt 1Bh. You
- can restore it by using the DOS unit in your program, and executing the
- following statement prior to chaining: SetIntVec($1B, SaveInt1B). (Note
- that in Turbo Pascal 5.0, interrupt $1B is managed by the SYSTEM unit and
- no additional steps are required on your program's part.)
- o Memory allocation changes for the chained-to program (normally handled by
- the DOS EXE loader in accordance with the $M directive for the new
- program) are not performed. The new process will have all available memory
- when it starts up, overriding any {$M } heap settings that you may have
- specified. You can call the supplied SetMaxHeap procedure to adjust the
- maximum heap when another Turbo Pascal program is started.
- o Neither CHAIN nor the operating system can tell that the file you chain to
- is a valid executable file. If you chain to a text file or some other
- inappropriately formatted file, the system will crash.
-
- The DOS function call used by CHAIN is not used very often, and it appears
- that Microsoft or IBM didn't test it well in the days of DOS 2.X. As a result,
- CHAIN as supplied doesn't work with PC-DOS 2.0 and 2.1, or with some OEM
- versions of MS-DOS 2.x.
-
- To work around this problem, you must reassemble CHAIN.ASM after first editing
- it to modify an assembly directive. Change the line "DOS2X = 0" near the top
- of CHAIN.ASM to say
-
- DOS2X = 1
-
- You can reassemble CHAIN.ASM using Microsoft's MASM (version 4.0 or later)
- with the following DOS command line:
-
- MASM CHAIN;
-
- Or you can reassemble CHAIN.ASM using Borland's TASM with the following:
-
- TASM CHAIN
-
- Our testing indicates that this modified version of the Chain unit also runs
- fine under DOS 3.x. Even so, we have mixed feelings about using it -- the way
- memory allocation is handled in this version is different than the DOS
- documentation says it should be, but that's the only way it seems to work. If
- your chaining applications must run under DOS 2.x, set the DOS2X symbol to 1.
- Otherwise, don't.
-
- Many authors of large programs are using the public domain Extend facility to
- increase the maximum number of open files. If you are doing so, be warned that
- Chain4's automatic file closing feature does not work properly with Extend. In
- this case, your program must explicitly close any open files and call
- "UnExtend" before chaining.
-
- CHAIN provides a procedure to help solve the problem mentioned in the second
- bullet above, that of dangling interrupt vectors and other uncompleted exit
- processing.
-
- procedure ChainHalt(Path, CmdLine : string);
- {-Execute all exit handlers after the CHAIN unit, then chain as specified}
-
- If you call ChainHalt instead of Chain, all program exit handlers from units
- following CHAIN in the program USES list will be executed prior to chaining.
- ChainHalt does this by storing Path and CmdLine in global variables, halting
- the program, and then actually chaining when its exit handler is reached. For
- this technique to be effective, CHAIN must be first, or near the start, of the
- program's USES list.
-
- If a chaining error occurs while using ChainHalt, it simply halts the program
- with an exit code given by the chain status values described above.
-
- The CHAIN unit interfaces another routine, SetMaxHeap, that allows you to
- modify the heap setting of an executing program, and thereby work around the
- limitation described in the third bullet above. Here is the declaration for
- the routine:
-
- procedure SetMaxHeap(Bytes : LongInt);
- {-Set maximum heap and adjust DOS memory allocation block}
-
- If you need this routine (perhaps because the chained-to program must make an
- EXEC call), then you should call it immediately after the chained-to program
- starts, prior to any heap allocation. The parameter you specify to SetMaxHeap
- is the same as the number you would specify as the third parameter in a {$M }
- compiler directive. If there isn't sufficient memory to provide the requested
- number of bytes, SetMaxHeap doesn't do anything, since Chain4 will have
- already allocated all available memory to the process.
-
-
- Sharing Data
- ------------------------------------------------------------------------------
- Since the Turbo 4/5 runtime library is not completely incorporated into
- programs as it was in Turbo 3, it is not so easy to share data between chained
- programs. In fact, it is very likely that the new program will overwrite the
- data segment of the old. Without playing further tricks, about the only
- information that can be passed from the original program to the new program is
- the DOS command line. There is no way to directly share a data segment between
- two chaining programs, as there was in Turbo 3.
-
- By taking advantage of DOS memory allocation functions, however, there is a
- way to accomplish the same goal. The CHAIN unit exports three additional
- routines to make this task easy. Here are their declarations:
-
- procedure GetMemDos(var P : Pointer; Bytes : LongInt);
- {-Allocate memory from DOS, returning a pointer to the new block.
- Shrink Turbo allocation and relocate free list if forced to.
- Returns P = nil if unable to allocate space}
-
- function Pointer2String(P : Pointer) : string;
- {-Convert a pointer to a string suitable for passing on command line}
-
- function String2Pointer(S : string) : Pointer;
- {-Convert a string formatted by Pointer2String to a pointer
- Returns nil if S is an invalid string}
-
- We'll show how to use these routines after first describing what they do.
-
- GetMemDos works in a manner analogous to Turbo's own GetMem procedure,
- returning a pointer to a region of memory of size Bytes. Unlike GetMem,
- GetMemDos uses DOS allocation services rather than Turbo's own heap. By using
- DOS services, GetMemDos allocates a region of memory that cannot be
- overwritten during chaining. Also note that you can allocate regions larger
- than 64K bytes if desired.
-
- GetMemDos first tries to allocate space from DOS's own free area. If your
- program has freed memory previously, perhaps by setting the maximum heap size
- at compile time, DOS will immediately return a pointer to the free area. If
- DOS doesn't succeed, GetMemDos tries again a different way by checking the
- free space on Turbo's own heap. If sufficient heap space exists to meet the
- request, GetMemDos moves Turbo's free list down in memory, shrinks the current
- allocation of the process, and then allocates the freed up space as a separate
- DOS block. In either of these cases, GetMemDos returns a pointer to the base
- of the allocated block. If neither approach succeeds, GetMemDos returns a nil
- pointer.
-
- The Pointer2String and String2Pointer functions are used to pass the shared
- data area pointer between chaining programs. Pointer2String converts a pointer
- into an 8 character ASCII string suitable for passing on the command line to
- the chained program. String2Pointer performs the reverse operation. Note that
- the intermediate string format is not intended for printing.
-
- The best way to show these routines in action is with an example. Let's assume
- a simple two program system: MEM1 starts up the action and chains to MEM2,
- which can then chain back to MEM1. Here are the programs:
-
- SHARE.INC - holds common data declarations
- ------------------------------------------
- type
- ShareRec =
- record
- {Any shared data declared here as a record field}
- Counter : Integer;
- end;
- SharePtr = ^ShareRec;
- var
- ShareData : SharePtr;
-
-
- MEM1.PAS - the first program
- ------------------------------------------
- program Mem1;
- uses
- Chain;
- {$I SHARE.INC}
- var
- Status : Word;
- begin
- if ParamCount = 0 then begin
- {First time in, allocate shared memory space}
- GetMemDos(pointer(ShareData), sizeof(ShareRec));
- {See if we could allocate}
- if ShareData = nil then begin
- WriteLn('error allocating shared data area');
- Halt;
- end;
- {Initialize data}
- with ShareData^ do begin
- Counter := 0;
- {Initialize any other fields}
- end;
- end else begin
- {Get sharing pointer}
- ShareData := String2Pointer(ParamStr(1));
- {Do something with the data}
- with ShareData^ do
- Inc(Counter);
- end;
-
- WriteLn('in mem1 with counter=', ShareData^.Counter);
-
- {Chain to other program}
- Status := Chain4('mem2.exe', Pointer2String(ShareData));
- {Check for chaining error}
- end.
-
- MEM2.PAS - the second program
- ------------------------------------------
- program Mem2;
- uses
- Chain;
- {$I SHARE.INC}
- var
- Status : Word;
- begin
- {Get sharing pointer}
- ShareData := String2Pointer(ParamStr(1));
-
- {Check if it's ok}
- if ShareData = nil then begin
- WriteLn('program must be chained to');
- Halt;
- end;
-
- {Do something with the data}
- with ShareData^ do begin
- Inc(Counter);
- WriteLn('in mem2 with counter=', Counter);
- end;
-
- {Chain back}
- Status := Chain4('mem1.exe', ParamStr(1));
- {Check for error}
- end.
-
- Let's go through this step by step. The SHARE.INC file holds data declarations
- to be shared by all programs. The ShareRec record can hold up to 64K worth of
- shared data. Turbo limits any given record to 64K bytes, so if more than that
- is needed, additional records should be defined. The ShareData variable is
- simply a pointer used to access this record type. It is very important that
- both MEM1.PAS and MEM2.PAS include the same declaration file to guarantee that
- the data is identical.
-
- MEM1.PAS is the program to call first, from the DOS prompt. It uses a simple
- scheme to determine whether it has been called directly from DOS or by
- chaining from another program. In this example, we assume that no parameters
- on the DOS command line means that the program is being called for the first
- time. You could use more bullet-proof techniques to make the same decision --
- one way would be to have programs chaining to this one send it a special
- password as the first parameter on the command line.
-
- In any case, the first time MEM1 starts, it calls the GetMemDos procedure to
- allocate a shared data area. Here, GetMemDos allocates the number of bytes
- required to hold ShareRec, the shared data declaration. GetMemDos returns a
- pointer to this region. Note that GetMemDos returns an untyped pointer.
- Because of Pascal's strict data typing, we must _typecast_ the ShareData typed
- pointer as an untyped one. If GetMemDos can't allocate the required space, it
- returns a nil pointer, which we check for here.
-
- Next, MEM1 initializes the shared data area. Within the statement With
- ShareData^ Do Begin, we can refer to any of the shared data fields by name.
- Any other programs we chain to will be able to do the same thing.
-
- Let's follow this through assuming that MEM1 chains to MEM2. In MEM1's call to
- Chain4, it specifies the executable file name MEM2.EXE, and passes the
- ShareData pointer in ASCII format on the DOS command line. If Chain4 is
- successful it won't return, but we should check for errors by polling the
- Status variable after the call to Chain4.
-
- The next statement executed will be in the main block of MEM2.PAS. (Actually,
- any initialization blocks in units of MEM2 would be executed earlier if there
- were any. As it stands, these initialization blocks cannot refer to the shared
- data area.) MEM2 immediately takes the first command line parameter and
- converts it back to a pointer. If there is no command line, or if the first
- parameter isn't formatted like Pointer2String does it, String2Pointer will
- return nil. MEM2 checks for this and halts if there was an error.
-
- Again, this validation process could be bulletproofed. MEM1 could always pass
- MEM2 a password as the first command line parameter. Perhaps better yet, the
- MEM2.EXE file could be renamed to a non-executable DOS filename (like
- MEM2.CHN) so that it could never be executed unless chained to. The Chain4
- routine doesn't care what extension a file has.
-
- MEM2 can then refer to and modify any of the shared data inside of a With
- ShareData^ Do Begin statement. Then MEM2 chains back to MEM1, passing the same
- ShareData pointer back. MEM1 determines that it is being chained to, and does
- _not_ again allocate a shared data area.
-
- In the example, this cycle never stops. Obviously, in a real program certain
- actions would stop the program, returning control to DOS. No matter which
- program is active, DOS automatically deallocates not only the main memory
- block allocated to the program, but also the shared data block allocated
- separately.
-
- The easiest way to make programs share more than 64K of data is to call
- GetMemDos more than once, each time returning a pointer to a sub-64K region.
- Two or more pointers can be passed on the command line to other programs.
-
- If your program calls GetMemDos, Pointer2String, or String2Pointer, Turbo's
- smart linker will pull in about 300 bytes more code from these routines.
-
-
- Revision History
- ------------------------------------------------------------------------------
- Version 1.0 11/17/87
- First release.
- Version 1.1 11/18/87
- Check for sufficient memory before chaining.
- Add CloseFilesBeforeChaining constant to control file closing.
- Version 1.2 11/22/87
- Add routines for data sharing.
- Version 5.0 9/29/88
- Modified to work with either Turbo 4 or 5.
- Version 5.1 10/31/88
- Add ChainHalt routine
-
-
- Copyright and Acknowledgement
- ------------------------------------------------------------------------------
- The Chain Facility for Turbo Pascal is copyright (c) 1987 by TurboPower
- Software. All rights reserved.
-
- TurboPower Software hereby grants a limited license to use this software as
- follows:
-
- o The CHAIN unit and its source code may be distributed freely as long as
- there is no charge, with the exception of a handling fee not to exceed $10.
- o Programs using the CHAIN unit may be distributed without restriction,
- commercially or otherwise.
- o TurboPower Software's copyright notices must remain in the source code and
- documentation.
-
- TurboPower Software accepts no liability for the use of this software.
-
- Thanks to Ray Lambert, who showed that this function could be done in Turbo
- 3.0, which buoyed the morale during a few days of machine crashes while trying
- to make this one work.
-
- Contact Kim Kokkonen at Compuserve account 72457,2131 with comments regarding
- the Chain Facility.
-
-
- Advertisement
- ------------------------------------------------------------------------------
- TurboPower Software is in the business of providing powerful tools for the
- Turbo Pascal programmer.
-
- Our products for Turbo Pascal 4 and 5 include Turbo Professional, a library of
- more than 500 routines for screen management, TSRs, mouse support, huge
- arrays, and much more. And Turbo Analyst, a collection of programmer's
- utilities: pretty printer, cross-referencer, program lister, 3 execution
- profilers, and an integrated environment to make them easy to use.
-
- Call TurboPower Software at 408-438-8608 for more information.
-