Can't figure out how to pass a structure by pointer to a C function from Python.
Here is what I have (it's a part of bigger effort of implementing nn_recvmsg for nanomsg-python project):
...
msgHdr = NN_MSGHDR(iovecList)
pointer_type = ctypes.POINTER(NN_MSGHDR)
pp = pointer_type.from_address(ctypes.addressof(msgHdr))
print("argument type: "+str(pp))
print("function arguments: "+str(_nn_recvmsg.argtypes))
rtn = _nn_recvmsg(socket, pp, 0)
...
Which gives me:
argument type: <_nanomsg_ctypes.LP_NN_MSGHDR object at 0x10b6d8d40>
function arguments: (<class 'ctypes.c_int'>, <class '_nanomsg_ctypes.LP_NN_MSGHDR'>, <class 'ctypes.c_int'>)
Traceback (most recent call last):
File "./test.py", line 11, in <module>
nnc.nn_recvmsg(s, [4, frameSize])
File "/Users/peetonn/Documents/Work/ptn-nanomsg-python/_nanomsg_ctypes/__init__.py", line 311, in nn_recvmsg
rtn = _nn_recvmsg(socket, pp, 0)
ctypes.ArgumentError: argument 1: <type 'exceptions.TypeError'>: wrong type
from the output, I see that argument type is the same as the function would expect it to be. However, it still fails.
Below are structure definitions and full code for the function I'm implementing:
class NN_IOVEC(ctypes.Structure):
_fields_ = [("iov_base", ctypes.c_void_p),
("iov_len", ctypes.c_size_t)]
class NN_MSGHDR(ctypes.Structure):
_fields_ = [("msg_iov", ctypes.POINTER(NN_IOVEC)), # ctypes.c_void_p),
("msg_iovlen", ctypes.c_int),
("msg_control", ctypes.c_void_p),
("msg_controllen", ctypes.c_size_t)]
def __init__(self, iovecList):
elems = (NN_IOVEC * len(iovecList))()
self.msg_iovlen = len(iovecList)
self.msg_iov = ctypes.cast(elems, ctypes.POINTER(NN_IOVEC))
for i in range(0, len(iovecList)):
self.msg_iov[i].iov_base = iovecList[i].iov_base
self.msg_iov[i].iov_len = iovecList[i].iov_len
self.msg_controllen = 0
self.msg_control = 0
def nn_recvmsg(socket, sizes = None):
"receive message/messages"
if sizes:
iovecList = []
for sz in sizes:
iovec = NN_IOVEC()
iovec.iov_len = sz
buf = (ctypes.c_char * sz)()
iovec.iov_base = ctypes.cast(buf, ctypes.c_void_p)
iovecList.append(iovec)
msgHdr = NN_MSGHDR(iovecList)
pointer_type = ctypes.POINTER(NN_MSGHDR)
pp = pointer_type.from_address(ctypes.addressof(msgHdr))
print("argument type: "+str(pp))
print("function arguments: "+str(_nn_recvmsg.argtypes))
rtn = _nn_recvmsg(socket, ctypes.byref(pp), 0)
print("here's the result: "+str(rtn))
if rtn < 0 :
print(nn_strerror(nn_errno()))
else:
pass # tbd
Related
I want to empirically show that a Red-Black tree has an average height of logn.
Why am I getting an "TypeError: height_rb() takes 2 positional arguments but 3 were given"?
import numpy as np
import pandas as pd
from random import randint
from time import perf_counter
from matplotlib import pyplot as plt
RED, BLACK = 'R', 'B'
# Tnil necessary since code has reference assignments like y.right.p
class Tn:
def __init__(self):
self.p = None
self.color = BLACK
Tnil = Tn()
# All references are assigned to Tnil
class RBNode:
def __init__(self, value):
self.value = value
self.left = Tnil
self.right = Tnil
self.p = None
self.color = None
self.height = None
def height_rb(self, node):
if node is None:
return -1
return max(self.height_rb(node.left), self.height_rb(node.right)) + 1
# For measuring insertion cost of Red-Black tree
def new_rb_val(_n): # So there will be collisions
return randint(0, _n / 10), randint(0, _n)
# Empirical Time Complexity
def measure_cost(n_runs, height_f, node_f):
ds = None
t = []
for n in n_runs:
runs = []
for j in range(10): # reduce the variation of the measurement
ds = None # starting from an empty data structure
st = perf_counter()
for i in range(n):
ds = height_f(ds, *node_f(n))
runs += [perf_counter() - st]
t += [np.mean(runs)]
print('clock: ', ' '.join(['{:g}'.format(v) for v in t]))
# ds dataset can be used for search
return t, ds
N_RUNS = [10, 100, 500, 700, 1000, 2000, 3000, 5000, 7000, 9000, 10000]
t, ds = measure_cost(N_RUNS, height_rb, new_rb_val)
Error:
TypeError: height_rb() takes 2 positional arguments but 3 were given
The full stacktrace (that you did not provided) :
Traceback (most recent call last):
File "C:/PycharmProjects/stack_overflow/68002339.py", line 64, in <module>
t, ds = measure_cost(N_RUNS, height_rb, new_rb_val)
File "C:/PycharmProjects/stack_overflow/68002339.py", line 52, in measure_cost
ds = height_f(ds, *node_f(n))
TypeError: height_rb() takes 2 positional arguments but 3 were given
You call measure_cost(N_RUNS, height_rb, new_rb_val).
On the line ds = height_f(ds, *node_f(n)), height_f is height_rb and you call it with (None, 0, 10) because node_f(n) evaluates to (0, 10).
So indeed you call height_rb() with three parameters.
This is your error, now you have to find how to fix it.
I don't know much about what is actually going on with _fields_ and __init__, so I'm not sure if it's possible or not, but I'm trying to do the something like the following:
class Matrix(Structure):
def __init__(self, dtype, nrow, ncol, data = []):
self._fields_ = [("nrow", c_int), ("ncol", c_int)]
self.dtype = dtype
self.nrow = nrow
self.ncol = ncol
if dtype == float:
self._fields_.append(("data", POINTER(c_double)))
arr_type = c_double * (nrow * ncol)
arr_inst = arr_type(*data)
self.data = arr_inst
The goal is to call functions from a shared library I wrote in C that has the structure:
typedef struct DoubleMatrix DoubleMatrix;
struct DoubleMatrix {
int nrow;
int ncol;
double *data;
};
void func1(DoubleMatrix *mat) {
// stuff
}
but it's not working. When I do:
m = DoubleMatrix(float, 2, 2, [1, 2, 3, 4])
I get
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-28-4e74b99b208f> in <module>
----> 1 m = DoubleMatrix(float, 2, 2, [1, 2, 3, 4])
TypeError: __init__() takes from 3 to 4 positional arguments but 5 were given
The reason I want to do this is because I will have matrices whose entries are of type other than double
Edit
I want to share what has worked but does not have my desired functionality:
from ctypes import *
lib = CDLL("/usr/local/lib/ccalc/libccalc.so")
class DoubleMatrix(Structure):
_fields_ = [("nrow", c_int),
("ncol", c_int),
("data", POINTER(c_double))]
def __init__(self, nrow, ncol, data=[]):
self.nrow = nrow
self.ncol = ncol
arr_type = c_double * (nrow * ncol)
arr_inst = arr_type(*data)
self.data = arr_inst
return
Second Edit
I am sorry everyone, I think I didn't restart kernel of my jupyter notebook properly when probing this. Now the case is that python crashes with the following code:
class DoubleMatrix(Structure):
_fields_ = [("nrow", c_int), ("ncol", c_int)]
def __init__(self, dtype, nrow, ncol, data=[]):
self.dtype = dtype
self.nrow = nrow
self.ncol = ncol
if dtype == float:
self._fields_.append(("data", POINTER(c_double)))
arr_type = c_double * (nrow * ncol)
arr_inst = arr_type(*data)
self.data = arr_inst
return
Maybe This question should be closed/deleted so I can ask it properly? The heart of my question is still the same, how do I take my working code in the first edit, and add the functionality I mentioned. Again I am very sorry.
I am trying to write bytearray into the ctypes c_uint8 buffer of a ctypes structure
class RpRes(LittleEndianStructure):
_pack_ = 1
_fields_ = [
("count", c_uint16),
("buf", c_uint8 * 512)
]
def read_func(req):
res = RpRes()
buf = os.read(req.fd, req.count)
res.buf.from_buffer(buf)
res.count = len(buf)
return res
res.buf.from_buffer(buf)
gives the below error:
AttributeError: 'c_ubyte_Array_512' object has no attribute
'from_buffer'
How can this be accomplished?
This worked for me.
def read_func(req):
res = RpRes()
buf = os.read(req.fd, req.count)
res.buf = (c_uint8 * sizeof(res.buf))(*buf)
res.count = len(buf)
return res
I am writing the deepcopy function using python2.7 for my class. I encountered a weird problem
My code is the following
import copy
from ctypes import *
class Graph (Structure):
_fields_ = [("numVertices", c_ulong),
("numEdges", c_ulong)]
def __init__(self):
self.numVertices = c_ulong(0)
self.numEdges = c_ulong(0)
def __deepcopy__(self,memo={}):
newInstance = Graph()
newInstance.numVertices = c_ulong(self.numVertices.value)
newInstance.numEdges = c_ulong(self.numEdges.value)
return newInstance
graph = Graph()
anotherGraph = copy.deepcopy(graph)
I get the following error:
<ipython-input-46-a0cdaa4ef3f7> in __deepcopy__(self, memo)
9 def __deepcopy__(self,memo={}):
10 newInstance = Graph()
---> 11 newInstance.numVertices = c_ulong(self.numVertices.value)
12 newInstance.numEdges = c_ulong(self.numEdges.value)
13 return newInstance
AttributeError: 'long' object has no attribute 'value'
If you try:
type(graph.numVertices)
The result is long
I declared the numVertices as c_ulong(). Why does it become long?
The type of the fields in the structure is still maintained, but ctypes has some "helpful" conversions when reading the values:
from ctypes import *
class Test(Structure):
_fields_ = [('a',c_ulong),
('b',c_char_p)]
t = Test(1,b'hello')
print(type(t.a),type(t.b))
print(t._fields_)
Output:
<class 'int'> <class 'bytes'>
[('a', <class 'ctypes.c_ulong'>), ('b', <class 'ctypes.c_char_p'>)]
So you can write your code as the following and it will work correctly:
import copy
from ctypes import *
class Graph (Structure):
_fields_ = [("numVertices", c_ulong),
("numEdges", c_ulong)]
def __init__(self):
self.numVertices = 0
self.numEdges = 0
def __deepcopy__(self,memo={}):
newInstance = Graph()
newInstance.numVertices = self.numVertices
newInstance.numEdges = self.numEdges
return newInstance
graph = Graph()
anotherGraph = copy.deepcopy(graph)
You can suppress the conversions by deriving from the classes, but it is usually unnecessary. One use case is when using ctypes to call a function that returns an allocated string. You need to suppress the c_char_p to Python byte string conversion so you can later free the c_char_p.
from ctypes import *
class ulong(c_ulong): pass
class char_p(c_char_p): pass
class Test(Structure):
_fields_ = [('a',ulong),
('b',char_p)]
t = Test(1,b'hello')
print(type(t.a),type(t.b))
print(t.a,t.b)
Output:
<class '__main__.ulong'> <class '__main__.char_p'>
<ulong object at 0x0000000006263748> char_p(b'hello')
The win32 function NetShareDel takes three arguments, LPCWSTR LPCWSTR and DWORD.
So I use the following list for argtypes:
import ctypes as C
C.windll.Netapi32.NetShareDel.argtypes = [LPCWSTR, LPCWSTR, c_int]
C.windll.Netapi32.NetShareDel.restype = c_int
C.windll.Netapi32.NetShareDel(server, shareName, 0)
That works fine, but I can't figure out what to use for NetShareAdd, especialle the byte array for NET_SHARE_INFO struct and the last byref(c_int) argument.
Here's the code:
def Share(server, shareName, dir):
info = SHARE_INFO_2()
STYPE_DISKTREE = 0
info.shi2_netname = shareName
info.shi2_path = dir
info.shi2_type = STYPE_DISKTREE
info.shi2_remark = "Shared: " + time.strftime("%Y%m%d-%H:%M")
info.shi2_max_uses = -1
info.shi2_passwd = ""
info.shi2_current_uses = 0
info.shi2_permissions = 0xFFFFFFFF
i = c_int()
bytearray = buffer(info)[:]
windll.Netapi32.NetShareAdd.argtypes = [LPCWSTR, c_int, ????, ????]
windll.Netapi32.NetShareAdd(server, 2, bytearray, C.byref(i))
What would be the correct argtypes list for NetShareAdd?
Got it working finally
First the line
bytearray = buffer(info)[:]
was changed into byte pointer type
byteptr = C.POINTER(C.wintypes.BYTE)(info)
and then the argtypes and call will become POINTER(BYTE) too of course:
C.windll.Netapi32.NetShareAdd.argtypes = [LPCWSTR, c_int, C.POINTER(C.wintypes.BYTE), C.POINTER(c_int)]
C.windll.Netapi32.NetShareAdd.restype = c_int
windll.Netapi32.NetShareAdd(server, 2, byteptr, C.byref(i))