Discover Engineering
Science makes it known,
Engineering makes it work,
Art makes it beautiful.
|
|
Using a simple D program to call
Numerical Recipes: The Art of Scientific Computing1
Silverfrost Fortran 95 subroutines
This page discusses developing a simple
Win32 Command Prompt
D
program calling
statistical analysis subroutines in a FORTRAN .dll
(sttstcs.dll). sttstcs.dll was developed on a PIII running
Win XP Pro SP2 using
SilverFrost FTN95. The
D program (dstat.d) was developed on a HP P6 AMD Dual Core
running Win 7 Home Premium with Notepad++ and
DMD32 D Compiler v2.066.0.
Why FORTRAN? Doing a Google search for ieee why Fortran scientific programming
shows Fortran is widely
used in engineering and scientific programming. Being able to link in FORTRAN
libraries in the form of a .dll in other
programming languages combines the strengths of both.
Sample D Code
Below is a listing of the D main program
(dstat.d) to call the various FORTRAN
statistical analysis subroutines to
calculate Average, Median, Standard Deviation, Variance, etc., for a series of
grades (referred to as data points). The
FORTRAN statistical analysis
subroutines called are IMDIAN1(...), IMOMENT(...), and
IMODE(...).
import std.stdio;
// sttstcs.dll subroutine declarations
extern (Pascal)
{
void* IMDIAN1(ref short, ref short, ref short, ref short[300]);
void* IMOMENT(ref short, ref float, ref float, ref float, ref float, ref float, ref float, ref short, ref short[300]);
void* IMODE(ref short, ref short, ref short, ref short, ref short, ref short[300]);
}
int main(string[] args)
{
short[300] idata;
short i, NumVal, NumUnqVal, iMed, intMode, modeCnt, iErr;
float avg, aDev, sDev, varnc, skew, curt;
printf("dstat - d Statistics Copyright 2014 Rodney Roberts\n");
printf("Silverfrost FTN95 Numerical Recipes .dll demonstration\n\n");
NumVal = 21;
NumUnqVal=iMed = intMode = modeCnt = iErr = 0;
avg = aDev = sDev = varnc = skew = curt = 0.0;
idata[] = 0;
idata[0] = 84;
idata[1] = 62;
idata[2] = 96;
idata[3] = 68;
idata[4] = 86;
idata[5] = 72;
idata[6] = 86;
idata[7] = 75;
idata[8] = 86;
idata[9] = 75;
idata[10] = 86;
idata[11] = 78;
idata[12] = 88;
idata[13] = 82;
idata[14] = 88;
idata[15] = 83;
idata[16] = 81;
idata[17] = 83;
idata[18] = 92;
idata[19] = 94;
idata[20] = 95;
printf("Number of data points: %d\n\n", NumVal);
printf("Data points: \n");
IMDIAN1(iErr,iMed,NumVal,idata);
if (iErr!=0)
{
printf("IMDIAN1 call error %d\n", iErr);
return(iErr);
}
for (i = 0; i < NumVal; i++)
{
printf(" %d ", idata[i]);
if (((i/5)*5)==i)
printf("\n");
}
printf("\n");
printf("Statistical Descriptions of Data\n");
printf("--------------------------------\n");
printf(" Median Value: %d\n", iMed);
IMOMENT(iErr,curt,skew,varnc,sDev,aDev,avg,NumVal,idata);
if (iErr!=0)
{
printf("IMOMENT call error %d\n", iErr);
return(iErr);
}
printf(" Average: %f\n", avg);
printf("Std. Deviation: %f\n", sDev);
printf("Avg. Deviation: %f\n", aDev);
printf(" Variance: %f\n", varnc);
printf(" Skew: %f\n\n", skew);
IMODE(iErr,NumUnqVal,modeCnt,intMode,NumVal,idata);
if ((iErr!=0)&&(iErr!=2))
{
printf("IMODE call error %d\n", iErr);
return(iErr);
}
printf(" Mode: %d\n", intMode);
printf(" Mode Count: %d\n", modeCnt);
if (iErr==2)
{
printf("Multiple modes\n");
}
return (0);
}
Static and Dynamic Arrays
FORTRAN arrays are dimensioned 1..size, Pascal arrays are
dimensioned <low ordinal value>..<high ordinal value>,
COBOL arrays (tables) are dimensioned 1..size,
and D arrays are
dimensioned 0..size-1. FORTRAN matrices are
dimensioned (rows, columns)
(same as the mathematical definition),
D (and Pascal) matrices are dimensioned [columns][rows].
Therefore, FORTRAN's X(3,5) refers to the same element as
D's x[4][2] (D treats it as an array of
rows of column arrays, adding complexity;2
when processing
matrices in a D multi-language program, it may be simpler to
perform the bulk of matrix processing in FORTRAN and/or
Pascal) and Pascal's x[5,3]
(if <low ordinal value> = 1).
As can be seen, used a static array for idata. On another
project, tried
to use a D dynamic array with the FORTRAN subroutine defining it as
<type> <number of values>,
<array name>(<number of values>). In this
example, it would be:
INTEGER*2 NumVal, idata(NumVal)
|
|
(the above is an example of dynamically
dimensioned (or dimensioned with variable bounds) FORTRAN
array)
Unfortunately, there is something in the D dynamic array structure
which causes the array element start addresses not
to line up with the corresponding FORTRAN array elements. However, it is
possible to pass a D static array to a
FORTRAN array dimensioned with variable bounds as above. FORTRAN allows
passing individual subscripted array
elements by reference; see
tir33.for Subprograms
for an example.
When working with matrices, explicitly define a static size.
(FORTRAN's real, dimension(*) :: <array name>
may
work with D dynamic arrays but is currently outside the scope of
these tutorials and has not been tested.)
extern (...) {...}
An extern (...) {...} declaration with a linkage attribute
is needed since IMDIAN1(...), IMOMENT(...), and
IMODE(...) are
defined in another object file. FORTRAN converts the identifiers
in its symbol table to upper case, therefore the
subroutine names must be in upper case.
It may help to think of it as:
extern (<calling model>)
{<returned data type> <external procedure name>
(<passed parameter data types list>)}
3
extern (Pascal)
{
void* IMDIAN1(ref short, ref short, ref short, ref short[300]);
void* IMOMENT(ref short, ref float, ref float, ref float, ref float, ref float, ref float, ref short, ref short[300]);
void* IMODE(ref short, ref short, ref short, ref short, ref short, ref short[300]);
}
void* (or void) is used for FORTRAN subroutine
and Pascal procedure calls, indicating the subprogram does not
return a value.
Using anything other than extern (Pascal) {...} to declare the
external
sttstcs.dll subroutines (which were originally
meant for the stdcall model) caused a variant of
Error 42: Symbol Undefined IMDIAN.4 extern (Pascal) {...}
causes the passed arguments to be Pushed/Popped to/from the stack
in a different order, requiring the parameters
passed to the external sttstcs.dll subroutines be listed in reverse
order in the call statement. By default, FORTRAN
requires its parameters to be
passed by reference
(can be overridden in the FORTRAN subroutine in a conventional
all FORTRAN application). FORTRAN's parameter passing by
reference requirement necessitated the ref keywords
in the extern (...) {...} declaration
(void* IMDIAN1(ref short, ref short,
ref short, ref short[300]), etc.).
Trying to avoid side effects by combining FORTRANs
INTENT(IN):: with Ds in in the extern
statement produced an
object. Error@(0): Access Violation,
though it is possible I may have missed something. On another project,
tried
leaving
out the ref keyword in the extern (...) {...} declaration,
which produced a throwable exception.
Compiling and Linking
First, prepare the
sttstcs.lib import library
using
implib (download file bup.zip)
(if you have not already done so); the SilverFrost generated sttstcs.lib
is not compatible with the Digital Mars D compiler/linker.
Then compile and link dstat.d in a Command Prompt in a single step using
the D command line compiler.
(type dmd --help > dmd.txt to print help to a text file)
(For interoperatibility with default SilverFrost
Fortran 95 and Free Pascal, use the D2 32-bit Command Prompt)
|
|
Execute dstat.exe in a Command Prompt.
Obviously, this is a simple program (like the infamous "Hello World"
program). dstat.exe passes the array to the three subroutines (which calculate
average, etc.) and then displays the results.
(Screenshots taken from HP P6 AMD Dual Core, Win 7
Home Premium, Command Prompt)
|
|
Known Problems D Calling FORTRAN
-
eX functions [EXP( ), SINH( ), etc.] in FORTRAN
subroutines
-
FORTRAN subroutines converting REAL data to INTEGER data
(with or without the FLOOR( ) or INT( ) functions)
-
returning COMPLEX variables from a Silverfrost
FORTRAN COMPLEX FUNCTION
-
Dynamic Arrays, mentioned above
All conditions execute fine in an all FORTRAN solution
(the first item works in a Pascal/FORTRAN solution,
the second item works in a
COBOL / FORTRAN solution).
However, when a D program calls a FORTRAN subroutine doing either of
the first two conditions, it crashes with an
object.Error@(0): Invalid Floating Point Operation5
This reduces the versatility of D.
1. Press, William H., Brian P. Flannery, Saul A Teukolsky,
and William T. Vetterling (1986). Numerical
Recipes: The Art of
Scientific Computing. New York:Press Syndicate of the University of Cambridge.
2. In one instance, even though the matrix was dimensioned [columns][rows],
it was behaving
as if it was dimensioned [rows][columns]. Was able to satisfactorily
process the matrix by passing
it to Object Pascal procedures.
3. See
Variable Storage Compatibility and Equivalency
for D / FORTRAN data type cross-reference.
4. Using implib's /system switch when generating sttstcs.lib
may resolve this issue.
5. Have ran into Floating Point errors/problems in D calling Pascal and
Pascal calling D projects:
a. In a main D Windows module calling an Object Pascal procedure reading a file,
converting float data
to a string and then outputting using SendMessageA(...,WM_SETTEXT,...,...)
immediately prior to calling Pascal procedure to read a file, caused file error 6 (Invalid file handle)
to be reported (moving code around resolved issue, could have been a Win32 API problem as well);
b. In an Object Pascal main Windows program
(missle05.pas)
calling a D function declared extern (C),
it was necessary to call _control87(_MCW_EM, _MCW_EM) to prevent an
Invalid-floating-point-operation in the D function (which did not perform any floating operations).
|