Linux下Python ctypes传递结构体到C++共享库结果异常求助
问题描述
使用Python的ctypes调用Ubuntu下的C++共享库时,传递结构体参数出现数据异常,Windows下测试正常。相关代码及现象如下:
C++侧代码
test.h
#include <stdint.h> #pragma pack (push, 1) typedef struct { uint8_t a; int b; }param; typedef struct { int a; bool b; }intbool; #pragma pack (pop) extern "C" void set_struct_test(param params);
test.cpp
#include "test.h" #include <stdio.h> void set_struct_test(param params) { printf("size:[%d]\n", sizeof(params)); uint8_t* pdata = (uint8_t*)¶ms; for (int i = 0; i < sizeof(params); i++) { printf("data[%d]:[%x]\n", i, pdata[i]); } printf("%s: %d %d\n", __FUNCTION__,params.a, params.b); }
C++测试代码(main.cpp)
#include "HelloWorld/api.h" #include <iostream> using namespace std; int main() { param test_param; test_param.a = 8; test_param.b = 4; set_struct_test(test_param); return 0; }
C++测试输出(正确):
size:[5] data[0]:[8] data[1]:[4] data[2]:[0] data[3]:[0] data[4]:[0] set_struct_test: 8 4
Python侧代码(pytest.py)
from ctypes import * class pyparam(Structure): _pack_ = 1 _fields_ = [("a", c_uint8), ("b", c_int32)] class TestLib(): def __init__(self): libpath = "xxx/HelloWorldLib.so" self.dll = cdll.LoadLibrary(libpath) def pyset_struct_test(self,params): ptr = pointer(params) raw_bytes = string_at(ptr, sizeof(params)) print("test: ",raw_bytes) return self.dll.set_struct_test(params) helloworld = TestLib() param = pyparam() param.a = 8 param.b = 4 helloworld.pyset_struct_test(param)
Python测试输出(异常):
test: b'\x08\x04\x00\x00\x00' size:[5] data[0]:[6] data[1]:[0] data[2]:[0] data[3]:[0] data[4]:[0] set_struct_test: 6 0
关键现象
- 仅Ubuntu系统出现异常,Windows下正常
- Python侧打印的结构体原始字节正确,但C++侧接收到的数据完全错误
问题原因
Linux(x86-64)采用System V AMD64 ABI调用约定,Windows采用Microsoft x64 ABI,两者对结构体作为函数参数的传递规则存在差异:
- System V ABI中,大小<=16字节的结构体优先通过寄存器传递;你的
param结构体大小为5字节,符合该规则,但ctypes默认未明确指定函数参数类型和调用约定,导致参数传递时的寄存器映射出错。 - Windows下的ABI规则与ctypes默认调用方式匹配,因此无异常。
- 未显式指定函数的参数类型和返回类型,ctypes的默认类型推断在跨平台场景下易引发兼容性问题。
解决方案
方案1:显式指定函数参数与返回类型
修改Python代码,明确告知ctypes函数的参数类型和返回类型,使其匹配System V ABI的传递规则:
from ctypes import * class pyparam(Structure): _pack_ = 1 _fields_ = [("a", c_uint8), ("b", c_int32)] class TestLib(): def __init__(self): libpath = "xxx/HelloWorldLib.so" self.dll = cdll.LoadLibrary(libpath) # 显式指定函数参数类型和返回类型 self.dll.set_struct_test.argtypes = [pyparam] self.dll.set_struct_test.restype = None def pyset_struct_test(self,params): ptr = pointer(params) raw_bytes = string_at(ptr, sizeof(params)) print("test: ",raw_bytes) return self.dll.set_struct_test(params) helloworld = TestLib() param = pyparam() param.a = 8 param.b = 4 helloworld.pyset_struct_test(param)
方案2:改用指针传递结构体(规避ABI差异)
如果方案1仍有问题,可修改C++函数通过指针接收结构体,避免值传递的ABI差异:
修改C++代码
// test.h extern "C" void set_struct_test(const param* params); // test.cpp void set_struct_test(const param* params) { printf("size:[%d]\n", sizeof(*params)); uint8_t* pdata = (uint8_t*)params; for (int i = 0; i < sizeof(*params); i++) { printf("data[%d]:[%x]\n", i, pdata[i]); } printf("%s: %d %d\n", __FUNCTION__,params->a, params->b); }
修改Python对应部分
# 在TestLib的__init__中更新参数类型 self.dll.set_struct_test.argtypes = [POINTER(pyparam)] # 调用时传递结构体指针 return self.dll.set_struct_test(byref(params))
内容的提问来源于stack exchange,提问作者刘鑫鹏Peter
相关产品推荐
相关产品推荐

