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

Fortran调用共享库BMI初始化dflowfm模型时段错误排查

Fortran调用DFlowFM的BMI初始化触发段错误解决方案

背景

我尝试使用Basic Model Interface (BMI)从Fortran程序中初始化DFlowFM模型(已从源码编译生成可执行文件和共享库),通过OpenEarth的Python封装已成功实现该功能,但在Fortran程序中调用时出现无明确提示的段错误,已定位到初始化模型的函数/子程序源码片段,需要协助解决。

问题详情

Fortran程序中尝试以子程序和函数两种方式调用共享库中的例程,均触发错误:forrtl: severe (174): SIGSEGV, segmentation fault occurred,错误追踪指向函数/子程序调用。

完整错误追踪

forrtl: severe (174): SIGSEGV, segmentation fault occurred
Image              PC                Routine            Line        Source             
libpthread-2.31.s  00007F04C9E19140  Unknown               Unknown  Unknown
libdflowfm.so.0.0  00007F04CB517080  Unknown               Unknown  Unknown
libdflowfm.so.0.0  00007F04CB517319  Unknown               Unknown  Unknown
testbmifort        000000000040F2E1  MAIN__                     39  bmi_fort-test.f90
testbmifort        000000000040F25D  Unknown               Unknown  Unknown
libc-2.31.so       00007F04C9C4CD0A  __libc_start_main     Unknown  Unknown
testbmifort        000000000040F17A  Unknown               Unknown  Unknown

GDB调试追踪

39          call init(sharedDLLHandle, c_config_file, strln)
(gdb) n

Program received signal SIGSEGV, Segmentation fault.
0x00007f03c2181080 in GetDllProcedure () from /opt/dflowfm/lib/libdflowfm.so.0
(gdb) bt
#0  0x00007f03c2181080 in GetDllProcedure () from /opt/dflowfm/lib/libdflowfm.so.0
#1  0x00007f03c2181319 in bmi_initialize_ () from /opt/dflowfm/lib/libdflowfm.so.0
#2  0x000000000040f384 in test_df_bmi () at bmi_fort-test.f90:39
#3  0x000000000040f25d in main ()
#4  0x00007f03c08b6d0a in __libc_start_main (main=0x40f240 <main>, argc=1, argv=0x7fff9bf4d658, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fff9bf4d648)
    at ../csu/libc-start.c:308
#5  0x000000000040f17a in _start ()

测试代码与编译命令

bmi_fort-test.f90

module bmi
    use iso_c_binding
    implicit none
    integer(C_INT64_T), BIND(C, name="MAXSTRLEN") :: MAXSTRLEN
    ! interface
    !     integer(C_INT64_T) function init(config) result(iresult) bind(C, name='bmi_initialize_')
    !         use iso_c_binding
    !         character*(c_char) :: config
    !     end function
    ! end interface
    interface
        subroutine init(sharedDLLHandle, config, strln) bind(C, name='bmi_initialize_')
            use iso_c_binding
            type(c_ptr), value :: sharedDLLHandle
            ! integer(C_INT64_T), pointer :: dll_ptr
            character*(c_char) :: config
            integer(c_int) :: strln
        end subroutine
    end interface
end module bmi



program test_df_bmi
    use iso_c_binding
    use bmi    

    type(c_ptr) :: sharedDLLHandle
    ! integer(C_INT64_T), pointer :: dll_ptr
    character*(c_char) :: c_config_file
    character(*), parameter :: config_file = "/home/bmi-python/test_data/e02_f14_c040_westerscheldt/westerscheldt.mdu"
    integer(C_INT64_T) :: iresult
    integer(c_int) :: strln

    c_config_file = "/home/bmi-python/test_data/e02_f14_c040_westerscheldt/westerscheldt.mdu"
    strln = len(config_file)

    ! iresult = init(c_config_file)
    call init(sharedDLLHandle, c_config_file, strln)

end program test_df_bmi

编译命令:
ifort bmi_fort-test.f90 -L/opt/dflowfm/lib/ -ldflowfm -o testbmifort -traceback -warn interface -O0
(注:共享库同样使用ifort编译)

已通过nm -D /opt/dflowfm/lib/libdflowfm.so确认目标例程存在:00000000015a5300 T bmi_initialize_,末尾下划线表明这是C例程。

定位到的源码片段

sf1.f90(生成initialize函数的Fortran源码)

...
!> The initialize() function accepts a string argument that
!! gives the name (and path) of its "main input file", called
!! a configuration file. This function should perform all tasks
!! that are to take place before entering the model's time loop.
integer(c_int) function initialize(c_config_file) result(c_iresult) bind(C, name="initialize")
   !DEC$ ATTRIBUTES DLLEXPORT :: initialize
    
   use iso_c_binding, only: c_char

