【问题标题】:The correct way to pass a string from Fortran to C/C++将字符串从 Fortran 传递到 C/C++ 的正确方法
【发布时间】:2017-07-26 14:49:20
【问题描述】:

我想将一个字符串从 Fortran 传递到 C/C++。这是我的 Fortran 代码:

subroutine zdplaskinGetSpeciesName(cstring, index) bind(C, name='zdplaskinGetSpeciesName')
  use iso_c_binding
  use ZDPlasKin
  implicit none
  integer, intent(in) :: index
  CHARACTER(10), TARGET :: fstring = ''
  TYPE(C_PTR) :: cstring
  fstring = species_name(index+1)
  cstring = c_loc(fstring)
end subroutine zdplaskinGetSpeciesName

ZDPlasKin 是一个具有species_name(i) 的模块。

extern "C" void zdplaskinGetSpeciesName(char* cstring[], size_t* index);
char* cstring[1];
size_t index = 1;
zdplaskinGetSpeciesName(cstring, &index);
string speciesName = string(cstring[0]);
cout << speciesName << endl;

这种方法的输出似乎很好。但是,我想修剪尾随空格(character(10)提供额外的空间),所以我的 C++ 代码可以正确读取字符串。我尝试了另一种方法。

subroutine zdplaskinGetSpeciesName(cstring, index) bind(C, name='zdplaskinGetSpeciesName')
  use iso_c_binding
  use ZDPlasKin
  implicit none
  integer, intent(in) :: index
  CHARACTER(:), allocatable, TARGET :: fstring
  TYPE(C_PTR) :: cstring
  fstring = trim(species_name(index+1))
  cstring = c_loc(fstring)
end subroutine zdplaskinGetSpeciesName

但是这样我得到了一些奇怪的符号。


我想正确地做事,所以以后不用担心。内存泄漏不是我想要的。所以我想我会尝试你建议的替代方式。我想我想知道我怎么知道我是否需要释放一个指针。这是我在 StackOverflow 上找到的另一个代码(虽然这个将 C++ 字符串传递给 Fortran。https://stackoverflow.com/a/30430656/721644

你觉得这个可以用吗?或者可能存在内存泄漏。你也能给我一些关于你建议的替代方式的提示吗?

【问题讨论】:

  • 但是这样我得到了一些奇怪的符号。你得到了什么?
  • 请注意,没有 C/C++ 语言,这里的一些使用 C 和 C++ 的人对此非常过敏。 Fortranners通常不在乎。我知道它是通过 C 互操作性在 C++ 中调用的,所以我不反对这两个标签,但很多其他人都反对。
  • 这两个版本都无法正常工作。当子例程退出时,本地可分配字符串将被释放。局部变量只在执行期间在栈上有效。
  • 您链接的代码可能是正确的,但它还有其他作用。正如我所说,你可以在 StackOverflow 上找到很多有用的东西,但你可能不应该把它们都带到这里。

标签: c++ string fortran fortran-iso-c-binding


【解决方案1】:

在许多其他问题中处理了这种变体。搜索完全重复的关闭可能是没有希望的,但我强烈建议您搜索并阅读这些问题和答案。

C 字符串以空值结尾。如果要修剪它,只需将空字符放在正确的位置即可。实际上,您在第一个版本中根本没有终止字符串,因此很容易出现缓冲区溢出。

但更糟糕的是,变量fstring 只是函数的局部变量。永远不要将指针传递给局部变量。

所以,第一个版本真的应该是

subroutine zdplaskinGetSpeciesName(cstring, index) bind(C, name='zdplaskinGetSpeciesName')
  use iso_c_binding
  use ZDPlasKin
  implicit none
  integer, intent(in) :: index
  CHARACTER(11), POINTER :: fstring
  TYPE(C_PTR) :: cstring
  allocate(fstring)
  fstring = species_name(index+1)
  fstring(11:11) = c_null_char
  cstring = c_loc(fstring)
end subroutine zdplaskinGetSpeciesName

要修剪字符串,只需将空字符放在正确的位置

   integer :: len
   ...
   len = len_trim(fstring)
   fstring(len+1:len+1) = c_null_char

您还可以使用指向长度匹配字符串长度 + 1 的字符数组的指针或类似于您的第二种方法的延迟长度 (character(:)) 字符指针。请记住始终为空终止保留 1 个字符。

现在代码会有内存泄漏。为了避免内存泄漏,必须从 Fortran 中释放字符串!所以你必须创建一个特殊的子程序来释放这些指针。


另一种方法是从 C++ 传递一个指向缓冲区的指针,然后在 Fortran 中填充字符串。我实际上更喜欢这种方式。您可以在 C++ 中将其设为空终止符,但请确保不要让 Fortran 覆盖终止符。

你可以试试:

subroutine zdplaskinGetSpeciesName(cstring, index) bind(C, name='zdplaskinGetSpeciesName')
  use iso_c_binding
  use ZDPlasKin
  implicit none
  integer, intent(in) :: index
  TYPE(C_PTR), intent(in) :: cstring
  CHARACTER(kind=C_CHAR), POINTER :: fstring(:)
  integer :: i, c_len, name_len

  c_len = c_strlen(cstring)
  c_f_pointer(cstring, fstring, [c_len])

  associate(name => species_name(index+1))
    name_len = trim_len(name)
    do i = 1, name_len
      fstring(i) = name(i:i)
    end do
    fstring(name_len+1:name_len+1)
  end do
end subroutine zdplaskinGetSpeciesName

未经测试。您有责任从 C++ 提供足够大的空终止缓冲区。 c_strlen 可以在 http://fortranwiki.org/fortran/show/c_interface_module 找到


要 100% 符合标准,您应该使用长度为 1 的字符数组 character(kind=c_char),但字符串很可能在实践中起作用。

【讨论】:

  • 感谢您的回答。我想正确地做事,所以以后不用担心。内存泄漏不是我想要的。所以我想我会尝试你建议的替代方式。我想我想知道我怎么知道我是否需要释放一个指针。这是我在 stackoverflow 上找到的另一个代码:
  • 您不需要在此处粘贴其他 SO 问题的代码,一个链接就足够了。但是你想用那个代码做什么?它做了一些不同的事情。
【解决方案2】:

我终于找到了一个好方法。首先,下载http://fortranwiki.org/fortran/show/c_interface_module。并使用 F_C_string_ptr。

subroutine zdplaskinGetSpeciesName(cstring, index) bind(C, name='zdplaskinGetSpeciesName')
  use ZDPlasKin
  use C_interface_module
  implicit none
  integer, intent(in) :: index
  TYPE(C_PTR), intent(in) :: cstring
  character(:), allocatable :: fstring
  fstring = trim(species_name(index+1))
  call F_C_string_ptr(fstring, cstring)
end subroutine zdplaskinGetSpeciesName

而c和c++代码分别是:

extern "C" void zdplaskinGetSpeciesName(char* cstring[], size_t* index);
for (size_t i = 0; i < zdplaskinNSpecies(); i++) {
    char* cstring[10];
    zdplaskinGetSpeciesName(cstring, &i);
    printf("%s\n",*cstring);
    string speciesName(*cstring);
    cout << speciesName << endl;
}

【讨论】:

  • 完全没有必要分配给 fstring。您可以完全删除 fstring 变量。 call F_C_string_ptr(trim(species_name(index+1)), cstring) 另外,您的cstring[10] 可能太短,并预计缓冲区溢出。
  • "如果不传长度,C字符串必须至少为:len(F_string)+1"。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-12
  • 1970-01-01
  • 2014-10-25
  • 1970-01-01
相关资源
最近更新 更多