Updating an LP_c_ubyte buffer created in a C DLL - python

I am creating a Python wrapper for a C DLL using Python ctypes.
In the Python code below I am creating a array connectionString of c_ubyte that I need to fill int the individual. For example 1,2,3,4,5,6... This connection string is passed to the DLL's DoCallBack function and printed. A buffer is created for the callback function to fill in and everything is passed to the python call back function.
I am looking for a way to update the connectionString bytes before passing them to the DLL's DoCallBack.
Then how to extract the bytes from the connectionString in the python callbackFnk function.
I am looking for a way to update the bytes in outBuffer from the callbackFnk python function
A continuation of this question
In python how do I set the value of a LP_c_ubyte
C DLL Code
typedef void(*FPCallback)(unsigned char * outBuffer, unsigned short MaxOutBufferLength, unsigned char * connectionString);
FPCallback g_Callback;
extern "C" __declspec( dllexport ) void RegisterCallback(void(*p_Callback)( unsigned char * outBuffer, unsigned short MaxOutBufferLength, unsigned char * connectionString)) {
g_Callback = p_Callback ;
}
extern "C" __declspec( dllexport ) void DoCallBack( unsigned char connectionString) {
printf( "connectionString=[%02x %02x %02x %02x %02x %02x...]\n", connectionString[0], connectionString[1], connectionString[2], connectionString[3], connectionString[4], connectionString[5] );
const unsigned short MAX_BUFFER_SIZE = 6 ;
unsigned char outBuffer[MAX_BUFFER_SIZE];
g_Callback( outBuffer, MAX_BUFFER_SIZE, connectionString, 6 );
// Print the results.
printf( "buffer=[%02x %02x %02x %02x %02x %02x...]\n", buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5] );
}
Python code
def callbackFnk( outBuffer, outBufferMaxSize, connectionString )
# (Q2) How do I extract individual bytes of the connectionString?
# (Q3) How do I update individual bytes of the out buffer?
customDLL = cdll.LoadLibrary ("customeDLL.dll")
# RegisterCallback
CustomDLLCallbackFUNC = CFUNCTYPE(None, POINTER( c_ubyte), c_ushort, POINTER( c_ubyte) )
CustomDLLCallback_func = CustomDLLCallbackFUNC( callbackFnk )
RegisterCallback = customDLL.RegisterCallback
RegisterCallback.argtypes = [ CustomDLLCallbackFUNC ]
RegisterCallback( CustomDLLCallback_func )
# DoCallBack
DoCallBack = customDLL.DoCallBack
DoCallBack.argtypes = [ POINTER( c_ubyte) ]
connectionString = c_ubyte(6)
# (Q1) How do I update this array of bytes?
# Call the callback
DoCallBack(connectionString)

The OP's example has a number of errors and doesn't compile, so I put this together. I assume connectionString is just a nul-terminated input string, and demonstrate updating the output string in the callback.
Note with an input string, c_char_p can be the type and a Python byte string can be passed. c_wchar_p is used for Python Unicode strings. The string must not be modified in the C code. The callback will receive it as a Python string as well, making it easy to read.
The output buffer can just be indexed, being careful to not index past the length of the buffer. Output buffers allocated by the caller should always be passed as a pointer-and-length.
C++ DLL
#include <stdio.h>
typedef void (*CALLBACK)(const char* string, unsigned char* buffer, size_t size);
CALLBACK g_pCallback;
extern "C" __declspec(dllexport) void RegisterCallback(CALLBACK pCallback) {
g_pCallback = pCallback;
}
extern "C" __declspec(dllexport) void DoCallBack(char* string) {
unsigned char buf[6];
printf("string = %s\n", string);
g_pCallback(string, buf, sizeof(buf));
printf("buf = [%02x %02x %02x %02x %02x %02x]\n", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]);
}
Python
from ctypes import *
CALLBACK = CFUNCTYPE(None,c_char_p,POINTER(c_ubyte),c_size_t)
#CALLBACK
def callback(string,buf,length):
print(string)
for i in range(length):
buf[i] = i * 2
dll = CDLL('test')
# RegisterCallback
RegisterCallback = dll.RegisterCallback
RegisterCallback.argtypes = [CALLBACK]
RegisterCallback.restype = None
RegisterCallback(callback)
# DoCallBack
DoCallBack = dll.DoCallBack
DoCallBack.argtypes = [c_char_p]
DoCallBack.restype = None
DoCallBack(b'test string')
Output
string = test string
b'test string'
buf = [00 02 04 06 08 0a]

