For character strings you have a few different options, but keep in mind that strings in C are generally null-terminated. So either you need to infer the length in your Fortran program, pass a string of fixed-length (set in advance), or pass the length as an additional argument.
You also need to consider carefully whether you are trying to pass a value or a reference. Currently, the C prototype of your function would look like this (see “Working with C pointers” in the GCC documentation):
void logical_character_real_array(double *arr,
int *dim,
bool *istrue,
void **charactervalue)
I think it would be more natural like this:
void logical_character_real_array(double *arr,
int dim,
bool *istrue,
char *charactervalue)
with the corresponding Fortran interface:
subroutine logical_character_real_array(arr, dim, istrue, charactervalue) bind(c)
integer(c_int), value :: dim
real(c_double), intent(inout) :: arr(dim)
logical(c_bool), intent(inout) :: istrue
character(kind=c_char), intent(inout) :: charactervalue(*)
Note the character pointer is associated with an array of characters (len=1
) on the Fortran side.
There are a few other possibilites to pass a string from C to Fortran. I’ve tried to demonstrate these below:
! libf.f90 --
! Library demonstrating how to pass string from C to Fortran
!
! We showcase 4 methods:
! 1. passing the string as a pointer to a null-terminated array of characters
! 2. passing the string as a "reference" to an array of characters
! (null-terminated)
! 3. passing a string as an array of characters of known length
! 4. passing a string as a C descriptor using the Fortran 2018
! enhanced interop features
! void printmsg_c_ptr(char *msg);
subroutine printmsg_c_ptr(msg) bind(c)
use, intrinsic :: iso_c_binding
type(c_ptr), value :: msg
interface
! The strlen function from the C runtime library
! https://en.cppreference.com/w/c/string/byte/strlen
pure function c_strlen(str) bind(c,name="strlen")
import c_ptr, c_size_t
type(c_ptr), value :: str
integer(c_size_t) :: c_strlen
end function
end interface
character(kind=c_char,len=:), pointer :: pmsg
character(kind=c_char,len=:), allocatable :: fmsg
! Creates a pointer to the C character array
pmsg => p2cstr(msg)
print *, "In Fortran (reference): ", pmsg
! Creates a copy via allocation on assignment
! (known to raise warning in gfortran, but technically it is completely legal)
fmsg = p2cstr(msg)
print *, "In Fortran (copy): ", fmsg
contains
! Return pointer to C string
function p2cstr(str) result(p_str)
type(c_ptr), value :: str
character(kind=c_char,len=:), pointer :: p_str
block
character(kind=c_char,len=c_strlen(str)), pointer :: tmp
call c_f_pointer(msg, tmp)
p_str => tmp
nullify(tmp)
end block
end function
end subroutine
! void printmsg_c_char(char *msg);
subroutine printmsg_c_char(msg) bind(c)
use, intrinsic :: iso_c_binding
character(kind=c_char), intent(in) :: msg(*) ! null-terminated string
integer :: lmsg
lmsg = strlen(msg)
print *, "In Fortran (reference): ", msg(1:lmsg)
contains
! If string is not null-terminated, behavior is undefined
integer function strlen(str)
character(kind=c_char), intent(in) :: str(*)
i = 0
do while (str(i+1) /= c_null_char)
i = i + 1
end do
strlen = i
end function
end subroutine
! void printmsg_c_char_n(int n, const char msg[n]);
subroutine printmsg_c_char_n(n, msg) bind(c)
use, intrinsic :: iso_c_binding
integer(c_int), value :: n
character(kind=c_char), intent(in) :: msg(n)
print *, "In Fortran (reference): ", msg
end subroutine
! Requires gfortran 12 or higher
!
! void printmsg_char(CFI_cdesc_t *msg);
subroutine printmsg_char(msg) bind(c)
use, intrinsic :: iso_c_binding
character(kind=c_char,len=*), intent(in) :: msg
print *, "In Fortran (reference): ", msg
end subroutine
// main.c
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <ISO_Fortran_binding.h>
extern void printmsg_c_ptr(const char *msg);
extern void printmsg_c_char(const char *msg);
extern void printmsg_c_char_n(int n, const char msg[n]);
extern void printmsg_char(CFI_cdesc_t *msg);
int main(void) {
const char *str = "I'm in the C main!"; // A sample string
// Use printf to print the string
printf("In C: %s\n", str);
printmsg_c_ptr(str);
printmsg_c_char(str);
int lstr = strlen(str);
printmsg_c_char_n(lstr,str);
// The last approach requires a F2018-compatible compiler
// With gfortran, you'll need version 12 or higher
CFI_CDESC_T(0) str_desc;
int istat = CFI_establish(
(CFI_cdesc_t *) &str_desc,
(void *) str,
CFI_attribute_other,
CFI_type_char,
strlen(str),
(CFI_rank_t) 0,
NULL /* ignored */);
assert(istat == CFI_SUCCESS);
printmsg_char((CFI_cdesc_t *) &str_desc);
return 0;
}
The output of the C program:
$ ./main
In C: I'm in the C main!
In Fortran (reference): I'm in the C main!
In Fortran (copy): I'm in the C main!
In Fortran (reference): I'm in the C main!
In Fortran (reference): I'm in the C main!
In Fortran (reference): I'm in the C main!
Build instructions so you can replicate this:
# Makefile
FC = gfortran-13
CC = gcc-13
FFLAGS=-Wall
CFLAGS=-Wall -std=c99
.PHONY: all clean
all: main
main: main.c libf.so
$(CC) $(CFLAGS) -o $@ $^ -lgfortran
libf.so: libf.f90
$(FC) $(FFLAGS) -shared -fPIC -o $@ $<
clean:
rm -vf *.o *.so *.mod
One more note on Fortran character passing. It is legal to pass a scalar character, as a dummy argument to an array of characters. This is useful, among other things, when you want to pass Fortran strings routines implemented in C:
program character_association
implicit none
character(len=32) :: str ! a scalar
str = "Greetings from Munich"
call print_as_array(len_trim(str),str)
contains
subroutine print_as_array(n,str)
integer, intent(in) :: n
character(len=1), intent(in) :: str(n) ! an array
print *, str
end subroutine
end program
Python wrappers
When it comes to the wrapping the Fortran routines using the Python ctypes module, I believe it’s good practice to specify the return and argument types. This will help you catch argument errors.
Here’s a wrapper for the example above:
"""
fwrap.py --
Wrapper library for the libf Fortran module
"""
import ctypes
# Exported functions
__all__ = ["printmsg"]
libf = ctypes.CDLL("libf.so")
# void printmsg_c_ptr(char *msg);
libf.printmsg_c_ptr.restype = None
libf.printmsg_c_ptr.argtypes = [ctypes.c_char_p]
# void printmsg_c_char(char *msg);
libf.printmsg_c_char.restype = None
libf.printmsg_c_char.argtypes = [ctypes.c_char_p]
# void printmsg_c_char_n(int n, const char msg[n]);
libf.printmsg_c_char_n.restype = None
libf.printmsg_c_char_n.argtypes = [ctypes.c_int, ctypes.c_char_p]
def printmsg(msg):
"""
Prints a message using the libf Fortran library
Args:
msg (bytes): A message encoded as an ASCII sequence of bytes
Examples:
>>> printmsg(b'Hello')
>>> printmsg('Hello'.encode('ascii'))
"""
# quick return on failure
if not isinstance(msg,(bytes,bytearray)):
raise TypeError(
"msg must be a byte object or bytearray; "
"received {} instead.".format(type(msg)))
# call the Fortran routine
libf.printmsg_c_char(msg)
if __name__ == '__main__':
# Python byte objects and integers can be passed directly.
# Integers will be passed as the default C int type, with their value
# masked to fit. Byte objects are passes as pointer to the memory
# block that contains their data
print("--- Using raw libf methods ---")
msg = b'Hello from Python'
libf.printmsg_c_ptr(msg)
libf.printmsg_c_char(msg)
libf.printmsg_c_char_n(len(msg),msg) # integers can be passed directly
print("\n--- Using wrapper function ---")
#
# Examples of using Python wrapper method
#
printmsg(msg)
# A byte object is an immutable byte sequence
bytestr = "Just an ASCII string".encode('ascii')
printmsg(bytestr)
# A Python bytearray is a mutable byte sequence
byte_array = bytearray(b'Qorld')
byte_array[0] = 87 # W
printmsg(b'Hello, ' + byte_array)
# The built-in string type is Unicode, hence this doesn't work
try:
printmsg("Grüße aus München!")
except TypeError:
print("Knew that wouldn't work...")
The output is the following:
$ python fwrap.py
--- Using raw libf methods ---
In Fortran (reference): Hello from Python
In Fortran (copy): Hello from Python
In Fortran (reference): Hello from Python
In Fortran (reference): Hello from Python
--- Using wrapper function ---
In Fortran (reference): Hello from Python
In Fortran (reference): Just an ASCII string
In Fortran (reference): Hello, World
Knew that wouldn't work...