我正在使用需要字符标量的旧版 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)似乎限制了我可以作为参数传递给函数的内容,尽管我还没有找到任何官方文档。 转换为 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/