'Python ctypes pass pointer in structure field to Fortran derived types

I want to make a variable-size array to pass to a Fortran DLL and get the result (by reference), so that I can get value directly.

In the Fortran code, I use allocatable variables, which I think is like a pointer to point allocated address.

I can do as following but I don't know how to do it in structure:

test = POINTER(c_double)()

sim.structtest(input, byref(test))

the definition of test in Fortran is

real(kind=8), allocatable, dimension (:) :: test

allocate(test(1))

The original code:

Python code (structtest.py):

from ctypes import *
import sys
import os


sim = cdll.LoadLibrary("struct.so")

class Input( Structure ):
    _fields_ = [( "a", c_double * 1 ),
                ( "b", c_double )]

class Output( Structure ):
    _fields_ = [( "a", c_double ),
                ( "b", POINTER(c_double) )] #-> don't know how to do

def main(): 
    input = Input()
    output = Output()
    input.a[0] = 1   
    input.b = 2

    sim.structtest(input, byref(output))

Fortran code (struct.f90):

subroutine structtest(input, output) bind(c, name='structtest')

    USE ISO_C_BINDING
    IMPLICIT NONE

    !define input structure
    TYPE T_INPUT
        !real*8, allocatable :: a(:)
        real(kind=8) :: a(1)
        real(kind=8) :: b
    END TYPE T_INPUT

    !define output structure
    TYPE T_OUTPUT
        real(kind=8) :: a
        real(kind=8), allocatable, dimension (:) :: b
    END TYPE T_OUTPUT


    !define a variable "input" with structure "INPUT"
    TYPE (T_INPUT), value :: input

    TYPE (T_OUTPUT) :: output


    allocate(output%d(1))


    output%b(1) = 5
    PRINT *, output%d(1)

I compile the Fortran to DLL like:

ifort -shared -fPIC -static-intel -o struct.so struct.f90

I execute the Python as:

python structtest.py

I get the result:

