You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Python中传递含自定义数据类型的C结构体调用C共享库

解决方案

ctypes 本身不具备解析 C 头文件 typedef 逻辑、自动适配平台类型的能力,要实现不硬编码基础类型、跨平台兼容的目标,核心思路是不要在 Python 侧重复实现 C 侧的类型适配逻辑,直接复用 C 编译器的适配结果,以下是可落地的实现方式,按可靠性从高到低排序:

  • 编译期自动探测类型映射(最稳妥,适配任意多层typedef、任意平台)
    你不需要手动追溯每个自定义类型最终对应的基础类型,只需要在编译C共享库时,用完全相同的编译参数、宏定义(比如是否开启OFFSET64、32/64位编译选项、对应操作系统的头文件),额外运行一段极简的类型探测代码,直接输出每个自定义类型的长度、有符号/无符号属性、结构体字段偏移量,用这些输出自动生成Python侧的ctypes类型定义即可。
    探测代码示例:
    #include <stdio.h>
    #include <stddef.h>
    // 引入你生产环境实际使用的头文件
    #include "your_prod_header.h"
    
    int main() {
        // 输出每个自定义类型的大小
        printf("SIZEOF_PID_T=%zu\n", sizeof(pid_t));
        printf("SIZEOF_FILESIZE=%zu\n", sizeof(filesize));
        printf("SIZEOF_INODE_T=%zu\n", sizeof(inode_t));
        printf("SIZEOF_TIMEVAL=%zu\n", sizeof(struct timeval));
        // 输出结构体每个字段的偏移,彻底规避对齐问题
        printf("OFFSET_INFO_PID=%zu\n", offsetof(struct info, pid));
        printf("OFFSET_INFO_ATIME=%zu\n", offsetof(struct info, atime));
        printf("OFFSET_INFO_SZ=%zu\n", offsetof(struct info, sz));
        printf("OFFSET_INFO_INUMBER=%zu\n", offsetof(struct info, i_number));
        return 0;
    }
    
    运行这段探测代码拿到输出后,在Python侧按长度匹配对应ctypes类型即可:长度为1用c_byte/c_ubyte,2用c_int16/c_uint16,4用c_int32/c_uint32,8用c_int64/c_uint64。这种方式完全不关心你的typedef嵌套了多少层,也不关心不同操作系统下类型的别名差异,只要和编译正式so用的环境一致,结果就100%准确,不会出现长度不匹配、对齐错误的问题。
  • 标准类型轻量映射(适合类型规则明确的POSIX标准类型)
    如果你用到的是pid_t、struct timeval这类POSIX标准定义的通用类型,不需要额外探测,直接按标准规则映射即可:
    • pid_t 在所有兼容POSIX的系统(Linux/Solaris/AIX)下均为32位有符号整型,直接对应ctypes.c_int32
    • struct timeval 结构固定为两个long类型字段(tv_sec、tv_usec),直接按如下方式定义即可:
      class timeval(ctypes.Structure):
          _fields_ = [
              ("tv_sec", ctypes.c_long),
              ("tv_usec", ctypes.c_long)
          ]
      
    • 对于你代码里受OFFSET64宏控制的filesize类型,可以在加载so时先做一个极小的测试调用:预留一块足够大的内存传入C函数,读取已知固定值(比如示例里C端会给sz赋值512)的位置,反推字段长度,自动切换c_uint32/c_uint64类型,不需要人工判断当前编译环境是否开了64位偏移。
  • 自动生成绑定代码(适合结构体、类型数量多的场景)
    如果你的库包含大量自定义结构体和类型,可以用cffi、ctypeslib这类工具,直接解析C头文件,在安装部署阶段针对当前运行环境自动生成对应的ctypes结构体定义,全程不需要手动维护类型映射,生成的代码和C侧定义完全匹配。
适配示例(对应提供的info结构体场景)
import ctypes
from ctypes import c_int32, c_uint32, c_int64, c_uint64, c_long, c_ulong, c_byte

# 加载共享库
lib = ctypes.CDLL("/root/Documents/python/mylib.so")

# 标准POSIX类型固定映射
class timeval(ctypes.Structure):
    _fields_ = [
        ("tv_sec", c_long),
        ("tv_usec", c_long)
    ]
pid_t = c_int32
inode_t = c_ulong

# 自动探测filesize类型长度,不需要手动判断OFFSET64宏
class TestInfo(ctypes.Structure):
    # 预留128字节足够容纳info结构体,避免溢出
    _fields_ = [("pad", c_byte * 128)]
lib.did_something.argtypes = [ctypes.POINTER(TestInfo)]
test_inst = TestInfo()
lib.did_something(test_inst)
# 已知atime字段是timeval,在pid之后,所以sz的起始偏移是sizeof(pid_t)+sizeof(timeval)
sz_offset = ctypes.sizeof(pid_t) + ctypes.sizeof(timeval)
# 分别按4字节、8字节读取sz位置的值,看哪个等于C端写入的固定值512,确定类型
sz_val_32 = ctypes.cast(ctypes.addressof(test_inst) + sz_offset, ctypes.POINTER(c_uint32)).contents.value
sz_val_64 = ctypes.cast(ctypes.addressof(test_inst) + sz_offset, ctypes.POINTER(c_uint64)).contents.value
filesize = c_uint32 if sz_val_32 == 512 else c_uint64

# 正式定义info结构体
class info(ctypes.Structure):
    _fields_ = [
        ("pid", pid_t),
        ("atime", timeval),
        ("sz", filesize),
        ("i_number", inode_t)
    ]

# 调用测试
if __name__ == "__main__":
    s = info()
    lib.did_something.argtypes = [ctypes.POINTER(info)]
    lib.did_something(s)
    print(f"pid: {s.pid}")
    print(f"atime: {s.atime.tv_sec}.{s.atime.tv_usec}")
    print(f"file size: {s.sz}")
    print(f"inode number: {s.i_number}")

注意:永远不要在Python侧硬编码自定义类型对应的基础类型。只要你手动写死c_int、c_char这类映射,跨架构、跨操作系统时大概率会遇到字段读值错误、内存越界、段错误的问题。让编译器帮你做类型适配,比人工核对多层typedef的逻辑可靠得多。

内容的提问来源于stack exchange,提问作者MankPan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.02 03:01:16