Tuesday, January 11, 2011

Callback to C# from Unmanaged Fortran - PASSING ARRAYS

Thanks to reader kensun87 for requesting this feature.

This article builds on another article which should be read first:
Callback to C# from Unmanaged Fortran
http://xtechnotes.blogspot.com/2008/07/callback-to-c-from-unmanaged-fortran.html


The original article on callback referenced above passes a single value (scalar) between callbacks. This example passes an ARRAY of integers. This is much trickier because arrays need to be handled using IntPtr when passing between callbacks.

The Fortran code and C# code is listed below first, then some explanations. Please note that the fundamental explanations on callback will not be here, instead please see the previous article referenced above.

-------  Fortran code  ------

module f90Callback
    contains


    ! used by CScallbackDriver::Program.cs    
    subroutine func1(iArr, progressCllBak)
    !DEC$ ATTRIBUTES DLLEXPORT ::func1
    !DEC$ ATTRIBUTES REFERENCE :: iArr, progressCllBak
        implicit none
        external progressCllBak
        integer :: iCB
        integer, INTENT(OUT) :: iArr(2)
        
     ! Body of f90Callback
        print *, "Hello from Fortran func1(iArr, progressCllBak)"  
        iCB = 3
        iArr(1) = 5
        iArr(2) = 7
        call progressCllBak(iArr, 2)
        print *, "Final Fortran arrays are ", iArr
         
        return
    end subroutine func1
    
end module f90Callback


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

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


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


        // 1. Define delegate type
        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public delegate void dgateIntPtr( IntPtr numYears, ref int iSize);


        // 2. Create a delegate variable
        public dgateIntPtr dg_progCBPtr;


        public Program() {
            // 3. Instantiate delegate, typically in a Constructor of the class
            dg_progCBPtr = new dgateIntPtr(onUpdateProgressPtr);
        }


        // 4. Define the c# callback function
        ///

        /// Callback function where an array is passed from Fortran
        ///

        /// pointer to the unmanaged array
        /// size of the array represented by progCount
        public void onUpdateProgressPtr( IntPtr progCount, ref int iSize)
        {
            //Unsafe code
            unsafe
            {
                Console.WriteLine("In C# callback function");


// reading in array from unmanaged code
                int[] managedArr2 = new int[2];
                Marshal.Copy(progCount, managedArr2, 0, iSize);
                
// writing out array to unmanaged code
                managedArr2[1] = 99;
                Marshal.Copy(managedArr2, 0, progCount, iSize);
                Console.WriteLine("Going out of C# callback function");
            }


        }
         
        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
            func1Ptr(ref iArrB[0], myProg.dg_progCBPtr);


            Console.ReadKey();   
        }


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






A few things to note:
1. in the Main method, you can ignore iArrB as it does not have implications in the callback.
2. In the Fortran code, when the call to the C# callback is made, note it is important to pass the correct size of the array, in this example 2:
              call progressCllBak(iArr, 2)
3. In the Fortran code, the iArr is initialised with values {5,7}
4. In C# code, in step 1, see exactly how the arguments of the delegate dgateIntPtr are defined.
5. In C# code, in step 4, this is where the actual callback function is defined.
- the name of the callback function is onUpdateProgressPtr with arguments: ( IntPtr progCount, ref int iSize)
- the array called managedArr2 is defined with size iSize.
- Marshal.Copy is used to copy the unmanaged array progCount, into C# array managedArr2.
- the second element of the array is changed to 99.
- Marshal.Copy is used again but to copy the opposite way from managed array manageArr2 to unmanaged array progCount.
6. The call back returns to the Fortran function which then prints out the array where second element has new value of 99.
7. When the Fortran function finishes, it returns control back to C# main method.
8. All other steps in this example are similar to the previous callback example where only a scalar integer is passed.