zephyr
zephyr

Reputation: 2332

Have C library call python function with ctypes

I have a DLL in c and meant for windows platforms which has a structure similar to the following:

C Structure

typedef struct some_struct {
    int (_stdcall *function)(int arg1, int arg2);
    ...
}SOME_STRUCT;

I've defined a python ctypes structure to mimic this with the following

Python Structure

class SOME_STRUCT(Structure):
    _fields_ = [('function', POINTER(CFUNCTYPE(c_int, c_int, c_int))), ...]

The point of this structure in the C code is to register a callback function that gets executed upon certain triggers in its own thread. What I want to be able to do, if possible, is set that callback to be a Python function such that when the function in the C structure gets called from the C code, it is the python function that gets executed.

What I've got in python to try and accomplish this (which doesn't work) is the following:

def func(arg1,arg2):
    print('I was called!')
    return 0

struct = SOME_STRUCT()
prototype = CFUNCTYPE(c_int, c_int, c_int)
struct.function = byref(prototype(func))

The specific error I get (that may not be my only issue) is that it complains that struct.function was expecting a LP_CFunctionType instance but got a CArgObject instance. How can I do what I'm trying to do?

Upvotes: 2

Views: 1143

Answers (1)

Mark Tolonen
Mark Tolonen

Reputation: 178369

Here's a working example and test DLL source. Oddly, I couldn't get it to work when the callback was the only member of the struct (crash). It seemed like a bug, because a callback without a struct wrapper or adding a second member to the struct made it work.

Things to note:

  • Use WINFUNCTYPE with __stdcall. CFUNCTYPE is for __cdecl.
  • You don't need POINTER or byref to make it work.
  • The @CALLBACK decorator is equivalent to func = CALLBACK(func).

test.c

#include <stdio.h>

typedef int (__stdcall *CALLBACK)(int arg1, int arg2);

typedef struct some_struct {
    CALLBACK function;
    int other;
} SOME_STRUCT;

__declspec(dllexport) int func(SOME_STRUCT* pss)
{
    printf("%d\n",pss->other);
    return pss->function(1,2);
}

test.py

from ctypes import *

CALLBACK = WINFUNCTYPE(c_int,c_int,c_int)

class SOME_STRUCT(Structure):
    _fields_ = [('function', CALLBACK),
                ('other', c_int)]

@CALLBACK
def callback(arg1,arg2):
    return arg1 + arg2

dll = CDLL('test')
dll.argtypes = POINTER(SOME_STRUCT),
dll.restype = c_int

struct = SOME_STRUCT(callback,7)
print(dll.func(byref(struct)))

Output

7
3

Upvotes: 1

Related Questions