I have a C# (NET 6) project where I try to call fortran subroutine and passing a struct
.
I have made simplified version here:
c#:
using System.Runtime.InteropServices;
Console.WriteLine("C# starting");
Console.WriteLine($"Size of GrandParent: {Marshal.SizeOf(typeof(GrandParent))}");
var gp = new GrandParent();
Natives.SizeCheck(gp);
public static class Natives
{
public const int MaxSize = 100;
[DllImport(dllName: "FortranLib.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void SizeCheck(GrandParent gp);
}
[StructLayout(LayoutKind.Sequential)]
public struct Child
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = Natives.MaxSize)]
internal double[] Array;
public Child()
{
Array = new double[Natives.MaxSize];
}
}
[StructLayout(LayoutKind.Sequential)]
public struct Parent
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = Natives.MaxSize)]
internal Child[] Children;
public Parent()
{
Children = new Child[Natives.MaxSize];
}
}
[StructLayout(LayoutKind.Sequential)]
public struct GrandParent
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = Natives.MaxSize)]
internal Parent[] Children;
public GrandParent()
{
Children = new Parent[Natives.MaxSize];
}
}
and the FortranLib.dll code:
module FortranLib
use ISO_C_BINDING
implicit none
integer,parameter :: MaxSize = 100
type, bind(C) :: Child
real(C_DOUBLE) :: Array(MaxSize)
end type Child
type, bind(C) :: Parent
type(Child) :: Children(MaxSize)
end type Parent
type, bind(C) :: GrandParent
type(Parent) :: Children(MaxSize)
end type GrandParent
contains
subroutine SizeCheck(gp)
!DEC$ ATTRIBUTES DLLEXPORT::SizeCheck
!DEC$ ATTRIBUTES DECORATE,ALIAS:"SizeCheck" :: SizeCheck
! Variables
type(GrandParent), intent(in) :: gp
! Body of SizeCheck
write(*,*) "In fortran dll first line"
write(*,*) "Number of parents: ", size(gp%Children)
write(*,*) "Number of children for each parent : ", size(gp%Children(0)%Children)
write(*,*) "Size of child array : ", size(gp%Children(0)%Children(0)%Array)
end subroutine SizeCheck
end module FortranLib
Using a MaxSize = 45
works but not 46. From 46 and up I get a "Stack overflow" exception / crash. At 45 the size of GrandParent is 729000.
I have tried to set, in Visual Studio 2022 under the fortran project properties, Configuration Properties -> Optimization -> Heap Arrays = 0 (for always allocate arrays on heap) as well as Configuration Properties -> Linker -> System -> Stack Reserve Size = 100000000 which I thought would be enough, but to no avail.
I start to think that it should be done in the c# project but where I do not know.. Anyone have an idea of what could be done?
After (for the first time) trying out disassembly window
I found that the error is thrown at the second to last line shown here ( 00007FF8CB397F98 E8 73 FC FF FF call CLRStub[MethodDescPrestub]@7ff8cb397c10 (07FF8CB397C10h)
):
5: var gp = new GrandParent();
00007FF8CB397F8B 48 8D 4D 78 lea rcx,[rbp+78h]
00007FF8CB397F8F E8 BC F4 FF FF call Method stub for: GrandParent..ctor() (07FF8CB397450h)
6: Natives.SizeCheck(ref gp);
00007FF8CB397F94 48 8D 4D 78 lea rcx,[rbp+78h]
00007FF8CB397F98 E8 73 FC FF FF call CLRStub[MethodDescPrestub]@7ff8cb397c10 (07FF8CB397C10h)
00007FF8CB397F9D 90 nop
I am a n00b when it comes to disassembly but perhaps it gives someone else a clue...
The Fortran compiler exposes the structure as a reference. This can be seen after (Intel here) compilation and disassembly below as GRANDPARANT *GP
:
So the C# declaration should this instead:
public static extern void SizeCheck(ref GrandParent gp);
PS: the calling convention is not important in x64 (fastcall
is by default)
To fix the stack overflow, you can either 1) change the maximum stack size compiled into your executable by using EditBin from the Visual Studio SDK like this for example (with 10Mb of stack):
EditBin.exe <your.exe> /stack:10000000
or 2) decide to marshal the big object "manually" like this:
var gp = new GrandParent();
var ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf<GrandParent>());
try
{
Marshal.StructureToPtr(gp, ptr, false);
Natives.SizeCheck(ptr);
}
finally
{
Marshal.FreeCoTaskMem(ptr);
}
With the DllImport statement modified like this:
[DllImport("FortranLib.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void SizeCheck(IntPtr gp);