Fortran 派生类型包含可从 C 访问的指针

Posted

技术标签:

【中文标题】Fortran 派生类型包含可从 C 访问的指针【英文标题】:Fortran derived types containing pointers to be accessible from C 【发布时间】:2015-04-17 01:37:06 【问题描述】:

我有一个 Fortran 代码,其中包含许多包含指针的派生类型。我正在编写需要访问这些变量的 C++ 代码。如果没有指针,我无法重写这些派生类型,因为它们在 Fortran 代码中的数百个不同地方使用。

下面是示例代码:

module simple
use  iso_c_binding

TYPE,bind(C) :: SIMPLEF
INTEGER :: A
INTEGER, POINTER :: B, C(:)
END TYPE SIMPLEF

end module simple

我需要从 C 中访问 SIMPLEF 派生类型。我知道我不能按原样使用它,因为如果应该从 C 中访问 Fortran 指针,它就不能属于派生类型。有什么解决方法吗?

扩展:作为前一个问题的扩展(感谢 IanH 解决),我有派生类型,这些派生类型本身具有派生类型的成员。示例如下:

TYPE COMPLEXF
  INTEGER :: X
  TYPE (SIMPLEF) :: Y
END TYPE COMPLEXF

我是否需要为 COMPLEXF 创建 Y 的每个成员的子例程,即 SETY_A、QUERYY_A、SETY_B、QUERYY_BSIZE、QUERYY_B 等?还是有更好的方法来解决这个问题?

【问题讨论】:

我认为是时候提出一个单独的问题了。 我现在做到了here 【参考方案1】:

您可以在 Fortran 中编写一些可互操作的访问器过程,它们对派生类型进行操作并将必要的变量公开给 C++ 代码。这与一般 C++ 代码与类的私有成员变量交互的方式非常相似。

您可以使用 SIMPLEF 类型对象的 C 地址作为 C++ 代码中的不透明句柄 - Fortran 中的类型不必具有 BIND(C) 属性以允许将该类型的对象传递给 C_LOC (尽管该类型的对象需要具有 TARGET 属性)。

对于数组数据,您可能需要为数据获取器提供多个入口点,以便适当协调用于将数据从 Fortran 传输到 C 的内存缓冲区。

MODULE simple
  IMPLICIT NONE
  ! An example of an non-interoperable type (no BIND(C)).
  TYPE :: SIMPLEF
    INTEGER :: A
    ! Note that given the problem description, the component B 
    ! appears to have value semantics.  If so, as of Fortran 2003 
    ! this should be an ALLOCATABLE component.  Because it is 
    ! a pointer component, we will default initialize it to 
    ! help avoid its pointer association status becoming 
    ! inadvertently undefined 
    INTEGER, POINTER :: B(:) => NULL()
  END TYPE SIMPLEF
