与 Ctypes 相比,除了语法之外,一切都相同,我的 CFFI 实现不再抛出访问冲突。
Process finished with exit code -1073741819 (0xC0000005)
我已经编写了两个单独的调用 DLL 函数的实现,它返回一个点云。 DLL 接受指向为结构分配的缓冲区的指针和缓冲区的大小。
注意:在将数据写入点云缓冲区时,DLL 仅考虑 iBufSize 参数中传递的大小,而不考虑结构中的 nPoints 变量。
from cffi import FFI
ffi = FFI()
lib = ffi.dlopen("PointCloud.dll")
ffi.cdef(
"""
typedef struct {
double x;
double y;
double z;
} PointXYZ;
typedef struct {
uint32_t nPoints;
PointXYZ * pointcloud;
} PointCloud;
int GetPointCloud(void * ptrPointCloudBuf, int iBufSize);
"""
)
nPts = 5000000
buf_size = nPts * ffi.sizeof('PointXYZ')
pc_struct = ffi.new(
"PointCloud *", {
"nPoints": ffi.cast("uint32_t", 0),
"pointcloud": ffi.new("PointXYZ []", nPts )
}
)
retcode = lib.GetPointCloud(pc_struct, buf_size)
如果一切正常,GetPointCloud() 函数返回
retcode
为 SUCCESS
数据写入点云缓冲区,否则适当的错误代码到 `retcode'。
使用 Ctypes 执行相同的实现没有任何问题。
from ctypes import *
lib = cdll.LoadLibrary("PointCloud.dll")
class PointXYZ(Structure):
_fields_ = [
('x', c_double),
('y', c_double),
('z', c_double)
]
class PointCloud(Structure):
_fields_ = [
('nPoints', c_uint32),
('pointcloud', POINTER(PointXYZ)),
]
GetPointCloud = lib['GetPointCloud']
GetPointCloud.restype = c_int
GetPointCloud.argtypes = [POINTER(PointCloud), c_int]
nPts = 5000000
buf_size = nPts * sizeof(PointXYZ)
pc_struct = pointer(
PointCloud(
nPoints=c_uint32(0),
pointcloud=cast((PointXYZ * nPts)(), POINTER(PointXYZ))
)
)
retcode = lib.GetPointCloud(pc_struct, buf_size)
注:
- DLL(纯
C
实现)源代码对我不可用。
- CFFI 和 Ctypes 的实现对于以前版本的 DLL 都运行顺利,直到它们更改了 DLL 中的某些内容,同时保持外部接口不变。
我可以访问他们使用 DLL 的 C/C++ 演示应用程序的源代码,它运行得非常好。相应
C
代码的代码片段是:
struct PointXYZ
{
double x;
double y;
double z;
};
struct PointCloud {
uint32_t nPoints;
PointXYZ * pointcloud;
PointCloud() {
pointcloud = nullptr;
nPoints = 0;
}
};
PointCloud pcloud;
int nPts = 5000000;
pcloud.pointcloud = new PointXYZ[nPts];
buf_size = sizeof(PointXYZ) * nPts;
int n = GetPointCloud(&pcloud, buf_size);
if(n == 0)
{
printf("Success, result = %d\n", n);
}
else
{
printf("Failure, result = %d\n", n);
}
/*
Do the rest with the point cloud.
*/
我明白了,它是一个无效的临时引用,导致使用 Undefined Behavior(常见的陷阱,特别是对于接触 C 的 Python 程序员)。
根据 [ReadTheDocs]:Using the ffi/lib objects - Working with pointers, structures and arrays(重点是我的):
然而,这总是错误的(释放内存的使用):
p = ffi.new("char **", ffi.new("char[]", "hello, world")) # WRONG! as soon as p is built, the inner ffi.new() gets freed!
我准备了一个小例子
dll00.c:
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#if defined(_WIN32)
# define DLL00_EXPORT_API __declspec(dllexport)
#else
# define DLL00_EXPORT_API
#endif
#if defined(__cplusplus)
extern "C" {
#endif
DLL00_EXPORT_API int GetPointCloud(void *pBuf, int bSize);
#if defined(__cplusplus)
}
#endif
typedef struct {
double x;
double y;
double z;
} PointXYZ;
typedef struct {
uint32_t nPoints;
PointXYZ *pointcloud;
} PointCloud;
int GetPointCloud(void *pBuf, int bSize)
{
if ((pBuf == NULL) || (bSize <= 0)) {
return -1;
}
unsigned int seed = (unsigned int)time(NULL);
//printf("%d %d\n", RAND_MAX, seed);
srand(seed);
int r = rand();
printf("From C: Attempting to return %d points\n", r);
size_t req = r * sizeof(PointXYZ);
if (req > bSize) {
return req;
}
PointCloud *ppc = (PointCloud*)pBuf;
for (int i = 0; i < r; ++i)
ppc->pointcloud[i] = (PointXYZ){i * 3, i * 3 + 1, i * 3 + 2};
ppc->nPoints = r;
return 0;
}
code00.py:
#!/usr/bin/env python
import ctypes as cts
#import random
import sys
DLL_NAME = "./PointCloud.{:s}".format("dll" if sys.platform[:3].lower() == "win" else "so")
class PointXYZ(cts.Structure):
_fields_ = (
("x", cts.c_double),
("y", cts.c_double),
("z", cts.c_double),
)
PPointXYZ = cts.POINTER(PointXYZ)
class PointCloud(cts.Structure):
_fields_ = (
("nPoints", cts.c_uint32),
("pointcloud", PPointXYZ),
)
PPointCloud = cts.POINTER(PointCloud)
def call_dll_func(func, pc, size, pc_transform=lambda arg: arg):
res = func(pc_transform(pc), size)
print("\nFunction {:} returned: {:d}".format(func, res))
if res != 0:
return
idxs = (0, pc.nPoints // 2, pc.nPoints - 1)
print("\nPoints: {:d}\nData ({:d} points):".format(pc.nPoints, len(idxs)))
for i in idxs:
p = pc.pointcloud[i]
print(" {:d}: {:.1f} {:.1f} {:.1f}".format(i, p.x, p.y, p.z))
def main(*argv):
#random.seed()
dll = cts.CDLL(DLL_NAME)
GetPointCloud = dll.GetPointCloud
GetPointCloud.argtypes = ()
GetPointCloud.restype = cts.c_int
pts = 5000000
buf_size = pts * cts.sizeof(PointXYZ)
pc = PointCloud(nPoints=cts.c_uint32(0), pointcloud=cts.cast((PointXYZ * pts)(), PPointXYZ))
call_dll_func(GetPointCloud, pc, buf_size, pc_transform=lambda arg: cts.byref(arg))
if __name__ == "__main__":
print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
64 if sys.maxsize > 0x100000000 else 32, sys.platform))
rc = main(*sys.argv[1:])
print("\nDone.\n")
sys.exit(rc)
code01.py:
#!/usr/bin/env python
import sys
from cffi import FFI
from code00 import DLL_NAME, \
call_dll_func
ffi = FFI()
ffi.cdef(
"""
typedef struct {
double x;
double y;
double z;
} PointXYZ;
typedef struct {
uint32_t nPoints;
PointXYZ * pointcloud;
} PointCloud;
int GetPointCloud(void *pBuf, int bSize);
"""
)
def main(*argv):
dll = ffi.dlopen(DLL_NAME)
pts = 5000000
buf_size = pts * ffi.sizeof("PointXYZ")
pc_buf = ffi.new("PointXYZ []", pts) # @TODO - cfati: - Take it outside
pc = ffi.new(
"PointCloud *", {
"nPoints": ffi.cast("uint32_t", 0),
"pointcloud": pc_buf,
}
)
GetPointCloud = dll.GetPointCloud
call_dll_func(GetPointCloud, pc, buf_size)
if __name__ == "__main__":
print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
64 if sys.maxsize > 0x100000000 else 32, sys.platform))
rc = main(*sys.argv[1:])
print("\nDone.\n")
sys.exit(rc)
输出:
[cfati@CFATI-5510-0:e:\Work\Dev\StackExchange\StackOverflow\q076053999]> sopr.bat ### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ### [prompt]> "c:\Install\pc032\Microsoft\VisualStudioCommunity\2019\VC\Auxiliary\Build\vcvarsall.bat" x64 > nul [prompt]> [prompt]> dir /b code00.py code01.py dll00.c [prompt]> [prompt]> cl /nologo /MD /DDLL dll00.c /link /NOLOGO /DLL /OUT:PointCloud.dll dll00.c Creating library PointCloud.lib and object PointCloud.exp [prompt]> [prompt]> dir /b code00.py code01.py dll00.c dll00.obj PointCloud.dll PointCloud.exp PointCloud.lib [prompt]> [prompt]> :: CTypes [prompt]> "e:\Work\Dev\VEnvs\py_pc064_03.10_test0\Scripts\python.exe" ./code00.py Python 3.10.9 (tags/v3.10.9:1dd9be6, Dec 6 2022, 20:01:21) [MSC v.1934 64 bit (AMD64)] 064bit on win32 From C: Attempting to return 9724 points Function <_FuncPtr object at 0x0000020CA1C40EE0> returned: 0 Points: 9724 Data (3 points): 0: 0.0 1.0 2.0 4862: 14586.0 14587.0 14588.0 9723: 29169.0 29170.0 29171.0 Done. [prompt]> [prompt]> :: CFFI [prompt]> "e:\Work\Dev\VEnvs\py_pc064_03.10_test0\Scripts\python.exe" ./code01.py Python 3.10.9 (tags/v3.10.9:1dd9be6, Dec 6 2022, 20:01:21) [MSC v.1934 64 bit (AMD64)] 064bit on win32 From C: Attempting to return 9740 points Function <cdata 'int(*)(void *, int)' 0x00007FFB33351020> returned: 0 Points: 9740 Data (3 points): 0: 0.0 1.0 2.0 4870: 14610.0 14611.0 14612.0 9739: 29217.0 29218.0 29219.0 Done.
可能还需要 ro 检查 [SO]:通过 ctypes 从 Python 调用的 C 函数返回不正确的值(@CristiFati 的回答) 一个(另一个)陷阱:这次使用 CTypes(调用函数)。
@CristiFati,这是随DLL提供的头文件的内容。当然它还有一些其他的功能。但我只粘贴了我们感兴趣的部分。我确认,除此之外,没有其他内容。
#ifndef POINTCLOUD_H
#define POINTCLOUD_H
#if defined(_MSC_VER)
#define POINTCLOUD_DLL_EXPORT __declspec(dllexport)
#else
#define POINTCLOUD_DLL_EXPORT __attribute__ ((visibility ("default")))
#define _stdcall
#endif
// Point structs
typedef struct {
double x;
double y;
double z;
} PointXYZ;
struct PointCloud {
uint32_t number_of_points;
PointXYZ * pointcloud;
PointCloud() {
pointcloud= nullptr;
number_of_points = 0;
}
};
extern "C" POINTCLOUD_DLL_EXPORT int GetPointCloud(void* buffer, int buffer_size);
#endif // POINTCLOUD_H