关于python:区分“基本” ctypes数据类型及其子类?

Distinguish between “fundamental” ctypes data types and subclasses thereof?

介绍

我正在使用一个代码生成器,它将围绕ctypes.cdll加载的函数生成函数。生成器将获取有关参数ctypes和返回值的信息,并生成如下行为(在某种程度上看起来是这样):

1
2
3
4
5
6
func = getattr(dll, 'UglyLongAndUselessCName')
func.argtypes = [ctypes.c_uint32, ctypes.c_int8, ctypes.c_char_p]
func.restype = ctypes.c_int16

def nice_python_name(handle: int, arg1: int, arg2: str) -> int:
    return func(handle, arg1, arg2)

请注意python类型注释如何与函数参数的ctypes数据类型很好地配合使用。另请注意,nice_python_name函数和func函数中的python类型之间没有转换代码。这就是我的问题。

接近问题

ctypes文档说,如果使用"基本数据类型"指定了已加载DLL函数的argtypes属性,则在调用已加载的DLL函数时,ctypes将为您转换为python类型。很好,因为在这种情况下,我生成的代码将类似于上面的示例-我不需要将ctypes对象显式转换为返回值的python类型的值,而将参数相反。

但是,文档还说,对于"基本数据类型的子类",此技巧将不起作用,调用加载的DLL函数将需要ctypes对象作为参数,结果将是ctypes对象。

这是ctypes文档的摘录:

Fundamental data types, when returned as foreign function call results, or, for example, by retrieving structure field members or array items, are transparently converted to native Python types. In other words, if a foreign function has a restype of c_char_p, you will always receive a Python bytes object, not a c_char_p instance.

Subclasses of fundamental data types do not inherit this behavior. So, if a foreign functions restype is a subclass of c_void_p, you will receive an instance of this subclass from the function call. Of course, you can get the value of the pointer by accessing the value attribute.

所以,我想解决这个问题。

似乎我需要知道类型是"基本"还是"子类"。这将帮助我定义代码的生成方式,即,对于"基本"类型,所生成的代码将类似于上述示例,对于"基本"类型的子类,它将具有从ctypes对象到合理对象的额外转换。 python类型(否则生成器将抛出一个异常,说明"不支持此功能")。

题:

如何区分"基本ctypes数据类型"和"基本ctypes数据类型的子类"?

我查看了ctypes python模块的代码,发现c_void_pc_char_p都是ctypes._SimpleCData的子类,因此无论如何都不是另一个的子类。

另外,我是否会误解为这个问题也适用于输入参数,还是这仅适用于返回值?


... do I misunderstand it that this problem also applies to the input arguments, or is this only the deal for the returned values?

它不适用于输入参数,如以下序列所示:

1
2
3
4
5
6
7
8
9
10
>>> dll=CDLL('msvcrt')
>>> dll.printf.argtypes = c_char_p,
>>> dll.printf(b'abc') # Note: 3 is the return value of printf
abc3
>>> class LPCSTR(c_char_p): # define a subtype
...  pass
...
>>> dll.printf.argtypes = LPCSTR,
>>> dll.printf(b'abc')
abc3

转换对于输入子类型仍然有效; 但是,输出子类型的工作方式与您提到的文档引号不同:

1
2
3
4
5
6
7
8
9
10
11
12
>>> dll.ctime.argtypes = c_void_p,
>>> dll.ctime.restype = c_char_p
>>> dll.ctime(byref(c_int(5)))
b'Wed Dec 31 16:00:05 1969
'

>>> dll.ctime.restype = LPCSTR
>>> dll.ctime(byref(c_int(5))) # not converted to Python byte string
LPCSTR(1989707373328)
>>> x = dll.ctime(byref(c_int(5))) # but can get the value
>>> x.value
b'Wed Dec 31 16:00:05 1969
'