CONTAINS
  FUNCTION GetHandle() RESULT(handle) BIND(C, NAME='GetHandle')
    USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR, C_LOC
    TYPE(C_PTR) :: handle
    TYPE(SIMPLEF), POINTER :: p
    !***
    ! For the sake of example we are exposing an interface that 
    ! allows client code to create an object.  Perhaps in your 
    ! case the object already exists and its lifetime is managed 
    ! in some other way, in which case:
    !
    !   handle = C_LOC(existing_object_with_target_attribute)
    !
    ! and you are done - no need for ReleaseHandle.
    ALLOCATE(p)
    ! Perhaps some constructory sort of stuff here?
    p%A = 666
    ! Use the C address of the object as an opaque handle.
    handle = C_LOC(p)
  END FUNCTION GetHandle

  ! If you create objects, you need to be able to destroy them.
  SUBROUTINE ReleaseHandle(handle) BIND(C, NAME='ReleaseHandle')
    USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR, C_F_POINTER
    TYPE(C_PTR), INTENT(IN), VALUE :: handle
    TYPE(SIMPLEF), POINTER :: p
    !***
    CALL C_F_POINTER(handle, p)
    DEALLOCATE(p)
  END SUBROUTINE ReleaseHandle

  SUBROUTINE SetA(handle, a) BIND(C, NAME='SetA')
    USE, INTRINSIC :: ISO_C_BINDING, ONLY:  &
        C_PTR, C_F_POINTER, C_INT
    TYPE(C_PTR), INTENT(IN), VALUE :: handle
    INTEGER(C_INT), INTENT(IN), VALUE :: a  
    TYPE(SIMPLEF), POINTER :: p
    !***
    CALL C_F_POINTER(handle, p)
    p%A = a
  END SUBROUTINE SetA

  FUNCTION QueryA(handle) RESULT(a) BIND(C, NAME='QueryA')
    USE, INTRINSIC :: ISO_C_BINDING, ONLY:  &
        C_PTR, C_F_POINTER, C_INT
    TYPE(C_PTR), INTENT(IN), VALUE :: handle
    INTEGER(C_INT) :: a  
    TYPE(SIMPLEF), POINTER :: p
    !***
    CALL C_F_POINTER(handle, p)
    a = p%A
  END FUNCTION QueryA

  SUBROUTINE SetB(handle, data, data_size) BIND(C, NAME='SetB')
    USE, INTRINSIC :: ISO_C_BINDING, ONLY:  &
        C_PTR, C_F_POINTER, C_INT
    TYPE(C_PTR), INTENT(IN), VALUE :: handle
    INTEGER(C_INT), INTENT(IN), VALUE :: data_size
    INTEGER(C_INT), INTENT(IN) :: data(data_size)
    TYPE(SIMPLEF), POINTER :: p
    !***
    CALL C_F_POINTER(handle, p)
    ! Allocate p%B to appropriate size.
    !
    ! Assuming here the pointer association status of p%B is always 
    ! defined or dissociated, never undefined.  This is much easier 
    ! with allocatable components.
    IF (ASSOCIATED(p%B)) THEN
      IF (SIZE(p%B) /= data_size) THEN
        DEALLOCATE(p%B)
        ALLOCATE(p%B(data_size))
      END IF
    ELSE
      ALLOCATE(p%B(data_size))
    END IF
    p%B = data
  END SUBROUTINE SetB

  SUBROUTINE QueryBSize(handle, data_size) BIND(C, NAME='QueryBSize')
    USE, INTRINSIC :: ISO_C_BINDING, ONLY:  &
        C_PTR, C_F_POINTER, C_INT
    TYPE(C_PTR), INTENT(IN), VALUE :: handle
    INTEGER(C_INT), INTENT(OUT) :: data_size
    TYPE(SIMPLEF), POINTER :: p
    !***
    CALL C_F_POINTER(handle, p)
    ! See comments about assumed association status above.
    IF (ASSOCIATED(p%B)) THEN
      data_size = SIZE(p%B, KIND=C_INT)
    ELSE
      data_size = 0_C_INT
    END IF
  END SUBROUTINE QueryBSize

  SUBROUTINE QueryBData(handle, data) BIND(C, NAME='QueryBData')
    USE, INTRINSIC :: ISO_C_BINDING, ONLY:  &
        C_PTR, C_F_POINTER, C_INT
    TYPE(C_PTR), INTENT(IN), VALUE :: handle
    INTEGER(C_INT), INTENT(OUT) :: data(*)
    TYPE(SIMPLEF), POINTER :: p
    !***
    CALL C_F_POINTER(handle, p)
    ! See comments about assumed association status above.
    IF (ASSOCIATED(p%B)) THEN
      data(:SIZE(p%B)) = p%B
    ELSE
      ! Someone is being silly.
    END IF
  END SUBROUTINE QueryBData

  ! ...etc...
END MODULE simple

//~~~~~~
#include <vector>
#include <iostream>

extern "C" void* GetHandle();
extern "C" void ReleaseHandle(void* handle);
extern "C" void SetA(void* handle, int a);
extern "C" int QueryA(void* handle);
extern "C" void SetB(void* handle, const int* data, int data_size);
extern "C" void QueryBSize(void* handle, int* data_size);
extern "C" void QueryBData(void *handle, int *data);

