Wednesday, July 23, 2008

Callback to C# from Unmanaged Fortran

Using unmanaged code eg Fortran to callback to C# can be a nightmare to get right. But once you know it, it is just following a recipe. Hence without further explanation, an example and recipe is given below. It is quite self-expalnatory I hope.

Just note that, the example is more than a simple call back. The C# actually calls a Fortran function in a dll. Within the Fortran function calls the callback in C#.

--- Fortran code -----
module f90Callback

contains

subroutine func1(iArr, progressCllBak)
!DEC$ ATTRIBUTES DLLEXPORT ::func1
!DEC$ ATTRIBUTES REFERENCE :: iArr, progressCllBak
implicit none
external progressCllBak
integer :: iCB
integer, INTENT(OUT) :: iArr(2)

! Variables

! Body of f90Callback
print *, "Hello Before"
iCB = 3
iArr(1) = 5
iArr(2) = 7
call progressCllBak(iCB)
print *, "setting callback value in Fortran as :", iCB
print *, "Hello After"

return
end subroutine func1

end module f90Callback


------- C# code ---------

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace CScallbackDriver
{
class Program
{
// 0. Define a counter for the Progress callback to update
public int localCounter;

// 1. Define delegate type
[UnmanagedFunctionPointer(CallingConvention=CallingConvention.Cdecl)]
public delegate void dgateInt(ref int numYears);

// 2. Create a delegate variable
public dgateInt dg_progCB;


public Program() {
// 3. Instantiate delegate, typically in a Constructor of the class
dg_progCB = new dgateInt(onUpdateProgress);
}

// 4. Define the c# callback function
public void onUpdateProgress(ref int progCount) {
localCounter = progCount;
}

int iArg;
static void Main(string[] args)
{

Program myProg = new Program();
myProg.localCounter = 0;
int[] iArrB = new int[2];

//6. Call normal Fortran function from DLL, and passing the callback delegate
func1(ref iArrB[0], myProg.dg_progCB);


Console.WriteLine("Retrieve callback value as {0}", myProg.localCounter);
Console.ReadKey();
}

// 5. Define the dll interface
[DllImport("f90Callback", EntryPoint="F90CALLBACK_mp_FUNC1", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
public static extern void func1([In, Out] ref int iArr, [MarshalAs(UnmanagedType.FunctionPtr)] dgateInt blah);




}
}

5 comments:

kensun87 said...

This seems to work perfectly for callbacks that take simple numbers as arguments (the progress, in your example). However, how would you modify your code to send both the array and progress value back to C#?

For example, if your fortran looked like this:

call progressCllBak(iArr, iCB)

Any help would be appreciated.

Elkanah said...

I have not tried using an array as an argument, but I imagine it would be similar to the integer in the example. Try using a 1D array first.

Key changes to be made to the Fortran are the lines:
call progressCllBak(iCB)


Key changes to be made to the C# are the lines:
public delegate void dgateInt(ref int numYears);

public void onUpdateProgress(ref int progCount)


Of course there are details of the array itself which I have not included here. It may take some trial and error. If you manage to be successful, feel free to put your solution here.

kensun87 said...

I've managed to get the array reference passed back to C#, but it always has length 1. I can't seem to find a way to get C# to remember the length of the array when it's passed from Fortran to C#.

Elkanah said...

Hi
I have been trying to pass an integer array through the callback and I also got the first element only. Could not figure out how to get the full array. After some googling, I believe the solution to this lies in using IntPtr. I tried a bit using IntPtr but still can get it to work (given that I have very little knowledge in using IntPtr). I have attached some links below which perhaps could give you some ideas of what to try. If you are successful, please post your solution in this blog as I'm sure many others would like to know too. Please feel free to investigate more with IntPtr.

http://www.pcreview.co.uk/forums/thread-2510031.php
http://msdn.microsoft.com/en-us/library/0t7xwf59.aspx

Elkanah said...

Hi Kensun87,

I have solved the problem of passing arrays in callbacks, in the article:

Callback to C# from Unmanaged Fortran - PASSING ARRAYS

http://xtechnotes.blogspot.com/2011/01/callback-to-c-from-unmanaged-fortran.html