How to Call Fortran DLLs from C#

C# has direct support for calling Fortran via DLLs using P/Invoke. In C#, simply define a static method with the same method signature as the Fortran method. No code generation tools are needed.

Using Fortran with a high-level language like C# allows you to leverage the strengths of both languages. C# can be used to create a GUI as well as preprocess data from files, database, or other applications. This leaves Fortran to focus solely on the number crunching.

Here are some general rules for calling Fortran from C#.

  • The Fortran DLL should reside in the same directory as the C# assembly (DLL or EXE). Automatically copy the Fortran DLL using post-build events.
  • Array indices are switched between Fortran and C#. Fortran and C# arrays are stored column and row major order respectively.
  • Fortran derived types map to C# structs. Be sure to explicitly define the alignment. The default alignment in Intel Fortran is 8 bytes.
  • Scalar values in Fortran are passed by reference. The equivalent C# scalar should be passed by reference (ref) as well. Failing to do so will usually cause memory access violations.
  • C# strings should be converted to character arrays padded with spaces.

Solution Setup

The Visual Studio solution contains and Fortran dynamic link library project and a C# console application. In order for the C# console application to call the Fortran DLL, it must be in the same directory as the C# executable. Other configurations are possible, but are not recommended due to the potential for chaos (DLL hell).

Use post-build events to automatically copy the Fortran DLL to the C# build directory as shown below. Here we use some of the Visual Studio macros. Remember to include the quotes if you solution is located in a path with spaces.
copy "$(OutDir)\$(TargetName).dll" "$(SolutionDir)\CSharpApp\bin\Debug"
copy "$(OutDir)\$(TargetName).dll" "$(SolutionDir)\CSharpApp\bin\Release"

post-build_fortran

Passing Arrays

The following example shows how to pass arrays. The Fortran subroutine translates an array of coordinates by the vector delta.

coords is a 3 x n array where n is the number of coordinates. n is passed into the subroutine.

Several compiler directives are used to export the subroutine to the public interface of the DLL. The ATTRIBUTES DLLEXPORT directive exports the subroutine. The ATTRIBUTES ALIAS overrides any compiler specific name mangling. Compiler directives are prepended by !DIR$, !DEC$, or !MS$.

subroutine TranslateViaArrays(delta, n, coords)

    !DIR$ ATTRIBUTES DLLEXPORT :: TranslateViaArrays
    !DIR$ ATTRIBUTES ALIAS: 'TranslateViaArrays' :: TranslateViaArrays

    real(8), intent(in) :: delta(3)
    integer, intent(in) :: n
    real(8), intent(inout) :: coords(3,n)

    integer :: i

    do i = 1, n
        coords(:,i) = coords(:,i) + delta(:)
    end do

end subroutine

The calling code in C# wraps the DLL call in a static class call FortranLib. The name of the DLL is defined in a private field.

Within the class a static extern method is defined with the [DllImmport] attribute. The DllImport attribute defines how the DLL is called – most importantly the DLL name, calling convention, and the character set.

The signature of the method matches the Fortran version with some extra attributes to describe how each argument is marshalled from C# to Fortran and back.

  • The scalar argument n is passed by reference (ref) as this is the Fortran convention.
  • The array arguments do not use the ref keyword since arrays are reference types in C# (as opposed to scalar primitives like n which are value types).
  • The array arguments are annotated with the [In] and [Out] attributes. These should match the intent of the Fortran version.
  • The default calling convention for Intel Fortran is C (cdecl).
public static class FortranLib
{
    private const string _dllName = "FortranLib.dll";

    public const int PathLength = 256;

