我有一个从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_PTR
和VALUE
时有问题。)