casting to Arrays in Python ctypes - python

I'm trying to convert a 16 byte blob of data returned by socket.inet_pton into a ctypes array of unsigned bytes. My data structure looks like this:
class in6_addr(ctypes.Structure):
_fields_ = (("Byte", ctypes.c_ubyte * 16),)
And the blob is just:
data = socket.inet_pton(socket.AF_INET6, "2001::3")
However, these attempts get errors:
sin6 = in6_addr()
# TypeError: expected c_ubyte_Array_16 instance, got str
sin6.Byte = data
# TypeError: cast() argument 2 must be a pointer type, not c_ubyte_Array_16
sin6.Byte = ctypes.cast(data, ctypes.c_ubyte * 16)
# TypeError: incompatible types, LP_c_ubyte instance instead of c_ubyte_Array_16 instance
sin6.Byte = ctypes.cast(data, ctypes.POINTER(ctypes.c_ubyte))
All of the code: http://codepad.org/2cjyVXBA
Any ideas what type I need to cast to?

I might be completely wrong here (and it does seem a bit complex) but this works for me:
sin6.Byte = (ctypes.c_ubyte*16)(*list(bytearray(data)))
I had to convert the data into a list of integers and unpack them for the constructor. There must be an easier way!

Arguably easier:
sin6.Byte = cast(data, ctypes.POINTER(ctypes.c_ubyte * 16)).contents
or:
sin6.Byte = (ctypes.c_ubyte * 16)(*[x for x in data])
Using bytes stream:
import io
io.BytesIO(data).readinto(sin6.Byte)
And since the considered structure contains the single field, the field name can be omited:
sin6 = cast(data, ctypes.POINTER(ctypes.c_ubyte * 16)).contents
sin6 = (ctypes.c_ubyte * 16)(*[x for x in data])
io.BytesIO(data).readinto(sin6)

Related

Type annotation for a simple ctype array

What is the correct way to use a ctype Array in a type annotation?
Note: I have python2 code so I'm using the comment style type annotations
from ctypes import c_int32, Array
# create a new type (array of 100 int32)
MyArray = c_int32 * 100
def silly_func(
my_array, # type: MyArray
index, # type: int
): # type: (...) -> c_int32
return my_array[ index ]
mypy gives me errors about MyArray not being a valid type.
I've tried all sorts of variations of TypeVar, NewType and Array, Array[ c_int32 ], ...,
but can't find something that keeps mypy happy.
What's the trick ???
Array[c_int32] works for me with mypy:
from ctypes import c_int32, Array
# create a new type (array of 100 int32)
MyArray = c_int32 * 100
def silly_func(
my_array, # type: Array[c_int32]
index, # type: int
):
# type: (...) -> int
return my_array[index]
silly_func(MyArray(), 3)

How to convert independent bytes to bytearray?

Iam receiving single bytes via serial and I know, that every 4 of them are a float. F.e. I receive b'\x3c' and b'\xff' and I want it to be b'\x3c\xff'.
What is the best way to convert it?
You can use join() as you do with strings.
byte_1 = b'\x3c'
byte_2 = b'\xff'
joined_bytes = b''.join([byte_1, byte_2]) #b'\x3c\xff'
You can use it along the struct module to obtain your decoded float, be aware it returns a tuple even if it has only one element inside.
import struct
byte_1 = b'\x3c'
byte_2 = b'\xff'
byte_3 = b'\x20'
byte_4 = b'\xff'
joined_bytes = b''.join([byte_1, byte_2, byte_3, byte_4])
result = struct.unpack('f', joined_bytes)
print(result[0])

Convert ctype Array to bytearray

I have a C function that takes a pointer (for output) to the following structure:
class MacFrame(ctypes.Structure):
_fields_ = [("buffer", ctypes.c_ubyte * 128),
("length", ctypes.c_ubyte),
("nodeid", ctypes.c_uint)]
Then, I need to make the following Python function that calls the C function converts the buffer and length field to a bytearray object to return it, but I am unclear on what the syntax should be:
def recv(self, bufsize):
frame = MacFrame()
self.Api.otListenerRead(ctypes.byref(frame))
return bytearray(frame.buffer, frame.length), frame.nodeid
I know the bytearray(frame.buffer, frame.length) is incorrect, but I don't know what the best way is.

Saving XML as ctypes c_byte in variable gives TypeError: an integer is required

