[Tutor] Registering callbacks and .DLL
eryksun
eryksun at gmail.com
Sat Oct 18 05:53:11 CEST 2014
On Thu, Oct 16, 2014 at 6:35 PM, Wilson, Pete <Pete.Wilson at atmel.com> wrote:
>
> The .DLL was written in C++ is working with C++ apps calling it.
ctypes doesn't support the platform C++ ABI (I don't think the VC++
ABI is even stable), classes, STL containers, or exceptions [*]. It
isn't "cpptypes". To work with ctypes, a C++ library needs an extern
"C" interface. The library you're using probably qualifies, but try a
simple test program in C before jumping into ctypes.
[*] On Windows ctypes has limited support for Structured Exception
Handling (SEH), a Microsoft extension of ANSI C. Inadvertently it also
handles any exception raised by Win32 RaiseException, such as VC++
exceptions. The code for VC++ exceptions is 0xE06D7363, i.e. "\xE0"
"msc". ctypes isn't looking for this code and doesn't delve deeper to
get the C++ exception type, so the OSError it raises is almost
useless. Pretend this doesn't exist. SEH support is only implemented
for the few cases ctypes handles explicitly such as access violations.
> I tried the methods in section 15.17.1.17 with the qsort() and CFUNCTYPE,
> but it is not working. My code and the .dll are attached.
>
> from ctypes import *
>
> pt_dll = cdll.LoadLibrary("c:/py_stuff/ProductionTest.dll")
You can use CDLL instead. It's fewer keystrokes.
from ctypes import *
pt_dll = CDLL("c:/py_stuff/ProductionTest.dll")
If the functions use the stdcall convention, substitute WinDLL for
CDLL. If there's a mix of calling conventions you can simply load the
library twice, once as CDLL and again as WinDLL. They'll each have the
same _handle attribute.
You can also define prototypes manually via CFUNCTYPE and WINFUNCTYPE.
Then instantiate them with a 2-tuple (name_or_ordinal, library), e.g.
libc = CDLL('msvcr100')
atoi_t = CFUNCTYPE(c_int, c_char_p)
atoi = atoi_t(('atoi', libc))
>>> atoi(b'42')
42
FYI, 64-bit Windows has a single calling convention, so if you switch
to 64-bit Python you don't have to worry about cdecl vs stdcall.
http://en.wikipedia.org/wiki/X86_calling_conventions
> reg_send_serial_data = pt_dll.RegSendSerialData
>
> class SendSerialData_t(Structure):
> _fields_ = [("tx_data", c_void_p),
> ("size", c_uint8)]
>
> send_serial_data = SendSerialData_t()
SendSerialData_t is a function pointer type, not a data structure.
Here are the C prototypes from the attached PDF:
typedef void (*SendSerialData_t) (uint8_t *tx_data, uint8_t size);
void RegSendSerialData(SendSerialData_t SendSerialData);
A SenedSerialData_t function takes two parameters (uint8_t *, uint8_t)
and returns nothing (void).
ctypes declarations:
SendSerialData_t = CFUNCTYPE(None, POINTER(c_uint8), c_uint8)
reg_send_serial_data = pt_dll.RegSendSerialData
reg_send_serial_data.argtypes = [SendSerialData_t]
reg_send_serial_data.restype = None
The first argument to CFUNCTYPE is the return type. Use None for void.
Next define the Python callback.
def send_serial_data(tx_data, size):
# testing
print tx_data, size
print tx_data[:size]
cb_send_serial_data = SendSerialData_t(send_serial_data)
Finally, register the callback with the library:
reg_send_serial_data(cb_send_serial_data)
It's vital that you keep a reference to cb_send_serial_data (as a
global, an instance attribute, in a container, etc). This prevents the
callback from being deallocated while it's possible the library can
call it. Otherwise at best you'll get an access violation (or
segfault on POSIX systems), but probably a less obvious error.
Next your test code sets up ProdBatVolRequest, which is prototyped as follows:
typedef void (*BatVolReadRequest_cb)(uint16_t bat_vol, uint8_t status);
typedef struct {
BatVolReadRequest_cb BatVolReadConf;
} BatVolReadRequest_t;
void ProdBatVolReadRequest(BatVolReadRequest_t BatVolReadParam);
ProdBatVolReadRequest is passed a struct by value that consists of a
single function pointer. You can skip defining this struct and just
pass the function pointer. It's the same ABI for x86 and x64.
BatVolReadRequest_t = CFUNCTYPE(None, c_uint16, c_uint8)
prod_bat_vol_read_request = pt_dll.ProdBatVolReadRequest
prod_bat_vol_read_request.argtypes = [BatVolReadRequest_t]
prod_bat_vol_read_request.restype = None
def bat_vol_read(bat_vol, status):
# testing
print bat_vol, status
cb_bat_vol_read = BatVolReadRequest_t(bat_vol_read)
prod_bat_vol_read_request(cb_bat_vol_read)
Remember to keep a reference to cb_bat_vol_read.
HTH
More information about the Tutor
mailing list