    [DllImport(_dllName, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
    public static extern void TranslateViaArrays([In] double[] delta, ref int n, [In, Out] double[,] coords);
}

The snippet below shows the C# calling code. Notice that the array indices in the coords are reversed between C# and Fortran. The rest is standard C#.

static void RunTranslateViaArrays()
{
    var delta = new[] { 1.0, 2.0, 3.0 };
    int n = 10;
    var coords = new double[n, 3]; // Note that the indices are reversed from Fortran.
    for (int i = 0; i < n; i++)
    {
        coords[i, 0] = i;
        coords[i, 1] = i + 1;
        coords[i, 2] = -i;
    }

    FortranLib.TranslateViaArrays(delta, ref n, coords);

    for (int i = 0; i < n; i++)
        Console.WriteLine("Point {0}: ({1}, {2}, {3})", i + 1, coords[i, 0], coords[i, 1], coords[i, 2]);
}

Passing Types

The following Fortran code performs the same operation as before, except now the point coordinates are encapsulated in a derived type PointType. This derived type will map to an equivalent C# struct.

The bind(c) attribute dictates that the memory layout should be the same as in C – sequential with padding as necessary. The default padding in Intel Fortran is 8 bytes (/align:rec8byte). In PointType a padding integer is placed after the Id field so that the double precision coordinate fields start on the subsequent 8-byte boundary. You can confirm this at run-time using the inquire statement (shown below).

type, bind(c) :: PointType
    integer :: Id
    real(8) :: Coords(3)
end type

subroutine TranslateViaTypes(delta, n, points)

    !DIR$ ATTRIBUTES DLLEXPORT :: TranslateViaTypes
    !DIR$ ATTRIBUTES ALIAS: 'TranslateViaTypes' :: TranslateViaTypes

    real(8), intent(in) :: delta(3)
    integer, intent(in) :: n
    type(PointType), intent(inout), target :: points(n)

    integer :: i, words
    type(PointType), pointer :: point

    inquire(iolength=words) point
    print *, words ! Confirm the size of PointType. Id(1) + Pad(1) + Coords(3*2) = 8 words. 1 word = 4 bytes.

    do i = 1, n
        point => points(i) ! Use a local (pointer) variable to avoid writing points(i) multiple times.
        point%Coords(:) = point%Coords(:) + delta(:)
    end do    

end subroutine

The equivalent C# struct is shown in the code below:

  • The StructLayout attribute dictates that the memory layout is sequential (like bind(c)). The Pack option specifies a padding of 8 bytes.
  • The coords array has a special MarshalAs attribute which tells C# to reserve space for 3 doubles. C# does not have native support for fixed size arrays (essentially all C# arrays are like Fortran allocatable arrays). C# does have a notion of a fixed array which requires an unsafe code block which I have not tried.
  • The downside of the current approach is that if the struct is created with the default constructor (i.e. points = new Points[10]), it is in an unsafe state since the Coords field is null (high potential for NullReferenceExceptions).
  • The sample code has examples of other implementations.
[StructLayout(LayoutKind.Sequential, Pack = 8)]
public struct Point
{
    public int Id;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
    public double[] Coords;

    public Point(int id, double x, double y, double z)
    {
        Id = id;
        Coords = new double[3] { x, y, z };
    }
}

The P/Invoke signature of the C# call is practically the same as the previous example. Switching from raw arrays to types helps make the code self documenting.

[DllImport(_dllName, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern void TranslateViaTypes([In] double[] delta, ref int n, [In, Out] Node[] nodes);

static void RunTranslateViaTypes()
{
    var delta = new[] { 1.0, 2.0, 3.0 };
    int n = 10;
    var points = new Point[n];
    for (int i = 0; i < n; i++)
        points[i] = new Point(id: i + 1, x: i, y: i + 1, z: -i);

    FortranLib.TranslateViaTypes(delta, ref n, points);

    for (int i = 0; i < n; i++)
    {
        var point = points[i];
        Console.WriteLine("Point {0}: ({1}, {2}, {3})", point.Id, point.Coords[0], point.Coords[1], point.Coords[2]);
    }
}

Passing Strings

Passing strings to Fortran requires that you convert the C# string to a fixed length character array. The following Fortran code shows an example use case where the path to an input file is passed to the DLL subroutine.

subroutine TranslateViaInputFile(inputPath)

    !DIR$ ATTRIBUTES DLLEXPORT :: TranslateViaInputFile
    !DIR$ ATTRIBUTES ALIAS: 'TranslateViaInputFile' :: TranslateViaInputFile

    character(len=LEN_PATH), intent(in) :: inputPath

    print *, "Input File: " // trim(inputPath)

    ! Do something amazing....

end subroutine

The P/Invoke call is similar to the ones before.

[DllImport(_dllName, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern void TranslateViaInputFile([In] char[] inputPath);

When converting C# strings to character arrays you must ensure that: 1) the array has the correct length and 2) leftover characters are written as whitespace. The following extension method handles this conversion.

public static char[] ToCharacterArrayFortran(this string source, int length)
{
    var chars = new char[length];
    int sourceLength = source.Length;
    for (int i = 0; i < length; i++)
    {
        if (i < sourceLength)
            chars[i] = source[i];
        else
            chars[i] = ' '; // Important that these are blank for Fortran compatibility.
    }
    return chars;
}

The calling code uses the extension method to create the character array to pass into Fortran.

static void RunTranslateViaInputFile()
{
    const string inputPath = @"C:\temp\input.txt";

    // Convert string to Fortran array of characters.
    char[] inputPathChars = inputPath.ToCharacterArrayFortran(FortranLib.PathLength);

    FortranLib.TranslateViaInputFile(inputPathChars);
}

Debugging

To debug Fortran from the C# application you must activate the unmanaged code debugging in the C# project. Project | Properties | Debug | Enable unmanaged code debugging. Once this option is enabled, you can step directly into the Fortran DLL from the C# application.

enable_unmanaged_debugging_fortran

Sample Code

The link below contains sample code using Visual Studio 2010 and Intel Fortran.
CSharpFortranInterop.zip