【问题标题】:Using ctypes, how can I pass a Python 'bytes' value to a C function that expects an unsigned char pointer?使用 ctypes,如何将 Python 'bytes' 值传递给需要 unsigned char 指针的 C 函数?
【发布时间】:2021-05-25 17:32:29
【问题描述】:

我有一个 C 库,其中包含一个函数,其中包括一个 (const unsigned char *) 类型参数,我想为此编写一个基于 ctypes 的 Python 绑定。

在 Python 版本中,我希望能够传递“字节”值。

直接传递“字节”值是行不通的。我得到一个 ctypes.ArgumentError:

ctypes.ArgumentError: argument 6: <class 'TypeError'>: expected LP_c_ubyte instance instead of bytes

我可以通过使用 ctypes.cast() 方法将 'bytes' 值转换为 unsigned char * 来使其工作。这似乎是显而易见的事情,效果很好,但是当我在我的代码上运行 mypy 时,我收到以下错误:

source/pydwf/core/api/DigitalCanAPI.py:65: error: Argument 1 to "cast" has incompatible type "bytes"; expected "Union[_CData, _CArgObject, int]"
Found 1 error in 1 file (checked 52 source files)

那么,有没有一种方法可以同时满足 Python(所以它可以工作)和 mypy(所以它对演员阵容感到满意?)

这可能是 mypy 中的错误吗?

【问题讨论】:

    标签: python casting ctypes mypy


    【解决方案1】:

    您可以直接构造一个c_ubyte 数组,也可以通过c_char_p() 实例小心地对其进行强制转换以使mypy 警告静音:

    test.c

    #include <stdio.h>
    
    #ifdef _WIN32
    #   define API __declspec(dllexport)
    #else
    #   define API
    #endif
    
    // dump data as hexadecimal
    API void func(unsigned char* data, size_t length) {
        for(size_t i = 0; i < length; ++i)
            printf("%02x",data[i]);
        printf("\n");
    }
    

    test.py

    from ctypes import *
    
    dll = CDLL('./test')
    dll.func.argtypes = POINTER(c_ubyte), c_size_t
    dll.func.restype = None
    
    # demo passing data that might not be nul-terminated
    data = b'\x11\x22\x33\x00\x44\x55\x66'
    dll.func(cast(c_char_p(data),POINTER(c_ubyte)), len(data))
    
    bdata = (c_ubyte * len(data))(*data)
    dll.func(bdata,len(data))
    

    输出:

    11223300445566
    11223300445566
    

    脚本通过mypy:

    C:\>mypy test.py
    Success: no issues found in 1 source file
    

    【讨论】:

    • 我特别喜欢双重情况的想法,但至少在我的 mypy 版本(0.812,这是最新的 afaik)上,我仍然收到警告。你用的是什么版本?开始在我看来越来越像一个 mypy 错误。
    【解决方案2】:

    你的演员表错了,因为char != unsigned char。如果您真正的意思是 unsigned char,那么您不能使用所有 char 辅助方法。在定义要签名的 char 的设置上,您会遇到奇怪的错误。但是,如果您对您的 implementation/c 编译器总是将 char 定义为 unsigned char 感到高兴,那么就不要理会u_byte。只需根据需要将 arg 类型和返回类型定义为 c_char 或 c_char_p。

    如果您需要坚持使用 u_byte,那么这是正确的,并且可以保持 mypy happ(无需演员表)。

    import ctypes.util
    
    # or whatever to get some library function
    libc_path = ctypes.util.find_library('msvcrt')
    libc = ctypes.WinDLL(libc_path)
    
    # snprintf actually takes char, but this should be fine for most setups
    snprintf = libc._snprintf
    snprintf.argtypes = (
        ctypes.POINTER(ctypes.c_ubyte),
        ctypes.c_size_t,
        ctypes.POINTER(ctypes.c_ubyte),
        ctypes.c_int, # white lie about the number of arguments snprintf takes
    )
    snprintf.restype = ctypes.c_int
    
    
    def to_ubytes(bytes_):
        # nul terminate the string just to be safe
        u_bytes = (ctypes.c_ubyte * (len(bytes_) + 1))()
        u_bytes[:-1] = bytes_
        u_bytes[-1] = 0
        return u_bytes
    
    buffer = (ctypes.c_ubyte * 20)()
    format_ = to_ubytes(b'hello %i\n')
    
    result = snprintf(buffer, len(buffer), format_, 1234)
    
    if result >= 0:
        result_bytes = bytes(buffer[:result])
        print('result is:', result_bytes)
    else:
        print('snprintf encountered an error')
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-07-15
      • 2017-01-22
      • 1970-01-01
      • 1970-01-01
      • 2011-05-22
      • 1970-01-01
      • 2020-11-04
      • 1970-01-01
      相关资源
      最近更新 更多