2

I'm trying to use ctypes for passing a bidimensional array and an unidimensional array to a C function from my python code then this function return an unidimensional array to my python terminal.

1-I need to pass the array from python, as you will see below (how i tried to do). 2-Probably i don't set up argument types and return types correctly. I've been searching a lot but nothing solve my problems.

My C code named Thomas_PYTHON_DLL.c:

#include"stdio.h"
#include"stdlib.h"
#include"Thomas.h"


EXPORT double* Thomas(int dimension, double MatrizTridiagonal[dimension] 
[dimension],double vec_b[dimension]){


double* Thomas(int dimension, double MatrizTridiagonal[dimension] 
[dimension],double vec_b[dimension]){


double a[dimension];                            
double b[dimension];                            
double c[dimension];                            
double resp[dimension];                         

double *solution;
solution=(double *) malloc(dimension*sizeof(double));                       

for(int i=0;i<dimension;i++){resp[i]=vec_b[i];}


for(int i=0;i<dimension;i++){                    
        if(i==0){a[i]=0.0;}
        else{               
            a[i]=MatrizTridiagonal[i][i-1]; 
            }
}

for(int i=0;i<dimension;i++){                           
        b[i]=MatrizTridiagonal[i][i];       
    }

    for(int i=0;i<dimension;i++){               
        if(i==dimension-1){c[dimension-1]=0.0;}
        else{
            c[i]=MatrizTridiagonal[i][i+1];     
    }
}


for(int i=0;i<dimension;i++){                   
        if(i==0){
            c[i]=c[i]/b[i];                     
            resp[i]=resp[i]/b[i];               
                        }
        else{
            c[i]=c[i]/(b[i]-c[i-1]*a[i]);                           
            resp[i]=(resp[i]-a[i]*resp[i-1])/(b[i]-a[i]*c[i-1]);                
    }
}


for(int i=dimension-1;i>=0;i--){            

    if(i==dimension-1){
        solution[i]=resp[i];
    }

    else{

        solution[i]=resp[i]-c[i]*solution[i+1];
    }   

}

for(int i=0;i<dimension;i++){printf("x%d=|%0.2f| \n",i,solution[i]);}  

return solution;
//free(solution);
}

}

My C code named Thomas.h:

#define EXPORT __declspec(dllexport)

EXPORT double* Thomas(int dimension, double MatrizTridiagonal[dimension] 
[dimension],double vec_b[dimension]);

And finally my Python code, named Thomas_Python.py:

from ctypes import *

x=(c_double*5)
Tridiagonal = cdll.LoadLibrary('Thomas_dll.dll')
Tridiagonal.Thomas.restype=POINTER(x)
Tridiagonal.Thomas.argtypes=[c_int,((c_double*5)*5),(c_double*5)]

#arrays that i want to pass to C code
a=((c_double*5)*5)((2,-1,0,0,0),(-1,2,-1,0,0),(0,-1,2,-1,0),(0,0,-1,2,-1), 
(0,0,0,-1,2))
b=(c_double*5)(4,2,2,2,4)

r=Tridiagonal.Thomas(5,a,b)

print(r[2])

In the code above, i expected to print the value of the array r in the position "2", but the print show me:

<__main__.c_double_Array_5 object at 0x03A77350>

A great help would be beyond knowing how to read array values, getting the entire array as a list. Thank you so much for all your help and time, and I apologize for my English.

CristiFati
  • 38,250
  • 9
  • 50
  • 87
Moreira12
  • 21
  • 2
  • `Tridiagonal.Thomas.restype = POINTER(c_double)`. And does this `double* Thomas(int dimension, double MatrizTridiagonal[dimension] [dimension],double vec_b[dimension]){` compile? – CristiFati Jul 31 '19 at 20:17

1 Answers1

0

Mentioning [Python.Docs]: ctypes - A foreign function library for Python.

There are a number of problems with your code. Here are a few:

  • I don't know how a function header like this double *Thomas(int dimension, double MatrizTridiagonal[dimension][dimension], double vec_b[dimension]) compiles (because of dimension). However, I didn't test it with gcc

  • Your C and Python function header (return value) differs: double* vs. ctypes.POINTER(ctypes.c_double * 5)

  • You never deallocate the returned array, resulting in memory leaks

  • The codestyle (including naming) could be very much improved

When dealing with arrays (especially multidimensional - since dimension needs to be known at compile time) as function arguments, meaning that they are passed from outside, there are a couple of ways to handle things:

  1. Use a maximum constant value for dimension. The limitation is pretty obvious

  2. Use pointers instead. The drawback is that the function header is not that clear, and in general people tend to run away from pointers in general, but especially if they have more than one level of indirection (2 star pointer :) )

