Calling Fortran from other .NET languages

In Visual Studio a solution is formed from several projects. For example, a solution might contain a project to create an executable and another to create a library that is used by the executable. It is convenient to use the terms solution and project in this sense even though you may not be using Visual Studio.

The code for a project will usually be written in one programming language but a different language might be used for another project in the solution. For example a library written in Fortran might be called from an executable written in C#.

However, when mixing languages in this way, it is important to understand that not all Fortran types have exact equivalents inside .NET. For example, the Fortran CHARACTER type, with its fixed size and blank padding rules is not the same as a .NET string (which incidently also caters for UNICODE characters). Also Fortran multi-dimensional arrays are stored in a different order (i.e. column major order) and Fortran arrays are not necessarily zero based.

If your intention is to have a main program written in (say) C# and call an FTN95 DLL then it is important you tag the C# application as 32-bit. If you do not do this then on 64-bit operating systems the C# application may get JIT compiled in 64-bit mode. When a call is made to the FTN95 DLL then it will fail to JIT because it will force 32-bit mode. The result will be a program ending exception.

The linker DBK_LINK or DBK_LINK2 is used to link code produced by one or more calls to the FTN95 compiler. Calls to DBK_LINK or DBK_LINK2 are made a) during Visual Studio build processes, b) when DBK_LINK or DBK_LINK2 is used on a command line, and c) when FTN95 is called using /LINK or /LGO. DBK_LINK creates a file which is known as an assembly. The assembly file can have a .EXE or .DLL extension, and in the latter case it can be called by other software.

When you want a particular FTN95 routine to be callable from other .NET languages you can include an ASSEMBLY_INTERFACE directive in the FTN95 code. This directs the compiler to create a stub that can be used in a call to the routine from another language. The purpose of the stub is to resolve the differences that arise when a Fortran type is not compatible with its .NET equivalent. For example:

ASSEMBLY_INTERFACE(NAME="MyCalculation")

The resulting Fortran name for the routine will use only upper case letters (even when lower case letters are used in the code). In contrast, the external name (MyCalculation) is case sensitive.

Consider the following example:

SUBROUTINE CALC(A,B,C)
INTEGER A
CHARACTER(LEN= *)B
REAL(KIND=2)C(0:,0:)
ASSEMBLY_INTERFACE(NAME="MyCalc")

and suppose that the name of the resulting DLL is MyLib.dll.

CALC would be callable by the following C# fragment:

int a=1;
string b="xyz";
double[,]c={{1.0,2.0},{3.0,4.0},{5.0,6.0}};
MyLib.MyCalc(a,b,c);

You can use "Calc" instead of "MyCalc" as the interface name when calling from C# but not when calling from Visual Basic.

For Visual Basic, the interface name, when converted to upper case, must not be the same as the Fortran name of the subprogram.

You can think of the name MyCalc as a synonym for CALC but when MyCalc is called from other .NET languages, the following argument transformations are performed:

1. Fortran arrays are mapped to .NET arrays. The (column major) ordering is not changed because this transformation would be expensive for large arrays. Your code must allow for this difference. Thus the C# array element c[1,2] becomes C(2,1) in Fortran. Arrays are passed by reference.

2. Fortran CHARACTERs are mapped to System.String objects and passed by value (this means that changes made to a dummy argument are not passed back to the actual argument at the point of call).

3. Scalar INTEGER and REAL values are passed by value unless INTENT(OUT) or INTENT(INOUT) is used in which case the argument is passed by reference.

4. All other arguments, including objects (i.e. instances of a class, see .NET Objects), are passed by reference.

These transformations greatly simplify the calling of Fortran from other .NET languages.

The use of an ASSEMBLY_INTERFACE is not necessary when all of the arguments are scalar INTEGERs or REALs. However, the call (from C# say) must stipulate that all of the arguments are passed as 'unsafe' pointer types (using 'fixed' if necessary) and the name of the routine (in C#) must not use lower case letters.

It is simpler if you use an array index with a lower bound of zero when you declare Fortran array arguments. This makes them consistent with C# arrays. However, you can use a different lower bound. For example, if you declared a two-dimensional Fortran array with both indexes starting at 1 by using say C(:,:), then the C# element c[0,1] would map to C(2,1) (i.e. both indexes are increased by one and their order is reversed).

A C# string is converted into the corresponding Fortran CHARACTER string and any Unicode characters that cannot be represented in ASCII form are replaced according to the rules of System.Text.Encoding.Default. Any changes made to the string from within the Fortran code are not reflected back to the C# caller.

.NET objects (such as .NET strings and .NET arrays) are passed to your Fortran code without transformation. Passing objects avoids the overhead incurred when using the standard transformations and the resulting flexibilty can sometimes be used to resolve complex interfacing problems.

Note that, in a FTN95 program, you cannot declare a Fortran array whose elements are .NET objects. Such arrays can only be passed as .NET arrays from another non-Fortran assembly.

You can use the FTN95 complex number classes in C# and Visual Basic .NET. These are defined in ftn95lib.dll with the namespace Salford.Fortran. The class names are Complex8 (for single precision) and Complex16 (for double precision) and the public data members in both classes are called Real and Imaginary. Objects of these types can be passed to and from a Fortran DLL. Here is some sample C# code.

Salford.Fortran.Complex16 x = new Salford.Fortran.Complex16(1.0,1.0);
MessageBox.Show(x.ToString());

 

 

Basket
Empty
 
Copyright © 1999-2024 Silverfrost Limited