我有一个大型 Fortran 代码库,我正在尝试为其编写一个 Python 接口。我决定走 Cython 路线。但是,我有不能与 C 互操作的派生类型。举个例子,
type :: SomeType
real, allocatable :: not_interoperable
[other members]
contains
procedure :: init
end type
...
subroutine init(this) bind(c)
class(SomeType), intent(inout) :: this
...
end subroutine init
我会非常高兴无法从 Python 访问成员
not_interoperable
,并且实际上更不想将其暴露给用户。即一些只包含其他成员的 python 类就完全没问题,只要 Fortran 内部的函数仍然可以“看到”它们。在这里,编译器失败,因为 init
有一个多态虚拟参数。我可以删除 init
作为类型绑定过程,然后进行更改 class(SomeType)
-> type(SomeType)
,但我仍然收到错误,this
是 bind(c) 过程的虚拟参数,但是不能与 C 互操作,因为派生类型不能与 C 互操作。
有什么办法可以解决这个问题吗?我能看到的唯一解决方案是从
SomeType
中删除所有非 C 互操作成员,并使它们成为 Fortran 代码中的全局/模块变量,但我宁愿避免这样做。基本上,我想要一种方法来隐藏 Python 中的变量(例如那些不可与 C 互操作的变量),同时仍然保持 Fortran 代码模块化/防止全局变量。
更多细节:
我的程序大致工作原理如下:
type(SomeType) :: instance
instance%init() ! no input arguments, but reads a file
instance%do_some_stuff()
instance%do_stuff_with_args(...) ! args are all C-interoperable
instance%finalize()
我想要一个类似的 Python 工作流程
from FortranModule import SomeType
instance = SomeType(...) # does not read a file, variables passed directly
instance.do_stuff()
instance.member = 3
instance.do_stuff_with_args(...)
instance.finalize()
但在 Python 中,
not_interoperable
永远不会被访问。我希望我的 Fortran 代码仍然拥有它并使用它,并且它仍然受到 SomeType%init
的影响,但我不希望/不需要在 Python 中使用它。原则上我想我可以将它隐藏为一个全局变量,该变量会被这个函数修改,但是(a)我只能有一个实例并且(b)我不喜欢全局变量作为一般规则(普遍共识是它降低了模块化和可维护性)。
另外,为了清楚起见:我有很多不可互操作的类型,它们并不像
real, allocatable
那么简单,所以重写所有它们并不现实。
AFAIK 无法与包含可分配组件的派生类型与 C 进行互操作)。解决方案是仅向 C 公开派生类型实例的不透明句柄(void* C 指针)。然后可以选择让组件对 C 完全隐藏,或者为 C 提供一些指向它们的指针。
插图代码(可能还剩下一些bug,我没有尝试编译它,这就是总体思路):
Fortran 部分:
! ===========================================
! file: somemodule.f90
module somemodule
use, intrinsic :: iso_c_binding
implicit none
! c_float is needed only if one wants to
! access %a directly from C
type, bind(c) :: sometype
real(c_float), allocatable :: a(:)
end type
contains
! example routine that just prints some stuff from a sometype instance
subroutine sometype_print_f(x)
type(sometype), intent(in) :: x
if (allocated(x%a)) then
write(*,*) lbound(x%a), size(x%a), ubound(x%a)
write(*,*) x%a(:)
else
write(*,*) "%a component is unallocated"
end if
end subroutine
end module
! ===========================================
! ===========================================
! file: somemodule_wrap.f90
module somemodule_wrap
use, intrinsic :: iso_c_binding
use somemodule
implicit none
contains
! function that creates a sometype instance,
! allocates the %a component,
! and returns a C pointer to the instance
type(c_ptr) function sometype_create(len) bind(c)
integer(c_int), value :: len
type(sometype), pointer :: p
allocate(p)
allocate(p%a(len))
sometype_create = c_loc(p)
end function
! routine that releases a handle
subroutine sometype_free(ptr) bind(c)
type(c_ptr) :: ptr
type(sometype), pointer :: p
call c_f_pointer(ptr,p)
if (allocated(p%a)) deallocate(p%a)
deallocate(p)
ptr = c_null_ptr
end subroutine
! function that returns a C pointer to the %a component,
! allowing to work directly on it from C
type(c_ptr) function sometype_a_ptr(ptr, n) bind(c)
type(c_ptr), value :: ptr
integer(c_int), intent(out) :: n
type(sometype), pointer :: p
call c_f_pointer(ptr,p)
if (allocated(p%a)) then
n = size(p%a)
sometype_a_ptr = c_loc(p%a)
else
n = 0
sometype_a_ptr = c_null_ptr
end if
end function
! wrapper routine to a Fortran routine that performs
! some processing on a sometype instance
subroutine sometype_print(ptr) bind(c)
type(c_ptr), value :: ptr
type(sometype), pointer :: p
call c_f_pointer(ptr,p)
call sometype_print_f(p)
end subroutine
end module
! ===========================================
C部分:
//===========================================
//file: main.c
#include "somemodule_wrap.h"
int main(void)
{
// creates an instance of sometype with 6 elements in %a
void* h = sometype_create( 6 );
// get a pointer to %a and fill it
int n;
float* a = sometype_a_ptr(h, &n);
for (int i = 0; i < n; i++) {
a[i] = (float) i;
}
// print the content of the instance
sometype_print_c(h);
// releases the instance
sometype_free(&h);
return 0;
}
//===========================================
//===========================================
//file: somemodule_wrap.h
void* sometype_create(int len);
void* sometype_free(void** h);
float* sometype_a_ptr(void* h, int* n);
void* sometype_print(void* h);
//===========================================
但是,从Python管理C指针可能不方便/不可能(我对Python了解不够......)。然后,我们可以返回标识符,而不是返回 void* 指针句柄,该标识符引用全局数组中的实例:
! ===========================================
! file: somemodule_wrap.f90
module somemodule_wrap
use, intrinsic :: iso_c_binding
use somemodule
implicit none
type(sometype), allocatable :: h(:)
logical :: active(:)
contains
! function that creates a sometype instance,
! allocates the %a component,
! and returns an identifier of the instance
integer(c_int) function sometype_create(len) bind(c)
integer(c_int), value :: len
integer(c_int) :: id
if (.not.allocated(h)) allocate(h(0))
id = findloc(active,.false.,dim=1)
if (id == 0) then
h = [h, sometype()]
id = size(h)
end if
active(id) = .true.
allocate(h(id)%a(len))
sometype_create = id
end function
! routine that releases an instance
subroutine sometype_free(id) bind(c)
integer(c_int), intent(inout) :: id
if (allocated(h(id)%a)) deallocate(h(id)%a)
active(id) = .false.
if (id == size(h)) then
do while (.not.active(id) .and. id>0) then
h = h(1:id-1)
active = active(1:id-1)
id = id-1
end do
end if
id = 0
end subroutine
! function that returns a C pointer to the %a component,
! allowing to work directly on it from C
type(c_ptr) function sometype_a_ptr(id, n) bind(c)
integer(c_int), value :: id
integer(c_int), intent(out) :: n
if (allocated(h(id)%a)) then
n = size(h(id)%a)
sometype_a_ptr = c_loc(h(id)%a)
else
n = 0
sometype_a_ptr = c_null_ptr
end if
end function
! wrapper routine to a Fortran routine that performs
! some processing on a sometype instance
subroutine sometype_print(id) bind(c)
integer(c_int), value :: id
call sometype_print_f(h(id))
end subroutine
end module
! ===========================================
C部分:
//===========================================
//file: main.c
#include "somemodule_wrap.h"
int main(void)
{
// creates an instance of sometype with 6 elements in %a
int id = sometype_create( 6 );
// get a pointer to %a and fill it
int n;
float* a = sometype_a_ptr(id, &n);
for (int i = 0; i < n; i++) {
a[i] = (float) i;
}
// print the content of the instance
sometype_print_c(id);
// releases the instance
sometype_free(&id);
return 0;
}
//===========================================
//===========================================
//file: somemodule_wrap.h
int sometype_create(int len);
void* sometype_free(int* id);
float* sometype_a_ptr(int id, int* n);
void* sometype_print(int id);
//===========================================