0

I want to pass a packed array representing a register to a C function (in a DLL) that accepts a char *. This is the code I have so far:

from ctypes import * 
class BitField():
    def __init__(self,position,size):
        self.position = position
        self.size = size

class Reg1(BitField):
    self.dll = "win32.dll"
    self.B6 = BitField(self,58,6)
    self.B5 = BitField(self,54,4)
    self.B4 = BitField(self,48,6)
    self.B3 = BitField(self,36,12)
    self.B2 = BitField(self,24,12)
    self.B1 = BitField(self,16,8)
    self.B0 = BitField(self,0,16)

pack_register(Reg1,(expects a tuple)):
   pass    # created a stub , need help on this   
#I want to pack the to 64 bit data, by padding all other zero's, 
#if none of bit #fields specified by user.

obj = Reg1()
obj.B5 = 4
obj.B4 = 12
obj.B0 = 70
charBuffer = create_string_buffer(64)
packed_value =pack_register(Reg1,(B5,B4,B0)) 
charBuffer  = packed_value
obj.dll.createBuff(charBuffer) # accepting a character pointer

This is the win32.dll function (in C)

int createBuff (char * charBuffer){
   print charBuffer
}

As you can see from the comment in the code, I am not sure how to pack the register into 64 bits. I have 32 bits and 128 bits register as well.

How can I pack a register and output it in a suitable format to pass to my C function that expects a char *?

poke
  • 369,085
  • 72
  • 557
  • 602
Sandy
  • 233
  • 1
  • 2
  • 18
  • 2
    That’s not valid Python syntax, please fix it. – poke Aug 12 '15 at 08:51
  • 1
    Still not valid Python; there is no name `self` on the class level. `BitField` is not an instance or class attribute either, nor does the class take 3 arguments (the `self` in the initialiser method is taken care of by Python). – Martijn Pieters Aug 12 '15 at 08:56
  • And that `pack_register` thing also doesn’t make sense. – poke Aug 12 '15 at 08:56
  • @ Martijn @ poke @ PM 2Ring could you please tell me the way , how to achieve this? – Sandy Aug 12 '15 at 09:07
  • @ Martijn @ poke @ PM 2Ring could you please reply on this? – Sandy Aug 12 '15 at 09:32
  • Do you need to keep using your types (`BitField` and `Reg1`) or not? There are a number of errors that suggest some deeper issues - for instance your lines `obj.Bn =` replace the `BitField` attributes in `obj` with `int`s is that what you intend? Do you really want `Reg1` to be a subclass of `BitField`. There are easier ways to achieve what you're doing, but if it's an exercise in learning about classes etc, let us know that in the question – J Richard Snape Aug 12 '15 at 10:28
  • @Sandy I'm working on some code for your problem, but I want to test it before I post it here. Also, I might think of a better way to do this... – PM 2Ring Aug 12 '15 at 10:53
  • @Sandy Do you want B0 to contain the least significant bits and B6 the most significant bits? Or should it be the other way around? – PM 2Ring Aug 12 '15 at 11:11
  • @ PM ring I want to B0 to contain the least significant bits and B6 the most significant bits. This is the way , to achieve my purpose. – Sandy Aug 12 '15 at 11:49
  • @ J Richard Snape , Yes sure you could suggest me easier ways to achieve this. You're welcome! – Sandy Aug 12 '15 at 11:50
  • There's some interesting info in [Does Python have a bitfield type?](http://stackoverflow.com/q/142812/4014959) – PM 2Ring Aug 12 '15 at 12:14
  • @ PM 2Ring, but there Is no such to specify in import ctypes with this structure self.B6 = BitField(self,58,6) self.B5 = BitField(self,54,4) self.B4 = BitField(self,48,6) self.B3 = BitField(self,36,12) self.B2 = BitField(self,24,12) self.B1 = BitField(self,16,8) self.B0 = BitField(self,0,16) I have to tweak more to bring on ctype list. Isn't it? – Sandy Aug 12 '15 at 12:33
  • 1
    @Sandy: Correct. You can't use that stuff. But I'll post some code in a few minutes that lets you access fields using syntax like `reg.b6 = 3`. However, to pass the register value to the `.dll` you will need to use `ctypes.create_string_buffer()` and `.to_bytes(8, 'big')`, as poke mentions. Note that `.to_bytes()` is a Python 3 method, but it's not too hard to do the conversion in Python 2. – PM 2Ring Aug 12 '15 at 12:47

2 Answers2

5

This code creates a class that lets you read & write bitfields using field names. It wasn't written with ctypes in mind, but you may still find it useful.

#!/usr/bin/env python

