'Unhashable type in python

I am trying to call functions of a C based .dll from python, by creating wrappers.I face a scenario where i need to create a callback in python for a function in that .C dll. In C, The callback function is of type

typedef void (*Log_Callback_t)(void * pDataParams, uint8_t bOption, Log_Entry* pLogEntries, uint16_t wEntryCount);
      

src.py

    # I am adding excerpts of the .py file. Pls indicate if it's incomplete or unclear

    class Log_Entry(Structure):
        _pack_ = 1
        _fields_ = [('bLogType',c_ubyte),
                    ('wDataLen',c_uint16)
                   ]

    logArray = (Log_Entry)()

    def LogCallBack(pDataParams,bOption,pLogEntries,wEntryCount):
             GlobalVars.log.info("In LogCallBack")
 
    # Creating function ptr type 
    callback_type = CFUNCTYPE(None,c_void_p,c_ubyte,pointer(LogEntry),c_uint16)

    # Creating function ptr of LogCallBack 
    callback_ptr = callback_type(LogCallBack)  

    # Passing the created function ptr to python wrapper
    status1 = LogInit (callback_ptr,32)

While running this, i face the below issue with pointer(Log_Entry) i suppose. Can someone pls point me where am i going wrong

callback_type = CFUNCTYPE(None,c_void_p,c_ubyte,pointer(LogEntry),c_uint16)
  File "C:\python\lib\ctypes\__init__.py", line 99, in CFUNCTYPE
    return _c_functype_cache[(restype, argtypes, flags)]
TypeError: unhashable type
Exception TypeError: unhashable type

I referred to already posted questions related to unhashable type, but i did not find related to it, hence posting here. BTW, i tried with byref as well.



Solution 1:[1]

pointer(LogEntry) is an instance of a pointer to an instance of a variable called LogEntry that isn't defined.

POINTER(type) is the type of a pointer to the specified type. Types are needed by .argtypes, so POINTER(Log_Entry) is needed.

Working example:

test.c

#include <stdint.h>

#define API __declspec(dllexport)

#pragma pack(push,1)
typedef struct Log_Entry {
    uint8_t  bLogType;
    uint16_t wDataLen;
} Log_Entry;
#pragma pack(pop)

typedef void (*Log_Callback_t)(void * pDataParams, uint8_t bOption,
              Log_Entry* pLogEntries, uint16_t wEntryCount);

API int LogInit(Log_Callback_t cb, uint8_t num) {
    Log_Entry entry[2];
    entry[0].bLogType = 1;
    entry[0].wDataLen = 2;
    entry[1].bLogType = 3;
    entry[1].wDataLen = 4;
    cb(NULL, num, entry, 2);
    return 6;
}

test.py

from ctypes import *

class Log_Entry(Structure):
    _pack_ = 1
    _fields_ = [('bLogType',c_ubyte),
                ('wDataLen',c_uint16)]
    def __repr__(self):
        return f'Log_Entry(bLogType={self.bLogType}, wDataLen={self.wDataLen})'

CALLBACK = CFUNCTYPE(None, c_void_p, c_ubyte, POINTER(Log_Entry), c_uint8)

dll = CDLL('./test')
LogInit = dll.LogInit
LogInit.argtypes = CALLBACK, c_uint16
LogInit.restype = c_int

@CALLBACK
def LogCallBack(pDataParams, bOption, pLogEntries, wEntryCount):
         print(f'{pDataParams=} {bOption=} {wEntryCount=}')
         for i in range(wEntryCount):
            print(f'LogEntry[{i}]={pLogEntries[i]}')

status1 = LogInit(LogCallBack, 32)

Output:

pDataParams=None bOption=32 wEntryCount=2
LogEntry[0]=Log_Entry(bLogType=1, wDataLen=2)
LogEntry[1]=Log_Entry(bLogType=3, wDataLen=4)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Mark Tolonen