Related

How does ctypes define the class for C structure with function pointer only and init the variable in python?

I'm working on the python with ctypes to call the c so file, but the c file define the structure with function pointer
// mem ==================================================================
typedef struct StdMemFunc
{
void* (*const malloc) (unsigned long size);
void (*const free) (void* ptr);
void* (*const realloc) (void* ptr, unsigned long size);
void* (*const calloc) (unsigned long count, unsigned long size);
void* (*const set) (void* ptr, int value, unsigned long num);
void* (*const copy) (void* dest, const void* src, unsigned long num);
}*StdMemFunc;
typedef struct StdLib
{
const uint32_t version;
bool (*const is_version_compatible) (uint32_t version, uint32_t func_mask);
void (*const delay) (int32_t milli_sec);
const StdMemFunc mem;
}*StdLib;
and mock the function in another file as below
void *std_malloc(unsigned long size)
{
return malloc(size);
}
void std_free(void *ptr)
{
free(ptr);
}
void *std_realloc(void *ptr, unsigned long size)
{
return realloc(ptr, size);
}
void *std_calloc(unsigned long count, unsigned long size)
{
return calloc(count, size);
}
void *std_memset(void *ptr, int value, unsigned long num)
{
return memset(ptr, value, num);
}
void *std_memcopy(void *dest, const void *src, unsigned long num)
{
return memcpy(dest, src, num);
}
struct StdMemFunc mem_func =
{
.malloc = std_malloc,
.free = std_free,
.realloc = std_realloc,
.calloc = std_calloc,
.set = std_memset,
.copy = std_memcopy
};
then the python need to call another method with std_lib as paramater, the std_lib with call mem->malloc() method in C part, so how to define the class in the python with ctypes?
I have tried the below one, but it was not work
class StdMemFunc(Structure):
_fields_ = [
("malloc", ctypes.CFUNCTYPE(c_void_p, c_ulong)),
("free", ctypes.CFUNCTYPE(None, c_void_p)),
("realloc", ctypes.CFUNCTYPE(c_void_p, c_void_p, c_ulong)),
("calloc", ctypes.CFUNCTYPE(c_void_p, c_ulong, c_ulong)),
("set", ctypes.CFUNCTYPE(c_void_p, c_void_p, c_int, c_ulong)),
("copy", ctypes.CFUNCTYPE(c_void_p, c_void_p, c_ulong))
]
class StdLib(Structure):
_fields_ = [
("version", c_uint32),
("is_version_compatible", c_bool),
("delay", c_void_p),
("mem", POINTER(StdMemFunc)),
]
libc_std_lib = CDLL('/home/linus/code/galileo/mock_std_lib.so')
std_lib = StdLib()
std_lib.mem.malloc = libc_std_lib.std_malloc
libc_modbus.modbus_create_server_station.argtypes = [POINTER(ModbusNodeDef), c_int, StdLib, PlcDrvAccessor]
libc_modbus.modbus_create_server_station.restype = POINTER(ModbusStation)
libc_modbus.modbus_create_server_station(node_def, node_num, std_lib, plc_drv_accessor)
It looks like there are two problems here:
The is_version_compatible and delay fields in the StdLib struct are functions, but you are defining them as constants.
You are not instantiating all the fields in the struct, meaning that the program might be trying to dereference a null pointer, as null pointers are the default value for pointer types.
The StdLib struct definition should look something like this:
class StdLib(Structure):
_fields_ = [
("version", c_uint32),
("is_version_compatible", CFUNCTYPE(POINTER(c_bool), c_uint32, _uint32)),
("delay", CFUNCTYPE(c_void_p, c_int32)),
("mem", POINTER(StdMemFunc)),
]
For the instantiation, I would do something like this:
libc_std_lib = CDLL('/home/linus/code/galileo/mock_std_lib.so')
std_mem_func = StdMemFunc(
libc_std_lib.std_malloc,
libc_std_lib.std_free,
libc_std_lib.std_realloc,
libc_std_lib.std_calloc,
libc_std_lib.std_set,
libc_std_lib.std_copy
)
std_lib = StdLib(
1,
reference_to_is_version_compatible_func,
reference_to_delay_func,
std_mem_func
)
Of course, you need to pass the correct params/function references to StdLib. Maybe you will need to mock the is_version_compatible and delay functions as well.
Disclaimer: this is entirely untested, so I don't guarantee it will work.
The OP's code isn't quite reproducible, but I was able to get the same error message on the following line:
std_lib.mem.malloc = libc_std_lib.std_malloc
If I am following correctly, the OP wants to initialize the C structure with functions that are provided in C, but libc.std_lib.std_malloc isn't wrapped properly to do that. It is a function that wraps a C function that is callable from Python, not C.
ctypes function prototypes can be instantiated a number of ways, and the one that works is:
prototype(func_spec[, paramflags])
Returns a foreign function exported by a shared library. func_spec must be a 2-tuple
(name_or_ordinal, library). The first item is the name of the exported
function as string, or the ordinal of the exported function as small
integer. The second item is the shared library instance.
For example:
std_lib.mem.malloc = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_ulong)(('std_malloc',libc_std_lib))
Here's a working set of files:
test.cpp
#include <stdlib.h>
#include <stdint.h>
#include <memory.h>
#include <stdio.h>
#ifdef _WIN32
# define API __declspec(dllexport)
#else
# define API
#endif
extern "C" {
typedef struct StdMemFunc {
void* (*const malloc)(unsigned long size);
void (*const free)(void* ptr);
void* (*const realloc)(void* ptr, unsigned long size);
void* (*const calloc)(unsigned long count, unsigned long size);
void* (*const set)(void* ptr, int value, unsigned long num);
void* (*const copy)(void* dest, const void* src, unsigned long num);
} *StdMemFuncPtr;
typedef struct StdLib {
const uint32_t version;
bool (*const is_version_compatible)(uint32_t version, uint32_t func_mask);
void (*const delay)(int32_t milli_sec);
const StdMemFunc mem;
} *StdLibPtr;
API void* std_malloc(unsigned long size) {
return malloc(size);
}
API void std_free(void* ptr) {
free(ptr);
}
API void* std_realloc(void* ptr, unsigned long size) {
return realloc(ptr, size);
}
API void* std_calloc(unsigned long count, unsigned long size) {
return calloc(count, size);
}
API void* std_memset(void* ptr, int value, unsigned long num) {
return memset(ptr, value, num);
}
API void* std_memcopy(void* dest, const void* src, unsigned long num) {
return memcpy(dest, src, num);
}
// A couple of test functions that accepts the initialized structure
// and calls sum of the function pointers.
API char* testit(StdLib* test) {
// This is how I debugged this, by comparing the *actual*
// function pointer value to the one received from Python.
// Once they matched the code worked.
printf("%p %p\n", std_malloc, test->mem.malloc);
char* p = static_cast<char*>(test->mem.malloc(10));
test->mem.set(p, 'A', 9);
p[9] = 0;
return p;
}
API void freeit(StdLib* test, char* p) {
test->mem.free(p);
}
}
test.py
import ctypes as ct
# prototypes
MALLOC = ct.CFUNCTYPE(ct.c_void_p,ct.c_ulong)
FREE = ct.CFUNCTYPE(None,ct.c_void_p)
REALLOC = ct.CFUNCTYPE(ct.c_void_p, ct.c_void_p, ct.c_ulong)
CALLOC = ct.CFUNCTYPE(ct.c_void_p, ct.c_ulong, ct.c_ulong)
SET = ct.CFUNCTYPE(ct.c_void_p,ct.c_void_p,ct.c_int,ct.c_ulong)
COPY = ct.CFUNCTYPE(ct.c_void_p, ct.c_void_p, ct.c_ulong)
class StdMemFunc(ct.Structure):
_fields_ = [("malloc", MALLOC),
("free", FREE),
("realloc", REALLOC),
("calloc", CALLOC),
("set", SET),
("copy", COPY)]
class StdLib(ct.Structure):
_fields_ = [("version", ct.c_uint32),
# Note these two fields were function pointers as well.
# Declared correctly now.
("is_version_compatible", ct.CFUNCTYPE(ct.c_bool, ct.c_uint32, ct.c_uint32)),
("delay", ct.CFUNCTYPE(None, ct.c_int32)),
("mem", StdMemFunc)]
dll = ct.CDLL('./test')
dll.testit.argtypes = ct.POINTER(StdLib),
dll.testit.restype = ct.POINTER(ct.c_char)
dll.freeit.argtypes = ct.POINTER(StdLib), ct.c_char_p
dll.freeit.restype = None
lib = StdLib()
lib.mem.malloc = MALLOC(('std_malloc', dll))
lib.mem.realloc = REALLOC(('std_realloc', dll))
lib.mem.calloc = CALLOC(('std_calloc', dll))
lib.mem.free = FREE(('std_free', dll))
lib.mem.set = SET(('std_memset', dll))
lib.mem.copy = COPY(('std_memcopy', dll))
p = dll.testit(lib)
# One way to access the data in the returned pointer is to slice it to the known length
print(p[:10])
# If known to be null-terminated, can also cast to c_char_p, which expects
# null-terminated data, and extract the value.
print(ct.cast(p,ct.c_char_p).value)
dll.freeit(lib,p)
Output:
b'AAAAAAAAA\x00'
b'AAAAAAAAA'

Loading c DLL with python, problems with pointer

Currently I have C++ loading DLL. I need replace C++ code with python. My problems are:
in callback function device_ID1_callback, all values seems empty, i'm guessing i did not use pointer correctly.
after call device_get_info, all values are 0, I suppose get some values none zero back.
I have tried anything I can think of for weeks but with very little luck.
To simplified the problem, here's partial of my code. Thanks for your time and help!!
in my lib.h file, i have
typedef unsigned int DeviceHandler;
typedef struct {
unsigned int fpga_version;
}DeviceInfo_t;
typedef struct {
unsigned int check_id;
float distance[256];
}MeasureResult_t;
DLLEPXORT int EXCALL device_add(DeviceHandler* outHandler, char* device_ip, MeasureModeCallback callback);
DLLEPXORT void EXCALL device_get_info(DeviceHandler handler, DeviceInfo_t* p_device_info);
in sample C++ file:
"""
void device_ID1_callback(const void *out,unsigned int out_num){
MeasureResult_t *ptr = (MeasureResult_t *)out;
printf("[ChechID:0x%x] %d pack's data\n",ptr[0].check_id,out_num);
}
void demo_callback_mode(){
int ret;
DeviceHandler device_handler;
DeviceInfo_t device_info;
ret = device_add(&device_handler,"192.168.1.2",&device_ID1_callback);
device_get_info(device_handler,&device_info);
printf("[FPGA] version : %d\n", device_info.fpga_version);
}
"""
*end of c++ *
Here's my python code:
"""
import ctypes as c
class MeasureResult_t(c.Structure):
_fields_ = [
('check_id', c.c_int),
('distance[256]', c.c_float)]
class DeviceInfo_t(c.Structure):
_fields_ = [
('fpga_version', c.c_int)
]
def device_ID1_callback(out, out_num):
print("---enter device call back function---")
print(dir(out))
print("out: ",out.contents)
#print(dir(out))
print("out_num:",out_num)
print("---exit device call back function---\n\n")
return 0
_dev = c.CDLL("./OPSensor/osp_lidar")
T_device_handler = c.c_int
T_device_handler_ptr = c.POINTER(T_device_handler)
_dev.device_add.argtypes = [T_device_handler_ptr, c.c_char_p]
_dev.device_add.restype = c.c_int
device_handler = c.c_int()
ip_val = c.c_char_p("192.168.1.2".encode('utf-8'))
out = MeasureResult_t()
out_num = c.c_int()
CMPFUNC_t = c.CFUNCTYPE(None, c.POINTER(MeasureResult_t), c.c_int)
MeasureModeCallback = CMPFUNC_t(device_ID1_callback)
ret = _dev.device_add(c.byref(device_handler), (ip_val), MeasureModeCallback(c.byref(out), out_num))
_dev.device_get_info.argtypes = [T_device_handler_ptr, c.POINTER(DeviceInfo_t)]
_dev.device_get_info.restype = c.c_void_p # assume it returns C int
p_device_info = DeviceInfo_t()
#_dev.device_get_info(c.byref(device_handler), c.byref(p_device_info)) # does not work
_dev.device_get_info((device_handler), c.byref(p_device_info)) #does not work either
print(device_handler) # I have correct device_handler value
print(p_device_info.fpga_version) # the value i got is 0, does seem right
"""
Here's my attempt at a minimal reproducible example. I implemented dummy functions that demonstrate the callback you described:
// lib.c
#define DLLEXPORT __declspec(dllexport)
#define EXCALL
typedef unsigned int DeviceHandler;
typedef struct {
unsigned int fpga_version;
} DeviceInfo_t;
typedef struct {
unsigned int check_id;
float distance[256];
} MeasureResult_t;
typedef void (*MeasureModeCallback)(MeasureResult_t*, unsigned int);
DLLEXPORT int EXCALL device_add(DeviceHandler* outHandler, char* device_ip, MeasureModeCallback callback) {
*outHandler = 123; // dummy device ID
MeasureResult_t m; // some fake measurement results
m.check_id = 456;
for(int i = 0; i < 256; ++i)
m.distance[i] = (float)(i * .25);
callback(&m, 789); // call the callback
return 1;
}
DLLEXPORT void EXCALL device_get_info(DeviceHandler handler, DeviceInfo_t* p_device_info) {
p_device_info->fpga_version = handler * 2; // fake fpga version
}
To create a callback in Python, assign the CFUNCTYPE prototype to the callback type, decorate the callback function with that type, and use that type in the callback argument definition, and the actual function name when passing it as an argument.
Also note the that float distance[256] is declared in Python as c.c_float * 256 to create an array and the differences in the .argtypes/.restype attributes for the functions. device_get_info takes a DeviceHandler, not a c.POINTER(DeviceHandler) for example.
# test.py
import ctypes as c
class MeasureResult_t(c.Structure):
_fields_ = (('check_id', c.c_uint),
('distance', c.c_float * 256))
def __repr__(self): # defines how to display this class
return f'MeasureResult_t(check_id={self.check_id}, distance=[{self.distance[0]}, ..., {self.distance[255]}])'
class DeviceInfo_t(c.Structure):
_fields_ = ('fpga_version', c.c_uint),
def __repr__(self): # defines how to display this class
return f'DeviceInfo_t(fpga_version={self.fpga_version})'
# Declare the callback type
MeasureModeCallback = c.CFUNCTYPE(None, c.POINTER(MeasureResult_t), c.c_uint)
DeviceHandler = c.c_uint
# apply the decorator so this function can be called from C
#MeasureModeCallback
def device_ID1_callback(out, out_num):
print('---enter device call back function---')
print('out: ',out.contents)
print('out_num:',out_num)
print('---exit device call back function---')
_dev = c.CDLL('./lib')
# Use the argument type
_dev.device_add.argtypes = c.POINTER(DeviceHandler), c.c_char_p, MeasureModeCallback
_dev.device_add.restype = c.c_int
_dev.device_get_info.argtypes = DeviceHandler, c.POINTER(DeviceInfo_t)
_dev.device_get_info.restype = None
device_handler = DeviceHandler()
ip_val = b'192.168.1.2'
# Use the callback function name when calling the function
ret = _dev.device_add(c.byref(device_handler), ip_val, device_ID1_callback)
device_info = DeviceInfo_t()
_dev.device_get_info(device_handler, c.byref(device_info))
print(f'{device_handler.value=}')
print(f'{device_info=}')
Output. Note the classes know how to display themselves and the fake data agrees with my implementation:
---enter device call back function---
out: MeasureResult_t(check_id=456, distance=[0.0, ..., 63.75])
out_num: 789
---exit device call back function---
device_handler.value=123
device_info=DeviceInfo_t(fpga_version=246)

Map data in C++ to memory and read data in Python

I am mapping integers to memory in C++ (Process 1) and trying to read them in Python (Process 2) ..
Current Results:
1) map integer 3 in C++ ==> Python (b'\x03\x00\x00\x00')
2) map integer 4 in C++ ==> Python (b'\x04\x00\x00\x00'), and so on ..
code:
Process 1
#include <windows.h>
#include <iostream>
using namespace std;
void main()
{
auto name = "new";
auto size = 4;
HANDLE hSharedMemory = CreateFileMapping(NULL, NULL, PAGE_READWRITE, NULL, size, name);
auto pMemory = (int*)MapViewOfFile(hSharedMemory, FILE_MAP_ALL_ACCESS, NULL, NULL, size);
for (int i = 0; i < 10; i++)
{
* pMemory = i;
cout << i << endl;
Sleep(1000);
}
UnmapViewOfFile(pMemory);
CloseHandle(hSharedMemory);
}
Process 2
import time
import mmap
bufSize = 4
FILENAME = 'new'
for i in range(10):
data = mmap.mmap(0, bufSize, tagname=FILENAME, access=mmap.ACCESS_READ)
dataRead = data.read(bufSize)
print(dataRead)
time.sleep(1)
However, my goal is to map an array that is 320*240 in size but when I try a simple array as below
int arr[4] = {1,2,3,4};
and attempt to map to memory by * pMemory = arr;
I am getting the error "a value of type int* cannot be assigned to an entity of type int" and error code "0x80070002" ..
Any ideas on how to solve this problem??
P.S for some reason integer 9 is mapped as b'\t\x00\x00\x00' in python ==> what am I missing?
Use memcpy to copy the array to shared memory.
#include <cstring>
#include <windows.h>
int main() {
int array[320*240];
const int size = sizeof(array);
const char *name = "new";
HANDLE hSharedMemory = CreateFileMapping(NULL, NULL, PAGE_READWRITE, NULL, size, name);
void *pMemory = MapViewOfFile(hSharedMemory, FILE_MAP_ALL_ACCESS, NULL, NULL, size);
std::memcpy(pMemory, array, size);
UnmapViewOfFile(pMemory);
CloseHandle(hSharedMemory);
}

