Lazer
Lazer

Reputation: 289

Fortran optional arguments in Python using ctypes

How should I properly deal with optional Fortran arguments in Python using CTypes? For example let's assume the Fortran subroutine looks something like:

MODULE test_module
INCLUDES
   SUBROUTINE fstr_test(iunit, istat, file1, file2, file3, test, file4, file5)
      IMPLICIT NONE
      INTEGER, INTENT(inout) :: iunit
      INTEGER, INTENT(out) :: istat
      CHARACTER(LEN=*), INTENT(in) :: file1, file2, file3
      CHARACTER(LEN=*), INTENT(in), OPTIONAL :: file4, file5
      INTEGER, OPTIONAL :: test
      WRITE(6,*) file1, file2, file3
      RETURN
   END SUBROUTINE fstr_test
END MODULE test_module

Now the python should look something like

import ctypes as ct
libtest = ct.cdll.LoadLibrary("libtest.so")
fstr_h = getattr(libtest,'__test_module_MOD_fstr_test')
fstr_h.argparse =     [ ct.c_int, \ #iunit
                        ct.c_int, \ #istat
                        ct.c_char_p, \ #file1
                        ct.c_char_p, \ #file2
                        ct.c_char_p, \ #file3
                        ct.c_int, \ #test
                        ct.c_char_p,\ #file4
                        ct.c_char_p, \ #file5
    ct.c_bool, ct.c_bool, ct.c_bool,\ # Three optional arguments
    ct.c_long, ct.c_long, ct.c_long, ct.c_long, ct.c_long] # lengths
fstr_h.restype=None
iunit_temp = ct.c_int(0)
istat_temp = ct.c_int(0)
record_in_temp = ct.c_int(0)
opt1 = ct.c_bool(True)
opt2 = ct.c_bool(True)
opt3 = ct.c_bool(True)
file1 = "one"
file2 = "two"
file3 = "three"
file4 = "four"
file5 = "five"
fstr_h(iunit_temp,ct.byref(istat_temp), \
    file1.encode('UTF-8'),file2.encode('UTF-8'),file3.encode('UTF-8'),\
    record_in_temp,file4.encode('UTF-8'),file5.encode('UTF-8'), \
    ct.byref(opt1), ct.byref(opt2), ct.byref(opt3),\
    len(file1),len(file2),len(file3),len(file4),len(file5))

From gcc's documentation on its argument passing conventions I believe that I'm passing the variables correctly but there may be a subtlety I'm missing? When I try to run the code it crashes.

Upvotes: 4

Views: 432

Answers (1)

Lazer
Lazer

Reputation: 289

OK figured a few things out.

  • I needed to be using argtypes not argparse
  • OPTIONAL arguments only need the hidden logical variable when they're defined as passed by VALUE in Fortran. If they're not passed then a NULL pointer would still need to be passed.
  • The integers all need to be passed in by reference.

So the Fortran code remains as above but the Python code should read like

import ctypes as ct
libtest = ct.cdll.LoadLibrary("libtest.so")
fstr_h = getattr(libtest,'__test_module_MOD_fstr_test')
fstr_h.argtypes =     [ ct.POINTER(ct.c_int), \ #iunit
                        ct.POINTER(ct.c_int), \ #istat
                        ct.c_char_p, \ #file1
                        ct.c_char_p, \ #file2
                        ct.c_char_p, \ #file3
                        ct.POINTER(ct.c_int), \ #test
                        ct.c_char_p,\ #file4
                        ct.c_char_p, \ #file5
                        ct.c_long, ct.c_long, ct.c_long, ct.c_long, ct.c_long] # lengths
fstr_h.restype=None
iunit_temp = ct.c_int(0)
istat_temp = ct.c_int(0)
record_in_temp = ct.c_int(0)
file1 = "one"
file2 = "two"
file3 = "three"
file4 = "four"
file5 = "five"
fstr_h(ct.byref(iunit_temp),ct.byref(istat_temp), \
       file1.encode('UTF-8'),file2.encode('UTF-8'),file3.encode('UTF-8'),\
       ct.byref(record_in_temp),file4.encode('UTF-8'),file5.encode('UTF-8'), \
       len(file1),len(file2),len(file3),len(file4),len(file5))

Now this is conjecture but I suspect any variable typed as INTNET(in) in Fortran can be passed as a value and not a pointer. However, since INTENT(inout) and INTENT(out) variables have their value changed, I believe the need to be passed by pointer. The same can be said of just regularly typed arguments in Fortran.

Upvotes: 2

Related Questions