home *** CD-ROM | disk | FTP | other *** search
- TPFort v 1.82 - A link from Turbo Pascal 5.0-6.0 to MS Fortran 5.0 or 5.1.
-
- Copyright (c) 1989,1990,1991 D.J. Murdoch. All rights reserved.
- Portions copyright (c) TurboPower Software. Used with permission.
-
-
- 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 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.
- If you modify it in any way, please do not distribute the modified version.
- Full source code is included.
-
-
- 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.
-
- The Fortran loader will be loaded at the top of the TP heap. The program
- NEEDMEM is provided to print out the memory requirements of the loader. If the
- total required is greater than TP's MaxAvail, TPFORT won't run.
-
-
- INSTRUCTIONS
-
- There are several steps involved in preparing 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. I've only
- tested TPFORT with the default large memory model, but it should work in the
- huge model as well. It won't work in the medium model, because TPFORT expects
- full 4 byte addresses.
-
- NB: With version 5.1 of the Fortran compiler, the loader routine (and possibly
- others) MUST be compiled with the /Gb option for backward compatibility.
- If not, programs will crash on the first call.
-
- I recommend using the /FPi87 floating point option, to get the smallest code,
- but if you don't have a coprocessor you'll need /FPi or one of the other /FP
- options. (It would be safe to use /FPi87 even if you don't have a
- coprocessor, if you link the TP emulator in, but I don't think the Fortran
- startup code will run if you don't.)
-
- 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 (special - see note below)
- CHARACTER*(*) (special - see note below)
- 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 6.0, 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 TP 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 TP 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.
-
- If you want to reclaim the memory used by your loader, call UnloadFort
- when you're *not* in Fortran mode. If you call it in Fortran mode, the
- call will be ignored. You might want to use this feature to load
- different Fortran libraries; only one may be loaded at a time.
-
-
- PASSING STRINGS AND CHARACTER VARIABLES
-
- In Turbo Pascal, the "string" type stores the current length in the first
- byte. MS Fortran doesn't have a corresponding type; however, it does allow
- variable length character variables to be passed to a routine with the
- "CHARACTER*(*)" declaration. The way this is handled internally is
- undocumented, as far as I know, but I've tried to make a guess and my tests
- seem to work.
-
- It appears that any time CHARACTER variables are passed as arguments to a
- SUBROUTINE or FUNCTION, their lengths are stored in a table of word-sized
- values. A pointer to this table is stored at a fixed location, in the global
- variable __fcclenv. To give access to this table, FORTLINK sets up a
- pointer to __fcclenv in the global Size_Table.
-
- Thus, to pass CHARACTER variables to a Fortran routine, two extra steps are
- necessary. First, you have to construct a table of the current lengths of
- each CHARACTER argument; second, you have to set __fcclenv to point to it.
- Typical code to pass strings s1 and s2 to Fortran procedure UCHAR would be
-
- type
- my_length_rec = record
- dummy, { I don't know what the first entry in the table is for }
- s1_length,
- s2_length : word;
- end;
- var
- my_lengths : my_length_rec;
- s1,s2 : string;
-
- begin
- with my_lengths do
- begin
- s1_length := length(s1);
- s2_length := length(s2);
- end;
- Size_Table^ := @my_lengths; { Note the ^ and @ ! Size_Table itself
- should never be changed }
-
- Uchar(..{non character args}...,
- s1[1], ....{more non characters} ..., { Skip the length byte! }
- s2[1], ....{etc.} );
-
- In the TP declaration of Uchar, the character arguments could be type char, or
- untyped. The same sort of technique would be used to pass arrays of char to
- Fortran.
-
- As described below, it's also possible to have Fortran call TP procedures and
- functions. In this case, the TP procedure should save the sizes of any
- character arguments just after Enter_Pascal is called; the Size_Table^ pointer
- will be changed by any activity in Fortran at all. The length of the first
- character argument will be in Size_Table^^[1]; others will be in appropriate
- places in the array.
-
- Note: Because all of the above is undocumented by Microsoft, there may be
- mistakes. Test it carefully before you depend on it!
-
-
- 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 TP 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 TP 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 TP 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.)
-
- It's also possible to call a routine that was passed in as an Extval: use the
- Ext_Pointer function to convert it into a procedure pointer, and call that.
- For example, the following TP routine might be called from Fortran:
-
- procedure callit(routine:extval);
- type
- proc_type = procedure(i:integer); { Declare a procedure type }
- var
- the_routine : ^proc_type; { and a pointer to it }
- begin
- the_routine := ext_pointer(routine); { Convert the extval to a pointer }
- the_routine^(5); { and call it with argument 5 }
- end;
-
- This works well as long as the routine being passed is known to be a TP
- routine. If it's Fortran, you must "Leave_Pascal" before the call and
- "Enter_Pascal" after it. The catch is that once you Leave_Pascal, you won't
- have access to the TP data segment. Only local variables will be accessible.
- If the function could be written in either language, it's probably safest to
- assume Fortran, and be sure that any TP routine that might be passed uses the
- Enter_Pascal and Leave_Pascal routines. They're safe to use in any TP
- routine, whether it's being called from TP or Fortran.
-
- Note 1: Leave_Pascal will only restore the Fortran context if Enter_Pascal
- was used to save it. This means you can't call a Fortran routine using the
- Ext_Pointer pointer unless you're in a routine that was called by Fortran. I
- don't think this is a big restriction, however, since you wouldn't normally
- need to use an Extval at all, unless you're dealing pretty closely with
- Fortran.
-
- Note 2: The 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.
-
-
- SUPPRESSING FORTRAN ERRORS
-
- You can suppress many Fortran intrinsic errors by reprogramming the
- coprocessor, but that doesn't always work. (This is a bug in MS Fortran,
- not in TPFORT. MS gives no way to suppress some errors.) There's a
- variable called _MERRQQ which appears to suppress the abort when set to
- a non-zero value; FortErrorFlag is a pointer to it.
-
- The MS Fortran 5.1 documentation claims to allow more control over errors than
- was allowed in version 5.0. I don't know if it works, or whether the method
- above is still necessary.
-
-
- LIMITATIONS
-
- I had real doubts that Fortran I/O would work properly when called from TP,
- but have successfully used it in several programs. Still, I mistrust it.
-
- 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 ASM A86 source code for CALLTP
- 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 TP source code
- FORTLINK PAS Source to the main TPFORT unit
- CALLFORT ASM A86 source code for external routines in FORTLINK
- CALLFORT OBJ Object file for linking into FORTLINK.TPU
- OPRO INC A few routines extracted (with permission) from the
- Object Professional library
- FORTRAN BUG Description of a bug and a patch for MS FORTRAN 5.0
- NEEDMEM EXE Program to determine memory requirements of Fortran loader
-
-
- 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/177.40
- Compuserve: 71631,122
-
-
- SOURCE CODE
-
- TPFORT is a mixture of Turbo Pascal and A86 assembler. A few Object
- Professional routines have been included in the file OPRO.INC with the
- kind permission of Kim Kokkonen, of TurboPower Software. If you have
- Object Professional, you can set the OPRO_VER compiler variable, and the
- real OPro routines will be linked instead.
-
- If you don't own Object Professional, leave OPRO_VER undefined and
- OPRO.INC will be automatically included. However, if you don't own
- Object Professional you're really missing out; I'd suggest buying it.
- You can contact TurboPower at 800-333-4160 or 719-260-6641 (voice),
- 719-260-7151 (fax), or by email to Compuserve ID 76004,2611 (that's
- 76004.2611@compuserve.com on Internet).
-
-
- 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.82 - Added OPRO.INC to distribution; added UnLoadFort procedure.
- 1.81 - Fixed MAKEFILE errors, added check for valid proc addresses
- 1.8 - Cleaned up memory management, added version tests and Loaderror
- variable and messages
- 1.7 - added FortErrorFlag
- 1.6 - saves coprocessor or emulator state before running Fortran loader
- 1.5 - corrected bugs, and added Ext_Pointer function
- 1.4 - corrected support for CHARACTER types
- 1.3 - fixed bug to allow large Fortran programs, added NEEDMEM program
- 1.2 - added support for floating point emulation.
- 1.1 - added support for limited recursion, and many function return types.
- 1.0 - first release.