6

我有一个从 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 本身仍然像前面的例子一样定义。

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

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

4

1 回答 1

4

BIND(C)Fortran 过程中具有不具有 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_PTR和时存在问题VALUE。)

于 2014-01-17T10:18:20.887 回答