Skip to content

Instantly share code, notes, and snippets.

@ryandvmartin
Last active September 28, 2021 03:05
Show Gist options
  • Save ryandvmartin/a3b9bb0c485d2ae58143d71f55a8d9e4 to your computer and use it in GitHub Desktop.
Save ryandvmartin/a3b9bb0c485d2ae58143d71f55a8d9e4 to your computer and use it in GitHub Desktop.
Fortran Character Arrays and Python Ctypes .... the pain is real.
! testing in the ipython notebook with the fortran code in one cell:
! %%file test.f90
! and with the build call in the following cell:
! `!gfortran -shared test.f90 -o test.dll`
! note: some useful stuff found here: https://stackoverflow.com/a/13880611/5545005
module c_interop
use iso_c_binding
implicit none
integer, parameter :: STRLEN = 64
contains
subroutine py2fortran(nstring, cptr) bind(C, name="py2fortran_")
integer(c_int), intent(in) :: nstring
type(c_ptr), intent(in), value :: cptr
character, pointer :: fptr(:, :)
character(len=STRLEN), allocatable :: fstrings(:)
integer :: i, lenstr
call c_f_pointer(cptr, fptr, [STRLEN, nstring])
allocate(fstrings(nstring))
do i = 1, nstring
lenstr = cstrlen(fptr(:, i))
fstrings(i) = transfer(fptr(1:lenstr, i), fstrings(i))
enddo
do i = 1, nstring
print *, fstrings(i)
enddo
end subroutine py2fortran
subroutine py2fortran2(nstring, c_string) bind(C, name="py2fortran2_")
integer(c_int), intent(in) :: nstring
character, dimension(STRLEN, nstring), intent(in), target :: c_string
character(len=STRLEN) :: fstrings(nstring)
integer :: i, lenstr
do i = 1, nstring
lenstr = cstrlen(c_string(:, i))
fstrings(i) = transfer(c_string(1:lenstr, i), fstrings(i))
fstrings(i)(lenstr + 1:) = " "
enddo
do i = 1, nstring
print *, "transferred from ctypes: " // trim(fstrings(i))
enddo
end subroutine py2fortran2
subroutine fortran2py(nstring, cstring_p) bind(C, name="fortran2py_")
integer(c_int), intent(in) :: nstring
character(c_char), dimension(*), intent(inout) :: cstring_p
integer :: i, j, ks, kf, n
character(len=STRLEN) :: mystr(2)
mystr(1) = "This is the first string."
mystr(2) = "Wow. Fortran + Python + Strings = Pain !"
ks = 1
do i = 1, nstring
n = len_trim(mystr(i))
kf = ks + (n - 1)
cstring_p(ks:kf) = transfer(mystr(i)(1:n), cstring_p(ks:kf))
cstring_p(kf + 1) = c_null_char
ks = ks + n + 1
enddo
end subroutine fortran2py
function cstrlen(carray) result(res)
character(kind=c_char), intent(in) :: carray(:)
integer :: res
integer :: ii
do ii = 1, size(carray)
if (carray(ii) == c_null_char) then
res = ii - 1
return
end if
end do
res = ii
end function cstrlen
end module c_interop
from ctypes import *
lib = CDLL("test.dll")
func = getattr(lib, "py2fortran_")
# pass an array of strings to fortran
arr = (c_char * 64 * 2)()
arr[0].value = b"This is the first python string"
arr[1].value = b"This is the next one"
nstring = pointer(c_int(2))
func(nstring, arr)
"""
Output:
This is the first python string
This is the next one
"""
# fill python memory with c_null_char delimited strings from fortran
func = getattr(lib, "fortran2py_")
nstring = pointer(c_long(2))
carr = (c_char * 255)()
func(nstring, carr)
str1, str2 = ''.join([v.decode("utf-8") for v in carr]).rstrip("\x00").split("\x00")
str1, str2
"""
Output:
('This is the first string.', 'Wow. Fortran + Python + Strings = Pain !')
"""
@jreniel
Copy link

jreniel commented Sep 25, 2021

Thanks, this is gold. I wish there were some built-in methods for this. The struggle is real.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment