Python ctypes 将 bytes 转换为任意 C 类型

739 阅读2分钟

Python ctypesbytes 转换为任意 C 类型

1. 问题起因

C++ 数组初始化,对于不同类型的数组,可以使用 memset() 来初始化。

#include <cstring>

int a[10001];
double d[10001];

int main() {
    // 初始化为 0
    memset(a, 0, sizeof(a));
    // 初始化为很大的数(0x7f7f7f7f = 2139062143)
    memset(a, 0x7f, sizeof(a));
    // 初始化为很小的数(0xafafafaf = -1347440721)
    memset(a, 0xaf, sizeof(a));

    // 初始化为 0
    memset(d, 0, sizeof(d));
    // 初始化为很大的数(1.38242e+306)
    memset(d, 0x7f, sizeof(d));
}

我们想知道最后赋值出来的结果到底是多少。其实这个问题就是 C 类型的数据底层二进制表示是什么,以及我们修改二进制表示后,C 类型的值是多少。

如果学过计算机原理,int 类型的值很容易计算,但是 floatdouble 很难计算,因为需要应用 IEEE 754 等计算方法,如果不遵循这些标准的数据类型就更难以表示了。

Python 对于 C 类型有完善支持,我们使用 Python 快速将二进制表示和其值进行互相转换。

2. bytes 转换为任意 C 类型

下面的方法实现了 bytes 转换到任意类型的方式。

from ctypes import POINTER, c_char_p, c_double, cast, sizeof
from typing import TypeVar

_T = TypeVar('_T')

def cast_bytes(data_bytes: bytes, ctype: type[_T]) -> _T:
    """Cast bytes to a ctype"""
    assert len(data_bytes) >= sizeof(ctype)
    return cast(
        c_char_p(data_bytes),
        POINTER(ctype)
    ).contents

v = cast_bytes(b'\x7f' * 8, c_double)
print(v.value)

这里使用了 Python 的 泛型注解,可以删去。这样我们就获得了一个 c_double 类型的 v 变量,使用 v.value 很容易获得其值。

以上的代码相当于:

#include <iostream>
using namespace std;

int main() {
    char a[] = "\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f";
    double value = *(double*)a;
    cout << value << endl;
    return 0;
}

如果需要使用 Python 将 C 类型转换为二进制表示,使用 memoryview 即可。