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!