在 Fortran 派生类型中保存指向 C 函数的指针

Posted

技术标签:

【中文标题】在 Fortran 派生类型中保存指向 C 函数的指针【英文标题】:Holding a pointer to a C function inside a Fortran derived type 【发布时间】:2014-02-06 02:45:15 【问题描述】:

我有一个从 C 程序调用的 Fortran DLL,我的一个程序需要定期调用 C 程序提供的回调函数。我目前让它以其“简单”形式运行良好,但我希望能够将我的回调指针存储在派生类型中,以便可以更轻松地在我的 Fortran 代码中传递它。到目前为止,我尝试过的任何方法似乎都不起作用。

首先,这是我目前拥有的,这确实工作:

从C(OK,实际上是C++)程序开始,回调的头部原型是:

typedef void (*fcb)(void *)

Fortran 调用的原型是:

extern "C" __declspec(dllexport) int fortran_function(int n,
                                                      uchar *image_buffer,
                                                      fcb callback,
                                                      void *object);

实际的回调函数是:

void callback(void* pObject)

    // Cast the void pointer back to the appropriate class type:
    MyClass *pMyObject = static_cast<MyClass *>(pObject);
    pMyObject -> updateImageInGUI();

从 C++ 调用 Fortran 代码是:

int error = fortran_function(m_image.size(), m_image.data, callback, this);

其中m_image 是一个图像数据数组,它是当前对象的成员属性。发生的情况是 C++ 将原始图像数据传递给 Fortran DLL 并要求 Fortran 对其进行处理,并且由于这需要很长时间,因此 Fortran 会定期更新图像缓冲区并调用回调以刷新 GUI。无论如何,转到 Fortran 方面,我们为 C 回调定义一个接口:

abstract interface
    subroutine c_callback(c_object) bind(c)   
        use, intrinsic :: iso_c_binding
        type(c_ptr), intent(in) :: c_object     
    end subroutine c_callback
end interface

并这样定义我们的主 Fortran 例程:

integer(c_int) fortran_function(n, image, callback, c_object)     &
                                bind(c, name='fortran_function')

    integer(c_int), value :: n
    integer(4), intent(inout), dimension(n) :: image
    procedure(c_callback) :: callback
    type(c_ptr), intent(in) :: c_object

在主程序中的某个地方,我们称之为子程序foo

call foo(data, callback, c_object)

...foo 定义为:

subroutine foo(data, callback, c_object)

    type(my_type), intent(inout) :: data
    procedure(c_callback) :: callback
    type(c_ptr), intent(in) :: c_object
    ...
    call callback(c_object)
    ...
end function foo

正如我所说,所有这些都运作良好,并且已经这样做了很长时间。

现在对于我尝试过但不起作用的事情:

天真的方法,只是将参数复制到结构的字段中

我希望这能奏效,因为我所做的只是将原始元素复制到一个结构中而无需修改。 C 端没有任何变化,Fortran 主函数的定义和c_callback 的抽象接口也没有变化。我所做的只是创建一个新的 Fortran 派生类型:

type :: callback_data
    procedure(c_callback), pointer, nopass :: callback => null()
    type(c_ptr) :: c_object
end type callback_data

然后在我的主函数中,我用从 C 应用程序接收到的值填充它:

data%callback_data%callback => callback
data%callback_data%c_object = c_object
call foo(data)

子例程 foo 已稍作修改,现在它在结构中查找回调和 C 对象:

subroutine foo(data)
    type(my_augmented_type), intent(inout) :: data
    ...
    call data%callback_data%callback(data%callback_data%c_object)
    ...
end function foo

这在调用时失败,并显示“访问冲突读取位置 0xffffffffffffffff”。

使用更多 iso_c_binding 功能的复杂方法

C 端也没有任何变化,但我修改了主函数的 Fortran 端以接收回调作为 c_funptr:

integer(c_int) fortran_function(n, image, callback, c_object)     &
                                bind(c, name='fortran_function')

    integer(c_int), value :: n
    integer(4), intent(inout), dimension(n) :: image
    type(c_funptr), intent(in) :: callback
    type(c_ptr), intent(in) :: c_object

