0

I have written the following code to modify my custom python class Point using the ctypes library, following the approch I found in this tutorial. The wrap_function is just a little helper for ease of use, as this is a MWE from a bigger project.

On the python side:

import ctypes
import numpy as np 
libc = ctypes.WinDLL(r'C:\Path\lib.dll')
 
def wrap_function(lib, funcname, restype, argtypes): 
    func = lib.__getattr__(funcname) 
    func.restype = restype 
    func.argtypes = argtypes 
    return func 
 
class Point(ctypes.Structure): 
    _fields_ = [('x', ctypes.c_int), ('xdata', ctypes.c_void_p)]
     
list_of_points = []                                #unused for now 
xdata = np.zeros((40000,), dtype=np.double)
a = Point(1,xdata.ctypes.data) 
b = Point(3,xdata.ctypes.data) 

change_data_for_point = wrap_function(libc,'change_data_for_point', None, [ctypes.POINTER(Point)])
change_data_for_point(a)

And on the C-side:

---header: 

const int N = 40000;
typedef struct {
    double x; 
    double xdata[N];
} Point;

extern "C" LIB_API void change_data_for_point(Point* p);


---source:

void change_data_for_point(Point* p) {
    p->x++; 
    for (int i = 0; i < 40000; i++) {
        p->xdata[i] = 2.0*i;
        if (i % 1000 == 0) printf("xdata at index %d is %f\n", i, p->xdata[i]);
    }
}

When executing the python file in Windows 7 cmd, it prints the following output:

xdata at index 0 is 0.000000
xdata at index 1000 is 2000.000000
 // ... some more ... 
xdata at index 17000 is 34000.000000
xdata at index 18000 is 36000.000000
Traceback (most recent call last):
   File "test.py", line 40, in <module>

Why does it stop at 18.000 ? I tried it several times, sometimes the loop reaches 19 or 20k, but it never gets higher than that. Does it have something to do with the array initialization on the C-side? Did I mess up the parameter passing on the python side?


Bonus question: How can I pass a list of these points to the C-side with ctypes?

2
  • Surrounding the critical lines in python with a try-catch-block now lets me print exception: access violation writing 0x0000000006F60000, so I think I am most likely messing up on the C-side. Commented Mar 13, 2019 at 7:15
  • Point definition in C and Python don't match (xdata). Commented Mar 13, 2019 at 9:42

1 Answer 1

1

Although NumPy adds an additional complexity level, every piece of info can be found on [Python 3]: ctypes - A foreign function library for Python.

The (main) problem was that the Point structure was differently defined in C and Python.
Also, the function expects a Point*, so byref must be used (it works without it as well, I don't know whether this is Undefined Behavior's happy case, or ctypes does that silently - due to argtypes).

I've adapted your code in order to work.

dll.c:

#include <stdio.h>

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


const int N = 40000;

typedef struct {
    double x;
    double xdata[N];
} Point;


#if defined(__cplusplus)
extern "C" {
#endif

    DLL_EXPORT void change_data_for_point(Point *p);

#if defined(__cplusplus)
}
#endif


void change_data_for_point(Point *p) {
    p->x++; 
    for (int i = 0; i < 40000; i++) {
        p->xdata[i] = 2.0 * i;
        if (i % 10000 == 9999)
            printf("xdata at index %d is %f\n", i, p->xdata[i]);
    }
}

code.py:

#!/usr/bin/env python3

import sys
import ctypes
import numpy as np


DLL_NAME = "./dll.dll"

xdata_dim = 40000  # !!! Must match N (from C) !!!
DoubleArr = ctypes.c_double * xdata_dim

class Point(ctypes.Structure): 
    _fields_ = [
        ("x", ctypes.c_int),
        ("xdata", DoubleArr),
    ]


def wrap_function(lib, funcname, restype, argtypes):
    func = lib.__getattr__(funcname)
    func.restype = restype
    func.argtypes = argtypes
    return func


def main():
    dll = ctypes.CDLL(DLL_NAME)
    #xdata_dim = ctypes.c_int.in_dll(dll, "N")

    xdata = np.zeros((xdata_dim,), dtype=np.double)
    a = Point(1, DoubleArr.from_address(xdata.ctypes.data))
    b = Point(3, DoubleArr.from_address(xdata.ctypes.data))
    change_data_for_point = wrap_function(dll,"change_data_for_point", None, [ctypes.POINTER(Point)])
    change_data_for_point(ctypes.byref(a))
    print(a.xdata[30000])


if __name__ == "__main__":
    print("Python {:s} on {:s}\n".format(sys.version, sys.platform))
    main()

Output:

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

[prompt]> "c:\Install\x86\Microsoft\Visual Studio Community\2015\vc\vcvarsall.bat" x64

[prompt]> dir /b
code.py
dll.c

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

[prompt]> dir /b
code.py
dll.c
dll.dll
dll.exp
dll.lib
dll.obj

[prompt]> "e:\Work\Dev\VEnvs\py_064_03.06.08_test0\Scripts\python.exe" code.py
Python 3.6.8 (tags/v3.6.8:3c6b436a57, Dec 24 2018, 00:16:47) [MSC v.1916 64 bit (AMD64)] on win32

xdata at index 9999 is 19998.000000
xdata at index 19999 is 39998.000000
xdata at index 29999 is 59998.000000
xdata at index 39999 is 79998.000000
60000.0

@EDIT0:

If you want to handle a list of Points, you can use arrays. Something like:

PointArr = Point * len(list_of_points)
point_arr = PointArr(*list_od_points)
Sign up to request clarification or add additional context in comments.

7 Comments

Thanks a lot! That really helped :) How would I go If I wanted to pass a list of points to the C-side to edit them? I already tried nesting the structs on the python side, but upon execution I get an access violation error!
But you're already editing them. The values from the xdata array are set from C.
Haha, yes I know! Maybe I didnt formulate my intention clear enough - say, I have a list of n Point-objects on the Python-side. Now I want to pass that list to a function (the entire list, not all n points separately - they have to be present on the C side at the same time). This function, on the C-side, should iterate through the list and edit the data from each of these n points. I thought about doing something like class pointList(ctypes.Structure): _fields_ = [('1',point), ('2',point), ... ('n',point),] and then passing this to a function, along with the number n.
Then use an array of points. What you're suggesting is not possible (at least not in that form). Check stackoverflow.com/questions/55103298/… - you'd have to do something similar to CharArr (somewhere at the end of main).
Thanks man! I found a workaround by passing several points separately, but storing them in a std::vector<Point*> on the C-side. Appreciate your help!
|

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.