1

ユースケースは次のとおりです。

  • Cで実装された(固定された、変更不可能な)DLLが与えられた場合
  • 求む: Python で実装されたこの DLL のラッパー (選択したメソッド: ctypes)

DLL の一部の関数には、同期プリミティブが必要です。最大限の柔軟性を目指して、DLL の設計者はクライアント提供のコールバックに完全に依存しています。より正確には、この DLL には以下が必要です。

  • 同期オブジェクトを作成するためのコールバック関数
  • 同期オブジェクトのロックを取得/解放するコールバック関数
  • 同期オブジェクトを破棄する 1 つのコールバック関数

DLL の観点からは、同期オブジェクトは不透明であるため、void *エンティティによって表現されます。たとえば、DLL 関数の 1 つがロックを取得したい場合は、次のようにします。

void* mutex;

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

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

... etc

コールバックcreate_mutex入力パラメータには出力セマンティクスがあることがわかります。これはvoid **署名によって達成されます。

このコールバック (および他の 3 つ) は、Python で実装する必要があります。私は失敗しました :-) 簡単にするために、コールバックの作成のみに焦点を当てましょう。また、簡単にするために、不透明なオブジェクトをint.

コールバックの使用をエミュレートする toy-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

どこが間違っていますか?

4

2 に答える 2

0

返品タイプを誤って定義しているようです。Cコールバックはintを返すように見えますが、Pythonコールバックはc_intを返すと宣言していますが、明示的には何も返していません(したがって、実際にはNoneを返します)。「0を返す」と、クラッシュが停止する可能性があります。いずれの場合も、これを行うか、コールバックシグネチャをCFUNCTYPE(None, ...etc)に変更する必要があります。

また、ここでは現在の問題ではありませんが、「int」組み込み名をシャドウイングしています。これは後で問題につながる可能性があります。

編集: Cの戻り型を「void」ではなく「int」として正しく参照するように。

于 2009-12-12T13:50:59.083 に答える
0

segfault は、Python コールバックでのポインター処理が正しくないことが原因です。厳密に必要なレベルよりも多くのレベルのポインター間接化があり、これがおそらく混乱の原因です。を設定した Python コールバックでは、 Python の ctypes オブジェクトp_p_i.contentsが指すもののみが変更され、基になるポインターは変更されません。これを行うには、配列アクセス構文を使用してポインターの参照解除を行います。蒸留された例:

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 の回答で説明されている型の非互換性によるものです。

于 2010-09-09T02:19:17.290 に答える