home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Simtel MSDOS 1992 June
/
SIMTEL_0692.cdr
/
msdos
/
turbopas
/
tpfort12.arc
/
TPFORT.DOC
< prev
Wrap
Text File
|
1989-11-28
|
16KB
|
352 lines
TPFort v 1.2 - A link from Turbo Pascal to MS Fortran.
Copyright (c) 1989 D.J. Murdoch. All rights reserved.
PURPOSE
TPFort is a collection of several procedures that allow Microsoft Fortran
routines to be called from Turbo Pascal. I wrote it so that I could use the
binary-only NAG Fortran library for numerical routines in Turbo Pascal
programs, but it ended up being a general purpose linker.
PRICE
TPFort is absolutely free to use and incorporate into your own programs for
any purpose. Distribute it to anyone you like, but please don't remove my
copyright notice or modify it in any other way. Source code is available
(see below), but is not necessary to be able to use TPFort with Turbo Pascal
version 5.5.
METHOD
The Fortran routines are compiled into their own loader file which is loaded at
run time by a Turbo Pascal program, making most of the Fortran subroutines and
functions available to the Pascal program. The molasses-slow Fortran compiler
and linker need only be run once to create the loader; changes to the Pascal
part of the program don't force recompiling or re-linking of the Fortran part.
INSTRUCTIONS
There are several steps involved in preparing a Fortran routines to be called
from Turbo Pascal.
1. Preparing the Fortran Program
Write a Fortran program which includes the following declarations and a call
to CALLTP, in the following format:
EXTERNAL routine1, routine2, ..., routineX
CALL CALLTP(routine1, routine2, ..., routineX, X)
where routine1 through to routineX are the names of the Fortran routines you
wish to make available to your Turbo Pascal program, and X is an integer value
giving the number of routines being passed. The external declaration is
extremely important; if not given, Fortran will assume the routine names are
local integer or real variables, and things will get really messed up.
This loader may do anything else, such as reading data from files, allocating
space, etc. It's not all that important where the call to CALLTP takes place,
but more efficient use will be made of the program stack if the call comes
somewhere in the main program, rather than in a function or subroutine.
After this call and any other initialization necessary, the program should
exit. As this will close any open files, and I/O done while TP is active
is probably unreliable, it should complete any I/O operations before quitting,
and the routines being passed should avoid doing I/O.
Compile this routine and link it to the object file CALLTP.OBJ. Be sure to
specify to the linker that a larger than normal stack will be needed - I'd
suggest a minimum of 16K. The Turbo Pascal program will be using this stack
instead of its own much of the time, and TP makes much heavier use of the
stack than does Fortran.
Warning: Don't try running the loader program on its own, unless you avoid
executing the call to CALLTP. If TP isn't there to catch that call, you're
very likely to crash. It might be a good idea to rename the .EXE with a non-
executable extension such as .LDR just to be sure.
2. Preparing the TP dummy procedures
You need to create dummy versions of all the Fortran routines that you want to
call. They _must_ be declared as "far" routines, either through the use of
the $F+ compiler directive, or by putting them in the interface section of a
unit. I'd suggest isolating all of them into their own unit and interfacing
them.
Each of the dummy routines takes an argument list that corresponds exactly to
the argument list of the Fortran routine. By default, all Fortran arguments
are passed by reference, so these should be too, by declaring them as "var"
parameters. The following list gives corresponding types between the two
languages:
Fortran TP
INTEGER*2 integer
INTEGER*4 longint
INTEGER longint
REAL single
REAL*4 single
REAL*8 double
DOUBLE PRECISION double
CHARACTER*n array[1..n] of char
COMPLEX fort_complex8
COMPLEX*8 fort_complex8 These types will be declared in
COMPLEX*16 fort_complex16 the FortLink unit someday
LOGICAL fort_logical
EXTERNAL (special - see note below)
Note also that Fortran and TP use different conventions for the order of
indices in multi-dimensional arrays. For example, the Fortran array
REAL X(10,20,30)
would be declared as
x : array[1..30,1..20,1..10] of single;
in TP. Note also that TP (up to version 5.5, at least) has no facility for
variable dimensions on arrays: to handle an array which is declared as X(N,M)
you have to declare X as a one-dimensional array and handle the indexing
yourself.
Thus a call to the NAG matrix inversion routine F01AAF with Fortran
declaration
SUBROUTINE F01AAF(A, IA, N, UNIT, IUNIT, WKSPCE, IFAIL)
INTEGER IA, N, IUNIT, IFAIL
REAL*8 A(IA,N), UNIT(IUNIT,N), WKSPCE(N)
would be simulated with dummy declarations something like
procedure f01aaf(var a:realarray; { realarray is declared in the
FortLink unit }
var ia, n:longint;
var unit:realarray;
var iunit:longint;
var wkspce:realarray;
var ifail:longint);
and element A(I,J) would be addressed at a[i+(j-1)*ia].
The content of the dummy pascal routine is very simple, and should not be
varied. If the Fortran routine is a SUBROUTINE, use a definition like
const
f01aaf_num = 1; { this is the position of F01AAF in the call to CALLTP }
procedure f01aaf;
begin
callfort(f01aaf_num);
end;
If desired, additional instructions can be put before the call to callfort;
however, no local variables may be declared and no instructions may follow the
call.
If the Fortran routine is a FUNCTION, what to do depends on the function's
type. Fortran and TP agree on the convention for returning values up to 4
bytes (except singles/REAL*4), so callfort can be used for these functions.
The most common would be a Fortran INTEGER function being declared as a TP
longint function and using callfort.
However, Fortran and TP use different conventions for other return types, and
you need to use special calls to do the conversion. If the Fortran routine is
a REAL*8-valued FUNCTION, the "fdouble" procedure replaces callfort. Use
"fsingle" for REAL*4 values. For example, for the Gaussian random number
generator G05DDF, the Fortran declaration is
REAL*8 FUNCTION G05DDF(A, B)
REAL*8 A, B
and the Pascal declarations are
function g05ddf(var a,b:double):double;
with implementation
const g05ddf_num = 2;
function g05ddf;
begin
fdouble(g05ddf_num); { Note that this is a procedure! }
end;
Other structured types can also be returned with some care. You have to
declare the dummy function to be a pointer to the appropriate type, and use
the "fpointer(procnum)" call to the Fortran routine. TPFORT only reserves
8 bytes of space for return values, but larger values can be returned with
some trickery as described in FORTLINK.DOC in the header for fpointer.
3. Preparing the TP main program
Once you have your dummy procedure unit set up, you have to make some
modifications to the main program to link in the Fortran at run-time.
This is all done in a single call to
function loadfort(prog:string;TPentry:pointer):boolean;
The prog argument should contain a string giving the fully qualified name of
the Fortran program to be loaded; TPentry should give the address of a TP
routine taking no arguments, which is going to do all the calculations with
the Fortran routines. It's necessary to do things this way because the call
to loadfort switches over to the Fortran stack; TPentry^ and any routine it
calls must be able to execute there. If LoadFort is successful, it won't exit
until TPentry^ returns, and it'll give a True return value. If it fails
for some reason, it'll print a message and return a False value. In this
case TPEntry^ wasn't executed at all. It's possible to call loadfort several
times if you want to switch in and out of "Fortran mode", though I don't know
any reason to do so. Only the first time will load anything, but they'll all
attempt to switch to Fortran mode. Be sure never to call a Fortran
routine when you're not in Fortran mode, or you're likely to crash (or at
least get garbage out). To help determine the current status, the FortLink
unit interfaces two variables:
fortloaded : boolean; { True indicates Fortran routines are in memory }
fortsafe : boolean; { True indicates you're in Fortran mode }
If you like, you can put tests of fortsafe into your dummy routines before
the callfort or fdouble calls, to abort if there's a problem.
PASSING FUNCTION REFERENCES
It is possible to pass function or procedure references to a Fortran routine,
but it's a little tricky.
The Fortran setup is just the same as for any other kind of routine. Just
pass the procedure name in CALLTP.
The dummy definition is much the same. Declare the parameter which is the
routine being passed as type "extval", passed by value.
The main routine then calculates the reference extval using a call to
"fort_external(procnum)", where procnum is the number of a Fortran procedure
being passed, or "pas_external(@proc)", where proc is the Pascal procedure
being passed, and saves the answer in a temporary variable. It passes this
variable to the dummy routine.
The main bug in this procedure is that fort_external and pas_external
allocate space for a pointer on the stack, and leave it there. Thus you can't
execute them in the middle of an expression, or it will get messed up.
You should call the routine Clean_External as soon as possible after using
the temporary variable, to restore the stack to normal. Call it once for each
call you made to fort_external or pas_external. In a loop it's probably safe
to calculate the temporary once at the beginning and only clean it up once at
the end. You MUST reassign the temporary variable every time you enter the
routine that uses it, because its value becomes worthless as soon as you call
Clean_external or exit.
There's another bug in pas_external - the Pascal routine will be executed
fully in the Fortran context. In particular, this means all global references
will reference the wrong data segment, and TP is likely to overwrite registers
that Fortran expects to have preserved. To fix this up, at the very beginning
you must call Enter_Pascal, and you must call Leave_Pascal just before
exiting. This temporarily restores the Pascal context, and saves some
registers. Note that stack checking has to be disabled in a routine being
passed this way, since the stack checker makes a reference to the global
System.Stacklimit, and gets executed before Enter_Pascal.
Calls back to Fortran routines are allowed. Note that only dummy procedures
and functions defined with callfort may be recursive; functions using fsingle,
fdouble or fpointer can not be. For example, if the Fortran REAL*8 FUNCTION
Minimizer gets passed the TP Myfunction to minimize, then Myfunction can't
call Minimizer, but it can call some other Fortran routine. This isn't such a
large restriction though, because most Fortran routines don't allow recursive
calls anyways. (Actually there's a way around this: pass the Fortran
function through CALLTP several times. If the Fortran routine could handle
recursive calls normally, then the separate dummy functions will be able to
call each other.)
This method of passing routines works for TP functions only if they use the
same function-value passing convention as Fortran. Effectively this means
only char, integer and longint valued functions may be passed. There's no way
to call most other TP functions, but it's possible to construct TP functions
which simulate any Fortran function. Fortran expects the caller to allocate
temporary space for larger return values and expects the function to put the
value there. So, to write a TP routine that looks to Fortran as though it has
the declaration
REAL*8 FUNCTION SUMSQ(N,X)
INTEGER N
REAL*8 X(N)
write the header as follows:
function sumsq(var n:longint; var x:realarray; { Mimic the Fortran parameters
first }
value_ofs:word):double_ptr; { Always add another word for the
return address, and return a
pointer. "double_ptr" is a
pointer to a double declared in
FortLink }
See the sample program for the rest of the details.
EXAMPLE
A sample program is contained in the following files:
PSAMPLE.PAS The TP source for the main program
FSAMPLE.FOR The MS Fortran source for the loader & routines
FSAMPLE.PAS The dummy definitions for FSAMPLE
Also included is a Borland style MAKEFILE that compiles both parts.
Warning: There's a bug in MS Fortran 5.0 which means the sample program won't
run on some XT machines. If you crash when you try to run it, read about the
problem and a patch to fix it in FORTRAN.BUG.
LIMITATIONS
I have real doubts that Fortran I/O will work properly when called from TP,
but haven't tested it enough to know for sure.
Because Fortran keeps so much data in the stack segment, you might not be able
to increase the stack size large enough.
FILES
The following files should be included here:
MAKEFILE A Borland style make file for the demo. Just run MAKE.
TPFORT DOC This file
FSAMPLE FOR The demo Fortran source code
CALLTP OBJ The object code to be linked to the Fortran routine
FSAMPLE PAS The dummy definitions to access the Fortran code
PSAMPLE PAS The demo Pascal source code
FORTLINK TPU The unit that handles the linking, compiled under TP 5.5
FORTLINK DOC The interface section from FortLink.tpu
FORTRAN BUG Description of a bug and a patch for MS FORTRAN 5.0
COMMENTS AND QUESTIONS
Send any comments and/or bug reports to me at one of the following addresses:
Duncan Murdoch
79 John St W
Waterloo, Ontario, Canada
N2L 1B7
Internet: dmurdoch@watstat.waterloo.edu
Fidonet: dj murdoch at 1:221/180.4
Compuserve: 71631,122
SOURCE CODE
TPFORT makes some use of the Turbo Professional library from TurboPower. It's
a mixture of Turbo Pascal 5.5 and A86 assembler, and requires the Turbo
Professional library to recompile. The source code can be obtained from me
for $125. If you need the source for educational or other non-profit purposes
and this price is too high, write to me and we may be able to arrange a
discount.
WARRANTY
There is no warranty of any kind with this program. Use it for free; I hope
you get some value out of it.
RELEASE HISTORY
1.2 - added support for floating point emulation.
1.1 - added support for limited recursion, and many function return types.
1.0 - first release.