L'esposizione come memoryview
richiede innanzitutto la creazione di Py_buffer
. In Python 3.3+ c'è una comoda funzione di aiuto, PyMemoryView_FromMemory
che fa molto del lavoro per noi. Nelle versioni precedenti se abbiamo bisogno di fare qualche passo in più, quindi il nostro typemap base fuori assomiglia:
%typemap(out) std::vector<uint8_t>&, const std::vector<uint8_t>& {
Py_buffer *buf=(Py_buffer*)malloc(sizeof *buf);
const bool ro = info<$1_type>::is_readonly();
if (PyBuffer_FillInfo(buf, NULL, &((*$1)[0]), (*$1).size(), ro, PyBUF_ND)) {
// error, handle
}
$result = PyMemoryView_FromBuffer(buf);
}
Qui stiamo fondamentalmente l'allocazione della memoria per il Py_buffer
. Questo contiene solo i dettagli del buffer internamente per Python. La memoria che assegniamo sarà di proprietà dell'oggetto memoryview
una volta creata. Purtroppo poiché verrà rilasciato con una chiamata allo free()
, dobbiamo allocarlo con malloc()
, anche se è codice C++.
Oltre Py_buffer
e opzionale Py_Object
PyBuffer_FillInfo
guadagnato un void*
(buffer stesso), la dimensione del buffer, un booleano che indica se è scrivibile e una bandiera. In questo caso il nostro flag indica semplicemente che abbiamo fornito memoria contigua in stile C per il buffer.
Per decidere se è in sola lettura o no, abbiamo utilizzato la variabile integrata $n_type
di SWIG e un helper (che potrebbe essere un tratto di tipo C++ 11 se lo volessimo).
Per completare la nostra interfaccia SWIG dobbiamo prevedere che aiutante e includere il file di intestazione, in modo che il tutto diventa:
%module test
%{
#include "test.hh"
namespace {
template <typename T>
struct info {
static bool is_readonly() {
return false;
}
};
template <typename T>
struct info<const T&> {
static bool is_readonly() {
return true;
}
};
}
%}
%typemap(out) std::vector<uint8_t>&, const std::vector<uint8_t>& {
Py_buffer *buf=(Py_buffer*)malloc(sizeof *buf);
const bool ro = info<$1_type>::is_readonly();
if (PyBuffer_FillInfo(buf, NULL, &((*$1)[0]), (*$1).size(), ro, PyBUF_ND)) {
// error, handle
}
$result = PyMemoryView_FromBuffer(buf);
}
%include "test.hh"
Possiamo quindi verificare con:
import test
print test.vec()
print len(test.vec())
print test.vec()[0]
print test.vec().readonly
test.vec()[0]='z'
print test.vec()[0]
print "This should fail:"
test.cvec()[0] = 0
che ha funzionato come previsto, testato usando Python 2.7.
Rispetto al semplice wrapping tramite std_vector.i questo approccio presenta alcuni inconvenienti. Il più grande è che non possiamo ridimensionare il vettore, o convertirlo in un vettore in seguito banalmente. Potremmo aggirare questo problema, almeno in parte creando un proxy SWIG per il vettore come normale e utilizzando il secondo parametro di PyBuffer_FillInfo
per memorizzarlo internamente. (Questo sarebbe anche necessario se dovessimo gestire la proprietà del vettore per esempio).