In C header file I have :
long TEST_API test (
___OUT_ char DisplayText[41],
_IN____ const char XMLparams[2049]
);
In python code I have imported ctypes and I am trying to call "Test".
class A(Structure):
_fields_ = [("DisplayText", c_byte*41),
("XMLparams",c_byte*2049)
]
XMLparamsVal = (ctypes.c_byte*2049)(["<xml><MatchboxDataProviderValue>Openvez</MatchboxDataProviderValue><AlwaysPrintTwoTicketsFlag>FALSE</AlwaysPrintTwoTicketsFlag><DisplayWidthInCharacters>20</DisplayWidthInCharacters><!-- exclude Ikea-Card and Maestro from PAN truncation --><DontTruncateList>*119*1*</DontTruncateList></xml>"])
my_A = A("", XMLparamsVal)
lib.test(my_A.DisplayText, my_A.XMLparams)
I am gettting this error:
XMLparamsVal = (ctypes.c_byte*2049)(["<xml><MatchboxDataProviderValue>Openvez</MatchboxDataProviderValue><AlwaysPrintTwoTicketsFlag>FALSE</AlwaysPrintTwoTicketsFlag><DisplayWidthInCharacters>20</DisplayWidthInCharacters><!-- exclude Ikea-Card and Maestro from PAN truncation --><DontTruncateList>*119*1*</DontTruncateList></xml>"])
TypeError: an integer is required
How can I fix the issue. Thanks!
A c_byte array takes a variable number of ints as argument, you're trying to give it a list. Try this instead:
xml_bytes = bytearray(b'<xml>...')
XMLparamsVal = (ctypes.c_byte*2049)(*xml_bytes)
*xml_bytes is expanded to a series of positional int arguments.
For python3, you wouldn't need the bytearray, you can use a byte literal directly, as in python3 iterating a byte object yields ints:
XMLparamsVal = (ctypes.c_byte*2049)(*b'<xml>...')
Note that for the first argument of your A class, you'll also have to pass a c_byte_Array_41, not a string.

ctype question char**

I'm trying to figure out why this works after lots and lots of messing about with
obo.librar_version is a c function which requires char ** as the input and does a strcpy
to passed in char.
from ctypes import *
_OBO_C_DLL = 'obo.dll'
STRING = c_char_p
OBO_VERSION = _stdcall_libraries[_OBO_C_DLL].OBO_VERSION
OBO_VERSION.restype = c_int
OBO_VERSION.argtypes = [POINTER(STRING)]
def library_version():
s = create_string_buffer('\000' * 32)
t = cast(s, c_char_p)
res = obo.library_version(byref(t))
if res != 0:
raise Error("OBO error %r" % res)
return t.value, s.raw, s.value
library_version()
The above code returns
('OBO Version 1.0.1', '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', '')
What I don't understand is why 's' does not have any value? Anyone have any ideas? Thx
When you cast s to c_char_p you store a new object in t, not a reference. So when you pass t to your function by reference, s doesn't get updated.
UPDATE:
You are indeed correct:
cast takes two parameters, a ctypes
object that is or can be converted to
a pointer of some kind, and a ctypes
pointer type. It returns an instance
of the second argument, which
references the same memory block as
the first argument.
In order to get a reference to your string buffer, you need to use the following for your cast:
t = cast(s, POINTER(c_char*33))
I have no idea why c_char_p doesn't create a reference where this does, but there you go.
Because library_version requires a char**, they don't want you to allocate the characters (as you're doing with create_string_buffer. Instead, they just want you to pass in a reference to a pointer so they can return the address of where to find the version string.
So all you need to do is allocate the pointer, and then pass in a reference to that pointer.
The following code should work, although I don't have obo.dll (or know of a suitable replacement) to test it.
from ctypes import *
_OBO_C_DLL = 'obo.dll'
STRING = c_char_p
_stdcall_libraries = dict()
_stdcall_libraries[_OBO_C_DLL] = WinDLL(_OBO_C_DLL)
OBO_VERSION = _stdcall_libraries[_OBO_C_DLL].OBO_VERSION
OBO_VERSION.restype = c_int
OBO_VERSION.argtypes = [POINTER(STRING)]
def library_version():
s_res = c_char_p()
res = OBO_VERSION(byref(s_res))
if res != 0:
raise Error("OBO error %r" % res)
return s_res.value
library_version()
[Edit]
I've gone a step further and written my own DLL that implements a possible implementation of OBO_VERSION that does not require an allocated character buffer, and is not subject to any memory leaks.
int OBO_VERSION(char **pp_version)
{
static char result[] = "Version 2.0";
*pp_version = result;
return 0; // success
}
As you can see, OBO_VERSION simply sets the value of *pp_version to a pointer to a null-terminated character array. This is likely how the real OBO_VERSION works. I've tested this against my originally suggested technique above, and it works as prescribed.

Categories

Resources