简体   繁体   中英

C# calling fortran dll stack overflow

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?

EDIT 2022-09-09:


After adding `ref` to the `SizeCheck` parameter in c# it now works up to `MaxSize = 57` but larger values still crash with "Stack overflow".

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM