[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