""" A bit field class

    See http://stackoverflow.com/q/31960327/4014959

    Written by PM 2Ring 2015.08.12
"""

class BitFields(object):
    """ A bit field class

        fieldwidth is a tuple or list containing the
        bit width of each field, from least significant
        to most significant.
    """
    def __init__(self, totalwidth, fieldwidths):
        if sum(fieldwidths) != totalwidth:
            raise ValueError, "Field width error"

        self.fieldwidths = fieldwidths
        self.num_fields = len(fieldwidths)

        #Calculate field offsets
        self.offsets = []
        pos = 0
        for w in fieldwidths:
            self.offsets.append(pos)
            pos += w

        #Set up bitfield attribute names
        self.field_names = ['b' + str(i) for i in range(self.num_fields)]
        self.clear()

    #Set all fields to zero
    def clear(self):
        for f in self.field_names:
            setattr(self, f, 0)

    #A generator expression of all the field values
    def _all_fields(self):
        return (getattr(self, f) for f in self.field_names)

    def __str__(self):
        return ', '.join(['%s: 0x%x' % (f, v) 
            for f, v in zip(self.field_names, self._all_fields())])

    #Get the register value as an int
    @property
    def value(self):
       return sum(v<<p for v, p in zip(self._all_fields(), self.offsets))

    #Set field values 
    def regset(self, **kwargs):
        for f, v in kwargs.items():
            setattr(self, f, v)

#Test
fields = (16, 8, 12, 12, 6, 4, 6)
reg = BitFields(64, fields)

#Set some fields by attribute
reg.b0 = 10
reg.b1 = 1
reg.b2 = 3

#Print the register using its __str__ method
print reg

#Print a single field
print reg.b1

#Print the current value of the register in decimal and as a hex string
v = reg.value
print v, hex(v)

#Reset all fields to zero
reg.clear()
print reg

#Set some fields by keyword
reg.regset(b0=7, b1=3, b2=1)
print reg

#Set some fields using a dict
field_dict = {'b0':5, 'b3':0xa, 'b4':0xf}
reg.regset(**field_dict)
print reg

output

b0: 0xa, b1: 0x1, b2: 0x3, b3: 0x0, b4: 0x0, b5: 0x0, b6: 0x0
1
50397194 0x301000a
b0: 0x0, b1: 0x0, b2: 0x0, b3: 0x0, b4: 0x0, b5: 0x0, b6: 0x0
b0: 0x7, b1: 0x3, b2: 0x1, b3: 0x0, b4: 0x0, b5: 0x0, b6: 0x0
b0: 0x5, b1: 0x3, b2: 0x1, b3: 0xa, b4: 0xf, b5: 0x0, b6: 0x0

Here's a simple Python 2 to_bytes() function.

def to_bytes(n, width):
    b = bytearray(width)
    for i in range(width-1, -1, -1):
        b[i] = n & 0xff
        n >>= 8
        if n == 0:
            break
    return bytes(b)

n = 0x8182838485868788
print repr(to_bytes(n, 8))

output

'\x81\x82\x83\x84\x85\x86\x87\x88'

Here's a slightly modified version of the class with a new method, .setvalue(), which lets you set the value of the register from an integer. This method is called in the constructor, so you can now pass an optional integer to initialize the register. If no initial value is passed to the constructor then the register is initialized to zero, as before.

class BitFields(object):
    """ A bit field class

        fieldwidth is a tuple or list containing the
        bit width of each field, from least significant
        to most significant.
    """
    def __init__(self, totalwidth, fieldwidths, value=0):
        if sum(fieldwidths) != totalwidth:
            raise ValueError, "Field width error"

        self.fieldwidths = fieldwidths
        self.num_fields = len(fieldwidths)

        #Calculate field offsets
        self.offsets = []
        pos = 0
        for w in fieldwidths:
            self.offsets.append(pos)
            pos += w

        #Set up bitfield attribute names
        self.field_names = ['b' + str(i) for i in range(self.num_fields)]

        #Initialize
        self.setvalue(value)

    #Set all fields to zero
    def clear(self):
        for f in self.field_names:
            setattr(self, f, 0)

    #A generator expression of all the field values
    def _all_fields(self):
        return (getattr(self, f) for f in self.field_names)

    def __str__(self):
        return ', '.join(['%s: 0x%x' % (f, v) 
            for f, v in zip(self.field_names, self._all_fields())])

    #Get the register value as an int
    @property
    def value(self):
       return sum(v<<p for v, p in zip(self._all_fields(), self.offsets))

    #Set field values 
    def regset(self, **kwargs):
        for f, v in kwargs.items():
            setattr(self, f, v)

    #Set the register from an int 
    def setvalue(self, value):
        for f, w in zip(self.field_names, self.fieldwidths):
            #print f, w
            mask = (1<<w) - 1
            v = value & mask
            value >>= w
            setattr(self, f, v)


