Seb*_*rek 6 c c++ fortran language-interoperability
我正在使用一个旧的 Fortran 库,它需要一个字符标量PATH
作为子例程的参数。原来的界面是:
SUBROUTINE MINIMAL(VAR1, ..., PATH)
CHARACTER (LEN=4096) PATH
...
Run Code Online (Sandbox Code Playgroud)
我需要能够从 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
Run Code Online (Sandbox Code Playgroud)
根据这个答案。这可以将 C 风格的 char 数组转换为其 Fortran 标量数组,但存在两个问题:
我努力了:
CHARACTER (LENGTH=4096) :: new_path
直接与 ISO C 绑定,但我收到以下编译器错误:
Error: Character argument 'new_path' at (1) must be length 1 because procedure 'minimal' is BIND(C)
这个答案和我读过的其他答案表明 ISO C 绑定似乎限制了我可以作为参数传递给函数的内容,尽管我没有尚未找到任何官方文档。Undefined symbols for architecture x86_64:
"_minimal", referenced from:
Run Code Online (Sandbox Code Playgroud)
C++端函数声明:
extern "C" {
double minimal(int* var1, ..., const char* path);
}
Run Code Online (Sandbox Code Playgroud)
这表明我的编译器 (gcc) 在extern
块中时在函数名称前面添加了下划线。然而,gfortran 不允许我命名子例程_minimal
,因此链接器无法找到符号_minimal
。(上述链接建议在 C 端函数名称末尾添加下划线,但这也不起作用,因为前导下划线。)
我想在我的 C++ 代码中将 C 风格的字符串处理为 Fortran 风格的字符标量,并能够将其传递到原始接口中。有任何想法吗?
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
Run Code Online (Sandbox Code Playgroud)
并继续我们的生活,知道我们还通过使用假定长度标量而不是显式长度标量改进了 Fortran 代码。不需要此角色虚拟的“Fortran 端”副本。
这个故事的可悲之处在于虚拟参数path
不能与char
. 因此,C(或 C++)函数的形式参数char *
必须是 ,而不是CFI_cdesc_t *
。以(C)为例:
subroutine minimal(path) bind(c)
use, intrinsic :: iso_c_binding, only : c_char
character(*,c_char), intent(in) :: path
...
end subroutine minimal
Run Code Online (Sandbox Code Playgroud)
C++ 示例将是类似的。
这个故事值得注意的部分是,您需要一个 Fortran 编译器来实现 Fortran 2018 的这一部分。GCC 11 不需要。
IanH 的回答引起了人们对一种完全避免修改原始 Fortran 子例程的方法的关注。当然,有时候避免任何改变是有好处的(稍微重复一下 IanH 所说的):
bind(c)
意味着现在通过 Fortran 本身调用修改后的子例程时始终需要显式接口。也许代码的某些部分将其与隐式接口一起使用其中任何一个都可以成为一个很好的论据,因此本着这种精神,我将使用薄包装器添加到 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
Run Code Online (Sandbox Code Playgroud)
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;
}
Run Code Online (Sandbox Code Playgroud)
使用容器的 C++ 可以说会更好。
回想一下,这将责任放在 C 端,以确保数组足够长(就像在纯 Fortran 调用中一样)。
同样,如果您需要对该副本的默认字符和可互操作字符的差异保持鲁棒性(如 IanH 的答案),您可以应用这些相同的技巧来根据需要进行复制(或者您可以通过条件编译和配置时检查来做到这一点)。然而,此时,您也可以假设始终复制或使用数组参数。