Python - 将字符串对象转换为ctypes (unsigned char *)

2 投票
1 回答
3907 浏览
提问于 2025-04-17 18:39

我想使用一个DLL文件,里面有一个函数,函数的格式是这样的 -

bool isValid = isImageValid((unsigned char *) buff, const __uint32& buffLen, 
                            int imageW, int imageH, __uint32 requiredSize);

现在,buff需要是我从Python包装器中获取的一个字符串,等价于

pyBuff = open(someimagefile, 'rb').read()

因为pyBuff比较大(大部分情况下),我不想再分配一个新的c uchar数组并进行复制,而是想直接使用原来的缓冲区。基本上,就是抓取pyBuff对象的字符缓冲区,并将其作为ctypes(c_ubyte *)来引用。

我找到的最接近的代码是:

buff = cast(pyBuff, POINTER(c_ubyte))

但我不确定这是不是我想要的类型。无论如何,这个函数给了我以下错误

WindowsError: exception: access violation writing 0x00000000

任何帮助我都会很感激..

另外,有两个简短的问题:

  1. 以下定义是否正确?考虑到我正在参考的这个DLL的现有C包装器中,调用这个函数的函数的格式是:(const char * buff, const __uint32& buffLen)

    byref(c_ulong(len(pyBuff)) 
    
  2. CDLL和WinDLL有什么区别,哪个是正确的选择?

1 个回答

0

如果你手里只有一个Python字符串,并且你确定isImageValid不会改变它的内容,那么你可以把buff这个参数声明为c_char_p类型。这样,缓冲区的内容就不会被复制。

c_char_p_type_代码是'z'。在2.5.4版本的源代码中,设置函数的部分,你可以看到对于一个str对象,它直接使用了底层的ob_sval指针:

1309    if (PyString_Check(value)) {
1310        *(char **)ptr = PyString_AS_STRING(value);
1311        Py_INCREF(value);
1312        return value;

这里的宏定义如下:

#define PyString_AS_STRING(op) (((PyStringObject *)(op))->ob_sval)

不过,如果你能查看源文件,可能会更喜欢把内容读入一个可变的缓冲区。下面是如何将内容读入一个ctypes的c_char数组(在2.5.4中测试过):

首先,创建一个要读取的测试文件:

>>> open('tmp.txt', 'w').write('''\
... This is a test.''')

接着,获取文件大小并用c_uint32类型存储,创建缓冲区,然后读取文件内容:

>>> import os
>>> from ctypes import *
>>> buffLen = c_uint32()
>>> buffLen.value = os.path.getsize('tmp.txt')
>>> buff = (c_char * buffLen.value)()
>>> open('tmp.txt').readinto(buf)
15
>>> buff.value
'This is a test.'

关于你另外两个问题,我会这样设置函数的原型:

lib = CDLL(path_to_dll)
isImageValid = lib.isImageValid
isImageValid.argtypes = c_char_p, POINTER(c_uint32), c_int, c_int, c_uint32
isImageValid.restype = c_bool

根据上面的argtypes定义,ctypes会自动通过引用传递buffLen。你也可以明确使用byref(buffLen)

CDLL是用于cdecl(C声明)调用约定的,在这种情况下,调用者负责清理堆栈。这允许像printf这样的可变参数函数。WinDLL则是用于stdcall约定,在这种情况下,被调用者负责清理堆栈。stdcall主要用于32位的Win32 API,比如Kernel32函数。你可以查看维基百科关于x86调用约定的文章。

撰写回答