You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#调用Fortran DLL传递大结构体时栈溢出问题求助

.NET 6调用Fortran传递大结构体时栈溢出问题解决

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];
    }
}

FortranLib.dll代码

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

问题描述

我有一个.NET 6的C#项目,尝试调用Fortran子程序并传递嵌套结构体。当MaxSize设为45时程序正常运行,但设为46及以上时会触发栈溢出异常/崩溃(MaxSize=45时GrandParent的大小为729000字节)。我曾在Visual Studio 2022的Fortran项目中设置:

  • 配置属性->优化->堆数组=0(始终在堆上分配数组)
  • 配置属性->链接器->系统->堆栈保留大小=100000000
    但均无效,怀疑需要在C#项目中调整,求可行解决方法。

2022-09-09更新:在C#的SizeCheck参数中添加ref后,MaxSize可支持到57,但更大的值仍会因栈溢出崩溃。查看反汇编窗口,错误在调用SizeCheck的行抛出:

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  

解决方案

1. 改用堆分配结构体(最可靠方案)

C#结构体默认在栈上分配,超大结构体直接导致栈溢出。通过将结构体分配到堆上,用指针传递给Fortran彻底解决问题:

修改C#代码

using System.Runtime.InteropServices;

Console.WriteLine("C# starting");
int structSize = Marshal.SizeOf(typeof(GrandParent));
Console.WriteLine($"Size of GrandParent: {structSize}");

// 将结构体分配到堆上
var gp = new GrandParent();
IntPtr gpPtr = Marshal.AllocHGlobal(structSize);
Marshal.StructureToPtr(gp, gpPtr, false);

// 传递指针给Fortran
Natives.SizeCheck(gpPtr);

// 使用后释放堆内存,避免泄漏
Marshal.FreeHGlobal(gpPtr);

public static class Natives
{
    public const int MaxSize = 100;

    // 修改为接收IntPtr类型参数
    [DllImport(dllName: "FortranLib.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern void SizeCheck(IntPtr gp);
}

// 结构体定义保持不变
[StructLayout(LayoutKind.Sequential)]
public struct Child { /* ... */ }
[StructLayout(LayoutKind.Sequential)]
public struct Parent { /* ... */ }
[StructLayout(LayoutKind.Sequential)]
public struct GrandParent { /* ... */ }

修改Fortran代码

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_ptr)
     !DEC$ ATTRIBUTES DLLEXPORT::SizeCheck
    !DEC$ ATTRIBUTES DECORATE,ALIAS:"SizeCheck" :: SizeCheck
    ! Variables
    type(GrandParent), pointer :: gp
    type(c_ptr), intent(in) :: gp_ptr
    ! 将C指针转换为Fortran可访问的指针
    call c_f_pointer(gp_ptr, gp)
    
    ! Fortran数组默认是1-based索引,修正原代码的越界问题
    write(*,*) "In fortran dll first line"
    write(*,*) "Number of parents: ", size(gp%Children)
    write(*,*) "Number of children for each parent : ", size(gp%Children(1)%Children)
    write(*,*) "Size of child array : ", size(gp%Children(1)%Children(1)%Array)
    
    end subroutine SizeCheck
end module FortranLib

2. 调整C#项目栈大小(临时方案)

如果必须保持栈传递方式,可以增大C#项目的栈保留大小:

  • 右键C#项目 → 属性 → 生成 → 点击「高级」按钮
  • 在「高级生成设置」中,找到「堆栈大小」,输入更大的值(例如100000000,单位为字节)
  • 注意:32位程序栈大小有上限(通常不超过1GB),且过大的栈会浪费系统资源。

3. 拆分大结构体(可选优化)

将嵌套的大数组拆分为多个独立参数传递,避免一次性传递超大结构体,减少栈内存压力。


内容的提问来源于stack exchange,提问作者Erik Thysell

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 05:35:29