4

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.

4
  • Can you match your (Python-side) arguments to what you understand gcc (through the argument passing convention) is expecting? That would help me (who doesn't do python/ctypes) assess your belief. Commented Feb 24, 2018 at 9:46
  • 2
    What is argparse? Try setting the argtypes attribute instead. Commented Feb 24, 2018 at 10:57
  • 1
    If passing by reference, you want the argtype to be ct.POINTER(ct.c_int) to pass ct.byref(iunit_temp), for example. You can also use file1 = b'one' and skip encoding the files explicitly. Commented Feb 25, 2018 at 4:16
  • I switched to argtypes this doesn't solve my problem but possibly fixes some other code I had which was using argparse (see solution to this question stackoverflow.com/questions/42328866/…) Commented Feb 25, 2018 at 9:38

1 Answer 1

2

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.

Sign up to request clarification or add additional context in comments.

1 Comment

It is much MUCH better the official Fortran to C interoperability. See fortran-iso-c-binding. Only if you use bind(C) you can count with the fact that optional arguments use NULL pointers. Without bind(C) the compiler can do whatever it wants. It can alse change the weird mangeled name __test_module_MOD_fstr_test to whatever it wants, it may differ between compiler versions and it will differ between different compilers. You can read about passing-by-reference and similar there as well.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.