Python ctypes: copying Structure's contents

橙三吉。 提交于 2020-01-01 04:46:15

问题


I want to mimic a piece of C code in Python with ctypes, the code is something like:

typedef struct {
  int x;
  int y;
} point;

void copy_point(point *a, point *b) {
  *a = *b;
}

in ctypes it's not possible to do the following:

from ctypes import *

class Point(Structure):
  _fields_ = [("x", c_int),("y", c_int)]

def copy_point(a, b):
  a.contents = b.contents

p0 = pointer(Point())
p1 = pointer(Point())
copy_point(p0,p1)

as the contents still is a Python ctypes Structure object, that is managed as a reference itself.

An obvious workaround would be to manually copy each field (that is represented as immutable python int's), but that doesn't scale with more complex structures. Also, it would need to be done recursively for fields that are not basic, but structured types.

My other option is to use memmove and copy the objects as if they were buffers, but that seems very error prone (as Python is dynamically typed it would be too easy to use it with objects of distinct type and size, leading to memory corruption or segmentation faults)...

Any suggestions?

Edit:

I could also use a fresh new copy of the structure, so maybe this could be useful:

import copy
p0 = Point()
p1 = copy.deepcopy(p0) #or just a shallow copy for this example

but I don't know if there might be some kind of bizarre behaviours copying ctypes proxys as if they were regular Python objects...


回答1:


You can use sequence assignment to copy the pointed-to objects (rather than assigning to p.contents, which changes the pointer value):

def copy(dst, src):
    """Copies the contents of src to dst"""
    pointer(dst)[0] = src

# alternately
def new_copy(src):
    """Returns a new ctypes object which is a bitwise copy of an existing one"""
    dst = type(src)()
    pointer(dst)[0] = src
    return dst

# or if using pointers
def ptr_copy(dst_ptr, src_ptr):
    dst_ptr[0] = src_ptr[0]

ctypes will do type checking for you (which isn't fool-proof, but it's better than nothing).

Example of use, with verification that it does in fact work ;):

>>> o1 = Point(1, 1)
>>> o2 = Point(2, 2)
>>> print (o1.x, o1.y, addressof(o1)), (o2.x, o2.y, addressof(o2))
(1, 1, 6474004) (2, 2, 6473524)
>>> copy(o2, o1)
>>> pprint (o1.x, o1.y, addressof(o1)), (o2.x, o2.y, addressof(o2))
(1, 1, 6474004) (1, 1, 6473524)

>>> o1 = Point(1, 1), o2 = Point(2, 2)
>>> print (o1.x, o1.y, addressof(o1)), (o2.x, o2.y, addressof(o2))
(1, 1, 6473844) (2, 2, 6473684)
>>> p1, p2 = pointer(o1), pointer(o2)
>>> addressof(p1.contents), addressof(p2.contents)
(6473844, 6473684)
>>> ptr_copy(p1, p2)
>>> print (o1.x, o1.y, addressof(o1)), (o2.x, o2.y, addressof(o2))
(2, 2, 6473844) (2, 2, 6473684)
>>> addressof(p1.contents), addressof(p2.contents)
(6473844, 6473684)



回答2:


memmove is the correct operation here. By setting the argtypes of your CopyPoint function, you can easily enforce type-safety.

from ctypes import *

class Point(Structure):
    _fields_ = [("x", c_int), ("y", c_int)]
    def __str__(self):
        return "<Point: x=%d, y=%d, addr=%ld>" % (self.x, self.y, addressof(self))

def CopyPoint(a, b):
    memmove(a, b, sizeof(Point))
CopyPoint.argtypes = [POINTER(Point), POINTER(Point)]

pt0 = Point(x=0, y=10)
pt1 = Point(x=5, y=7)

print pt0, pt1

CopyPoint(byref(pt0), byref(pt1))
print pt0, pt1    

try:
    CopyPoint(byref(pt0), Point(x=2, y=3))
except ArgumentError as e:
    print "Could not copy!", e

outputs:

$ python ct.py 
<Point: x=0, y=10, addr=3083711192> <Point: x=5, y=7, addr=3083711120>
<Point: x=5, y=7, addr=3083711192> <Point: x=5, y=7, addr=3083711120>
Could not copy! argument 2: <type 'exceptions.TypeError'>: wrong type

Note that you could easily make a factory to generate this kind of function at run-time based on a specific type, if you need to generalize:

def CopierFactory(typ):
    def f(a,b):
        memmove(a,b, sizeof(typ))
    f.argtypes = [POINTER(typ), POINTER(typ)]

    return f

copy_point = CopierFactory(Point)

a = Point(x=1, y=2)
b = Point(x=-1, y=-1)
print a, b
copy_point(byref(a), byref(b))
print a, b

output:

<Point: x=1, y=2, addr=3085088024> <Point: x=-1, y=-1, addr=3085087952>
<Point: x=-1, y=-1, addr=3085088024> <Point: x=-1, y=-1, addr=3085087952>



回答3:


Pointer operations as rule are not very memory safe. I would create wrapper classes for each struct data type you are interested in and let them handle the pointer copy operations. Pretty much like you are doing here. There are lambda and map functions which you can use recursively as syntactic sugar.




回答4:


I'm now also thinking about defining a method like:

def safe_copy(dst, src):
  if type(src) != type(dst) or not isinstance(src, Structure):
    raise Exception("wrong types")
  memmove(addressof(dst), addressof(src), sizeof(src))

But there might be still nicer options out there...




回答5:


In python 3x,your code can run correctly. shown below:

>>> from ctypes import *
>>> class Point(Structure):
...   _fields_ = [("x", c_int),("y", c_int)]
>>> def copy_point(a, b):
...   a.contents = b.contents
>>> p0 = pointer(Point())
>>> p1 = pointer(Point(1,2))
>>> p0.contents.x
0
>>> copy_point(p0,p1)
>>> p0.contents.x
1


来源:https://stackoverflow.com/questions/1470343/python-ctypes-copying-structures-contents

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!