1

I was trying to send string from Fortran to Python. After that Python should change the string and send it back to Fortran. This should be accomplish using cffi.

The below code is based on several answers:

  1. embeding a Python function that returns string
  2. passing an array of strings from C to a Fortran
  3. arrays of strings in Fortran-C bridges using iso_c_binding
  4. passing string array from Fortran to C and get array with changed values
  5. freeing a C pointer returned by a C function to Fortran

The builder.py code:

from cffi import FFI
ffi = FFI()

with open('plugin.h') as f:
    data = ''.join([line for line in f if not line.startswith('#')])
    data = data.replace('CFFI_DLLEXPORT', '')
    ffi.embedding_api(data)

ffi.set_source("my_plugin", r'''
    #include "plugin.h"
''')

with open("callpy.py") as f:
    ffi.embedding_init_code(f.read())

ffi.cdef("""
    char *strdup(const char *);
""")

ffi.compile(target="plugin_result.*", verbose=True)

The callpy.py code:

from my_plugin import ffi, lib

@ffi.def_extern()
def interchange_message(nstring, message):
        print('Python received:\n', ffi.string(message).decode("UTF-8"), '\n')
        message = 'Python says hi!'.ljust(nstring)
        return lib.strdup(bytes(message, 'utf-8'))   # calls malloc()

The plugin.h code:

# ifndef CFFI_DLLEXPORT
# if defined(_MSC_VER)
# define CFFI_DLLEXPORT extern __declspec(dllimport)
# else
# define CFFI_DLLEXPORT extern
# endif
#endif
CFFI_DLLEXPORT char *interchange_message(int nstring, char message[]);

The program_example.F90 code:

program example
    use :: iso_c_binding
    implicit none

    integer(c_int), parameter :: max_string_size = 20
    character(len=max_string_size), target :: initial_message
    character(len=max_string_size) :: final_message

    initial_message = "Fortran says hi!"//char(0)!//C_NULL_CHAR
    call interchange_message_Fortran(initial_message, final_message, max_string_size)
    
    WRITE(*,*) 'Fortran received:'
    WRITE(*,*) final_message

    contains
        subroutine interchange_message_Fortran(Fortran_string_inserted, Fortran_string_received, string_size)
            use :: iso_c_binding
            interface
                function interchange_message(nstring, cptr) &
                    result(y) bind(C, name='interchange_message')
                    use iso_c_binding
                    type(c_ptr), value :: cptr
                    integer(c_int), value :: nstring
                    type(c_ptr) :: y
                end function interchange_message
                
                subroutine c_mem_free(W_ptr) bind(C, name='c_mem_free')
                    use :: iso_c_binding
                    type(c_ptr)     :: W_ptr !< The C pointer whose memory needs to be freed
                end subroutine c_mem_free
            end interface
                
                integer(c_int), value, intent(in) :: string_size
                character(len=string_size),intent(in), target :: Fortran_string_inserted
                type(c_ptr) :: c_pointer_inserted
                type(c_ptr) :: c_pointer_received
                character(len=string_size), pointer :: f_pointer(:)
                character(len=string_size),intent(inout) :: Fortran_string_received
                
                c_pointer_inserted = C_LOC(Fortran_string_inserted)
                c_pointer_received = interchange_message(string_size, c_pointer_inserted)
                
                CALL C_F_POINTER(c_pointer_received, f_pointer, [string_size])
                Fortran_string_received = TRANSFER(f_pointer, Fortran_string_received)
                
                
                ! if( c_associated(c_pointer_received) ) then
                !    WRITE(*,*) 'It is pointing to something.'
                ! end if

                ! apply free()
                CALL c_mem_free(c_pointer_received)

                ! if( .not. c_associated(c_pointer_received) ) then
                !    WRITE(*,*) 'It is not pointing anymore.'
                ! end if
            
        end subroutine interchange_message_Fortran

end program example

The c_mem_free.c code:

#include <stdio.h>
#include <stdlib.h>
void c_mem_free (void** W_ptr)
    {
        /* printf("C is checking what Python sent: '%s'\n", *W_ptr); */
        free(*W_ptr);
        *W_ptr=NULL;
    }

The above codes produce expected output:

Python received:
 Fortran says hi!

 Fortran received:
 Python says hi!

Question: In the answer 1. Armin Rigo writes that "the caller must remember to free() it, otherwise a leak occurs". In my case I created a separated c_mem_free.c code with C c_mem_free function and an interface block for that C function in Fortran code (based on 5.).

I am just wondering if having c_mem_free as a part of the code is really necessary in my case. When I commented CALL c_mem_free(c_pointer_received) in Fortran code I got the same output but it does not mean that it was correct to do so.

Additional request: I would very much appreciate if you can please check the code and tell me if something is in my code implementation very wrong, even if the output looks correct.

Compilation and running commands for the above code on Linux:

python3 builder.py && gcc -c c_mem_free.c && gfortran -c program_example.F90 && gfortran -o program_example program_example.o c_mem_free.o plugin_result.so && ./program_example

Moonwalk
  • 123
  • 3

0 Answers0