用内存中的Fortran数据调用C代码

Calling C code with in-memory data from Fortran

本文关键字:调用 代码 数据 Fortran 内存      更新时间:2023-10-16

我有一个复杂的c++对象,我想在我的Fortran代码中使用。一般来说,从Fortran调用c++代码是没有问题的(只需要提供一个合适的接口,例如C链接)。

然而,我这里的问题是,我希望我的Fortran对c++的调用操作一个我称之为持久对象的东西:一个由第一个init函数创建的c++对象,并由其他c++函数操作。

更具体地说,假设我有以下c++代码
struct A {
    public:
      void do() { // do something on complicated stuff
    private:
      ... // complicated stuff
};
extern "C" {
    void* init_A() {
         A* a = new A();
         return reinterpret_cast<void*>(a);
    }
    void doSth(void* ptr_to_A) {
         A* a = reinterpret_cast<A*>(ptr_to_A);
         a.do();
    }
    void teardown_A(void* ptr_to_A) {
         A* a = reinterpret_cast<A*>(ptr_to_A);
         delete a;
    }
}

和以下fortran代码(假设它是main()):

USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR
INTERFACE
    TYPE(C_PTR) FUNCTION init_A() BIND(C, NAME='init_A')
        USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR
        IMPLICIT NONE
    END FUNCTION init_A
    SUBROUTINE doSth(ptr_to_A) BIND(C, NAME='doSth')
        USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR
        IMPLICIT NONE
        TYPE(C_PTR), INTENT(IN), VALUE :: ptr_to_A
    END SUBROUTINE doSth
    SUBROUTINE teardown_A(ptr_to_A) BIND(C, NAME='teardown_A')
        USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR
        IMPLICIT NONE
        TYPE(C_PTR), INTENT(IN), VALUE  :: ptr_to_A
    END SUBROUTINE teardown_A
END INTERFACE

现在在我的实际代码中,这编译,链接,有时工作,但有时不:在init_A()中分配的内存似乎不能保证Fortran代码保持不变)

我在网上找不到任何关于这个的信息:

  • 你知道是否有任何标准机制来确保init_A_()分配的内存保持不变,仍然由fortran代码分配吗?
  • 你知道有什么其他的机制可以解决我的问题吗?

也,有人能解释我为什么内存管理不正确吗?直到现在,我一直认为

  • Fortran会向OS请求内存,c++也是,

  • 操作系统给Fortan和c++的内存段是不相关的,保证不重叠,

  • 如果请求新的内存,操作系统不会让Fortran使用c++内存,直到c++释放它

  • c++内存通过调用teardown_A()或当程序(即Fortran main)终止

  • 释放。

编辑:我用IanH的答案更新了我的代码,但这仍然不起作用(段错误,部分内存被释放,同时从Fortran调用doSth()

我发布的原始代码如下(用于引用它的注释)

struct A {
    public:
      void do() { // do something on complicated stuff
    private:
      ... // complicated stuff
};
extern "C" {
    void init_A_(long* ptr_to_A) { // ptr_to_A is an output parameter
         A* a = new A();
         *ptr_to_A = reinterpret_cast<long>(a);
    }
    void doSth_(long* ptr_to_A) {
         A* a = reinterpret_cast<A*>(*ptr_to_A);
         a.do();
    }
    void teardown_A_(long* ptr_to_A) {
         A* a = reinterpret_cast<A*>(*ptr_to_A);
         delete a;
    }
}

和Fortran代码:

integer :: ptr_to_A
call init_A(ptr_to_A)
do i=1,10000
    call doSth(ptr_to_A)
enddo
call teardown_A(ptr_to_A)

Fortran 2003在Fortran语言中引入了C的互操作性。这种语言特性使得编写Fortran和C(以及c++)源代码变得更加容易,这些源代码可以以一种可移植和健壮的方式一起工作。除非由于其他原因无法使用这一级别的语言,否则您应该充分使用此功能。

你有一个指针间接的问题——指向c++对象的指针是存储在long类型中还是存储在long类型中(在doSth_和teardown_A_中强制转换的操作数应该在它们前面有一个*)。这取决于您正在使用的c++和Fortran编译器,但有可能在C long, C指针和Fortran默认类型整数之间存在大小不匹配。

下面是使用Fortran 2003的C互操作性特性的修改示例。

// C++
struct A {
    public:
      void do_something()
      {
         // ...
      }
    private:
      // ...
};
// Note no need for trailing underscore.
extern "C" {
    // Note pointer to pointer to void.
    void init_A(void** ptr_ptr_to_A) {
         A* a = new A;
         *ptr_ptr_to_A = reinterpret_cast<void*>(a);
    }
    void doSth(void* ptr_to_A) {
         A* a = reinterpret_cast<A*>(ptr_to_A);
         a->do_something();
    }
    void teardown_A(void* ptr_to_A) {
         A* a = reinterpret_cast<A*>(ptr_to_A);
         delete a;
    }
}

! Fortran 2003
  USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR
  IMPLICIT NONE
  INTERFACE
    SUBROUTINE init_A(ptr_to_A) BIND(C, NAME='init_A')
      USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR
      IMPLICIT NONE
      ! This argument is a pointer passed by reference.
      TYPE(C_PTR), INTENT(OUT) :: ptr_to_A
    END SUBROUTINE init_A
    SUBROUTINE doSth(ptr_to_A) BIND(C, NAME='doSth')
      USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR
      IMPLICIT NONE
      ! This argument is a pointer passed by value.
      TYPE(C_PTR), INTENT(IN), VALUE :: ptr_to_A
    END SUBROUTINE doSth
    SUBROUTINE teardown_A(ptr_to_A) BIND(C, NAME='teardown_A')
      USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR
      IMPLICIT NONE
      ! This argument is a pointer passed by value.
      TYPE(C_PTR), INTENT(IN), VALUE :: ptr_to_A
    END SUBROUTINE teardown_A
  END INTERFACE
  TYPE(C_PTR) :: ptr_to_A
  INTEGER :: i
  !****
  CALL init_A(ptr_to_A)
  DO i = 1, 100
    CALL doSth(ptr_to_A)
  END DO
  CALL teardown_A(ptr_to_A)
END