Usando ctypes do Python para passar / ler um parâmetro declarado como “struct_name *** PARAM_NAME”?

StackOverflow https://stackoverflow.com/questions/383010

Pergunta

Eu estou tentando usar ctypes do Python biblioteca para acessar alguns métodos na biblioteca de digitalização SANE . Esta é a minha primeira experiência com ctypes ea primeira vez que eu tive que lidar com tipos de dados C em mais de um ano para que haja uma curva de aprendizagem justo aqui, mas acho que mesmo sem que esta declaração particular seria problemático:

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

Primeiro de tudo, eu tenho lidado com sucesso com SANE_Status (uma enumeração) e SANE_Bool (a typedef para c_int). Aqueles eram ao mesmo tempo simples. Esse primeiro parâmetro, por outro lado, está a causar-me todos os tipos de dor. Eu estou familiarizado com a notação "***" para começar e meus balas traçantes até agora não resultou em nada mais do que dados de lixo. Como faço para formatar a entrada para esta função de forma que eu possa ler uma lista dos meus estrutura-objetos Python? Para referência, a estrutura C a ser referenciado é:

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;

Onde SANE_String_Const é definido como um c_char_p.

Meu Python / ctypes versão deste objeto é:

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

Sugestões sobre o que eu deveria passar em tal que eu possa obter o comportamento esperado (a lista de estrutura-objetos) fora deste? Todas as respostas apreciada.

Update 1:

Usando o seguinte, eu era capaz de recuperar uma estrutura SANE_Device Python correto:

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

No entanto, 1) eca e 2) que parece que só iria funcionar se houver um único resultado. Eu não posso len () no devices.contents.contents ou devices.contents.contents.contents. Como sou eu para determinar o número de resultados? Os docs SANE especificar que "se a função é executado com êxito, ele armazena um ponteiro para uma matriz NULL terminada de ponteiros para estruturas SANE_Device in * device_list". Sugestões?

Update 2:

Eu era capaz de passar um array de dez item e, em seguida, acessar o primeiro elemento usando:

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

No entanto, dez é obviamente um número arbitrário e não tenho maneira de determinar o número real de resultados. Tentando acessar devices.contents.contents.contents[1].name quando apenas um dispositivo está conectado faz com que uma falha de segmentação. Deve haver uma maneira correta de lidar com construções de comprimento variável como estes em ctypes.

Foi útil?

Solução

A const SANE_Device *** é um ponteiro de três níveis: é um ponteiro para um ponteiro para um ponteiro para uma SANE_Device constante. Você pode usar o programa cdecl para decifrar complicado C / C ++ tipo definições.

De acordo com a SANE documentação , SANE_get_devices() irá armazenar um ponteiro para um NULL lista terminadas em de ponteiros para dispositivos SANE se bem sucedida. Assim, a forma adequada para chamar é para declarar uma variável do tipo const SANE_Device ** (isto é um apontador para um apontador para uma constante `SANE_Device), e passar o endereço de ponteiro que:

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

Agora, é assim que você chamaria isso de código C. Eu tenho desnatado a documentação sobre ctypes, e parece que você quiser usar a função byref para passar o argumento por referência, e que o valor que você passa deve ser um ponteiro para um ponteiro para um SANE_Device. Observe a distinção entre pointer e POINTER: o primeiro cria um ponteiro para uma instância , enquanto o segundo cria um ponteiro para um tipo . Assim, eu estou supondo que o seguinte código funcionará:

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

[Edit] Eu testei o código acima usando um marcador muito simples para SANE_get_devices, e ele funciona.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top