python - 可以结合 swig 生成的数据类型和 ctypes 函数
问题描述
我正在为带有 ctypes 的 C 库编写 python 包装器,并且我有一个返回结构的 C 函数。但是该结构是在另一个 C 文件中定义的,并且该文件由 swig 包装。
我简化了结构和代码。
这个结构是由 swig 包裹的。
struct point {
int x;
int y;
};
这个函数是用 ctypes 包装的。
struct point add_points(struct point a, struct point b) {
struct point c;
c.x = a.x + b.x;
c.y = a.y + b.y;
return c;
}
Python 包装器。
import swigModule # contains class point generated from c structure point
import ctypes
_libc = ctypes.CDLL('./c_file.so')
def add_points(a, b):
add_points = _libc.add_points
add_points.argtypes = [swigModule.point, swigModule.point,]
add_points.restype = swigModule.point,
result = add_points(a, b)
return result
问题是,我不能point
在 ctypes 中使用 swig 生成的类作为 restype 和 argtype。但是我不能像这样编写自己的结构包装器。
class Point(ctypes.Structure):
_fields_ = [("x", ctypes.c_int),
("y", ctypes.c_int)]
因为 C 结构的字段是隐藏的,所以我无权访问源代码。我只知道 swig wrapper 的结构名称。
我有两个 C 文件,一个是用 swig 生成的,具有结构数据类型。另一个具有功能并用 ctypes 包装。我想在我的 ctypes 函数中使用来自 swig 的数据类。
如何将从 c 结构生成的 swig 类映射到 ctypes 类,以便可以将其用作返回类型?
解决方案
让 SWIG 和 ctypes 以各种不同的方式一起工作是完全可能的,我将在下面通过几个示例进行展示。但是,话虽如此,但有一个很大的警告:根据我的经验,它几乎永远不会为您提供可维护、可移植、可扩展等的东西,因此我倾向于在可能的情况下支持替代选项。(这些可能包括使用任何适当的说服方式向原始作者询问来源,从头开始重写整个内容,或者只是使用替代库)。
无论如何,让我们假设您已经为给定的 Python 版本构建了一个 SWIG 包装库。无论出于何种原因,他们都没有包装您最喜欢的功能。因此,您只想使用 ctypes 将某些内容添加到 SWIG 生成的代码中。这实际上很容易,只要:
- 您要调用的函数仅按指针获取和返回对象,而不是按值。
- 您要调用的函数仅返回您的类型的现有实例,而不是 malloc 新实例。(有一个解决方法,但它又开始让事情变得困难,非常快)。
- 您关心的结构实际上包装了您关心的所有成员,并且(如果是 C++ 代码)它们是 POD 类型。
您的案例并不真正符合这些约束条件,但作为热身练习,让我们看一下使用以下“easy_mode”函数执行此操作,因为它确实让我们介绍了一个关键点:
struct point *easy_mode(struct point *a, struct point *b) {
a->x += b->x;
a->y += b->y;
return a;
}
鉴于现有的 SWIG 包装器尚未包装它,但确实包装了结构,此函数非常易于使用。我们可以简单地使用 SWIG 代码来创建结构的实例,并从中提取一个指针以(通过 ctypes)给导出但未包装的函数,例如:
from ctypes import *
from test import point
p1 = point()
p2 = point()
p1.x = 123
p1.y = 156
p2.x = 123
p2.y = 156
so = CDLL('./_test.so')
so.easy_mode(int(p1.this), int(p2.this))
print(p1.x)
print(p1.y)
调用这个函数就足够了,而且我们知道返回类型实际上只是在修改p1
我们可以使用该知识并使用它。不过,要摆脱这一点的关键是,调用int(p1.this)
会为您提供 SWIG 对象正在代理的指针的整数表示形式。这就是 ctypes 需要的指针。
不过,让我们把它推进到我们按值传递和返回结构的情况。这要困难得多,因为调用函数的方式取决于结构的大小和成员。它们的类型和顺序很重要。它因建筑而异。它甚至可以根据各种事物在给定架构内变化。幸运的是 ctypes(通过 libffi,如果您以前从未见过它,它本身就是一件有趣的事情)对我们隐藏了所有这些。
所以现在我们的目标缺失函数可以是这样的:
struct point add_them(struct point a, struct point b) {
struct point ret = { a.x + b.x, a.y + b.y };
return ret;
}
问题是,在只有一个不调用它的现有 SWIG 模块的情况下,我们对struct point
. 这对于能够按值调用至关重要。当然,我们可以做出一些猜测,如果它足够简单,只需猜测,那么您也可以这样做并为 ctypes 完成它。
幸运的是,结构的可用 SWIG 包装的存在使我们(如果一些假设成立)足以对结构的类型/布局做出足够好的猜测。此外,由于我们知道如何获取指向结构实例使用的底层内存的指针,我们可以构建测试来向我们展示有关布局的信息。如果一切顺利,我们可以使用它来构建Structure
兼容的字段的 ctypes 定义。
关键是我们要将memset
一个实例设置为 0,然后尝试使用 SWIG 为每个成员生成的设置器代码将每个字节设置为一个类型的标记值。当我们检查我们可以做出我们需要的扣除时。
然而,在我们这样做之前,对结构的大小设置一个上限会很有帮助。我们可以通过调用malloc_useable_size()
which 来得到它,它告诉我们堆分配被四舍五入到什么程度。所以我们可以这样做:
useable_size = CDLL(None).malloc_usable_size
upper_size_bound = useable_size(int(p1.this))
buffer_type = POINTER(c_char * upper_size_bound)
print('Upper size bound is %d' % upper_size_bound)
choose_type = dict([(1, c_uint8), (2, c_uint16), (4, c_uint32)]).get
def generate_members(obj):
for member_name in (x for x in dir(obj) if not x[0] == '_' and x != 'this'):
print('Looking at member: %s' % member_name)
def pos(shift):
test = point()
memset(int(test.this), 0, upper_size_bound)
pattern = 0xff << (8 * shift)
try:
setattr(test, member_name, pattern)
except:
return -1
return bytes(cast(int(test.this), buffer_type).contents).find(b'\xff')
a=[pos(x) for x in range(upper_size_bound)]
offset = min((x for x in a if x >= 0))
size = 1 + max(a) - offset
print('%s is a %d byte type at offset %d' % (member_name, size, offset))
pad = [('pad', c_ubyte * offset)] if (offset > 0) else []
class Member(Structure):
_pack_ = 1
_fields_ = pad + [(member_name, choose_type(size))]
yield Member
这需要 SWIG 知道给定结构的每个成员,并计算一个数组,计算给定字段的每个字节的偏移量,然后计算该成员的偏移量和大小。(使用 min/max 意味着它应该适用于 BE 和 LE 硬件)。我们可以获取大小并将其映射到类型上。鉴于我们现在拥有的知识,我们可以计算出最符合我们所学的布局。不过我作弊了,并且为每个成员生成了一个结构,该结构在开始时添加了填充,以将成员定位在我们计算的偏移量处。上面的 python 代码是一个生成器,它产生一个 ctypes Structure
,它在正确的偏移量处具有正确的大小/类型的成员。
实际上,您需要推断出更多。浮点数可能最好使用已知的可精确表示的值。我们需要考虑有符号和无符号类型。数组、字符串甚至嵌套类型都可以。在试错的基础上,这一切都是可能的,但这留给读者作为练习。
最后我们需要把它拉到一起。因为我欺骗了每个成员的一个结构,所以我们需要做的就是将它们合并到一个联合中:
class YourType(Union):
_pack_ = 1
_fields_ = list(zip(string.ascii_lowercase, generate_members()))
_anonymous_ = string.ascii_lowercase[:len(_fields_)]
有了这个,我们现在有足够的东西来调用我们的add_them
函数:
MyType=YourType
y=MyType()
y.x = 1
y.y = 2
add_them = so.add_them
add_them.argtypes = [MyType, MyType]
add_them.restype = MyType
v=add_them(y,y)
print(v)
print('V: %d,%d' % (v.x, v.y))
仅使用从预先存在的 SWIG 模块派生的信息来调用 ctypes 函数实际上确实有效。
我仍然建议不要在任何真实代码中这样做!
推荐阅读
- sql - 命名动态列 plsql plsql (pivot)
- java - 不显示按钮上的 Java 文本
- ionic-framework - 重新打开浏览器后,ionic 3 storage clear
- android - 如何通过 adb wifi 连接安装应用程序?
- python - 如何调用套接字在函数内部进行连接?
- ios - 如何将选定的问卷选择/答案快速保存为字符串?
- html - 如何使用href方法连接html页面
- postgresql - Prevent out of order inserts into a Postgres database
- sql - 将 Excel 导出到 SQL VBA:找不到存储过程
- gradle - 将项目资源添加到 Gradle 自定义插件