构造可从 C 调用的 python 函数,输入参数具有 *output* 语义

发布于 2024-08-15 03:50:27 字数 2611 浏览 4 评论 0原文

用例如下:

  • 给定一个用 C 实现的(固定的、不可更改的)DLL
  • 想要:用 python 实现的该 DLL 的包装器(选择方法:ctypes)

DLL 中的一些函数需要同步原语。为了获得最大的灵活性,DLL 的设计者完全依赖于客户端提供的回调。更准确地说,该 DLL 应具有:

  • 一个用于创建同步对象的回调函数
  • 用于获取/释放同步对象上的锁的回调函数
  • 以及一个用于销毁同步对象的回调函数

因为从 DLL 的角度来看,同步对象是不透明的,它将由一个 void * 实体来表示。例如,如果其中一个 DLL 函数想要获取锁,它应该这样做:

void* mutex;

/* get the mutex object via the create_mutex callback */
create_mutex(&mutex);

/* acquire a lock */
lock_mutex(mutex);

... etc

可以看出,回调 create_mutex 输入参数具有输出语义。这是通过 void ** 签名实现的。

该回调(以及其他三个)必须在 python 中实现。我失败了:-) 为了简单起见,让我们只关注创建回调,并且为了简单起见,让不透明对象为 int

模拟回调使用的玩具 DLL 如下 (ct_test.c):

#include <stdio.h>
#include <stdlib.h>

typedef int (* callback_t)(int**);
callback_t func;
int* global_i_p = NULL;

int mock_callback(int** ipp)
{
 int* dynamic_int_p = (int *) malloc(sizeof(int));
 /* dynamic int value from C */
 *dynamic_int_p = 2;
 *ipp = dynamic_int_p;
 return 0;
}

void set_py_callback(callback_t f)
{
 func = f;
}

void set_c_callback()
{
 func = mock_callback;
}

void test_callback(void)
{
 printf("global_i_p before: %p\n", global_i_p);
 func(&global_i_p);
 printf("global_i_p after: %p, pointed value:%d\n", global_i_p, *global_i_p);

 /* to be nice */
 if (func == mock_callback)
  free(global_i_p);
}

想要提供回调并使用 DLL 的 python 代码如下:

from ctypes import *

lib = CDLL("ct_test.so")

# "dynamic" int value from python
int  = c_int(1)
int_p = pointer(int)

def pyfunc(p_p_i):
 p_p_i.contents = int_p

# create callback type and instance
CALLBACK = CFUNCTYPE(c_int, POINTER (POINTER(c_int)))
c_pyfunc = CALLBACK(pyfunc)

# functions from .so
set_py_callback = lib.set_py_callback
set_c_callback = lib.set_c_callback
test_callback = lib.test_callback

# set one of the callbacks
set_py_callback(c_pyfunc)
#set_c_callback()

# test it
test_callback()

当使用 DLL 中提供的回调时 (通过 set_c_callback() 设置),这按预期工作:

~/dev/test$ python ct_test.py
global_i_p before: (nil)
global_i_p after: 0x97eb008, pointed value:2

但是,在另一种情况下 - 使用 python 回调 - 失败:

~/dev/test$ python ct_test.py
global_i_p before: (nil)
Traceback (most recent call last):
  File "/home/packages/python/2.5/python2.5-2.5.2/Modules/_ctypes/callbacks.c", line 284, in 'converting callback result'
TypeError: an integer is required
Exception  in <function pyfunc at 0xa14079c> ignored
Segmentation fault

我哪里错了?

The use case is the following:

  • Given a (fixed, not changeable) DLL implemented in C
  • Wanted: a wrapper to this DLL implemented in python (chosen method: ctypes)

Some of the functions in the DLL need synchronization primitives. To aim for maximum flexibility, the designers of the DLL completely rely on client-provided callbacks. More precisely this DLL shall have:

  • a callback function to create a synchronizaton object
  • callback functions to acquire/release a lock on the synchronizaton object
  • and one callback function to destroy the synchronizaton object

Because from the viewpoint of the DLL, the synchronizaton object is opaque, it will be repesented by a void * entity. For example if one of the DLL functions wants to acquire a lock it shall do:

void* mutex;

/* get the mutex object via the create_mutex callback */
create_mutex(&mutex);

/* acquire a lock */
lock_mutex(mutex);

... etc

It can be seen, that the callback create_mutex input parameter has output semantics. This is achieved with void ** signature.

This callback (and the other three) must be implemented in python. I've failed :-) For simplicity, let's focus on only the creating callback, and also for simplicity, let the opaque object be an int.

The toy-DLL, which emulates the use of callbacks, is the following (ct_test.c):

#include <stdio.h>
#include <stdlib.h>

