Calling Fortran from C# – Monitoring Progress Using Callbacks

For long running Fortran routines, you will typically need to monitor the progress from the calling application. If the Fortran routine is an a separate process (EXE) you will need to use some form of inter-process communication (IPC).

Files are a simple form of IPC. Files are great for transferring bulk inputs and outputs to another process. However things get hairy when using files to delivers progress updates since you have to deal with multiple processes simultaneously accessing the same file – one reading, another writing.

My preference for calling long running Fortran routines via C# is to compile the Fortran routine as a DLL and pass a callback (function pointer) into the Fortran routine. The Fortran routine can invoke the callback function to provide progress updates.

The example demonstrates this technique. See Calling FORTRAN function or subroutine in DLL from C# code for more info on the general mechanics of call Fortran DLLs from C#.

The DoWork function takes in a procedure with the explicit interface ProgressUpdateAction. You could also use the external statement instead of an explicit interface (see xTechNotes).

module MyLib
    
    implicit none
    
    interface
        subroutine ProgressUpdateAction(percentProgress)
            integer, intent(in) :: percentProgress
        end subroutine
    end interface    
    
contains
    
    subroutine DoWork(progressUpdate)

        !DIR$ ATTRIBUTES DLLEXPORT :: DoWork
        !DIR$ ATTRIBUTES ALIAS: 'DoWork' :: DoWork
        !DIR$ ATTRIBUTES REFERENCE :: progressCallBack

        procedure(ProgressUpdateAction), intent(in), pointer :: progressUpdate

        integer, parameter :: STEP_COUNT = 9
        integer, parameter :: OP_COUNT = 100000000
        integer :: i, j, percentProgress
        real(8) :: n

        do i = 1, STEP_COUNT
            
            ! Update the status.
            percentProgress = (i - 1) * 100.0 / STEP_COUNT
            call progressUpdate(percentProgress)
            
            ! Do something expensive.
            do j = 1, OP_COUNT
                n = sqrt(real(j,8))
                if (j == OP_COUNT) print *, n
            end do

        end do

    end subroutine
        
end module

To call the Fortran routine from C# you define an equivalent delegate (also named ProgressUpdateAction below). To invoke the Fortran routine, pass an instance of the delegate. In the example below the delegate instance is a static method that echos the progress percentage to the screen. Note that the progress argument is passed by reference.

public static class FortranLib
{
    private const string _dllName = "FortranLib.dll";

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void ProgressUpdateAction(ref int progress); // Important the int is passed by ref (else we could use built-in Action<T> instead of delegate).

    [DllImport(_dllName, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
    public static extern void DoWork([MarshalAs(UnmanagedType.FunctionPtr)] ref ProgressUpdateAction progressUpdate);
}

class Program
{
    static void Main(string[] args)
    {
        try
        {
            FortranLib.ProgressUpdateAction updateProgress = delegate(ref int p)
            {
                Console.WriteLine("Progress: " + p + "%");
            };

            FortranLib.DoWork(ref updateProgress);

        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
        }
    }
}

Leave a Reply