我有一个从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 回答
具有
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过程可能需要具有如下界面:
(你的原始代码中的图像数组声明似乎误入歧途 - 也许以上是合适的,也许不是)
和你的C回调接口的声明需要有一个界面:
(正如过去几个月在英特尔论坛上所讨论的那样(你去过哪里?),当前的ifort可能在处理
C_PTR
和VALUE
时遇到问题 . )