Dec 232017
 
Provides chain capablity to TP V4.0+ by Kim Kokkenon.
File TPCHAIN.ZIP from The Programmer’s Corner in
Category Pascal Source Code
Provides chain capablity to TP V4.0+ by Kim Kokkenon.
File Name File Size Zip Size Zip Type
CHAIN.ASM 14616 3526 deflated
CHAIN.DOC 20952 7432 deflated
CHAIN.OBJ 706 639 deflated
CHAIN.PAS 3635 1183 deflated
DEMO.PAS 1117 540 deflated
GETMEM.ASM 11899 2579 deflated
GETMEM.OBJ 579 516 deflated

Download File TPCHAIN.ZIP Here

Contents of the CHAIN.DOC file


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.



 December 23, 2017  Add comments

Leave a Reply