Fortranner
Fortranner

Reputation: 2605

R calling Fortran subroutine with character argument

How can I call from R a Fortran subroutine with a character argument? My attempt to do so does not work, although I am able to call Fortran subroutines with double precision arguments. For the Fortran code

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

and R code

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)

The output from

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

Upvotes: 3

Views: 652

Answers (1)

francescalus
francescalus

Reputation: 32441

When you use .C to call a Fortran subroutine the calling treats character arguments as being C-style char **. This is not compatible with the Fortran dummy argument of type character(len=255).

You have two 'simple' approaches available:

  • modify the Fortran subroutine to accept arguments looking like char **
  • use .Fortran instead of .C

Modifying the Fortran subroutine to use C interoperability with char ** is better the subject of a new question (for its breadth and being not specific to your R problem). In general, I prefer writing Fortran procedures to be used in R as exposing a C interoperable interface and .C or .Call. With what follows you may also come to that conclusion.

Even the R documentation is not optimistic about passing character arguments with .Fortran:

‘.Fortran’ passes the first (only) character string of a character vector as a C character array to Fortran: that may be usable as ‘character*255’ if its true length is passed separately. Only up to 255 characters of the string are passed back. (How well this works, and even if it works at all, depends on the C and Fortran compilers and the platform.)

You will need to read your documentation about argument passing conventions, such as that for gfortran (consult the appropriate version as these conventions may change).

With .Fortran and gfortran then with a procedure that is not C-interoperable you will need to pass a "hidden" argument to the Fortran procedure specifying the length of the character argument. That is true for explicit length characters (constant length, even length-1, or not) and assumed-length characters.

For gfortran before version 7, this hidden argument is of (R) type integer. Using pow of the question, or with assumed-length argument, we can try something like

bar <- .Fortran("pow", as.character("c"), as.double(x), as.double(0.0), 255L)

Note, however, that this is not standard and inherently not portable. Indeed, as janneb comments and the documentation linked above state, how you pass this hidden argument from R to a gfortran-compiled procedure depends on the version of gfortran used. Using 255L at the end probably won't work beyond gfortran 7. Instead you will need to pass the hidden argument as something matching an integer(c_size_t) (possibly a 64-bit integer). For compilers other than gfortran you may need to do something quite different.

It really is best to use a C-interoperable procedure either with argument interoperable with char ** (using .C) or char [] (using .Fortran). As I say, it's worth going for the first option here, as that leaves more flexibility (like longer characters, more portability, and more character arguments).

Upvotes: 3

Related Questions