typedef int (* callback_t)(int**);
callback_t func;
int* global_i_p = NULL;

int mock_callback(int** ipp)
{
 int* dynamic_int_p = (int *) malloc(sizeof(int));
 /* dynamic int value from C */
 *dynamic_int_p = 2;
 *ipp = dynamic_int_p;
 return 0;
}

void set_py_callback(callback_t f)
{
 func = f;
}

void set_c_callback()
{
 func = mock_callback;
}

void test_callback(void)
{
 printf("global_i_p before: %p\n", global_i_p);
 func(&global_i_p);
 printf("global_i_p after: %p, pointed value:%d\n", global_i_p, *global_i_p);

 /* to be nice */
 if (func == mock_callback)
  free(global_i_p);
}

The python code, which would like to provide the callback, and use the DLL is the following:

from ctypes import *

lib = CDLL("ct_test.so")

# "dynamic" int value from python
int  = c_int(1)
int_p = pointer(int)

def pyfunc(p_p_i):
 p_p_i.contents = int_p

# create callback type and instance
CALLBACK = CFUNCTYPE(c_int, POINTER (POINTER(c_int)))
c_pyfunc = CALLBACK(pyfunc)

# functions from .so
set_py_callback = lib.set_py_callback
set_c_callback = lib.set_c_callback
test_callback = lib.test_callback

# set one of the callbacks
set_py_callback(c_pyfunc)
#set_c_callback()

# test it
test_callback()

When using the in-DLL provided callback (set via set_c_callback()), this works as expected:

~/dev/test$ python ct_test.py
global_i_p before: (nil)
global_i_p after: 0x97eb008, pointed value:2

However, in the other case - with the python callback - fails:

~/dev/test$ python ct_test.py
global_i_p before: (nil)
Traceback (most recent call last):
  File "/home/packages/python/2.5/python2.5-2.5.2/Modules/_ctypes/callbacks.c", line 284, in 'converting callback result'
TypeError: an integer is required
Exception  in <function pyfunc at 0xa14079c> ignored
Segmentation fault

Where am I wrong?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

红ご颜醉 2024-08-22 03:50:27

您似乎错误地定义了返回类型。看起来你的 C 回调返回一个 int,而你声明的 Python 回调返回 c_int,但没有显式返回任何内容(因此实际上返回 None)。如果你“返回 0”,它可能会停止崩溃。无论如何,您都应该这样做或将回调签名更改为 CFUNCTYPE(None, ...etc)

另外,虽然这不是当前的问题,但您正在隐藏“int”内置名称。这可能会导致以后出现问题。

编辑:正确地将 C 返回类型引用为“int”,而不是“void”。

You appear to be incorrectly defining the return type. It looks like your C callback returns an int, while the Python one you are declaring as return c_int, yet not explicitly returning anything (thus actually returning None). If you "return 0" it might stop crashing. You should do that or change the callback signature to CFUNCTYPE(None, ...etc) in any case.

Also, although it's not a current problem here, you're shadowing the "int" builtin name. This might lead to problems later.

Edited: to correctly refer to the C return type as "int", not "void".

魂归处 2024-08-22 03:50:27

段错误是由于 Python 回调中的指针处理不正确造成的。您的指针间接级别超出了严格必要的级别,这可能是您感到困惑的根源。在 Python 回调中,您设置了 p_p_i.contents,但这只会更改 Python ctypes 对象 指向的内容,而不是底层指针。为此,请通过数组访问语法进行指针取消引用。一个简单的例子:

ip = ctypes.POINTER(ctypes.c_int)()
i = ctypes.c_int(99)
# Wrong way
ipp = ctypes.pointer(ip)
ipp.contents = ctypes.pointer(i)
print bool(ip) # False --> still NULL
# Right way
ipp = ctypes.pointer(ip)
ipp[0] = ctypes.pointer(i)
print ip[0] # 99 --> success!

类型错误是由于类型不兼容造成的,如 Peter Hansen 的答案中所述。

The segfault is due to incorrect pointer handling in your Python callback. You have more levels of pointer indirection than strict necessary, which is probably the source of your confusion. In the Python callback you set p_p_i.contents, but that only changes what the Python ctypes object points at, not the underlying pointer. To do that, do pointer derefrence via array access syntax. A distilled example:

ip = ctypes.POINTER(ctypes.c_int)()
i = ctypes.c_int(99)
# Wrong way
ipp = ctypes.pointer(ip)
ipp.contents = ctypes.pointer(i)
print bool(ip) # False --> still NULL
# Right way
ipp = ctypes.pointer(ip)
ipp[0] = ctypes.pointer(i)
print ip[0] # 99 --> success!

The type error is due to a type incompatibility as described in Peter Hansen's answer.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文