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

首先,这是我目前所拥有的,并且确实有效:

从C(好的,实际上是C++)程序开始,回调的 header 原型(prototype)为:

typedef void (*fcb)(void *)

Fortran调用的原型(prototype)是:
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回调定义了一个接口(interface):
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的抽象接口(interface)都没有改变。我要做的就是创建一个新的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

这在带有“访问冲突读取位置0xffffffffffffffffff”的调用中失败。

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

同样,C端也没有任何变化,但是我修改了main函数的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的抽象接口(interface),尽管我尝试过将bind(c)的一部分保留下来并省略它。现在,调用子例程foo的主函数中的代码为:
call c_f_procpointer(callback, data%callback_data%callback)
data%callback_data%c_object = c_object
call foo(data)

...子程序foo本身仍如前面的示例中所定义。

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

我假设有一种正确的语法可以实现我在这里想要实现的目标,并且我非常感谢您提供任何建议。

最佳答案

在C语言中,Fortran过程中带有BIND(C)属性且不具有VALUE参数的伪参数与指针参数等效(这与通过引用传递的常规Fortran约定大体一致)。因此,如果在Fortran端具有INTEGER(C_INT) :: a(无值属性),则在C端等效于int *a

也许这是显而易见的,但是却产生了令人惊讶的结果-如果您具有TYPE(C_PTR) :: p,它等效于void **p-C_PTR是一个指针,因此不带任何值传递的C_PTR是一个指针。鉴于此,您的回调接口(interface)已退出(您需要添加VALUE)。

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

Fortran过程指针恰好起作用的事实只是C函数指针和Fortran过程指针的基础实现以及Fortran过程指针的传递方式的巧合(并不令人惊讶)。

总之,您的Fortran过程可能需要具有一个类似于以下内容的接口(interface):

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回调的接口(interface)的声明需要具有以下接口(interface):
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

(如最近几个月在Intel论坛上所讨论的(您去哪儿了?),当前的ifort可能在处理C_PTRVALUE时有问题。)

10-02 02:01
查看更多