包含非 C 互操作派生类型的程序的 Fortran C 接口

问题描述 投票:0回答:1

我有一个大型 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
那么简单,所以重写所有它们并不现实。

fortran cython language-interoperability
1个回答
0
投票

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);
//===========================================
© www.soinside.com 2019 - 2024. All rights reserved.