...

   character(kind=c_char),intent(in)    :: c_config_file(MAXSTRLEN)
   character(len=strlen(c_config_file)) :: config_file

   ! Extra local variables
   integer :: inerr  ! number of the initialisation error
   
...
    
   c_iresult = 0 ! TODO: is this return value BMI-compliant?
#ifdef HAVE_MPI

...

   ! Store the name
   config_file = char_array_to_string(c_config_file, strlen(c_config_file))

   ! Now we can initialize with the config_file


...


   ! TODO: check why these are needed to avoid a segfault
   KNX    = 8
   MXB    = 10
   MAXLAN = 500
   MAXPOL = MAXLAN

...

   CALL INIDAT()
   call api_loadmodel(config_file)

...

   c_iresult = flowinit()

   time_user = tstart_user

   ! Just terminate if we get an error....
   ! if (inerr > 0) stop
   ! initialize = 0
end function initialize
...

sf2.c(生成bmi_initialize_的C源码)

...
long STDCALL BMI_INITIALIZE(int64_t * sharedDLLHandle,
    char   * config_file,
    int      config_file_len)
{
    typedef  (STDCALL * MyProc)(chvoid *ar *);
    MyProc proc = (MyProc)GetDllProcedure(sharedDLLHandle, "initialize");

    long error = -1;

    char * c_config_file = strFcpy(config_file, config_file_len);
    RemoveTrailingBlanks_dll(c_config_file);

    if (proc != NULL)
    {
        error = 0;
        (void *)(*proc)(c_config_file);
    }

    free(c_config_file); c_config_file = NULL;

    return error;
}
...

附加问题

是否有方法从共享库中检查该例程所需的参数及数据类型?

补充信息:Python成功案例

df_bmi-test.py(成功初始化的Python代码)

import bmi.wrapper as bw

dflowfm = bw.BMIWrapper(engine='dflowfm', configfile='test_data/retracted/path.mdu')

dflowfm.initialize()

bmi.wrapper相关实现片段

...

def wrap(func):
    """Return wrapped function with type conversion and sanity checks.
    """
    @functools.wraps(func, assigned=('restype', 'argtypes'))
    def wrapped(*args):
        if len(args) != len(func.argtypes):
            logger.warn("{} {} not of same length",
                        args, func.argtypes)

        typed_args = []
        for (arg, argtype) in zip(args, func.argtypes):
            if argtype == c_char_p:
                # create a string buffer for strings
                typed_arg = create_string_buffer(arg)
            else:
                # for other types, use the type to do the conversion
                if hasattr(argtype, 'contents'):
                    # type is a pointer
                    typed_arg = argtype(argtype._type_(arg))
                else:
                    typed_arg = argtype(arg)
            typed_args.append(typed_arg)
        result = func(*typed_args)
        if hasattr(result, 'contents'):
            return result.contents
        else:
            return result
    return wrapped


...


def _load_library(self):
        """Return the fortran library, loaded with """
        path = self._library_path()
        logger.info("Loading library from path {}".format(path))
        library_dir = os.path.dirname(path)
        if platform.system() == 'Windows':
            import win32api
            olddir = os.getcwd()
            os.chdir(library_dir)
            win32api.SetDllDirectory('.')

        result = cdll.LoadLibrary(path)

        if platform.system() == 'Windows':
            os.chdir(olddir)

        return result


    def initialize(self, configfile=None):
        """Initialize and load the Fortran library (and model, if applicable).

        The Fortran library is loaded and ctypes is used to annotate functions
        inside the library. The Fortran library's initialization is called.

        Normally a path to an ``*.ini`` model file is passed to the
        :meth:`__init__`. If so, that model is loaded. Note that
        :meth:`_load_model` changes the working directory to that of the model.

        """

        if configfile is not None:
            self.configfile = configfile
        try:
            self.configfile
        except AttributeError:
            raise ValueError("Specify configfile during construction or during initialize")
        abs_name = os.path.abspath(self.configfile)
        os.chdir(os.path.dirname(self.configfile) or '.')
        logmsg = "Loading model {} in directory {}".format(
            self.configfile,
            os.path.abspath(os.getcwd())
        )
        logger.info(logmsg)
        # Fortran init function.
        self.library.initialize.argtypes = [c_char_p]
        self.library.initialize.restype = None
        # initialize by abs_name because we already chdirred
        # if configfile is a relative path  we would have a problem
        ierr = wrap(self.library.initialize)(abs_name)
        if ierr:
            errormsg = "Loading model {config} failed with exit code {code}"
            raise RuntimeError(errormsg.format(config=self.configfile,
                                               code=ierr))
...

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 20:55:20