Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing string to Fortran DLL using ctypes and Python

I am trying to load a DLL in Python 2.7 using ctypes. The DLL was written using Fortran and has multiple subroutines in it. I was able to successfully set up couple of the exported functions that that long and double pointers as arguments.

import ctypes as C
import numpy as np

dll = C.windll.LoadLibrary('C:\\Temp\\program.dll')
_cp_from_t = getattr(dll, "CP_FROM_T")
_cp_from_t.restype = C.c_double
_cp_from_t.argtypes = [C.POINTER(C.c_longdouble),
                    np.ctypeslib.ndpointer(C.c_longdouble)]

# Mixture Rgas function
_mix_r = getattr(dll, "MIX_R")
_mix_r.restype = C.c_double
_mix_r.argtypes = [np.ctypeslib.ndpointer(dtype=C.c_longdouble)]

def cp_from_t(composition, temp):
    """ Calculates Cp in BTU/lb/R given a fuel composition and temperature.

    :param composition: numpy array containing fuel composition
    :param temp: temperature of fuel
    :return: Cp
    :rtype : float
    """
    return _cp_from_t(C.byref(C.c_double(temp)), composition)

def mix_r(composition):
    """Return the gas constant for a given composition.
    :rtype : float
    :param composition: numpy array containing fuel composition
    """
    return _mix_r(composition)

# At this point, I can just pass a numpy array as the composition and I can get the 
# calculated values without a problem
comps = np.array([0, 0, 12.0, 23.0, 33.0, 10, 5.0])
temp = 900.0

cp = cp_from_t(comps, temp)
rgas = mix_r(comps)

So far, so good.

The problem arises when I try another subroutine called Function2 which needs some strings as input. The strings are all fixed length (255) and they also ask for the length of each of the string parameters.

The function is implemented in Fortran as follows:

Subroutine FUNCTION2(localBasePath,localTempPath,InputFileName,Model,DataArray,ErrCode)
!DEC$ ATTRIBUTES STDCALL,REFERENCE, ALIAS:'FUNCTION2',DLLEXPORT :: FUNCTION2
Implicit None
Character *255 localBasePath,localTempPath,InputFileName
Integer   *4  Model(20), ErrCode(20)
Real      *8  DataArray(900)

The function prototype in Python is set up as follows

function2 = getattr(dll, 'FUNCTION2')
function2.argtypes = [C.POINTER(C.c_char_p), C.c_long,
                      C.POINTER(C.c_char_p), C.c_long,
                      C.POINTER(C.c_char_p), C.c_long,
                      np.ctypeslib.ndpointer(C.c_long , flags='F_CONTIGUOUS'),
                      np.ctypeslib.ndpointer(C.c_double, flags='F_CONTIGUOUS'),
                      np.ctypeslib.ndpointer(C.c_long, flags='F_CONTIGUOUS')]

And I call it using:

base_path = "D:\\Users\\xxxxxxx\\Documents\\xxxxx\\".ljust(255)
temp_path = "D:\\Users\\xxxxxxx\\Documents\\xxxxx\\temp".ljust(255)
inp_file = "inp.txt".ljust(255)

function2(C.byref(C.c_char_p(base_path)),
                  C.c_long(len(base_path)),
                  C.byref(C.c_char_p(temp_dir)),
                  C.c_long(len(temp_dir))),
                  C.byref(C.c_char_p(inp_file)),
                  C.c_long(len(inp_file)),
                  model_array,
                  data_array,
                  error_array)

The strings are essentially paths. The function Function2 does not recognize the paths and proves an error message with some non-readable characters at the end, such as:

forrtl: severe (43): file name specification error, unit 16, D:\Users\xxxxxxx\Documents\xxxxx\ωa.

What I wanted the function to receive was D:\Users\xxxxxxx\Documents\xxxxx\. Obviously, the strings are not passed correctly.

I have read that Python uses NULL terminated strings. Can that be a problem while passing strings to a Fortran dll? If so, how do I get around it?

Any recommendations?

like image 958
d0m1n0 Avatar asked Oct 20 '22 08:10

d0m1n0


1 Answers

Following comment from @eryksun, I made the following changes to make it work.

Changed the argtypes to:

function2 = getattr(dll, 'FUNCTION2')
function2.argtypes = [C.c_char_p, C.c_long,
                  C.c_char_p, C.c_long,
                  C.c_char_p, C.c_long,
                  np.ctypeslib.ndpointer(C.c_long , flags='F_CONTIGUOUS'),
                  np.ctypeslib.ndpointer(C.c_double, flags='F_CONTIGUOUS'),
                  np.ctypeslib.ndpointer(C.c_long, flags='F_CONTIGUOUS')]

And instead of passing the string as byref, I changed it to the following.

base_path = "D:\\Users\\xxxxxxx\\Documents\\xxxxx\\".ljust(255)
temp_path = "D:\\Users\\xxxxxxx\\Documents\\xxxxx\\temp".ljust(255)
inp_file = "inp.txt".ljust(255)

function2(base_path, len(base_path), temp_dir, len(temp_dir), inp_file, len(inp_file), 
          model_array, data_array, error_array)

It was sufficient to pass the values directly.

like image 122
d0m1n0 Avatar answered Oct 23 '22 02:10

d0m1n0