在回调中反向转换ctypes.py_object

时间:2010-07-14 12:05:30

标签: python ctypes

我正在尝试使用ctypes包装C库。该库的一个特性是ondestroy回调,当库将返回一个句柄时,将调用该回调。

回调有签名:

void cb(f *beingdestroyed);

API允许用户在库返回时将用户指定的void *与f关联。因此,我可以将用于包装它的py_object关联为用户数据。我的计划是拥有一个is_valid字段,并在触发回调时提取user_data并将此字段设置为false。

我的问题是如何提取我的高级py_object;我可以将用户数据作为ctypes.void_p获取并转换为ctypes.py_object,但之后我只能使用Python C API。通过编写user_object.is_valid = 0

,可以回溯到我可以使用的高级对象

3 个答案:

答案 0 :(得分:6)

详细阐述托马斯海勒的答案:

  • 回调原型应为上下文参数
  • 指定c_void_p
  • 库函数的argtypes应为上下文参数
  • 指定py_object
  • 应使用py_object(my_python_context_object)
  • 调用该函数
  • 您的回调函数的Python实现应该将上下文强制转换为py_object,并提取其值:cast(context, py_object).value

这是一个工作示例。从简单的DLL的C源开始:

// 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;
}

这是一个调用DLL的Python程序:

# 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

答案 1 :(得分:2)

通常的方法是完全避免这个问题。

使用方法代替函数作为回调,隐式self将允许访问您的user_data字段。

答案 2 :(得分:1)

我将这些信息应用于Bob Pyron对我的代码的回答,但并不是说回调必须知道它是由C程序调用的,并且处理ctypes-stuff。事实证明,通过一个小的改动,_private_enumerateBunnies()得到一个python对象而不是void *。我在我的代码中做了相同的操作:

  

FLUFFYBUNNY_CALLBACK = CFUNCTYPE(c_int, py_object

然后所有C-funky-stuff都隐藏在已经不得不处理它的代码中,并且它没有传播给该API的用户(即回调的提供者)。当然,那么你必须传递一个python对象,但这是一个非常小的限制。

我从鲍勃的原始答案中获益匪浅(一旦我越过了所有的兔子),谢谢!