2008-12-20 36 views
1

我正在嘗試使用Python的ctypes庫來訪問掃描庫SANE中的一些方法。這是我第一次使用ctypes並且第一次在一年內不得不處理C數據類型,所以這裏有一個公平的學習曲線,但我認爲即使沒有這個特定的聲明也會很麻煩:使用Python的ctypes傳遞/讀取聲明爲「struct_name *** param_name」的參數?

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

首先,我已經成功地處理了SANE_Status(一個枚舉)和SANE_Bool(一個typedef到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)好像它只會工作如果有一個單一的結果。我不能len()devices.contents.contentsdevices.contents.contents.contents。我如何確定結果的數量? SANE文檔指定「如果函數成功執行,它將存儲一個指向由* device_list中的SANE_Device結構指向NULL結尾的指針的指針。建議?

更新2:

我能夠傳遞一個十項數組,然後使用訪問第一元件:

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會導致分段錯誤。必須有一種處理ctypes中這些變長結構的正確方法。

回答

7

A const SANE_Device ***是一個三級指針:它是指向指向常量SANE_Device的指針的指針。您可以使用程序cdecl來解密複雜的C/C++類型定義。

根據SANE documentation,如果成功,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到SANE_Device的指針。請注意0​​和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測試上面的代碼,它的作品。

+0

我的天啊,這是一個非常徹底的答案,更好的是,它的工作原理!即便如此,我也不確定自己會不會這樣做。感謝Adam! – bouvard 2008-12-20 18:38:01