【问题标题】:How to call C functions involving opaque pointers from Fortran 2003 correctly?如何正确调用 Fortran 2003 中涉及不透明指针的 C 函数?
【发布时间】:2019-03-03 01:29:48
【问题描述】:

我正在学习 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

我已经验证,对于使用此类型定义从 Fortran 调用 C 函数 foo_create()foo_destroy() 的程序,Valgrind 没有显示错误。不过,这不能作为一个确定的证据。

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

【问题讨论】:

  • 对所有 Fortran 问题使用标签 fortran 以获得更多关注(与使用 c 而不是 c99 的方式相同)。
  • 我不明白为什么您希望您的 Fortran 包装器 foobar 具有互操作性 - 它使 Fortran 类型不那么明显。根据您的操作,大小和位模式相同是不够的 - 例如调用约定可能会有所不同,具体取决于您处理的是原始值还是聚合。或许可以举一个例子说明您的想法,以获得更相关的评论。
  • @IanH,我使用bind(c) 直接从 Fortran 调用库的 C API,以要求 Fortran 编译器遵循 C 调用约定。我想要一个 Fortran 类型,它 (1) 行为与指向结构的不透明指针完全一样,并且 (2) 与 Fortran 端的其他类型不同。我将在问题中添加一些示例。

标签: c fortran fortran2003 opaque-pointers


【解决方案1】:

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

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*)。)

【讨论】:

  • 感谢您的回答!我将示例声明中缺少的VALUE 放回它们所属的位置。包装类型方法是否意味着我必须包装原始库的所有功能?我是否必须在直接调用 C 接口但传递 type(c_ptr) 和能够传递不透明的 Fortran 类型但必须编写包装子程序之间进行选择?
猜你喜欢
  • 2016-01-01
  • 2016-01-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-12
  • 1970-01-01
相关资源
最近更新 更多