Passe a matriz de estruturas de Python para C

[Atualização: Problema resolvido! Veja parte inferior da postagem]

Eu preciso permitir que os desenvolvedores python passem uma matriz de dados compactados (neste caso, vértices) para minha API, que é uma série de interfaces C ++ expostas manualmente pela API Python C. Minha impressão inicial com isso é usar a classe ctypes Structure para permitir uma interface como esta:

class Vertex(Structure):
_fields_ = [
    ('x', c_float),
    ('y', c_float),
    ('z', c_float),
    ('u', c_float),
    ('v', c_float),
    ('color', c_int)
] 

verts = (Vertex * 3)()
verts[0] = Vertex(0.0, 0.5, 0.0, 0.0, 0.5, 0xFF0000FF)
verts[1] = Vertex(0.5, -0.5, 0.0, 0.5, -0.5, 0x00FF00FF)
verts[2] = Vertex(-0.5, -0.5, 0.0, -0.5, -0.5, 0x0000FFFF)

device.ReadVertices(verts, 3) # This is the interfaces to the C++ object

Onde a função que estou tentando passar tem a seguinte assinatura:

void Device::ReadVertices(Vertex* verts, int count);

E o wrapper Python se parece com isso:

static PyObject* Device_ReadVertices(Py_Device* self, PyObject* args)
{
    PyObject* py_verts;
    int count;

    if(!PyArg_ParseTuple(args, "Oi", &py_verts, &count)) 
        return NULL;

    // This Doesn't Work!
    Vertex* verts = static_cast<Vertex*>(PyCObject_AsVoidPtr(py_verts));

    self->device->ReadVertices(verts, count);

    Py_RETURN_NONE;
}

Obviamente, o maior problema que tenho é o seguinte: posso recuperar o PyObject para a estrutura, mas não tenho idéia de como o converteria no tipo correto. O código acima falha miseravelmente. Então, como exatamente eu permitiria que o usuário me passasse esse tipo de dados do Python?

Agora, algumas coisas a considerar: Primeiro, eu já tenho um pouco da minha camada Python / C ++ escrita e estou perfeitamente feliz com ela (mudei de SWIG para ter mais flexibilidade). Como não quero codificá-lo novamente, prefiro uma solução que funcione nativamente com a API C. Segundo, pretendo que a estrutura Vertex seja pré-definida no meu código C ++, então prefiro que o usuário não precise redefini-la no Python (reduz os erros dessa maneira), mas estou não sei como expor uma estrutura contígua como essa. Terceiro, não tenho motivos para tentar a estrutura de tipos, além de não saber outra maneira de fazê-lo. Todas as sugestões são bem-vindas. Finalmente, como esse é (como você deve ter adivinhado) para um aplicativo gráfico, eu preferiria um método mais rápido do que um conveniente, mesmo que o método mais rápido precise de um pouco mais de trabalho.

Obrigado por qualquer ajuda! Ainda estou tentando entender as extensões python, por isso é uma grande ajuda obter informações da comunidade sobre algumas das partes mais difíceis.

[SOLUÇÃO]

Então, primeiro, obrigado a todos que apresentaram suas idéias. Foram muitos boatos que resultaram na resposta final. No final, aqui está o que eu encontrei: a sugestão de Sam de usar o struct.pack acabou sendo acertada. Como estou usando o Python 3, tive que ajustá-lo um pouco, mas quando tudo foi dito e feito, na verdade, um triângulo apareceu na minha tela:

verts = bytes()
verts += struct.pack("fffffI", 0.0, 0.5, 0.0, 0.0, 0.5, 0xFF0000FF)
verts += struct.pack("fffffI", 0.5, -0.5, 0.0, 0.5, -0.5, 0x00FF00FF)
verts += struct.pack("fffffI", -0.5, -0.5, 0.0, -0.5, -0.5, 0x0000FFFF)

device.ReadVertices(verts, 3)

Com minha tupla analisando agora, fica assim:

static PyObject* Device_ReadVertices(Py_Device* self, PyObject* args)
{
    void* py_verts;
    int len, count;

    if(!PyArg_ParseTuple(args, "y#i", &py_verts, &len, &count)) 
        return NULL;

    // Works now!
    Vertex* verts = static_cast<Vertex*>(py_verts);

    self->device->ReadVertices(verts, count);

    Py_RETURN_NONE;
}

Observe que, embora eu não use olen variável neste exemplo (embora eu vá no produto final), preciso analisar a tupla usando 'y #' em vez de apenas 'y'; caso contrário, ela será interrompida no primeiro NULL (de acordo com a documentação). Também deve ser considerado: os vazamentos * lançamentos como esse são bastante perigosos; portanto, faça muito mais verificação de erro do que mostro aqui!

Então, trabalho bem feito, feliz dia, faça as malas e vá para casa, sim?

Esperar! Não tão rápido! Tem mais!

Sentindo-me bem sobre como tudo funcionou, decidi, por um capricho, ver se minha tentativa anterior ainda me explodiu e voltei ao primeiro trecho de python neste post. (Usando o novo código C, é claro) e ... funcionou! Os resultados foram idênticos à versão struct.pack! Uau!

Portanto, isso significa que seus usuários podem escolher como fornecer esse tipo de dados, e seu código pode lidar com isso sem alterações. Pessoalmente, incentivarei o método ctype.Structure, já que acho que facilita a legibilidade, mas, na verdade, é o que quer que o usuário se sinta confortável. (Caramba, eles poderiam digitar manualmente uma sequência de bytes em hexadecimal, se quisessem. Funciona. Tentei.)

Honestamente, acho que esse é o melhor resultado possível, por isso estou em êxtase. Obrigado a todos novamente e boa sorte para quem mais se deparar com esse problema!

questionAnswers(2)

yourAnswerToTheQuestion