Creating a FORTRAN interface to a C function that returns a char*

Mike Sadler picture Mike Sadler · Apr 2, 2012 · Viewed 7.4k times · Source

I've been held up on this for about a week, now, and have searched forum after forum for a clear explanation of how to send a char* from C to FORTRAN. To make the matter more frustrating, sending a char* argument from FORTRAN to C was straight-forward...

Sending a char* argument from FORTRAN to C (this works fine):

// The C header declaration (using __cdecl in a def file):
extern "C" double GetLoggingValue(char* name);

And from FORTRAN:

! The FORTRAN interface:
INTERFACE
    REAL(8) FUNCTION GetLoggingValue [C, ALIAS: '_GetLoggingValue'] (name)
        USE ISO_C_BINDING       
        CHARACTER(LEN=1, KIND=C_CHAR), DIMENSION(*),    INTENT(IN) :: name                  
    END FUNCTION GetLoggingValue
END INTERFACE

! Calling the function:
GetLoggingValue(user_name)

When trying to use analogous logic to return a char* from C, I get problem after problem. One attempt that I felt should work is:

// The C declaration header (using __cdecl in a def file):
extern "C" const char* GetLastErrorMessage();

And the FORTRAN interface:

INTERFACE
    FUNCTION GetLastErrorMessage [C, ALIAS: '_GetLastErrorMessage'] ()
        USE ISO_C_BINDING   
        CHARACTER(LEN=1, KIND=C_CHAR), DIMENSION(255), :: GetLastErrorMessage
    END FUNCTION GetLastErrorMessage
END INTERFACE

(I can't literally use the DIMENSION(*), so I've gone oversize to 255.)

This should return a pointer to an array of 255 C-style characters - but if it does, I've been unable to convert this to a meaningful string. In practice, it returns a random set of characters, anywhere from Wingdings to the 'bell' character...

I've also attempted to return:

  • A pointer to CHARACTER(LEN=255, KIND=C_CHAR).
  • Literally CHARACTER(LEN=255, KIND=C_CHAR).
  • A INTEGER(C_SIZE_T), and tried to finesse that into a pointer to a string array.
  • A CHARACTER.
  • etc.

If anybody can give me an example of how to do this, I would be very grateful...

Best regards,

Mike

Answer

haraldkl picture haraldkl · Apr 2, 2012

Strings of dynamic length are always a bit tricky with the C interaction. A possible solution is to use pointers.

First a simple case, where you have to hand over a null-character terminated string to a C-Function. If you really pass the string only in, you have to ensure to finalize it with the c_null_char, thus this direction is pretty straight forward. Here are examples from a LuaFortran Interface:

subroutine flu_getfield(L, index, k)
  type(flu_State)  :: L
  integer          :: index
  character(len=*) :: k

  integer(kind=c_int) :: c_index
  character(len=len_trim(k)+1) :: c_k

  c_k = trim(k) // c_null_char
  c_index = index
  call lua_getfield(L%state, c_index, c_k)
end subroutine flu_getfield

And the interface of lua_getfield looks like:

subroutine lua_getfield(L, index, k) bind(c, name="lua_getfield")
  use, intrinsic :: iso_c_binding
  type(c_ptr), value :: L
  integer(kind=c_int), value :: index
  character(kind=c_char), dimension(*) :: k
end subroutine lua_getfield

And the C-Code interface is:

void lua_getfield (lua_State *L, int idx, const char *k)

Now the little more complex case, where we have to deal with a returned string from C with a dynamic length. The most portable solution I found so far is using pointers. Here is an example with a pointer, where the string is given by the C-Routine (also from the Aotus library mentioned above):

function flu_tolstring(L, index, len) result(string)
  type(flu_State) :: L
  integer :: index
  integer :: len
  character,pointer,dimension(:) :: string

  integer :: string_shape(1)
  integer(kind=c_int) :: c_index
  integer(kind=c_size_t) :: c_len
  type(c_ptr) :: c_string

  c_index = index
  c_string = lua_tolstring(L%state, c_index, c_len)
  len = int(c_len,kind=kind(len))
  string_shape(1) = len
  call c_f_pointer(c_string, string, string_shape)
end function flu_tolstring

where lua_tolstring has the following interface:

function lua_tolstring(L, index, len) bind(c, name="lua_tolstring")
  use, intrinsic :: iso_c_binding
  type(c_ptr), value :: L
  integer(kind=c_int), value :: index
  integer(kind=c_size_t) :: len
  type(c_ptr) :: lua_tolstring
end function lua_tolstring

Finally, here is an attempt to clarify how a c_ptr can be interpreted as a Fortran character string: Assume you got a c_ptr pointing to the string:

type(c_ptr) :: a_c_string

And the length of it is given by a len variable with the following type:

integer(kind=c_size_t) :: stringlen

You want to get this string in a pointer to a character string in Fortran:

character,pointer,dimension(:) :: string

So you do the mapping:

call c_f_pointer(a_c_string, string, [ stringlen ])