Problems with passing and getting arrays for a C function using ctypes

前端 未结 1 1681
难免孤独
难免孤独 2021-01-21 16:03

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

相关标签:
1条回答
  • 2021-01-21 16:59

    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).

    dll0.c:

    #include <stdlib.h>
    
    #if defined(_WIN32)
    #  define DLL0_EXPORT_API __declspec(dllexport)
    #else
    #  define DLL0_EXPORT_API
    #endif
    
    
    DLL0_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;
    }
    
    
    DLL0_EXPORT_API void dealloc(double *ptr) {
        free(ptr);
    }
    

    script0.py:

    #!/usr/bin/env python3
    
    import sys
    import ctypes
    
    
    DLL_NAME = "./dll0.dll"
    
    
    def main():
        dim = 5
        DoubleArr = ctypes.c_double * dim
        DoubleArrArr = DoubleArr * dim
    
        DoublePtr = ctypes.POINTER(ctypes.c_double)
        DoublePtrPtr = ctypes.POINTER(DoublePtr)
    
        DoublePtrArr = DoublePtr * dim
    
        dll0 = ctypes.CDLL(DLL_NAME)
    
        dll0Func0 = dll0.dll0Func0
        dll0Func0.argtypes = [ctypes.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, ctypes.cast(DoublePtrArr(*(ctypes.cast(row, DoublePtr) for row in mat)), DoublePtrPtr), ctypes.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 {0:s} {1:d}bit on {2:s}\n".format(" ".join(item.strip() for item in sys.version.split("\n")), 64 if sys.maxsize > 0x100000000 else 32, sys.platform))
        main()
        print("\nDone.")
    

    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
    **********************************************************************
    ** Visual Studio 2017 Developer Command Prompt v15.9.14
    ** Copyright (c) 2017 Microsoft Corporation
    **********************************************************************
    [vcvarsall.bat] Environment initialized for: 'x64'
    
    [prompt]> dir /b
    dll0.c
    script0.py
    thomas.c
    Thomas.h
    
    [prompt]> cl /nologo /DDLL dll0.c  /link /NOLOGO /DLL /OUT:dll0.dll
    dll0.c
       Creating library dll0.lib and object dll0.exp
    
    [prompt]> dir /b *.dll
    dll0.dll
    
    [prompt]> "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\Scripts\python.exe" script0.py
    Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)] 64bit 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.
    
    0 讨论(0)
提交回复
热议问题