class SimpleF

private:
  void *handle;
public:
  SimpleF() 
   
    handle = GetHandle(); 
  

  ~SimpleF() 
   
    ReleaseHandle(handle); 
  

  void SetA(int a) 
   
    ::SetA(handle, a); 
  

  int QueryA()
   
    return ::QueryA(handle); 
  

  void SetB(const std::vector<int>& b)
  
     ::SetB(handle, &b[0], b.size());
  

  std::vector<int> QueryB()
  
    // Get the data size, construct a suitable buffer, populate the buffer.
    int data_size;
    ::QueryBSize(handle, &data_size);
    if (data_size == 0) return std::vector<int>();

    std::vector<int> data(data_size);
    ::QueryBData(handle, &data[0]);
    return data;
  
;

int main()

  SimpleF x;
  x.SetA(99);
  std::cout << x.QueryA() << std::endl;

  std::vector<int> testvector(2,100);
  x.SetB(testvector);
  std::cout << x.QueryB()[0] << ' ' << x.QueryB()[1] << std::endl;

  return 0;

如果您的编译器支持通过 TS29113“Fortran 与 C 的进一步互操作性”添加到语言中的功能,那么可互操作的过程可以具有指针参数,这可能会简化这些访问器的编写。该 TS 引入的功能旨在成为下一个标准版本的基础语言的一部分。

【讨论】:

感谢您提供详细而优雅的解决方案。我正在使用 gfortran,我可以看到它有 TS29113 的部分实现。我将尝试您的解决方案并报告。除了 Fortran 指针之外,我还有 SIMPLEF 派生类型的指针数组,因此我需要将 SHAPE 添加到该变量的 C_F_POINTER 调用中——如果它是 C++ 端的动态数组,这可能会导致问题。再次感谢! 我现在对其进行了测试,它运行良好——我只需要对FUNCTION QueryA 进行一些小改动,即删除RESULT(a)INTEGER(C_INT), INTENT(OUT) :: a 行并添加INTEGER(C_INT) :: QueryA,否则我会在 RESULT(a) 中抱怨 a 是一个错误参数。 对不起,我回答得太早了。通过您的示例,我可以将值更改为 p%a,但这是一个整数。我的问题是修改 p%B (整数指针)或 p%C (整数指针数组)。如果我尝试SUBROUTINE SetB(handle, b) BIND(C, NAME='SetB') USE, INTRINSIC :: ISO_C_BINDING, ONLY: &amp; C_PTR, C_F_POINTER, C_INT TYPE(C_PTR), INTENT(IN), VALUE :: handle INTEGER, INTENT(IN), POINTER :: b TYPE(SIMPLEF), POINTER :: p !*** CALL C_F_POINTER(handle, p) p%B = b END SUBROUTINE SetB 那么它会给我预期的错误 继续,错误是Error: Variable 'b' at (1) cannot have the POINTER attribute because procedure 'setb' is BIND(C) 我修改了子程序,使指针 p%B 从 C++ 指向目标 b,但这会导致崩溃。 SUBROUTINE SetB(handle, b) BIND(C, NAME='SetB') USE, INTRINSIC :: ISO_C_BINDING, ONLY: &amp; C_PTR, C_F_POINTER, C_INT TYPE(C_PTR), INTENT(IN), VALUE :: handle INTEGER, INTENT(IN), TARGET :: b TYPE(SIMPLEF), POINTER :: p CALL C_F_POINTER(handle, p) p%B =&gt; b END SUBROUTINE SetB

以上是关于Fortran 派生类型包含可从 C 访问的指针的主要内容,如果未能解决你的问题,请参考以下文章

在 Fortran 中多次循环后访问派生数据类型

派生数据类型中的 Fortran、参数和静态表

Fortran - 可分配派生类型的可分配数组

Fortran 派生类型

在派生类型中使用可分配的目标变量

在派生类型中使用可分配的目标变量