ctypes技术问询:安全返回Python函数对象且不改变指针地址
Alright, let's tackle this problem step by step. The core issue here is that your buffer variable in the func function is a local object—once the function finishes running, Python's garbage collector will clean it up, leaving the data pointer in your G struct pointing to freed memory (a dangling pointer). To fix this, we need to tie the buffer's lifecycle to the G instance so they stick around together.
The Fix: Bind the Buffer to the G Instance
We'll add a regular Python attribute to the G class to hold a reference to the buffer. This ensures the buffer won't be garbage-collected as long as the G instance exists, keeping the data pointer valid and its address unchanged.
Here's the revised code:
import ctypes class G(ctypes.Structure): _fields_ = [("data", ctypes.POINTER(ctypes.c_ubyte)), ("length", ctypes.c_int)] def __init__(self, length): self.length = length # Create the buffer and store a reference to it in the instance self._buffer = (ctypes.c_ubyte * length)() # Cast the buffer to the required pointer type once—address stays fixed self.data = ctypes.cast(self._buffer, ctypes.POINTER(ctypes.c_ubyte)) def func(filename): g = G(500) # Read file content directly into the buffer tied to the G instance with open(filename, 'rb') as f: f.readinto(g._buffer) return g h = func('your_file_path_here')
Key Explanations
- Buffer Lifecycle Binding: The
_bufferattribute is a regular Python attribute (not part of the ctypes_fields_, since ctypes doesn't need to interact with it). As long as theGinstance (hin your example) exists,_buffermaintains a reference to the memory block, so Python won't free it. - Fixed Pointer Address: We cast the buffer to the
datapointer once inG's__init__. The buffer's memory address never changes, sodatawill always point to the same valid location. - No Local Buffer Risk: We no longer use a local
buffervariable infunc—instead, we use the buffer attached directly to theGinstance, eliminating the chance of it being cleaned up when the function returns.
If you prefer not to modify the G class's __init__ method, you can alternatively attach the buffer in the func function like this:
def func(filename): g = G(500) # Attach the buffer to the G instance to preserve it g._buffer = (ctypes.c_ubyte * g.length)() with open(filename, 'rb') as f: f.readinto(g._buffer) g.data = ctypes.cast(g._buffer, ctypes.POINTER(ctypes.c_ubyte)) return g
The core principle here is simple: any memory pointed to by a ctypes pointer must have a corresponding Python object holding a reference to it—otherwise, Python will free that memory out from under you, leading to crashes or undefined behavior.
内容的提问来源于stack exchange,提问作者jtlz2

