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

Posted

技术标签:

【中文标题】使用 Fortran 中的内存数据调用 C 代码【英文标题】:Calling C code with in-memory data from Fortran 【发布时间】:2014-07-08 23:25:56 【问题描述】:

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

但是我的问题是我希望我对 C++ 的 Fortran 调用对我称之为持久对象的对象进行操作:由第一个 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 会向操作系统请求内存,C++ 也是如此,

操作系统为 Fortan 和 C++ 提供的内存段不相关,并且保证不重叠,

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

通过调用 teardown_A() 或程序(即 Fortran main)终止时释放 C++ 内存

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

我贴的原代码如下(供cmets参考)

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)

【问题讨论】:

所以这个问题是关于 C++,而不是 C,对吧? 嗯,除了它的 C++ 语法之外,这个问题在 C 中仍然是相关的,因为只使用了 C 机制(用 malloc 替换 new)。我认为 C 程序员处理低级东西的频率更高,他们可以知道答案 *ptr_to_A = reinterpret_cast&lt;long&gt;(a); 这是未定义的行为,就像 A* a = reinterpret_cast&lt;A*&gt;(ptr_to_A); 一样@IanH 的答案似乎是正确的做法,因为它在 fortran 端使用正确的类型并且没有t 将指针填充到 ints 我认为它依赖于实现而不是未定义。我也不喜欢这些演员表,但我不希望 Fortran 的界面会很好...... 不要假设 sizeof(long)==sizeof(void*)。 fortran 对整数使用什么大小?在不知道 fortran 的情况下,我猜您的指针不适合变量 ptr_to_A。 【参考方案1】:

Fortran 2003 将 C 互操作性引入了 Fortran 语言。这种语言特性使得编写可以以可移植和健壮的方式一起工作的 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

【讨论】:

好的,非常感谢您的详细回答。我实现了它,它现在似乎可以工作了。对于未来的读者:***.com/tags/fortran-iso-c-binding/info 上还有更多关于接口的信息。但是,我仍然不确定 2003 fortran std 保证我的 C 内存没有被这种接口声明损坏。对我来说,这些接口主要是为了方便和跨原始类型的可移植性 实际上这并不总是有效。在更复杂的情况下,C++ 代码在 init_A() 中动态分配更多内存(我使用的是 std::vector),我在 doSth() 中遇到了段错误。有人知道在这种情况下程序/操作系统是如何管理内存的吗? 在可能不同的情况下,我使用 C_PTR 句柄将 C++ 对象传递给 Fortran。您的错误可能是由于其他地方的问题。它们之间的 C(++) 和 Fortran 标准保证上述方法有效。 (您可以将 reinterpret_cast 替换为 static_cast。) 我的错。实际上,我确实在我的 C++ 代码中引用了一个已删除的对象。谢谢你的回答,你应该得到赏金

以上是关于使用 Fortran 中的内存数据调用 C 代码的主要内容,如果未能解决你的问题,请参考以下文章

C++ 从 dll 调用 FORTRAN 子例程

调用 free() 时发生混合语言程序崩溃

由 R 调用时,Fortran 子例程不计算

用于从 FORTRAN 代码调用的 C++ 函数调用 C++ 代码的 Cmake 配置文件

在 QtCreator 中从 Qt 调用 fortran 代码

分段错误 - Fortran中的内存引用无效