Search code examples
c#fortraninterop

Update [bool / int] parameter in C#, read in fortran [ NOK / OK]


I have a fortran subroutine that I call from C# and want to be able to terminate. This I can do using an integer variable that I alter in C# as the fortran routine runs. However changing this to a bool / logical(c_bool) does not work. (In the code below is also an option where I change a value in a callback function.)

Working code (with int parameters) C#:

using System.Runtime.InteropServices;

Console.WriteLine("C# starting");
var cb = new Natives.ActionRefInt(OnUpdateProgress);
var terminate = 0;
var t = Task.Run(() => Natives.TestCallBackAndVariable(cb, ref terminate));
for (int i = 0; i < 4; i++)
{
    await Task.Delay(900);
    terminate = i * 100;
    Console.WriteLine($"C# loop i = {i}, terminate = {terminate}");
}
terminate = -1;
await t;


static void OnUpdateProgress(ref int val2)
{
    Console.WriteLine($"C# OnUpdateProgress received val2 = {val2}");
    val2 += 10;
    Console.WriteLine($"C# OnUpdateProgress setting val2 = {val2}");
}
public static class Natives
{

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void ActionRefInt(ref int progress);

    [DllImport(dllName: "FortranLib.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern void TestCallBackAndVariable(
    [MarshalAs(UnmanagedType.FunctionPtr)] ActionRefInt callBack,
    ref int terminate);
}

Fortran:

subroutine TestCallBackAndVariable(callBack, terminate)
!DEC$ ATTRIBUTES DLLEXPORT::TestCallBackAndVariable
!DEC$ ATTRIBUTES DECORATE,ALIAS:"TestCallBackAndVariable" :: TestCallBackAndVariable
use ISO_C_BINDING
implicit none
! Variables
external                        ::  callBack
integer(C_INT),intent(inout)   ::  terminate
integer(C_INT)                  ::  ii
integer(C_INT)                  ::  val2
! Body of TestCallBackAndVariable
write(*,*) "Starting TestCallBackAndVariable in fortran lib"
do ii = 1,10
    call sleep(1)
    val2 = ii
    call callBack(val2)
    write(*,*) "Fortran loop ii = ",ii,", and terminate = ", terminate, ", and val from callback = ", val2
    if (terminate<0) exit
end do
write(*,*) "Finished TestCallBackAndVariable in fortran lib"
end subroutine TestCallBackAndVariable

Not working as expected code (with bool / logical) C#:

using System.Runtime.InteropServices;

Console.WriteLine("C# starting");
var cb = new Natives.ActionRefBool(OnUpdateProgress);
var terminate = false;
var t = Task.Run(() => Natives.TestCallBackAndVariable(cb, ref terminate));
for (int i = 0; i < 4; i++)
{
    await Task.Delay(900);
   
    Console.WriteLine($"C# loop i = {i}, terminate = {terminate}");
}
terminate = true;
await t;


static void OnUpdateProgress(ref bool val2)
{
    Console.WriteLine($"C# OnUpdateProgress received val2 = {val2}");
    val2 = true;
    Console.WriteLine($"C# OnUpdateProgress setting val2 = {val2}");
}

public static class Natives
{

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void ActionRefBool(ref bool progress);

    [DllImport(dllName: "FortranLib.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern void TestCallBackAndVariable(
    [MarshalAs(UnmanagedType.FunctionPtr)] ActionRefBool callBack,
    ref bool terminate);
}

Fortran:

subroutine TestCallBackAndVariable(callBack, terminate)
!DEC$ ATTRIBUTES DLLEXPORT::TestCallBackAndVariable
!DEC$ ATTRIBUTES DECORATE,ALIAS:"TestCallBackAndVariable" :: TestCallBackAndVariable
use ISO_C_BINDING
implicit none
! Variables
external                        ::  callBack
logical(C_BOOL),intent(inout)   ::  terminate
integer(C_INT)                  ::  ii
logical(C_BOOL)                 ::  val2
! Body of TestCallBackAndVariable
write(*,*) "Starting TestCallBackAndVariable in fortran lib"
do ii = 1,10
    call sleep(1)
    val2 = .false.
    call callBack(val2)
    write(*,*) "Fortran loop ii = ",ii,", and terminate = ", terminate, ", and val from callback = ", val2
    if (terminate) exit
end do

write(*,*) "Finished TestCallBackAndVariable in fortran lib"
end subroutine TestCallBackAndVariable
subroutine TestArray(i,j,arr)

In the bool/logical variant does not c# modify value of the logical terminate fortran variable. However, changing the logical value in the OnUpdateProgress does work. (propagates back to fortran)

Help on this is, as always, much appreciated.

(VS 2022 NET 6, IFORT 2021.6.0 64bit)


Solution

  • In Fortran, the logical(kind=c_bool) type maps to the C _Bool type of the companion C processor (=compiler).

    In C, _Bool is defined to be an unsigned integer type (size is implementation dependent but typically one byte) however with only two allowable values, 0 for false and 1 for true (0 and 1 here meaning the unsigned integer bit patterns that represent the integer values 0 and 1). Any other bit pattern is a trap representation and invokes undefined behavior.

    However, in C# the internal representation of Bool variables is, per the ECMA CLI spec ( What is the binary representation of a boolean value in c# ), 0 for false, and all other bit patterns (that is, with any of the bits set to 1) are true.

    Thus, AFAICT, the Fortran logical(c_bool) and the C# bool type are NOT interoperable, and you need some other mechanism to transfer boolean values between Fortran and C#. E.g. transferring them as integers.