1

Python の ctypes ライブラリを使用して、スキャン ライブラリSANEのいくつかのメソッドにアクセスしようとしています。これは ctypes の私の最初の経験であり、1 年以上にわたって C データ型を扱わなければならなかったのは初めてなので、ここにはかなりの学習曲線がありますが、この特定の宣言がなくても、この特定の宣言は面倒だと思います:

extern SANE_Status sane_get_devices (const SANE_Device *** device_list, SANE_Bool local_only);

まず、SANE_Status(enum) とSANE_Bool(typedef to c_int) の処理に成功しました。どちらもシンプルでした。一方、その最初のパラメーターは、私にあらゆる種類の悲しみを引き起こしています. そもそも" " 表記に慣れていない***ため、これまでのトレーサー弾はガベージ データしか生成しませんでした。Python 構造オブジェクトのリストを読み戻せるように、この関数への入力をフォーマットするにはどうすればよいですか? 参考までに、参照されている C 構造体は次のとおりです。

typedef struct
{
    SANE_String_Const name; /* unique device name */
    SANE_String_Const vendor;   /* device vendor string */
    SANE_String_Const model;    /* device model name */
    SANE_String_Const type; /* device type (e.g., "flatbed scanner") */
 }
SANE_Device;

どこSANE_String_Constで として定義されていc_char_pます。

このオブジェクトの私の Python/ctypes バージョンは次のとおりです。

class SANE_Device(Structure):
    _fields_ = [
        ("name", c_char_p),
        ("vendor", c_char_p),
        ("model", c_char_p),
        ("type", c_char_p)]

これから期待される動作(構造オブジェクトのリスト)を取得できるように、何を渡す必要があるかについての提案はありますか?すべての回答に感謝します。

更新 1:

以下を使用して、正しい SANE_Device Python 構造を取得できました。

devices = pointer(pointer(pointer(SANE_Device())))
status = libsane.sane_get_devices(devices, c_int(0))
print status, devices, devices.contents.contents.contents.name

ただし、1) うーん、2) 結果が 1 つしかない場合にのみ機能するようです。devices.contents.contentsまたはで len() を実行できませんdevices.contents.contents.contents。結果の数を決定するにはどうすればよいですか? SANE のドキュメントでは、「関数が正常に実行された場合、*device_list 内の SANE_Device 構造へのポインタの NULL 終了配列へのポインタを格納する」と指定されています。提案?

更新 2:

10 項目の配列を渡し、次を使用して最初の要素にアクセスできました。

devices = pointer(pointer(pointer((SANE_Device * 10)())))
status = libsane.sane_get_devices(devices, c_int(0))
print status, devices, devices.contents.contents.contents[0].name

ただし、10 は明らかに恣意的な数であり、実際の結果の数を決定する方法はありません。devices.contents.contents.contents[1].nameデバイスが 1 つしか接続されていないときにアクセスしようとすると、セグメンテーション フォールトが発生します。ctypes では、このような可変長構造を処理する適切な方法が必要です。

4

1 に答える 1

7

Aconst SANE_Device ***は3レベルのポインターです。これは、定数SANE_Deviceへのポインターへのポインターへのポインターです。プログラムcdeclを使用して、複雑なC /C++型の定義を解読できます。

SANEのドキュメントによると、SANE_get_devices()成功した場合、SANEデバイスへのポインタのNULLで終了するリストへのポインタを格納します。したがって、それを呼び出す適切な方法は、型の変数const SANE_Device **(つまり、定数 `SANE_Deviceへのポインターへのポインター)を宣言し、そのポインターのアドレスを渡すことです。

const SANE_Device **device_list;
SANE_get_devices(&device_list, local_only);  // check return value
// Now, device_list[0] points to the first device,
// device_list[1] points to the second device, etc.
// Once you hit a NULL pointer, that's the end of the list:
int num_devices = 0;
while(device_list[num_devices] != NULL)
    num_devices++;
// num_devices now stores the total number of devices

さて、これはあなたがCコードからそれを呼ぶ方法です。ctypesに関するドキュメントをざっと読みましたが、byref関数を使用して引数を参照で渡す必要があり、渡す値はPOINTERからPOINTER、SANE_Deviceである必要があるようです。pointerとの違いに注意してくださいPOINTER。前者はインスタンスへのポインタを作成し、後者はへのポインタを作成します。したがって、次のコードが機能すると思います。

// SANE_Device declared as you had it
devices = POINTER(POINTER(SANE_Device))()  // devices is a NULL pointer to a pointer to a SANE_Device
status = libsane.sane_get_devices(byref(devices), c_int(0))
if status != successful:   // replace this by whatever success is
    print error
else:
    num_devices = 0
    // Convert NULL-terminated C list into Python list
    device_list = []
    while devices[num_devices]:
        device_list.append(devices[num_devices].contents)  // use .contents here since each entry in the C list is itself a pointer
        num_devices += 1
    print device_list

[編集]の非常に単純なプレースホルダーを使用して上記のコードをテストしましたが、SANE_get_devices機能します。

于 2008-12-20T07:49:30.810 に答える