从Python2.7迁移至Python3.5:C数据类型适配问题咨询
Great question—moving from Python 2.7 to 3.5 with ctypes often trips people up because of key differences in string handling and type matching, especially on embedded systems like the Raspberry Pi. Let’s break down the issues in your code and how to fix them, plus actionable ways to debug your C library calls.
Critical Code Changes for Python 3.5
Most of your problems likely stem from Python 3’s strict separation between Unicode strings (str) and byte strings (bytes), which behaves very differently from Python 2’s unified str type. Here’s what to adjust:
1. Replace String Buffers with ctypes.create_string_buffer
In Python 2, " "*N creates a byte string compatible with C’s char* buffers. In Python 3, this creates a Unicode string, which ctypes can’t safely pass to C libraries for writing—this is almost certainly causing your initialization failures.
Before (Python 2):
self.DeviceNameBuffer = " "*DeviceNameBuffer
After (Python 3):
self.DeviceNameBuffer = ctypes.create_string_buffer(DeviceNameBuffer)
Do this for all your string buffers: DeviceTypeBuffer, ChannelName, ValText, cChanUnit, and status_text_buffer. This creates a writable, C-compatible byte array that the yasdi library can write to correctly. When you need to read the buffer later, decode it to a Python string with .value.decode('utf-8').
2. Use ctypes Arrays Instead of array.array
Your current code uses array.array("L") for device/channel handles, but this can lead to type mismatches in Python 3, especially on ARM architectures like the Pi. Ctypes has native array support that guarantees alignment with C types:
Before:
self.DeviceHandles = array.array("L",[0]*self.iDeviceHandleCount)
After:
self.DeviceHandles = (ctypes.c_ulong * self.iDeviceHandleCount)()
This creates an array of c_ulong values (matching C’s unsigned long) that’s directly compatible with the yasdi library’s expected handle types. You can remove the array import entirely after this change.
3. Explicitly Define Function Argument & Return Types
Ctypes defaults to loose type conversion, which can hide mismatches between Python and C types. Explicitly declaring argtypes and restype for each C function will catch errors early and ensure correct behavior:
Add this right after loading the library in __init__:
# Define argument types for yasdiMasterInitialize (match C function signature!) self.yasdiMaster.yasdiMasterInitialize.argtypes = [ctypes.c_char_p, ctypes.POINTER(ctypes.c_ulong)] # Set return type (adjust based on yasdi's docs—most C libs return int for error codes) self.yasdiMaster.yasdiMasterInitialize.restype = ctypes.c_int
Also, when passing the ini file path, encode it to bytes since C expects const char*:
ini_file_bytes = self.ini_file.encode('utf-8')
4. Always Check C Function Return Values
Your current code doesn’t check if yasdiMasterInitialize succeeded—most C libraries return an error code (0 for success, non-zero for failure). Add error handling to catch initialization issues:
result = self.yasdiMaster.yasdiMasterInitialize(ini_file_bytes, self.pDriverCount) if result != 0: raise RuntimeError(f"yasdiMasterInitialize failed with code {result}. Check yasdi.ini path and permissions.")
How to Debug C Library Calls
If you’re still having issues, use these tools to diagnose what’s going wrong:
Use
straceon Raspberry Pi: This tool tracks system calls made by your script and the C library. Run:strace -f python3 your_script.pyLook for errors like
ENOENT(file not found) for youryasdi.ini, or permission errors when accessing serial ports.Inspect Buffer Contents: After calling C functions, check if buffers are being populated. For example:
print("Device Name:", self.DeviceNameBuffer.value.decode('utf-8').strip())If it’s still empty, the library isn’t writing to it—confirm your buffer sizes match what the library expects.
Verify Library Architecture: Ensure
libyasdimaster.sois compiled for your Pi’s architecture (32-bit vs 64-bit). Run:file libyasdimaster.soCompare with your Python’s architecture:
python3 -c "import sys; print('64-bit' if sys.maxsize > 2**32 else '32-bit')"
Modified Code Snippet
Here’s the updated YasdiMaster class with all critical fixes applied:
import ctypes class YasdiMaster: def __init__(self, ini_file="./yasdi.ini", yasdiMaster_lib="libyasdimaster.so", iDeviceHandleCount=50, iChannelHandleCount=142, DeviceNameBuffer=30, DeviceTypeBuffer=30, ValText=15, ChannelName=30, cChanUnit=10, status_text_buffer=30): self.ini_file = ini_file self.yasdiMaster_lib = yasdiMaster_lib # Driver count setup self.DriverCount = ctypes.c_ulong() self.pDriverCount = ctypes.pointer(self.DriverCount) # Device/Channel handles (ctypes arrays instead of array.array) self.iDeviceHandleCount = iDeviceHandleCount self.DeviceHandles = (ctypes.c_ulong * self.iDeviceHandleCount)() self.iChannelHandleCount = iChannelHandleCount self.ChannelHandles = (ctypes.c_ulong * self.iChannelHandleCount)() # Writable C-compatible string buffers self.DeviceNameBuffer = ctypes.create_string_buffer(DeviceNameBuffer) self.DeviceTypeBuffer = ctypes.create_string_buffer(DeviceTypeBuffer) self.ChannelName = ctypes.create_string_buffer(ChannelName) self.ValText = ctypes.create_string_buffer(ValText) self.cChanUnit = ctypes.create_string_buffer(cChanUnit) self.status_text_buffer = ctypes.create_string_buffer(status_text_buffer) # Other C types self.SNBuffer = ctypes.c_ulong() self.pSNBuffer = ctypes.pointer(self.SNBuffer) self.dDevHandle = ctypes.c_ulong() self.pdDevHandle = ctypes.pointer(self.dDevHandle) self.dblValue = ctypes.c_double(0) self.pdblValue = ctypes.pointer(self.dblValue) self.ChanType = ctypes.c_ushort() self.pChanType = ctypes.pointer(self.ChanType) self.ChanIndex = ctypes.c_int() self.pChanIndex = ctypes.pointer(self.ChanIndex) self.range_min = ctypes.c_double() self.prange_min = ctypes.pointer(self.range_min) self.range_max = ctypes.c_double() self.prange_max = ctypes.pointer(self.range_max) # Load library and define function types self.yasdiMaster = ctypes.CDLL(self.yasdiMaster_lib) self.yasdiMaster.yasdiMasterInitialize.argtypes = [ctypes.c_char_p, ctypes.POINTER(ctypes.c_ulong)] self.yasdiMaster.yasdiMasterInitialize.restype = ctypes.c_int def yasdiMasterInitialize(self): ini_file_bytes = self.ini_file.encode('utf-8') result = self.yasdiMaster.yasdiMasterInitialize(ini_file_bytes, self.pDriverCount) if result != 0: raise RuntimeError(f"yasdiMasterInitialize failed with error code {result}. " f"Check if {self.ini_file} exists and has correct permissions.") print(f"Yasdi initialized successfully. Found {self.DriverCount.value} drivers.")
Final Raspberry Pi Tips
- Serial Port Permissions: Yasdi likely uses serial ports—add your user to the
dialoutgroup to access them:
Log out and back in for this to take effect.sudo usermod -aG dialout $USER - Library Path: If
libyasdimaster.soisn’t in a standard directory, set the library path before running your script:export LD_LIBRARY_PATH=/path/to/library/directory:$LD_LIBRARY_PATH python3 your_script.py
内容的提问来源于stack exchange,提问作者Eckhard M

