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

Eos*_*ern 6 c interop fortran derived-types fortran-iso-c-binding

我有一个从C程序调用的Fortran DLL,我的一个程序需要定期调用由C程序提供的回调函数.我目前在它的'简单'形式下运行良好,但我希望能够将我的回调指针存储在派生类型中,以便它可以更容易地在我的Fortran代码中传递.到目前为止,我尝试过的任何东西似乎都没有用.

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

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

typedef void (*fcb)(void *)
Run Code Online (Sandbox Code Playgroud)

Fortran调用的原型是:

extern "C" __declspec(dllexport) int fortran_function(int n,
                                                      uchar *image_buffer,
                                                      fcb callback,
                                                      void *object);
Run Code Online (Sandbox Code Playgroud)

实际的回调函数是:

void callback(void* pObject)
{
    // Cast the void pointer back to the appropriate class type:
    MyClass *pMyObject = static_cast<MyClass *>(pObject);
    pMyObject -> updateImageInGUI();
}
Run Code Online (Sandbox Code Playgroud)

并且从C++调用Fortran代码是:

int error = fortran_function(m_image.size(), m_image.data, callback, this);
Run Code Online (Sandbox Code Playgroud)

其中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
Run Code Online (Sandbox Code Playgroud)

并定义我们的主要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
Run Code Online (Sandbox Code Playgroud)

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

call foo(data, callback, c_object)
Run Code Online (Sandbox Code Playgroud)

......其中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
Run Code Online (Sandbox Code Playgroud)

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

现在我已经尝试但不起作用的东西:

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

我希望这可以工作,因为我所做的就是将原始元素复制到一个没有修改的结构中.C方面没有任何变化,主要的Fortran函数和抽象接口也没有变化c_callback.我所做的就是创建一个新的Fortran派生类型:

type :: callback_data
    procedure(c_callback), pointer, nopass :: callback => null()
    type(c_ptr) :: c_object
end type callback_data
Run Code Online (Sandbox Code Playgroud)

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

data%callback_data%callback => callback
data%callback_data%c_object = c_object
call foo(data)
Run Code Online (Sandbox Code Playgroud)

子程序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
Run Code Online (Sandbox Code Playgroud)

这在通话时失败,出现"访问冲突读取位置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
Run Code Online (Sandbox Code Playgroud)

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

call c_f_procpointer(callback, data%callback_data%callback)
data%callback_data%c_object = c_object
call foo(data)
Run Code Online (Sandbox Code Playgroud)

...子程序foo本身仍然如上例所定义.

不幸的是,这与前一个例子完全相同.

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

Ian*_*anH 4

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
Run Code Online (Sandbox Code Playgroud)

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

并且您对 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
Run Code Online (Sandbox Code Playgroud)

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