Nessy W.
Nessy W.

Reputation: 151

How to interpret a integer as a float

I am trying to use a DLL written in C# to communicate with a laser. I successfully load the DLL function using the ctypes modules. The function I want to use have a declaration which look like this :

LONG LJV7IF_GetStorageData( LONG lDeviceId, 
                            LJV7IF_GET_STORAGE_REQ* pReq,
                            LJV7IF_STORAGE_INFO* pStorageInfo,
                            LJV7IF_GET_STORAGE_RSP* pRsp, 
                            DWORD* pdwData, 
                            DWORD dwDataSize );

I want to access the data via the dword pointer pdwData. The laser send its storaged data via a structure which looks like this:

Bytes |  Meaning  |   Types
 0-3  |   time    |    dword          
  4   |  judgment |    byte
  5   | meas info |    byte
 ...  |    ...    |    ... 
 8-11 |   data    |    float

This is how i use the function :

self.dll = WinDLL( "LJV7_IF.dll" )
self._getStoredData = self.dll.LJV7IF_GetStorageData
self._getStoredData.restype = c_int32
self._getStoredData.argstypes = [ c_int32,
                                  POINTER( GET_STORAGE_REQ ),
                                  POINTER( STORAGE_INFO ),
                                  POINTER( GET_STORAGE_RSP ),
                                  POINTER( c_uint32 ),
                                  c_uint32 ]
dataSize = 132
dataBuffer = c_uint32 * ( dataSize / 4 )
outputData_p = POINTER( c_uint32 )( dataBuffer() )
self._getStoredData( deviceID,
                     byref( myStruct ),
                     byref( storageInfo ),
                     byref( storageResponse ),
                     outputData_p ),
                     dataSize )

myStruct, storageInfo and storageResponse are not detailed for brevity (they are used in other DLL functions calls, and they seem to work just fine).

My problem is when I try to access outputData_p[ 2 ], python return me an int, say 1066192077. Which is exactly what I asked him. But I want that int to be interpreted/converted as a float, which is supposed to be 1.1 or something like this (can't remember the exact value ). Casting it to float doesn't work ( I get 1066192077.00 ), using hex() -> bytes() -> struct.unpack( ) either. What can I do ??

Upvotes: 5

Views: 3438

Answers (1)

Eryk Sun
Eryk Sun

Reputation: 34270

Note: if you arrived here while searching for how to convert an integer to a single-precision float, then ignore the rest of this answer and just use the code from J.F. Sebastian's comment. It uses the struct module instead of ctypes, which is simpler and also always available, while ctypes is optionally included in Python's standard library:

import struct

def float_from_integer(integer):
    return struct.unpack('!f', struct.pack('!I', integer))[0]

assert float_from_integer(1066192077) == 1.100000023841858

You can interpret an array as a different type using the ctypes from_buffer method. In general you can pass any object to this method that has a writeable buffer interface, not just ctypes instances -- such as bytearray or a NumPy array.

For example:

>>> from ctypes import *
>>> int_array = (c_int * 4)(1, 2, 3, 4)
>>> n_doubles = sizeof(int_array) // sizeof(c_double)
>>> array_t = c_double * n_doubles
>>> double_array = array_t.from_buffer(int_array)

It's still the same bytes, just reinterpreted as two 8-byte doubles:

>>> bytes(double_array)
b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00'
>>> double_array[:]
[4.2439915824e-314, 8.4879831653e-314]

Since this array was created by calling from_buffer, as opposed to from_buffer_copy, it's actually a view that shares the same buffer as the original array. For example, if you shift the last 2 integers to the front, the values in double_array are swapped as well:

>>> int_array[:] = [3, 4, 1, 2]
>>> double_array[:]
[8.4879831653e-314, 4.2439915824e-314]

Note that your example code has a typo. The attribute name that defines a function's argument types is argtypes, not argstypes:

self._getStoredData.argstypes = [ c_int32,
                    ^^^^^^^^^

Defining this prototype isn't strictly necessary, but it is recommended. It makes ctypes call the corresponding from_param method for each each argument when the function is called. Without a prototype, the default argument handling accepts ctypes instances and automatically converts strings to char * or wchar_t * and integers to C int values; otherwise it raises an ArgumentError.

You can define the packed (i.e. without alignment padding) data record as follows:

class LaserData(Structure):
    _pack_ = 1
    _fields_ = (('time',      c_uint),
                ('judgement', c_byte),
                ('meas_info', c_byte * 3),
                ('data',      c_float))

Here's an example class that defines the generic data parameter type as POINTER(c_byte) and returns the result as an array of records as determined by the device instance. Obviously it's only the gist of how the class should actually be defined since I know almost nothing about this API.

class LJV7IF(object):
    # loading the DLL and defining prototypes should be done
    # only once, so we do this in the class (or module) definition.
    _dll = WinDLL("LJV7_IF")
    _dll.LJV7IF_Initialize()

    _dll.LJV7IF_GetStorageData.restype = c_long
    _dll.LJV7IF_GetStorageData.argtypes = (c_long,
                                           POINTER(GET_STORAGE_REQ),
                                           POINTER(STORAGE_INFO),
                                           POINTER(GET_STORAGE_RSP),
                                           POINTER(c_byte),
                                           c_uint)

    def __init__(self, device_id, record_type):
        self.device_id = device_id
        self.record_type = record_type

    def get_storage_data(self, count):
        storage_req = GET_STORAGE_REQ()
        storage_info = STORAGE_INFO()
        storage_rsp = GET_STORAGE_RSP()
        data_size = sizeof(self.record_type) * count
        data = (c_byte * data_size)()
        result = self._dll.LJV7IF_GetStorageData(self.device_id,
                                                 byref(storage_req),
                                                 byref(storage_info),
                                                 byref(storage_rsp),
                                                 data,
                                                 data_size)
        if result < 0: # assume negative means an error.
            raise DeviceError(self.device_id) # an Exception subclass.
        return (self.record_type * count).from_buffer(data)

For example:

if __name__ == '__main__':
    laser = LJV7IF(LASER_DEVICE_ID, LaserData)
    for record in laser.get_storage_data(11):
        print(record.data)

Upvotes: 5

Related Questions