开发者

Back-casting a ctypes.py_object in a callback

开发者 https://www.devze.com 2023-01-07 07:18 出处:网络
I am trying to wrap a C library using ctypes.One feature of the library is an ondestroy callback which is called when a handle returned by the library is about to be destroyed.

I am trying to wrap a C library using ctypes. One feature of the library is an ondestroy callback which is called when a handle returned by the library is about to be destroyed.

The callback has the signature:

void cb(f *beingdestroyed);

The API allows one to associate a user-specified void * with f when it is returned by the library. Hence I can associate the py_object being used to wrap it as user data. My plan is to have an is_valid field and when the callback is fired to extract the user_data and set this field to false.

My problem is how to go about extracting my high-level py_object; I can fetch the user data as a ctypes.void_p and cast to a ctypes.py_object but then I've only got the Python C API to work with. It is possible to back-cast to a high level object which I can work with by writing 开发者_StackOverflowuser_object.is_valid = 0?


To elaborate on Thomas Heller's answer:

  • The callback prototype should specify c_void_p for the context parameter
  • The argtypes for the library function should specify py_object for the context parameter
  • That function should be called with py_object(my_python_context_object)
  • Your Python implementation of the callback function should cast the context to a py_object, and extract its value: cast(context, py_object).value

Here is a working example. Start with C source for a simple DLL:

// FluffyBunny.c
// Compile on windows with command line
//      cl /Gd /LD FluffyBunny.c
// Result is FluffyBunny.DLL, which exports one function:
//      FluffyBunny() uses __cdecl calling convention.

#include <windows.h>

BOOL APIENTRY DllMain(HMODULE, DWORD, LPVOID) {
  return TRUE;
}

typedef int (*FLUFFYBUNNY_CALLBACK)(void *context);

__declspec(dllexport) int FluffyBunny(FLUFFYBUNNY_CALLBACK cb, void *context) {
  int result = 0;
  int count = 0;
  if (cb) {
    while (result == 0) {
      result = (*cb)(context);
      ++count;
    }
  }
  return count;
}

And here is a Python program that invokes the DLL:

# FluffyBunny.py
from ctypes import *

# Declare a class that will be used for context info in calls to FluffyBunny()
class Rabbit:
    def __init__(self):
        self.count = 0

# FluffyBunny() wants a callback function with the following C prototype:
#     typedef int (*FLUFFYBUNNY_CALLBACK)(void *context);
FLUFFYBUNNY_CALLBACK = CFUNCTYPE(c_int, c_void_p)

# This DLL has been compiled with __cdecl calling convention.
FluffyBunny_dll = CDLL('FluffyBunny.dll')

# Get the function from the library. Its C prototype is:
#     int FluffyBunny(FLUFFYBUNNY_CALLBACK cb, void *context);
# Note that I use "py_object" instead of "c_void_p" for the context argument.
FluffyBunny          = FluffyBunny_dll.FluffyBunny
FluffyBunny.restype  = c_int
FluffyBunny.argtypes = [FLUFFYBUNNY_CALLBACK, py_object]

# Create Python version of the callback function.
def _private_enumerateBunnies(context):
    # Convert the context argument to a py_object, and extract its value.
    # This gives us the original Rabbit object that was passed in to 
    # FluffyBunny().
    furball = cast(context, py_object).value
    # Do something with the context object.
    if furball:
        furball.count += 1
        print 'furball.count =', furball.count
        # Return non-zero as signal that FluffyBunny() should terminate
        return 0 if (furball.count < 10) else -1
    else:
        return -1

# Convert it to a C-callable function.
enumerateBunnies = FLUFFYBUNNY_CALLBACK(_private_enumerateBunnies)

# Try with no context info.
print 'no context info...'
result = FluffyBunny(enumerateBunnies, None)
print 'result=', result

# Give it a Python object as context info.
print 'instance of Rabbit as context info...'
furball = Rabbit()
result = FluffyBunny(enumerateBunnies, py_object(furball))
print 'result=', result


The usual way is to avoid this issue completely.

Use a method instead of a function as a callback, and the implicit self will allow to access your user_data field.


I applied the information in Bob Pyron's answer to my code, but didn't like that the callback had to be aware that it was being called by a C program, and deal with ctypes-stuff. It turns out that with a minor change, that _private_enumerateBunnies() gets a python object instead of a void*. I did the equivalent of this in my code:

FLUFFYBUNNY_CALLBACK = CFUNCTYPE(c_int, py_object)

And then all the C-funky-stuff was hidden in the code that already had to deal with it, and it didn't spread out to the users of that API (that is, the providers of callbacks). Of course, then you have to pass it a python object, but that's a pretty minor restriction.

I benefited greatly from Bob's original answer (once I got past all the bunnies), so Thanks!

0

精彩评论

暂无评论...
验证码 换一张
取 消