python using ctypes to work with dll - structure OUT argument

In the header file of the dll I have the following structure
typedef struct USMC_Devices_st{
DWORD NOD; // Number of the devices ready to work
char **Serial; // Array of 16 byte ASCII strings
char **Version; // Array of 4 byte ASCII strings
} USMC_Devices; // Structure representing connected devices
I would like to call a dll function:
DWORD USMC_Init( USMC_Devices &Str );
I tried with this:
class USMCDevices(Structure):
_fields_ = [("NOD", c_long),
("Serial", c_char_p),
("Version", c_char_p)]
usmc = cdll.USMCDLL #this is the dll file
init = usmc.USMC_Init
init.restype = c_int32; # return type
init.argtypes = [USMCDevices]; # argument
dev = USMCDevices()
init(dev)
I get an error here. I guess the problem is with "Serial" and "Version" which both are array corresponding to the NOD (number of devices).
Any ideas how to solve this problem?
I really appreciate your help!!!
Use POINTER(c_char_p) for the char ** pointers. Indexing Serial or Version creates a Python string for the given null-terminated string. Note that indexing in the array beyond NOD - 1 either produces garbage values or will crash the interpreter.
C:
#include <windows.h>
typedef struct USMC_Devices_st {
DWORD NOD; // Number of the devices ready to work
char **Serial; // Array of 16 byte ASCII strings
char **Version; // Array of 4 byte ASCII strings
} USMC_Devices;
char *Serial[] = {"000000000000001", "000000000000002"};
char *Version[] = {"001", "002"};
__declspec(dllexport) DWORD USMC_Init(USMC_Devices *devices) {
devices->NOD = 2;
devices->Serial = Serial;
devices->Version = Version;
return 0;
}
// build: cl usmcdll.c /LD
Python:
import ctypes
from ctypes import wintypes
class USMCDevices(ctypes.Structure):
_fields_ = [("NOD", wintypes.DWORD),
("Serial", ctypes.POINTER(ctypes.c_char_p)),
("Version", ctypes.POINTER(ctypes.c_char_p))]
usmc = ctypes.cdll.USMCDLL
init = usmc.USMC_Init
init.restype = wintypes.DWORD
init.argtypes = [ctypes.POINTER(USMCDevices)]
dev = USMCDevices()
init(ctypes.byref(dev))
devices = [dev.Serial[i] + b':' + dev.Version[i]
for i in range(dev.NOD)]
print('\n'.join(d.decode('ascii') for d in devices))
Output:
000000000000001:001
000000000000002:002