#Test
fields = (16, 8, 12, 12, 6, 4, 6)
reg = BitFields(64, fields)

#Set some fields by attribute
reg.b0 = 10
reg.b1 = 1
reg.b2 = 3

#Print the register using its __str__ method
print reg

#Print a single field
print reg.b1

#Print the current value of the register in decimal and as a hex string
v = reg.value
print v, hex(v)

#Reset all fields to zero
reg.clear()
print reg

#Set some fields by keyword
reg.regset(b0=7, b1=3, b2=1)
print reg

#Set some fields using a dict
field_dict = {'b0':5, 'b3':0xa, 'b4':0xf}
reg.regset(**field_dict)
print reg

#Set the register from an int or long
n = 0x111133337777ffff
reg = BitFields(64, fields, n)
print reg

v = reg.value
print v, hex(v), n == v

n = 0x123456789abcdef0
reg.setvalue(n)
print reg

v = reg.value
print v, hex(v), n == v

import random

print 'Testing .setvalue()...'
for _ in xrange(50000):
    n = random.randint(0, (1<<64) - 1)
    reg.setvalue(n)
    v = reg.value
    assert v == n, (n, v)
print 'OK'

output

b0: 0xa, b1: 0x1, b2: 0x3, b3: 0x0, b4: 0x0, b5: 0x0, b6: 0x0
1
50397194 0x301000a
b0: 0x0, b1: 0x0, b2: 0x0, b3: 0x0, b4: 0x0, b5: 0x0, b6: 0x0
b0: 0x7, b1: 0x3, b2: 0x1, b3: 0x0, b4: 0x0, b5: 0x0, b6: 0x0
b0: 0x5, b1: 0x3, b2: 0x1, b3: 0xa, b4: 0xf, b5: 0x0, b6: 0x0
b0: 0xffff, b1: 0x77, b2: 0x377, b3: 0x333, b4: 0x11, b5: 0x4, b6: 0x4
1229820469389557759 0x111133337777ffffL True
b0: 0xdef0, b1: 0xbc, b2: 0x89a, b3: 0x567, b4: 0x34, b5: 0x8, b6: 0x4
1311768467463790320 0x123456789abcdef0L True
Testing .setvalue()...
OK
PM 2Ring
  • 54,345
  • 6
  • 82
  • 182
  • @ PM 2Ring Excellent. Thanks for code, which is creating all the values on the dynamically by user input. Good to know this. My intention is to fix all register values to be fixed. Few of mine class Reg2(BitField): self.dll = "win32.dll" self.val = BitField(self,58,6) self.clk = BitField(self,54,4) self.dat = BitField(self,48,6) self.mi = BitField(self,36,12) self.so = BitField(self,24,12) self.si = BitField(self,16,8) self.mo = BitField(self,0,16) But m grateful to know this. Nice work. Thanks – Sandy Aug 12 '15 at 13:17
  • @ PM 2Ring ctypes.create_string_buffer(r.pack(70, 0, 0, 12, 4, 0, 0).to_bytes(8, 'big')) is giving AttributeError: 'long' object has no attribute 'to_bytes', long also int type but don't know why it is throwing error – Sandy Aug 12 '15 at 14:33
  • @Sandy: As I said earlier, `.to_bytes()` is a Python 3 method. It doesn't exist in Python 2. If you are using Python 2.6 or 2.7 you can use the `to_bytes()` function I've added to my answer. But if you're using a _really_ old version of Python then you won't have the `bytearray()` function, and you'll have to do the conversion a slightly different way, using the `chr()` function. – PM 2Ring Aug 12 '15 at 14:57
  • @ PM ring requesting you to see the recently edited post. – Sandy Aug 13 '15 at 12:43
  • @Sandy: You should **not** change your question substantially after it has already received answers. On Stack Exchange we want questions and answers to be useful for future readers. But changing your question after it's been answered sabotages that process. So ask a new question, and in the new question you can put a link to this one if you think it will help people understand your new problem. – PM 2Ring Aug 13 '15 at 13:00
  • @ PM ring, reg = BitFields(64, fields, 64 bit input) , I want to give 64 bit input in the constructor and should go to the corresponding field, could you pls tell me how can I achieve with existing code. Thanks in advance – Sandy Aug 26 '15 at 15:15
  • @Sandy: Ok, that's a reasonable request. :) I've written some code, but I want to test it properly. I'll post it tomorrow. – PM 2Ring Aug 26 '15 at 15:50
  • @ PM @Ring Thanks a lot!! – Sandy Aug 26 '15 at 15:53
  • @Sandy: I hope the new `.setvalue()` method does what you want. – PM 2Ring Aug 27 '15 at 01:50
