使用 ctypes 将数组从 Python 传递到 C++,无法完全处理它
Passing array from Python to C++ using ctypes, unable to process it entirely
我已经编写了以下代码来使用 ctypes
库修改我的自定义 python class Point
,遵循我在此 [=18= 中找到的方法]. wrap_function
只是一个易于使用的小帮手,因为这是一个来自更大项目的 MWE。
python 一方:
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)
C端:
---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]);
}
}
在Windows 7 cmd
中执行python文件时,打印出如下输出:
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>
为什么停在18.000?我试了几次,有时循环达到 19 或 20k,但它永远不会高于那个。跟C端的数组初始化有关系吗?我弄乱了 python 端传递的参数吗?
额外问题:如何使用 ctypes 将这些点的列表传递给 C 端?
虽然 NumPy 增加了额外的复杂度,但每条信息都可以在 [Python 3]: ctypes - A foreign function library for Python.
上找到
(主要)问题是 Point 结构在 C 和 [=64= 中的定义不同]。
此外,该函数需要一个 Point*
,因此必须使用 byref(没有它也可以工作,我不知道这是不是 Undefined Behavior's happy case,或者 ctypes 默默地做 - 由于 argtypes).
我已经修改了您的代码以便正常工作。
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()
输出:
[cfati@CFATI-5510-0:e:\Work\Dev\Whosebug\q055124400]> sopr.bat
*** Set shorter prompt to better fit when pasted in Whosebug (or other) pages ***
[prompt]> "c:\Install\x86\Microsoft\Visual Studio Community15\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:
如果你想处理一个点的列表,你可以使用数组。类似于:
PointArr = Point * len(list_of_points)
point_arr = PointArr(*list_od_points)
我已经编写了以下代码来使用 ctypes
库修改我的自定义 python class Point
,遵循我在此 [=18= 中找到的方法]. wrap_function
只是一个易于使用的小帮手,因为这是一个来自更大项目的 MWE。
python 一方:
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)
C端:
---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]);
}
}
在Windows 7 cmd
中执行python文件时,打印出如下输出:
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>
为什么停在18.000?我试了几次,有时循环达到 19 或 20k,但它永远不会高于那个。跟C端的数组初始化有关系吗?我弄乱了 python 端传递的参数吗?
额外问题:如何使用 ctypes 将这些点的列表传递给 C 端?
虽然 NumPy 增加了额外的复杂度,但每条信息都可以在 [Python 3]: ctypes - A foreign function library for Python.
上找到(主要)问题是 Point 结构在 C 和 [=64= 中的定义不同]。
此外,该函数需要一个 Point*
,因此必须使用 byref(没有它也可以工作,我不知道这是不是 Undefined Behavior's happy case,或者 ctypes 默默地做 - 由于 argtypes).
我已经修改了您的代码以便正常工作。
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()
输出:
[cfati@CFATI-5510-0:e:\Work\Dev\Whosebug\q055124400]> sopr.bat *** Set shorter prompt to better fit when pasted in Whosebug (or other) pages *** [prompt]> "c:\Install\x86\Microsoft\Visual Studio Community15\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:
如果你想处理一个点的列表,你可以使用数组。类似于:
PointArr = Point * len(list_of_points)
point_arr = PointArr(*list_od_points)