我像以前一样定义了subroutine c_callback 的抽象接口,尽管我已经尝试了保留bind(c) 的一部分并省略它。调用子程序foo 的主函数中的代码现在是:

call c_f_procpointer(callback, data%callback_data%callback)
data%callback_data%c_object = c_object
call foo(data)

...子例程 foo 本身仍然像前面的例子一样定义。

不幸的是,这与前面的示例完全相同。

我假设有一个正确的语法来实现我在这里想要实现的目标,我将非常感谢任何建议。

【问题讨论】:

您正在通过引用传递一个意图(输入)指针。这是故意的吗?由于您所说的处理程序的方式,您的第一个“工作”方法和天真的方法都不符合。请显示 a) C 调用的 Fortran 过程的 C 原型 b) 对 fortran 的实际 C 调用(传递回调),c) C 调用的 Fortran 过程的接口,以及 d) 的原型C 回调函数。 谢谢 IanH;我不确定我是否理解您的观点,而且我引用的代码显然有点过于简单化了。我将编辑问题并提供更多详细信息。 【参考方案1】:

Fortran 过程中具有BIND(C) 属性但没有 VALUE 参数的虚拟参数在 C 端等效于指针参数(这与通过引用传递事物的通常 Fortran 约定大体一致)。因此,如果在 Fortran 端有 INTEGER(C_INT) :: a(无值属性),则在 C 端相当于 int *a

也许这很明显,但它有一个令人惊讶的结果 - 如果你有 TYPE(C_PTR) :: p,那相当于 void **p - C_PTR 是一个指针,所以一个没有值传递的 C_PTR 是一个指向指针的指针。鉴于此,您的回调接口已失效(您需要添加VALUE)。

Fortran 中指向函数的 C 指针(在 C 中是无括号的函数名)在类型意义上的可互操作模拟是 TYPE(C_FUNPTR)。关于缺少 VALUE 属性和 C_PTR 的相同考虑适用 - 声明为 TYPE(C_FUNPTR) :: f 的参数是指向函数指针的指针。鉴于此以及您对 Fortran 的 C 端调用,对应于函数指针的参数应具有 VALUE 属性。

Fortran 过程指针恰好工作的事实只是 C 函数指针和 Fortran 过程指针的底层实现以及 Fortran 过程指针的传递方式的巧合(并不令人非常惊讶)。

总之,您的 Fortran 程序可能需要一个如下所示的界面:

integer(c_int) fortran_function(n, image, callback, c_object)     &
                                bind(c, name='fortran_function')

  integer(c_int), value :: n
  integer(c_signed_char), intent(inout), dimension(n) :: image
  type(c_funptr), intent(in), value :: callback
  type(c_ptr), intent(in), value :: c_object

(您在原始代码中对图像数组的声明似乎误入歧途 - 也许上面是合适的,也许不是)

你的C回调接口声明需要有一个接口:

abstract interface
  subroutine c_callback(c_object) bind(c)   
    use, intrinsic :: iso_c_binding
    implicit none
    type(c_ptr), intent(in), value :: c_object     
  end subroutine c_callback
end interface

(正如过去几个月在英特尔论坛上所讨论的那样(你去哪里了?),当前的 ifort 可能在处理 C_PTRVALUE 方面存在问题。)

【讨论】:

IanH,你是绅士和学者!这解决了问题。我所做的是重新实现我在原始问题中描述的“复杂”方法,但在您建议的地方添加“价值”限定词。现在可以了。 顺便说一句,英特尔论坛出于某种原因不允许我在那里发布任何内容:我只是被拒绝为垃圾邮件发送者! Intel Premier Support 正在为我调查这个问题。我错过了最近关于 C_PTR 和 VALUE 的任何讨论,尽管我在论坛中搜索了有关 C_FUNPTR 的任何提及,但没有发现任何相关内容。值得一提的是,我刚刚使用 ifort 编译器的 14.0.1.139 版本进行了成功的测试。

以上是关于在 Fortran 派生类型中保存指向 C 函数的指针的主要内容,如果未能解决你的问题,请参考以下文章

确定内存中的 Fortran 派生类型大小

在派生类型中使用可分配的目标变量

在派生类型中使用可分配的目标变量

在 fortran 中命名派生类型的良好做法

Fortran 派生类型

Fortran 派生类型运算符