4

You could create a register that is able to perform the conversion of multiple fields into a single int (which you can then convert to a byte string using int.to_bytes). For example like this:

class Register:
    def __init__ (self, *sizes):
        self.sizes = sizes

    def pack (self, *fields):
        offset = 0
        result = 0
        for value, size in zip(fields, self.sizes):
            result |= self.trimSize(value, size) << offset
            offset += size
        return result

    def unpack (self, value):
        offset = 0
        fields = []
        for size in self.sizes:
            fields.append(self.trimSize(value, size))
            value >>= size
        return fields

    def trimSize (self, value, size):
        return value & ((1 << size) - 1)

You can use it like this:

>>> r = Register(16, 8, 12, 12, 6, 4, 6)
>>> r.pack(70, 0, 0, 12, 4, 0, 0)
1126724540563526
>>> _.to_bytes(8, 'big')
b'\x00\x04\x00\xc0\x00\x00\x00F'

You can also unpack the value again:

>>> r.unpack(1126724540563526)
[70, 0, 0, 12, 4, 0, 0]

You could now create a new type that has multiple properties for your register fields and uses that Register internally:

class Reg1:
    def __init__ (self):
        self.r = Register(16, 8, 12, 12, 6, 4, 6)
        # default values
        self.B0 = self.B1 = self.B2 = self.B3 = self.B4 = self.B5 = self.B6 = 0
    def pack (self):
        return self.r.pack(self.B0, self.B1, self.B2, self.B3, self.B4, self.B5, self.B6)
    def unpack (self, value):
        self.B0, self.B1, self.B2, self.B3, self.B4, self.B5, self.B6 = self.r.unpack(value)

Used like this:

>>> obj = Reg1()
>>> obj.B5 = 4
>>> obj.B4 = 12
>>> obj.B0 = 70
>>> obj.pack()
75435293758455878
poke
  • 369,085
  • 72
  • 557
  • 602
  • Thanks Poke, I have few queries. How could I do corresponding bit filed should goto corresponding array. In the sense B3,B4,B5 if I call in function , it should accept only B3,B4,B5 rest all fill with 0. How can make sure that in method definition, it accepting only corresponding bit field rest all fill with zeros? – Sandy Aug 12 '15 at 09:25
  • Thanks Poke I really appreciate your answer, its real a useful. I have one more query, I want to pass packed array to c dll function it is accepting char pointer. charBuffer = create_string_buffer(64) packed_value =pack_register(Reg1,(B5,B4,B0)) charBuffer = packed_value obj.dll.createBuff(charBuffer) # accepting a character pointer , Is this the right approach? – Sandy Aug 12 '15 at 11:48
  • @Sandy I’m not sure why you use your `pack_register` there again; but if you wanted to create a string buffer from the register value, you could do `ctypes.create_string_buffer(r.pack(…).to_bytes(8, 'big'))` – poke Aug 12 '15 at 12:02
  • @ Poke after packing ctypes.create_string_buffer(r.pack(…).to_bytes(64, 'big')) is it right?since I am packing 64 bit – Sandy Aug 12 '15 at 12:14
  • 2
    The size is in bytes, so you need to specify 8 (8 bytes = 64 bits). – poke Aug 12 '15 at 12:17
  • ctypes.create_string_buffer(r.pack(70, 0, 0, 12, 4, 0, 0).to_bytes(8, 'big')) is giving AttributeError: 'long' object has no attribute 'to_bytes', long also int type but don't know why it is throwing. – Sandy Aug 12 '15 at 14:32
  • In Python 2, there is no `to_bytes` method on int/long objects. You can use the `to_bytes` function in PM 2Ring’s answer to emulate the behavior. You will have to do `ctypes.create_string_buffer(to_bytes(r.pack(…), 8))` then. – poke Aug 12 '15 at 15:51
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/86875/discussion-between-sandy-and-poke). – Sandy Aug 13 '15 at 11:27
  • obj = Reg1() obj.B4 = 0xf obj.B3 = 0xa obj.B0 = 5 count = obj.pack() print count print obj.unpack(count) After unpacking it is printing None, and also requesting you to see my recently edited post – Sandy Aug 13 '15 at 12:39
  • 2
    `obj.unpack` does not *return* the values, it sets the values in the object. Also, please **do not** update your question with your ongoing tries to solve your problem as it greatly invalidates the existing answers. Please post a new question instead. – poke Aug 13 '15 at 12:51