如何从 Fortran 2003 正确调用涉及不透明指针的 C 函数?

How to call C functions involving opaque pointers from Fortran 2003 correctly?

我正在学习 Fortran 2003。作为一项训练任务,我正在尝试从 Fortran 2003 调用使用不透明指针的 C 库:

struct foobar_s;
typedef struct foobar_s *foobar;
foobar foo_create(enum foo, unsigned int);
void foo_destroy(foobar);

我在互联网上找到的大多数建议都告诉我将 foobar 类型描述为 type(c_ptr),因此以下内容应该有效:

!foobar foo_create(enum foo, unsigned int);
function foo_create(mode,n) bind(c) ret(foo)
 type(c_ptr) :: foo
 integer(kind(ENUM_FOO_CONSTANT)), value :: mode
 integer(kind=c_int), value :: n
end function

这将 foo_create 声明为返回 void* 而不是 foobar = struct foobar_s *,但它仍然适用于现代架构。

我一直在尝试创建一个独特的 Fortran 类型,更接近不透明 C 指针的意图。唯一对我有用的是:

type, bind(c) :: foobar
  private
  type(c_ptr) :: ptr
end type

对应于:

typedef struct {
    void * ptr;
} foobar;

在C端。现在,C 标准的 §6.7.2.1 保证 struct 的开头地址是第一个元素的地址(对吗?)但它的末尾可能有一些填充(但在体系结构上我使用没有,因为指针是自对齐的),所以这整个装置在我的机器上工作:

!foobar foo_create(enum foo, unsigned int);
function foo_create(mode,n) bind(c) ret(foo)
 type(foobar) :: foo
 integer(kind(ENUM_FOO_CONSTANT)), value :: mode
 integer(kind=c_int), value :: n
end function

!void foo_destroy(foobar);
 sobroutine foo_destroy(foo) bind(c)
 type(foobar), value :: foo
end subroutine

我已验证 Valgrind 对于使用此类型定义从 Fortran 调用 C 函数 foo_create()foo_destroy() 的程序没有显示任何错误。尽管如此,它仍然不能成为确定的证据。

struct { void * ptr }struct foobar_s * 具有相同大小和位模式的假设会被打破吗?这是在 Fortran 2003 中包装不透明 C 指针(并创建不同类型)的最佳方式吗?

C 语言要求引用同一对象或函数的所有声明都具有兼容的类型。鉴于 Fortran 代码的有效 C 声明,您的方法违反了该要求。该要求的一个实际结果是,编译器可以使用不同的方法来 return 声明 struct { void * ptr } 的东西,而不是声明 struct foobar_s * 的东西(例如,聚合可能 return 在一个由传递给函数的隐藏参数指定的区域,指针结果可能 returned 在寄存器中)。这种实现上的差异对您的代码来说将是灾难性的。

Fortran 中的

TYPE(C_PTR) 可用于 void *struct foobar_s*,对于 Fortran 处理器的配套 C 编译器有一个隐含的要求,即相同的表示方法用于所有 C 对象指针类型(参见 f2003 注释 15.9)。

一种典型的方法是围绕 C 函数编写小型 Fortran 包装程序,适当地设置和引用私有 C_PTR 组件。具有 C_PTR 组件的 Fortran 类型不需要可互操作。如果 Fortran 类型不可互操作,您可以使用现代 Fortran 功能,例如类型扩展和终结器 - foo_destroy 看起来对于从终结器调用很有用。

MODULE Fortran_Wrapper
  USE, INTRINSIC :: ISO_C_BINDING, ONLY: xxxxx
  ...
  ! Enum definition in here somewhere.
  ...
  PUBLIC :: foobar
  PUBLIC :: Create

  ! Wrapper for a pointer to foobar_s.
  TYPE :: foobar
    PRIVATE
    TYPE(C_PTR) :: ptr = C_NULL_PTR
  CONTAINS
    FINAL :: final
  END TYPE foobar
  ...
CONTAINS
  ! Wrapper around foo_create, exposed to Fortran client code.
  FUNCTION Create(mode, n) RESULT(obj)
    INTEGER(KIND(ENUM_FOO_CONSTANT)), INTENT(IN) :: mode
    ! Perhaps the next argument is taken as default integer, and 
    ! you do kind conversion inside this wrapper.
    INTEGER(C_INT), INTENT(IN) :: n
    TYPE(foobar) :: obj

    INTERFACE
      FUNCTION foo_create(mode, n) BIND(C, NAME='foo_create')
        USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_INT, C_PTR
        IMPORT :: ENUM_FOO_CONSTANT
        IMPLICIT NONE
        INTEGER(KIND(ENUM_FOO_CONSTANT)), VALUE :: mode
        INTEGER(KIND=C_INT), VALUE :: n
        TYPE(C_PTR) :: foo_create
      END FUNCTION foo_create
    END INTERFACE

    obj%ptr = foo_create(mode, n)
  END FUNCTION Create

  ! Use a finalizer to do automatic cleanup off the C structures.
  ! (Impure elemental is F2008.)
  IMPURE ELEMENTAL SUBROUTINE final(obj)
    TYPE(foobar), INTENT(INOUT) :: obj
    INTERFACE
      SUBROUTINE foo_destroy(obj) BIND(C, NAME='foo_destroy')
        USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR
        IMPLICIT NONE
        TYPE(C_PTR), VALUE :: obj
      END SUBROUTINE foo_destroy
    END INTERFACE

    IF (C_ASSOCIATED(obj%ptr)) CALL foo_destroy(obj%ptr)
  END SUBROUTINE final 
END MODULE Fortran_Wrapper

(注意问题中的Fortran接口体缺少两个伪参数定义上的VALUE属性,否则对应的C原型为foobar foo_create(enum foo*, unsigned int*)。)