Python & Ctypes: Passing a struct to a function as a pointer to get back data

I've looked through other answers but can't seem to get this to work. I'm trying to call a function within a DLL for communicating with SMBus devices. This function takes a pointer to a struct, which has an array as one of it's fields. so...
In C:
typedef struct _SMB_REQUEST
{
unsigned char Address;
unsigned char Command;
unsigned char BlockLength;
unsigned char Data[SMB_MAX_DATA_SIZE];
} SMB_REQUEST;
I think I have to set values for the Address, Command and BlockLength while the DLL fills the Data array.
The function that requires this struct takes it as a pointer
SMBUS_API int SmBusReadByte( SMBUS_HANDLE handle, SMB_REQUEST *request );
So I've set up the struct in Python like so:
class SMB_REQUEST(ctypes.Structure):
_fields_ = [("Address", c_char),
("Command", c_char),
("BlockLength", c_char),
("Data", type(create_string_buffer(SMB_MAX_DATA_SIZE))]
*Note: I've also tried ctypes.c_char*SMB_MAX_DATA_SIZE for the data type*
To pass a pointer to a struct of this type to the function I have tried to initialise it first as follows:
data = create_string_buffer(SMB_MAX_DATA_SIZE)
smb_request = SMB_REQUEST('\x53', \x00', 1, data)
This responds with:
TypeError: expected string or Unicode object, c_char_Array_32 found
If I try leaving out the data array, like so:
smb_request = SMB_REQUEST('\x53', \x00', 1)
No, error.
However, then when I try to pass this to the function:
int_response = smbus_read_byte(smbus_handle, smb_request))
I get:
ArgumentError: argument 2: <type 'exceptions.TypeError'>: expected LP_SMB_REQUES
T instance instead of SMB_REQUEST
I've tried passing it as a pointer:
int_response = smbus_read_byte(smbus_handle, ctypes.POINTER(smb_request))
and I get:
----> 1
2
3
4
5
TypeError: must be a ctypes type
Here's how I've set up the art types:
smbus_read_byte.argtypes = (ctypes.c_void_p, ctypes.POINTER(SMB_REQUEST))
I've tried casting but still no go. Can anyone shed some light on this for me?
Update:
If I first initialise the struct like so:
smb_request = SMB_REQUEST('\xA6', '\x00', chr(1), 'a test string')
and then bass by reference:
int_response = smbus_receive_byte(smbus_handle, ctypes.byref(smb_request))
I get no error. However, the function returns -1 when it should return '0' for success and non-zero for a fail. Checking the value of smb_request.Data gives back 'a test string' so no change there.
Any suggestions as to what might be going on here would be greatly appreciated.
Thanks
UPDATE:
Since I've gotten a couple of enquiries about whether my handle is correct, here's how I'm using it. The header file for the DLL declares the following:
typedef void *SMBUS_HANDLE;
//
// This function call initializes the SMBus, opens the driver and
// allocates the resources associated with the SMBus.
// All SMBus API calls are valid
// after making this call except to re-open the SMBus.
//
SMBUS_API SMBUS_HANDLE OpenSmbus(void);
So here's how I'm doing this in python:
smbus_handle = c_void_p() # NOTE: I have also tried it without this line but same result
open_smbus = CDLL('smbus.dll').OpenSmbus
smbus_handle = open_smbus()
print 'SMBUS_API SMBUS_HANDLE OpenSmbus(void): ' + str(smbus_handle)
I call this before making the call to smbus_read_byte(). I have tried to set open_smbus.restype = c_void_p() but I get an error: TypeError: restype must be a type, a callable, or None
Here's a working example. It looks like you are passing the wrong type to the function.
Test DLL Code ("cl /W4 /LD x.c" on Windows)
#include <stdio.h>
#define SMBUS_API __declspec(dllexport)
#define SMB_MAX_DATA_SIZE 5
typedef void* SMBUS_HANDLE;
typedef struct _SMB_REQUEST
{
unsigned char Address;
unsigned char Command;
unsigned char BlockLength;
unsigned char Data[SMB_MAX_DATA_SIZE];
} SMB_REQUEST;
SMBUS_API int SmBusReadByte(SMBUS_HANDLE handle,SMB_REQUEST *request)
{
unsigned char i;
for(i = 0; i < request->BlockLength; i++)
request->Data[i] = i;
return request->BlockLength;
}
SMBUS_API SMBUS_HANDLE OpenSmbus(void)
{
return (void*)0x12345678;
}
Python code
from ctypes import *
SMB_MAX_DATA_SIZE = 5
ARRAY5 = c_ubyte * SMB_MAX_DATA_SIZE
class SMB_REQUEST(Structure):
_fields_ = [
("Address", c_ubyte),
("Command", c_ubyte),
("BlockLength", c_ubyte),
("Data", ARRAY5)]
smbus_read_byte = CDLL('x').SmBusReadByte
smbus_read_byte.argtypes = [c_void_p,POINTER(SMB_REQUEST)]
smbus_read_byte.restype = c_int
open_smbus = CDLL('x').OpenSmbus
open_smbus.argtypes = []
open_smbus.restype = c_void_p
handle = open_smbus()
print 'handle = %08Xh' % handle
smb_request = SMB_REQUEST(1,2,5)
print 'returned =',smbus_read_byte(handle,byref(smb_request))
print 'Address =',smb_request.Address
print 'Command =',smb_request.Command
print 'BlockLength =',smb_request.BlockLength
for i,b in enumerate(smb_request.Data):
print 'Data[%d] = %02Xh' % (i,b)
Output
handle = 12345678h
returned = 5
Address = 1
Command = 2
BlockLength = 5
Data[0] = 00h
Data[1] = 01h
Data[2] = 02h
Data[3] = 03h
Data[4] = 04h
You're almost there. You should use c_char * SMB_MAX_DATA_SIZE as the type for the definition of Data. This works for me on Mac OS X:
Shared library:
$ cat test.c
#include <stdio.h>
#define SMB_MAX_DATA_SIZE 16
typedef struct _SMB_REQUEST
{
unsigned char Address;
unsigned char Command;
unsigned char BlockLength;
unsigned char Data[SMB_MAX_DATA_SIZE];
} SMB_REQUEST;
int SmBusReadByte(void *handle, SMB_REQUEST *request)
{
printf("SmBusReadByte: handle=%p request=[%d %d %d %s]\n", handle,
request->Address, request->Command, request->BlockLength, request->Data);
return 13;
}
$ gcc test.c -fPIC -shared -o libtest.dylib
Python driver:
$ cat test.py
import ctypes
SMB_MAX_DATA_SIZE = 16
class SMB_REQUEST(ctypes.Structure):
_fields_ = [("Address", ctypes.c_ubyte),
("Command", ctypes.c_ubyte),
("BlockLength", ctypes.c_ubyte),
("Data", ctypes.c_char * SMB_MAX_DATA_SIZE)]
libtest = ctypes.cdll.LoadLibrary('libtest.dylib')
req = SMB_REQUEST(1, 2, 3, 'test')
result = libtest.SmBusReadByte(ctypes.c_voidp(0x12345678), ctypes.byref(req))
print 'result: %d' % result
$ python test.py
SmBusReadByte: handle=0x12345678 request=[1 2 3 test]
result: 13
UPDATE
You're having problems because you need to set the result type of open_smbus to void*. By default, ctypes assumes that functions return ints. You need to say this:
open_smbus.restype = ctypes.c_void_p
You were getting an error because you were using c_void_p() (note the extra parentheses). There's an important distinction between c_void_p and c_void_p(). The former is a type, and the latter is an instance of a type. c_void_p represents the C type void*, whereas c_void_p() represents an actual pointer instance (with a default value of 0).
Try changing
("Data", type(create_string_buffer(SMB_MAX_DATA_SIZE))
to
("Data", (c_char * SMB_MAX_DATA_SIZE)]

Categories