c++ - 如何在 C++ 数据结构中存储 Fortran 风格的长字符标量

标签 c++ c fortran language-interoperability

我正在使用需要字符标量的旧版 Fortran 库 PATH作为子程序的参数。原来的界面是:

SUBROUTINE MINIMAL(VAR1, ..., PATH)

CHARACTER (LEN=4096) PATH

...
我需要能够从 C++ 调用它,所以我做了以下更改:
SUBROUTINE MINIMAL(VAR1, ..., PATH) &
    BIND (C, NAME="minimal_f")

    USE ISO_C_BINDING, ONLY: C_CHAR, C_NULL_CHAR

    CHARACTER (KIND=C_CHAR, LEN=1), DIMENSION(4096), INTENT(IN) :: PATH
    CHARACTER (LEN=4096):: new_path

!   Converting C char array to Fortran CHARACTER.
    new_path = " "
    loop_string: do i=1, 4096
        if ( PATH (i) == c_null_char ) then
            exit loop_string
        else
            new_path (i:i) = PATH (i)
        end if
    end do loop_string
按照 this answer .这可以将 C 风格的 char 数组转换为它的 Fortran 标量等价物,但有两个问题:
  • 此代码位于关键路径上,因此每次在答案相同时进行相同的转换是低效的
  • 我强烈希望不必编辑遗留代码

  • 我试过了:
  • 只接受 CHARACTER (LENGTH=4096) :: new_path直接与 ISO C 绑定(bind),但我收到以下编译器错误:Error: Character argument 'new_path' at (1) must be length 1 because procedure 'minimal' is BIND(C) This answer和我读过的其他人建议 ISO C 绑定(bind)似乎限制了我可以作为参数传递给函数的内容,尽管我还没有找到任何官方文档。
  • This answer ,它提供了另一种算法来转换 C 风格的字符串
    转换为 C 代码中的 Fortran 样式等效项并将其传递给 Fortran 子例程 没有 使用 ISO C 绑定(bind)。 ( This function suggests a similar algorithm )。这似乎正是我想要的,但我有一个没有绑定(bind)的链接器错误:
  • Undefined symbols for architecture x86_64:
      "_minimal", referenced from:
    
    C++ 端函数声明:
    extern "C" { 
        double minimal(int* var1, ..., const char* path);
    }
    
    这表明我的编译器 (gcc) 在 extern 中时在函数名前面加上下划线堵塞。但是,gfortran 不允许我命名子程序 _minimal所以链接器找不到符号 _minimal . (上述链接建议在 C 端函数名称的末尾添加下划线,但由于前导下划线,这也不起作用。)
    我想在我的 C++ 代码中一次将 C 风格的字符串处理成 Fortran 风格的字符标量,并能够将其传递到原始接口(interface)中。有任何想法吗?

    最佳答案

    Fortran 2018 允许可互操作的过程具有假定长度的字符虚拟参数,放宽了此类虚拟参数长度必须为 1 的限制。
    所以我们可以写一个 Fortran 过程为

    subroutine minimal(path) bind(c)
      use, intrinsic :: iso_c_binding, only : c_char
      character(*,c_char), intent(in) :: path
      ...
    end subroutine minimal
    
    继续我们的生活,知道我们还通过使用假定长度标量而不是显式长度标量改进了我们的 Fortran 代码。不需要此角色虚拟人物的“Fortran 方”拷贝。
    这个故事可悲的部分是虚拟参数path不能与 char 互操作.因此,C(或 C++)函数的形式参数不是 char * ,它必须是 CFI_cdesc_t * .对于(C)示例:
    #include "ISO_Fortran_binding.h"
    #include "string.h"
    
    void minimal(CFI_cdesc_t *);
    
    int main(int argc, char *argv[]) {
      /* Fortran argument will be a scalar (rank 0) */
      CFI_CDESC_T(0) fpath;
      CFI_rank_t rank = 0;
    
      char path[46] = "afile.txt";
    
      CFI_establish((CFI_cdesc_t *)&fpath, path, CFI_attribute_other, 
            CFI_type_char, strlen(path)*sizeof(char), rank, NULL);
    
      minimal((CFI_cdesc_t *)&fpath);
      return 0;
    }
    
    C++ 示例将是类似的。
    故事的一个值得注意的部分是你需要一个 Fortran 编译器来实现 Fortran 2018 的这一部分。 GCC 11 没有。

    IanH's answer提请注意一种完全避免修改原始 Fortran 子例程的方法。有时候避免任何改变是好的(稍微重复 IanH 所说的):
  • 使用 bind(c)意味着在通过 Fortran 本身调用修改后的子例程时,现在总是需要显式接口(interface)。也许您的代码的某些部分将它与隐式接口(interface)一起使用
  • 原件已经过测试(或没有),你不想破坏任何东西
  • 您不想将参数从默认类型更改为可互操作类型(如果这些确实不同)
  • 确实需要显式长度虚拟参数
  • 如果不需要,您只是不想修改它

  • 这些中的任何一个都可以成为一个很好的论点,因此本着这种精神,我将使用薄包装器添加到 C 示例中。
    复式:
    subroutine minimal_wrap(path) bind(c, name='minimal')
      use, intrinsic :: iso_c_binding, only : c_char
      character(*,c_char), intent(in) :: path
    
      call minimal(path)
    end subroutine minimal_wrap
    
    subroutine minimal(path)
      character(4096) path
      print*, trim(path)
    end subroutine minimal
    
    C:
    #include "ISO_Fortran_binding.h"
    #include "string.h"
    
    void minimal(CFI_cdesc_t *);
    
    static const int pathlength=4096;
    
    int main(int argc, char *argv[]) {
      /* Fortran argument will be a scalar (rank 0) */
      CFI_CDESC_T(0) fpath;
      CFI_rank_t rank = 0;
    
      char path[pathlength];
    
      /* Set path as desired. Recall that it shouldn't be null-terminated
         for Fortran */
    
      CFI_establish((CFI_cdesc_t *)&fpath, path, CFI_attribute_other, 
            CFI_type_char, pathlength*sizeof(char), rank, NULL);
    
      minimal((CFI_cdesc_t *)&fpath);
      return 0;
    }
    
    使用容器的 C++ 可以说会更好。
    回想一下,这让 C 端负责确保数组足够长(就像在纯 Fortran 调用中一样)。
    同样,如果您需要对该拷贝的默认字符和可互操作字符的差异保持稳健(如 IanH 的回答),您可以根据需要应用这些相同的技巧进行复制(或者您可以通过条件编译和配置时检查来做到这一点)。然而,此时,您不妨假设始终复制或使用数组参数。

    关于c++ - 如何在 C++ 数据结构中存储 Fortran 风格的长字符标量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68840595/

    相关文章:

    C++ 函数名称分解 : What does this name suffix mean?

    c++ - 返回的字符串和获取的字符串不匹配

    C++ 捕获移动初始化是 const 吗?

    generics - 如何直接使用泛型函数定义一个新的Fortran过程?

    arrays - Fortran 字符数组

    c++ - 基于预加载器的锁跟踪工具中的神秘内存泄漏

    c - 尝试编译 libeemd 时找不到库

    c - C中的手动名称修改

    c - 反汇编 C 函数的 IA32 32 位 AT&T 汇编代码

    compiler-errors - 为什么在编译时MinGW无法识别我的目标文件?