首页 文章

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

提问于
浏览
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

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

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

The naive approach, just copying the arguments into the fields of a structure

我'd expect this to work, since all all I'做的是将原始元素复制到一个没有修改的结构中 . C方面没有任何变化,主要Fortran函数的定义也没有变化, c_callback 的抽象接口也没有变化 . 我所做的就是创建一个新的Fortran派生类型:

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

然后在我的main函数中,我用从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” .

The sophisticated approach using more of the iso_c_binding features

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 ,尽管我已经尝试将其中的 bind(c) 部分留在其中,并省略了它 . 调用子例程 foo 的main函数中的代码现在是:

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

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

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

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

1 回答

  • 4

    具有 BIND(C) 属性且不具有VALUE参数的Fortran过程中的伪参数在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 时遇到问题 . )

相关问题