However, I chose the latter approach. I created a dummy .dll, which contains a function that calculates the product of the 2 arrays (thinking of the 1D array as a 2D array that only has one column).

dll00.c:

#include <stdlib.h>

#if defined(_WIN32)
#  define DLL00_EXPORT_API __declspec(dllexport)
#else
#  define DLL00_EXPORT_API
#endif


DLL00_EXPORT_API double *dll0Func0(int dimension, double **arr2D, double *arr1D)
{
    double *solution = (double*)calloc(dimension, sizeof(double));
    for (int i = 0; i < dimension; i++) {
        for (int j = 0; j < dimension; j++) {
            solution[i] += arr2D[i][j] * arr1D[j];
        }
    }
    return solution;
}


DLL00_EXPORT_API void dealloc(double *ptr)
{
    free(ptr);
}

code00.py:

#!/usr/bin/env python3

import sys
import ctypes as ct


DLL_NAME = "./dll00.{:s}".format("dll" if sys.platform[:3].lower() == "win" else "so")


def main(*argv):
    dim = 5
    DoubleArr = ct.c_double * dim
    DoubleArrArr = DoubleArr * dim

    DoublePtr = ct.POINTER(ct.c_double)
    DoublePtrPtr = ct.POINTER(DoublePtr)

    DoublePtrArr = DoublePtr * dim

    dll0 = ct.CDLL(DLL_NAME)

    dll0Func0 = dll0.dll0Func0
    dll0Func0.argtypes = (ct.c_int, DoublePtrPtr, DoublePtr)
    dll0Func0.restype = DoublePtr

    dealloc = dll0.dealloc
    dealloc.argtypes = (DoublePtr,)

    mat = DoubleArrArr(
        (2, -1, 0, 0, 0),
        (-1, 2, -1, 0, 0),
        (0, -1, 2, -1, 0),
        (0, 0, -1, 2, -1),
        (0, 0, 0, -1, 2),
    )
    vec = DoubleArr(4, 2, 2, 2, 4)

    
    res = dll0Func0(dim, ct.cast(DoublePtrArr(*(ct.cast(row, DoublePtr) for row in mat)), DoublePtrPtr), ct.cast(vec, DoublePtr))
    print("{0:s} returned {1:}".format(dll0Func0.__name__, res))
    for i in range(dim):
        print("{0:d} - {1:.3f}".format(i, res[i]))

    dealloc(res)


if __name__ == "__main__":
    print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(item.strip() for item in sys.version.split("\n")),
                                                   64 if sys.maxsize > 0x100000000 else 32, sys.platform))
    main(*sys.argv[1:])
    print("\nDone.")
    sys.exit(rc)

The only tricky thing here is the DoublePtrArr cast, as the 2D array can't be cast to double (**, not the type) pointer directly (I mean it can be, but the 2 memory layouts differ, so it would generate Undefined Behavior, and most likely the program will segfault (Access Violation)), so each inner array is cast separately in the intermediary object, which will be then cast to a double (**) pointer (that the function expects).

Output:

cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q057295045]> sopr.bat
### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ###

[prompt]> "c:\Install\x86\Microsoft\Visual Studio Community\2017\VC\Auxiliary\Build\vcvarsall.bat" x64>nul

[prompt]> dir /b
dll00.c
code00.py
thomas.c
Thomas.h

[prompt]> cl /nologo /DDLL dll00.c  /link /NOLOGO /DLL /OUT:dll00.dll
dll0.c
   Creating library dll00.lib and object dll00.exp

[prompt]> dir /b *.dll
dll00.dll

[prompt]> "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\Scripts\python.exe" code00.py
Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)] 064bit on win32

dll0Func0 returned <__main__.LP_c_double object at 0x0000026CD4BEC4C8>
0 - 6.000
1 - -2.000
2 - 0.000
3 - -2.000
4 - 6.000

Done.

You could also check [SO]: How to pass a 2d array from Python to C? (@CristiFati's answer) for more details.

CristiFati
  • 38,250
  • 9
  • 50
  • 87
  • This explanation helped me a lot! I have some questions yet. I used the **free** function right after the **return** of the **Thomas** (C) function, but after several tests and errors I thought the **free** function could just be the problem because it frees the memory of the variable. couldn't i free the memory right after the **return**? Do you have any tips or bibliographies that you can recommend to improve my codestyle? I am always aware that I have to improve, but I always leave it aside. – Moreira12 Aug 01 '19 at 16:26
  • You need to free a pointer once you're done with it. If you free it before returning the function, when you access it from *Python*, it is invalid. That's why a separate function (that is called manually by the user) is required. Regarding codestyle, check https://www.python.org/dev/peps/pep-0008/, and regarding *C*, there are some, here's one example: https://www.gnu.org/prep/standards/html_node/Formatting.html#Formatting. – CristiFati Aug 02 '19 at 07:53