在 python 中的 byref 向量参数上使用 ctypes 进行 DLL 转换

Posted

技术标签:

【中文标题】在 python 中的 byref 向量参数上使用 ctypes 进行 DLL 转换【英文标题】:DLL conversion with ctypes on byref vector argument in python 【发布时间】:2018-08-20 14:47:37 【问题描述】:

我的机器是带有anaconda的Win7。 我最近将 C++ dll 函数转换为 python 项目。 我克服了许多困难,但我不知道如何处理以下转换:

typedef int (__stdcall *p_API_GetOrder)(vector<ApiOrder>& apiOrderList);

在哪里,

class ApiOrder(Structure):
  _fields_ = [
  ('Timestamp',      c_long),
  ('Item',           c_char * 16),
  ('Qty',            c_long),
  ]

在python中,我试过了,

mydll.API_GetOrder(POINTER(ApiOrder()))

错误是:

TypeError: must be a ctypes type

我不是 C++ 或编程方面的出口商。所以不太确定 byref 是什么。如果有人能澄清我的概念就好了。

【问题讨论】:

【参考方案1】:

POINTER(…) 构造一个新的指针类型,而不是该类型的值。所以,当你这样做时:

 mydll.API_GetOrder(POINTER(ApiOrder()))

...您传递的是 Python 类型对象,而不是 C 指针对象周围的 ctypes 包装器。


要获得指向ctypes 包装对象的指针,您需要调用pointerbyref。前者构造一个POINTER(…) 实例,将其设置为指向您的对象,并传递包装的指针;后者只是直接将指针传递给您的对象,而无需构造指针包装对象,通常这就是您所需要的。有关详细信息,请参阅文档中的传递指针。


但是,我认为这不会有什么好处,原因有两个。


首先,大多数接受指向某个结构的指针并返回一个 int 的函数都在这样做,以便他们可以用有用的值填充该结构。构造一个新的空结构并传递一个指向它的指针而不持有对它的引用意味着您无法查看填充的任何值。

另外,您可能想检查返回值。

一般来说,你需要做这样的事情:

order = ApiOrder()
ret = mydll.API_GetOrder(byref(order))
if ret:
    do some error handling with either ret or errno 
else:
    so something with order

当我们这样做的时候,你几乎肯定想要set the argtypes and restype of the function,所以ctypes 知道如何正确转换,如果你做了一些没有意义的事情,可以给你一个例外,而不是让它猜测如果猜错了如何转换和传递东西和段错误。

另外,对于返回成功或错误int 的函数,通常最好将函数分配给restype,它会查找错误并raises 是一个适当的异常。 (如果您需要比检查 int 返回非零或指针返回是否为零更灵活的方法,请使用 errcheck。)


但即使这在这里也无济于事,因为您尝试调用的函数首先没有指向 ApiOrder 的指针,而是引用了其中的 std::vector .所以你需要调用C++标准库来构造一个that类型的对象,然后你可以byrefthat作为参数。

但通常情况下,编写一些为库提供 C API 的 C++ 代码,然后使用 ctypes 调用该 C API 会更容易,而不是尝试从 Python 构建和使用 C++ 对象。

您的 C++ 代码如下所示:

int call_getorder(p_API_GetOrder func, ApiOrder *apiOrderArray, size_t apiOrderCount) 
    std::vector<ApiOrder> vec(apiOrderArray, apiOrderCount);
    ret = func(vec);
    if (ret) return ret;
    std::copy(std::begin(vec), std::end(vec), apiOrderArray);
    return 0;

现在,您可以通过创建 an array of 1 ApiOrder(或创建 POINTERApiOrder 并直接传递它,如果您愿意)从 Python 调用它:

orders = (ApiOrder*1)()
ret = mywrapperdll.call_order(mydll.API_GetOrder, byref(order), 1)
if ret:
    do some error handling with either ret or errno 
else:
    do something with order[0]

当然,您仍然需要argtypesrestype

【讨论】:

太好了,你的话和例子简洁明了。我应该早点访问***...当你建议用C++编写一些代码时,我担心我无法访问dll源代码。 @CherrimonShop 我不是告诉你访问 DLL 源代码,我是告诉你编写一个新的 DLL 来导入 C++ DLL 并导出一个更简单、对 C 友好的 API。 我现在了解你了。在我实现它之前,我想知道是否有任何方法不创建新的 DLL,因为现有 DLL 中有超过 30 个类似的(byref)函数。我的想法是,也许,在python中获取对象指针并尝试搜索整个列表,直到下一个指向NULL,或者像这样? @CherrimonShop 他们都使用相同的vector&lt;ApiOrder&gt; 类型吗?如果是这样,创建一个带有函数指针的call_vector_function 函数ApiOrder*size_t 可能就足够了。或者只是一个 make_vector 函数,它接受 ApiOrder*size_t 并返回一个 vector&lt;ApiOrder&gt;* 加上一个 free_vector 函数,它接受 vector*deletes 它。 @CherrimonShop 另一种选择是使用 PyCxx、Cython、boost::python 或 SWIG 从您的 C++ 接口创建 Python 绑定,而不是使用 ctypes。或者,或者,只使用 SWIG 或其他任何东西为 std::vector 提供包装器,专门用于 ApiOrder 和任何类型,而不是包装库中的实际函数。

以上是关于在 python 中的 byref 向量参数上使用 ctypes 进行 DLL 转换的主要内容,如果未能解决你的问题,请参考以下文章

VBA:私有子中的编译错误 ByRef 参数类型不匹配

VBA中ByVal和 ByRef有啥区别?

从被调用的函数调用函数时 VBA byref 参数类型不匹配

在运行时测试传入的 ByRef 参数是不是与给定的“其他”变量共享存储位置

ByRef 参数类型与布尔值不匹配

无法将 ByRef VARIANT 数组转换为 SAFEARRAY