如何在科学程序中使用 Fortran 中的函数指针

计算科学 宠物 正则
2021-12-14 02:27:22

这是 C 中函数指针的典型用法。我想在 Fortran 中做类似的事情。我有一些想法,但我想知道是否有一些规范的方法可以做到这一点。

用户传入的函数指针和上下文会被存储起来,然后再调用。

typedef PetscErrorCode (*TSIFunction)(TS,PetscReal,Vec,Vec,Vec,void*);
PetscErrorCode TSSetIFunction(TS ts,Vec res,TSIFunction f,void *ctx);

用户的函数在以后的不同时间使用他们的上下文被回调。

在 PETSc 中,他们还大量使用字符串 -> 函数指针表。一切都是插件,因此用户可以注册自己的实现,它们是一流的。

#define PCGAMG "gamg"
  PCRegisterDynamic(PCGAMG         ,path,"PCCreate_GAMG",PCCreate_GAMG);

这会将创建例程注册到“FList”中,然后 PCSetFromOptions() 提供了选择此方法而不是任何其他选择的能力。如果系统支持动态加载,您可以跳过对 PCCreate_GAMG 符号的编译时依赖,只传递 NULL,然后将在运行时在共享库中查找该符号。

请注意,这是超越“工厂”的一步,它是一种控制装置的反转,类似于 Martin Fowler 所说的“服务定位器”。

注意:这出现在我与 Jed Brown 的私人通信中,他向我提出了这个问题。我决定外包它,看看人们能想出什么答案。

2个回答

我猜你的问题中有很多是 PETSc 特定的语言(我对此并不熟悉),所以我不太明白这里可能有一个皱纹,但也许这对你来说仍然有用开始了。

基本上,您必须为过程定义接口,然后您可以将指针传递给该接口之后的函数。以下代码显示了一个示例。首先,有一个模块定义了接口,并显示了一段代码的快速示例,该代码块将执行用户提供的遵循该接口的例程。接下来是一个程序,显示用户将如何使用该模块并定义要执行的功能。

MODULE xmod

  ABSTRACT INTERFACE
  FUNCTION function_template(n,x) RESULT(y)
      INTEGER, INTENT(in) :: n
      REAL, INTENT(in) :: x(n)
      REAL :: y
  END FUNCTION function_template
  END INTERFACE

CONTAINS

  SUBROUTINE execute_function(n,x,func,y)
    INTEGER, INTENT(in) :: n
    REAL, INTENT(in) :: x(n)
    PROCEDURE(function_template), POINTER :: func
    REAL, INTENT(out) :: y
    y = func(n,x)
  END SUBROUTINE execute_function

END MODULE xmod


PROGRAM xprog

  USE xmod

  REAL :: x(4), y
  PROCEDURE(function_template), POINTER :: func

  x = [1.0, 2.0, 3.0, 4.0]
  func => summation

  CALL execute_function(4,x,func,y)

  PRINT*, y  ! should give 10.0

CONTAINS

  FUNCTION summation(n,x) RESULT(y)
    INTEGER, INTENT(in) :: n
    REAL, INTENT(in) :: x(n)
    REAL :: y
    y = SUM(x)
  END FUNCTION summation

END PROGRAM xprog

无需使用传输来模拟void *现代 Fortran 代码。相反,只需使用所有主流 Fortran 编译器都支持的ISO_C_BINDING内部模块。这个模块使 Fortran 和 C 之间的接口变得非常简单,但有一些非常小的警告。可以使用C_LOCC_FUNLOC函数分别获取指向 Fortran 数据和过程的 C 指针。

关于上面的 PETSC 示例,我假设上下文通常是指向用户定义结构的指针,它相当于 Fortran 中的派生数据类型。这不应该是处理使用的问题C_LOC不透明的 TSIFunction 句柄处理起来也很简单:只需使用 ISO_C_BINDING 数据类型c_ptr,它相当于void *C 中的数据类型。用 Fortran 编写的库c_ptr如果需要绕过现代 Fortran 的严格类型检查,可以使用。