使用R调用Fortran子程序并传递字符参数

3
我如何从R中调用一个带有字符参数的Fortran子程序?虽然我能够调用具有双精度参数的Fortran子程序,但我的尝试不能成功。对于Fortran代码:
subroutine square(x,x2)
  double precision, intent(in)  :: x
  double precision, intent(out) :: x2
  x2 = x*x
end subroutine square

subroutine pow(c,x,y)
  character (len=255), intent(in) :: c
  double precision, intent(in)    :: x
  double precision, intent(out)   :: y
  if (c == "s") then
     y = x**2
  else if (c == "c") then
     y = x**3
  else
     y = -999.0d0 ! signals bad argument
  end if
end subroutine pow

以及R代码

dyn.load("power.dll") # dll created with gfortran -shared -fPIC -o power.dll power.f90
x <- 3.0
foo <- .C("square_",as.double(x),as.double(0.0))
print(foo)
bar <- .C("pow_",as.character("c"),as.double(x),as.double(0.0))
print(bar)

从中输出

C:\programs\R\R-3.6.1\bin\x64\rterm.exe --vanilla --slave < xcall_power.r

is

[[1]]
[1] 3

[[2]]
[1] 9

[[1]]
[1] "c"

[[2]]
[1] 3

[[3]]
[1] -999
1个回答

3
当您使用.C调用Fortran子例程时,调用将字符参数视为C风格的char **。这与类型为character(len=255)的Fortran虚拟参数不兼容。
您有两种“简单”的方法可用:
  • 修改Fortran子例程以接受类似于char **的参数
  • 使用.Fortran而不是.C
修改Fortran子例程以使用C互操作性和char **最好是一个新问题的主题(由于其广度和不特定于您的R问题)。一般来说,我更喜欢编写Fortran过程以用作公开C互操作界面和.C.Call。通过以下内容,您也可能得出这个结论。
即使R文档对使用.Fortran传递字符参数也不乐观:
“.Fortran”将字符向量的第一个(也是唯一的)字符串作为C字符数组传递给Fortran:如果它的实际长度被单独传递,则可以使用“character * 255”。仅传递字符串的前255个字符。(这个功能的效果,甚至是否有效,取决于C和Fortran编译器以及平台。) 您需要阅读有关参数传递约定的文档,例如对于gfortran(请咨询适当版本,因为这些约定可能会更改)。
对于使用不可与C交互的过程的“ .Fortran”和gfortran,您需要传递一个“隐藏”的参数到Fortran过程,指定字符参数的长度。对于显式长度字符(常数长度,甚至是长度为1或未知长度的字符),以及假定长度字符,这都是正确的。
对于版本7之前的gfortran,这个隐藏参数是(R)整数类型。使用问题中的pow或具有假定长度参数的方法,我们可以尝试一些东西。
bar <- .Fortran("pow", as.character("c"), as.double(x), as.double(0.0), 255L)

请注意,这种方法并不标准,也不具备可移植性。正如janneb评论和上面链接的文档所述,如何将这个隐藏的参数从R传递到gfortran编译的过程中取决于所使用的gfortran版本。在结尾处使用255L可能无法在gfortran 7之后的版本中工作。相反,您需要将隐藏的参数传递为与integer(c_size_t)匹配的内容(可能是64位整数)。对于除gfortran以外的编译器,您可能需要做一些完全不同的事情。
最好使用与C互操作的过程,其参数可以与char **(使用.C)或char [](使用.Fortran)互操作。正如我所说,在这里选择第一种选项是值得的,因为它提供了更多的灵活性(例如更长的字符、更高的可移植性和更多的字符参数)。

1
如果您打算使用“.C”调用Fortran过程,则应在Fortran端使用BIND(C)。 - janneb
1
@janneb,更一般地说,对于.C,(Fortran)过程应该是C可互操作的(需要不仅仅是bind(c))。我觉得这个话题太广泛了,不适合在这个答案中讨论。 - francescalus
在你的 R 代码中,1L 参数不应该是 "pow" 后面的第二个参数吗?当我这样做时,示例就可以工作了--谢谢。 - Fortranner
@francescalus:说得好,我应该说ISO_C_BINDING或C可互操作性。 - janneb
关于隐藏字符长度参数,GFortran > 7期望它是size_t类型。我不确定如何在R中生成这样的整数。无论如何,省略长度可能比提供错误整数类型的长度更糟糕。请参见https://developer.r-project.org/Blog/public/2019/05/15/gfortran-issues-with-lapack/index.html。 - janneb
@francescalus:很抱歉再次挑剔,但您的结论是不正确的。如果存在字符参数,则无论是否使用它(即将其声明为character(len=*)),该过程始终期望一个隐藏的字符长度参数。唯一的例外是C可互操作的过程。 - janneb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接