*** Error in `python': free(): corrupted unsorted chunks: 0x0000000001f7af00 ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x777e5)[0x7fc9027f57e5]
/lib/x86_64-linux-gnu/libc.so.6(+0x8037a)[0x7fc9027fe37a]
/lib/x86_64-linux-gnu/libc.so.6(cfree+0x4c)[0x7fc90280253c]
/lib/x86_64-linux-gnu/libc.so.6(__open_catalog+0xe8)[0x7fc9027b2008]
/lib/x86_64-linux-gnu/libc.so.6(catopen+0x4c)[0x7fc9027b1c2c]
/home/mingster/simGeo.docker/simgeo/lib/struct.so(for__issue_diagnostic+0x11e)[0x7fc90147a77e]
/home/mingster/simGeo.docker/simgeo/lib/struct.so(for_allocate+0x303)[0x7fc90146b9a3]
/home/mingster/simGeo.docker/simgeo/lib/struct.so(structtest+0xae)[0x7fc90146b16e]
/usr/lib/x86_64-linux-gnu/libffi.so.6(ffi_call_unix64+0x4c)[0x7fc901757e40]
/usr/lib/x86_64-linux-gnu/libffi.so.6(ffi_call+0x2eb)[0x7fc9017578ab]
/usr/lib/python2.7/lib-dynload/_ctypes.x86_64-linux-gnu.so(_ctypes_callproc+0x48f)[0x7fc9019673df]
/usr/lib/python2.7/lib-dynload/_ctypes.x86_64-linux-gnu.so(+0x11d82)[0x7fc90196bd82]
python(PyObject_Call+0x43)[0x4b0c93]
python(PyEval_EvalFrameEx+0x602f)[0x4c9f9f]
python(PyEval_EvalFrameEx+0x5e0f)[0x4c9d7f]
python(PyEval_EvalCodeEx+0x255)[0x4c2705]
python(PyEval_EvalCode+0x19)[0x4c24a9]
python[0x4f19ef]
python(PyRun_FileExFlags+0x82)[0x4ec372]
python(PyRun_SimpleFileExFlags+0x191)[0x4eaaf1]
python(Py_Main+0x6c8)[0x49e208]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7fc90279e830]
python(_start+0x29)[0x49da59]
======= Memory map: ========
00400000-006e9000 r-xp 00000000 08:01 525090                             /usr/bin/python2.7
008e8000-008ea000 r--p 002e8000 08:01 525090                             /usr/bin/python2.7
008ea000-00961000 rw-p 002ea000 08:01 525090                             /usr/bin/python2.7
00961000-00984000 rw-p 00000000 00:00 0
01f10000-01ff6000 rw-p 00000000 00:00 0                                  [heap]
7fc8fc000000-7fc8fc021000 rw-p 00000000 00:00 0
7fc8fc021000-7fc900000000 ---p 00000000 00:00 0
7fc901246000-7fc90125c000 r-xp 00000000 08:01 38797837                   /lib/x86_64-linux-gnu/libgcc_s.so.1
7fc90125c000-7fc90145b000 ---p 00016000 08:01 38797837                   /lib/x86_64-linux-gnu/libgcc_s.so.1
7fc90145b000-7fc90145c000 rw-p 00015000 08:01 38797837                   /lib/x86_64-linux-gnu/libgcc_s.so.1
7fc90145c000-7fc901504000 r-xp 00000000 08:01 51643749                   /home/mingster/simGeo.docker/simgeo/lib/struct.so
7fc901504000-7fc901704000 ---p 000a8000 08:01 51643749                   /home/mingster/simGeo.docker/simgeo/lib/struct.so
7fc901704000-7fc90170a000 rw-p 000a8000 08:01 51643749                   /home/mingster/simGeo.docker/simgeo/lib/struct.so
7fc90170a000-7fc901752000 rw-p 00000000 00:00 0
7fc901752000-7fc901759000 r-xp 00000000 08:01 526635                     /usr/lib/x86_64-linux-gnu/libffi.so.6.0.4
7fc901759000-7fc901958000 ---p 00007000 08:01 526635                     /usr/lib/x86_64-linux-gnu/libffi.so.6.0.4
7fc901958000-7fc901959000 r--p 00006000 08:01 526635                     /usr/lib/x86_64-linux-gnu/libffi.so.6.0.4
7fc901959000-7fc90195a000 rw-p 00007000 08:01 526635                     /usr/lib/x86_64-linux-gnu/libffi.so.6.0.4
7fc90195a000-7fc901978000 r-xp 00000000 08:01 30543353                   /usr/lib/python2.7/lib-dynload/_ctypes.x86_64-linux-gnu.so
7fc901978000-7fc901b77000 ---p 0001e000 08:01 30543353                   /usr/lib/python2.7/lib-dynload/_ctypes.x86_64-linux-gnu.so
7fc901b77000-7fc901b78000 r--p 0001d000 08:01 30543353                   /usr/lib/python2.7/lib-dynload/_ctypes.x86_64-linux-gnu.so
7fc901b78000-7fc901b7c000 rw-p 0001e000 08:01 30543353                   /usr/lib/python2.7/lib-dynload/_ctypes.x86_64-linux-gnu.so
7fc901b7c000-7fc901e54000 r--p 00000000 08:01 529135                     /usr/lib/locale/locale-archive
7fc901e54000-7fc901f5c000 r-xp 00000000 08:01 38797852                   /lib/x86_64-linux-gnu/libm-2.23.so
7fc901f5c000-7fc90215b000 ---p 00108000 08:01 38797852                   /lib/x86_64-linux-gnu/libm-2.23.so
7fc90215b000-7fc90215c000 r--p 00107000 08:01 38797852                   /lib/x86_64-linux-gnu/libm-2.23.so
7fc90215c000-7fc90215d000 rw-p 00108000 08:01 38797852                   /lib/x86_64-linux-gnu/libm-2.23.so
7fc90215d000-7fc902176000 r-xp 00000000 08:01 38797934                   /lib/x86_64-linux-gnu/libz.so.1.2.8
7fc902176000-7fc902375000 ---p 00019000 08:01 38797934                   /lib/x86_64-linux-gnu/libz.so.1.2.8
7fc902375000-7fc902376000 r--p 00018000 08:01 38797934                   /lib/x86_64-linux-gnu/libz.so.1.2.8
7fc902376000-7fc902377000 rw-p 00019000 08:01 38797934                   /lib/x86_64-linux-gnu/libz.so.1.2.8
7fc902377000-7fc902379000 r-xp 00000000 08:01 38797927                   /lib/x86_64-linux-gnu/libutil-2.23.so
7fc902379000-7fc902578000 ---p 00002000 08:01 38797927                   /lib/x86_64-linux-gnu/libutil-2.23.so
7fc902578000-7fc902579000 r--p 00001000 08:01 38797927                   /lib/x86_64-linux-gnu/libutil-2.23.so
7fc902579000-7fc90257a000 rw-p 00002000 08:01 38797927                   /lib/x86_64-linux-gnu/libutil-2.23.so
7fc90257a000-7fc90257d000 r-xp 00000000 08:01 38797825                   /lib/x86_64-linux-gnu/libdl-2.23.so
7fc90257d000-7fc90277c000 ---p 00003000 08:01 38797825                   /lib/x86_64-linux-gnu/libdl-2.23.so
7fc90277c000-7fc90277d000 r--p 00002000 08:01 38797825                   /lib/x86_64-linux-gnu/libdl-2.23.so
7fc90277d000-7fc90277e000 rw-p 00003000 08:01 38797825                   /lib/x86_64-linux-gnu/libdl-2.23.so
7fc90277e000-7fc90293e000 r-xp 00000000 08:01 38797811                   /lib/x86_64-linux-gnu/libc-2.23.so
7fc90293e000-7fc902b3e000 ---p 001c0000 08:01 38797811                   /lib/x86_64-linux-gnu/libc-2.23.so
7fc902b3e000-7fc902b42000 r--p 001c0000 08:01 38797811                   /lib/x86_64-linux-gnu/libc-2.23.so
7fc902b42000-7fc902b44000 rw-p 001c4000 08:01 38797811                   /lib/x86_64-linux-gnu/libc-2.23.so
7fc902b44000-7fc902b48000 rw-p 00000000 00:00 0
7fc902b48000-7fc902b60000 r-xp 00000000 08:01 38797898                   /lib/x86_64-linux-gnu/libpthread-2.23.so
7fc902b60000-7fc902d5f000 ---p 00018000 08:01 38797898                   /lib/x86_64-linux-gnu/libpthread-2.23.so
7fc902d5f000-7fc902d60000 r--p 00017000 08:01 38797898                   /lib/x86_64-linux-gnu/libpthread-2.23.so
7fc902d60000-7fc902d61000 rw-p 00018000 08:01 38797898                   /lib/x86_64-linux-gnu/libpthread-2.23.so
7fc902d61000-7fc902d65000 rw-p 00000000 00:00 0
7fc902d65000-7fc902d8b000 r-xp 00000000 08:01 38797787                   /lib/x86_64-linux-gnu/ld-2.23.so
7fc902dc5000-7fc902f7b000 rw-p 00000000 00:00 0
7fc902f86000-7fc902f87000 rw-p 00000000 00:00 0
7fc902f87000-7fc902f88000 rwxp 00000000 00:00 0
7fc902f88000-7fc902f8a000 rw-p 00000000 00:00 0
7fc902f8a000-7fc902f8b000 r--p 00025000 08:01 38797787                   /lib/x86_64-linux-gnu/ld-2.23.so
7fc902f8b000-7fc902f8c000 rw-p 00026000 08:01 38797787                   /lib/x86_64-linux-gnu/ld-2.23.so
7fc902f8c000-7fc902f8d000 rw-p 00000000 00:00 0
7ffc38bde000-7ffc38bff000 rw-p 00000000 00:00 0                          [stack]
7ffc38cd8000-7ffc38cda000 r--p 00000000 00:00 0                          [vvar]
7ffc38cda000-7ffc38cdc000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
Aborted (core dumped)


Solution 1:[1]

Thanks All,

belows is the working example for Python to pass multi-dimension array to Fortran

python code:

from ctypes import *
import ctypes
import sys
import os
import numpy as np

sim = cdll.LoadLibrary(os.path.dirname(os.path.abspath(__file__)) + "/lib/struct.so")


class Param( Structure ):
    pass

class Result( Structure ):
    pass

def main():

    x = 2
    y = 3
    z = 4
    Param._fields_ = [( "len_x", c_int ),
                ( "len_y", c_int ),
                ( "len_z", c_int ),
                ( "c", POINTER(c_double)),
                ( "d", POINTER(c_double * x)),
                ( "e", POINTER(c_double * x * y))]

    Result._fields_ = [( "len_x", c_int ),
                ( "len_y", c_int ),
                ( "len_z", c_int ),
                ( "f", POINTER(c_double)),
                ( "g", POINTER(c_double * x)),
                ( "h", POINTER(c_double * x * y))]

    param = Param() 
    result = Result()

    cc = (c_double * x)()
    dd = ( (c_double * x) * y )()
    ee = ( ( ( (c_double * x) * y ) * z ) )()

    #[x]    
    cc[0] = 10.0
    cc[1] = 20.0

    #[y][x]
    dd[0][0] = 10.0
    dd[1][0] = 20.0
    dd[2][0] = 30.0
    dd[0][1] = 40.0
    dd[1][1] = 50.0
    dd[2][1] = 60.0

    #[z][y][x]
    ee[0][0][0] = 1.0
    ee[1][0][0] = 2.0
    ee[2][0][0] = 3.0
    ee[0][1][0] = 4.0
    ee[1][1][0] = 5.0
    ee[2][1][0] = 6.0


    param.len_x = x
    param.len_y = y
    param.len_z = z

    param.c = cc
    param.d = dd
    param.e = ee

    sim.structtest(byref(param), byref(result))

    #[x]
    print "1D"
    print result.f[0]
    print result.f[1]

    #[y][x]
    print "2D"
    print result.g[0][0] 
    print result.g[1][0]
    print result.g[2][0] 
    print result.g[0][1] 
    print result.g[1][1] 
    print result.g[2][1] 

    #[z][y][x]
    print "3D"
    print result.h[0][0][0]
    print result.h[1][0][0]
    print result.h[2][0][0]
    print result.h[0][1][0]
    print result.h[1][1][0]
    print result.h[2][1][0]



if __name__ == "__main__":
    result = main() 

fortran code:

subroutine structtest(param, result) bind(c, name="structtest")

    use, intrinsic :: ISO_C_BINDING
    implicit none

    type, BIND(C) :: args
        integer (C_INT) :: len_x
        integer (C_INT) :: len_y
        integer (C_INT) :: len_z
        type (C_PTR) :: c
        type (C_PTR) :: d
        type (C_PTR) :: e
    end type args

    type, BIND(C) :: output
        integer (C_INT) :: len_x
        integer (C_INT) :: len_y
        integer (C_INT) :: len_z
        type (C_PTR) :: f
        type (C_PTR) :: g
        type (C_PTR) :: h
    end type output    

    type (args), intent(in):: param   
    type (output), intent(out):: result   

    real (C_DOUBLE), pointer :: arg_array_c(:)
    real (C_DOUBLE), pointer :: arg_array_d(:,:)
    real (C_DOUBLE), pointer :: arg_array_e(:,:,:)
    real (C_DOUBLE), ALLOCATABLE, target, save :: result_array_f(:)
    real (C_DOUBLE), ALLOCATABLE, target, save :: result_array_g(:,:)
    real (C_DOUBLE), ALLOCATABLE, target, save :: result_array_h(:,:,:)

    ! Associate c_array with an array allocated in C
    call C_F_POINTER (param%c, arg_array_c, [param%len_x] )
    call C_F_POINTER (param%d, arg_array_d, [param%len_x,param%len_y] )
    call C_F_POINTER (param%e, arg_array_e, [param%len_x,param%len_y,param%len_z] )

    ![x]
    print *,"1D"
    print *,arg_array_c(1)
    print *,arg_array_c(2)

    ![x][y]
    print *,"2D"
    print *,arg_array_d(1,1)
    print *,arg_array_d(1,2)
    print *,arg_array_d(1,3)
    print *,arg_array_d(2,1)
    print *,arg_array_d(2,2)
    print *,arg_array_d(2,3)

    ![x][y][z]
    print *,"3D"
    print *,arg_array_e(1,1,1)
    print *,arg_array_e(1,1,2)
    print *,arg_array_e(1,1,3)
    print *,arg_array_e(1,2,1)
    print *,arg_array_e(1,2,2)
    print *,arg_array_e(1,2,3)

    ! Allocate an array and make it available in C

    result%len_x = param%len_x
    result%len_y = param%len_y
    result%len_z = param%len_z

    ALLOCATE (result_array_f(result%len_x))
    ALLOCATE (result_array_g(result%len_x, result%len_y))
    ALLOCATE (result_array_h(result%len_x, result%len_y, result%len_z))


    result%f = c_loc(result_array_f)
    result%g = c_loc(result_array_g)
    result%h = c_loc(result_array_h)

    ![x]
    result_array_f(1) = arg_array_c(1)
    result_array_f(2) = arg_array_c(2)

    ![x][y]
    result_array_g(1,1) = arg_array_d(1,1)
    result_array_g(1,2) = arg_array_d(1,2)
    result_array_g(1,3) = arg_array_d(1,3)
    result_array_g(2,1) = arg_array_d(2,1)
    result_array_g(2,2) = arg_array_d(2,2)
    result_array_g(2,3) = arg_array_d(2,3)        

    ![x][y][z]
    result_array_h(1,1,1) = arg_array_e(1,1,1)
    result_array_h(1,1,2) = arg_array_e(1,1,2)
    result_array_h(1,1,3) = arg_array_e(1,1,3)
    result_array_h(1,2,1) = arg_array_e(1,2,1)
    result_array_h(1,2,2) = arg_array_e(1,2,2)
    result_array_h(1,2,3) = arg_array_e(1,2,3)

end

output:

 1D
   10.0000000000000
   20.0000000000000
 2D
   10.0000000000000
   20.0000000000000
   30.0000000000000
   40.0000000000000
   50.0000000000000
   60.0000000000000
 3D
   1.00000000000000
   2.00000000000000
   3.00000000000000
   4.00000000000000
   5.00000000000000
   6.00000000000000
1D
10.0
20.0
2D
10.0
20.0
30.0
40.0
50.0
60.0
3D
1.0
2.0
3.0
4.0
5.0
6.0

Solution 2:[2]

The layout in memory of the so-called descriptor for an allocatable component is Fortran processor specific. For an allocatable array component it will invariably be much more than just a single memory address. You need to consult the documentation for your Fortran processor for the details around the descriptor. Code that relies on the layout of the descriptor is inherently processor specific.

The relevant documentation for the current release of the Intel compiler can be found in a section titled Handling Fortran Array Descriptors.

The current draft for Fortran 2018 provides additional facilities for interoperating with allocatable dummy arguments, but there are still platform specific characteristics.

(In the code shown, you use the intrinsic module ISO_C_BINDING, but the code does not appear to then reference anything from it...)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Jommy