Showing posts with label callback. Show all posts
Showing posts with label callback. Show all posts

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.

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);




}
}