Skip to content

Commit c13e305

Browse files
committed
populate Py_buffer strides field in ctypes arrays
1 parent 4b250fc commit c13e305

6 files changed

Lines changed: 205 additions & 57 deletions

File tree

Lib/ctypes/test/test_pep3118.py

Lines changed: 57 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import unittest
22
from ctypes import *
33
import re, sys
4+
from _ctypes_test import buffer_info
45

56
if sys.byteorder == "little":
67
THIS_ENDIAN = "<"
@@ -20,7 +21,7 @@ def normalize(format):
2021
class Test(unittest.TestCase):
2122

2223
def test_native_types(self):
23-
for tp, fmt, shape, itemtp in native_types:
24+
for tp, fmt, shape, stride, itemtp in native_types:
2425
ob = tp()
2526
v = memoryview(ob)
2627
try:
@@ -31,10 +32,7 @@ def test_native_types(self):
3132
self.assertEqual(len(v) * sizeof(itemtp), sizeof(ob))
3233
self.assertEqual(v.itemsize, sizeof(itemtp))
3334
self.assertEqual(v.shape, shape)
34-
# XXX Issue #12851: PyCData_NewGetBuffer() must provide strides
35-
# if requested. memoryview currently reconstructs missing
36-
# stride information, so this assert will fail.
37-
# self.assertEqual(v.strides, ())
35+
self.assertEqual(v.strides, stride)
3836

3937
# they are always read/write
4038
self.assertFalse(v.readonly)
@@ -49,6 +47,23 @@ def test_native_types(self):
4947
print(tp)
5048
raise
5149

50+
def test_native_types_shape_strides(self):
51+
# check that ctypes (not memoryview) correctly fills out shape and strides in buffer protocol
52+
for tp, fmt, shape, stride, itemtp in native_types:
53+
ob = tp()
54+
v = buffer_info(ob)
55+
try:
56+
if v['ndim'] == 0:
57+
self.assertEqual(v['shape'], None)
58+
self.assertEqual(v['strides'], None)
59+
else:
60+
self.assertEqual(v['shape'], shape)
61+
self.assertEqual(v['strides'], stride)
62+
except:
63+
# so that we can see the failing type
64+
print(tp)
65+
raise
66+
5267
def test_endian_types(self):
5368
for tp, fmt, shape, itemtp in endian_types:
5469
ob = tp()
@@ -61,8 +76,7 @@ def test_endian_types(self):
6176
self.assertEqual(len(v) * sizeof(itemtp), sizeof(ob))
6277
self.assertEqual(v.itemsize, sizeof(itemtp))
6378
self.assertEqual(v.shape, shape)
64-
# XXX Issue #12851
65-
# self.assertEqual(v.strides, ())
79+
self.assertEqual(v.strides, ())
6680

6781
# they are always read/write
6882
self.assertFalse(v.readonly)
@@ -141,73 +155,73 @@ class Complete(Structure):
141155

142156

143157
native_types = [
144-
# type format shape calc itemsize
158+
# type format shape stride calc itemsize
145159

146160
## simple types
147161

148-
(c_char, "<c", (), c_char),
149-
(c_byte, "<b", (), c_byte),
150-
(c_ubyte, "<B", (), c_ubyte),
151-
(c_short, "<" + s_short, (), c_short),
152-
(c_ushort, "<" + s_ushort, (), c_ushort),
162+
(c_char, "<c", (), (), c_char),
163+
(c_byte, "<b", (), (), c_byte),
164+
(c_ubyte, "<B", (), (), c_ubyte),
165+
(c_short, "<" + s_short, (), (), c_short),
166+
(c_ushort, "<" + s_ushort, (), (), c_ushort),
153167

154-
(c_int, "<" + s_int, (), c_int),
155-
(c_uint, "<" + s_uint, (), c_uint),
168+
(c_int, "<" + s_int, (), (), c_int),
169+
(c_uint, "<" + s_uint, (), (), c_uint),
156170

157-
(c_long, "<" + s_long, (), c_long),
158-
(c_ulong, "<" + s_ulong, (), c_ulong),
171+
(c_long, "<" + s_long, (), (), c_long),
172+
(c_ulong, "<" + s_ulong, (), (), c_ulong),
159173

160-
(c_longlong, "<" + s_longlong, (), c_longlong),
161-
(c_ulonglong, "<" + s_ulonglong, (), c_ulonglong),
174+
(c_longlong, "<" + s_longlong, (), (), c_longlong),
175+
(c_ulonglong, "<" + s_ulonglong, (), (), c_ulonglong),
162176

163-
(c_float, "<f", (), c_float),
164-
(c_double, "<d", (), c_double),
177+
(c_float, "<f", (), (), c_float),
178+
(c_double, "<d", (), (), c_double),
165179

166-
(c_longdouble, "<" + s_longdouble, (), c_longdouble),
180+
(c_longdouble, "<" + s_longdouble, (), (), c_longdouble),
167181

168-
(c_bool, "<" + s_bool, (), c_bool),
169-
(py_object, "<O", (), py_object),
182+
(c_bool, "<" + s_bool, (), (), c_bool),
183+
(py_object, "<O", (), (), py_object),
170184

171185
## pointers
172186

173-
(POINTER(c_byte), "&<b", (), POINTER(c_byte)),
174-
(POINTER(POINTER(c_long)), "&&<" + s_long, (), POINTER(POINTER(c_long))),
187+
(POINTER(c_byte), "&<b", (), (), POINTER(c_byte)),
188+
(POINTER(POINTER(c_long)), "&&<" + s_long, (), (), POINTER(POINTER(c_long))),
175189

176190
## arrays and pointers
177191

178-
(c_double * 4, "<d", (4,), c_double),
179-
(c_float * 4 * 3 * 2, "<f", (2,3,4), c_float),
180-
(POINTER(c_short) * 2, "&<" + s_short, (2,), POINTER(c_short)),
181-
(POINTER(c_short) * 2 * 3, "&<" + s_short, (3,2,), POINTER(c_short)),
182-
(POINTER(c_short * 2), "&(2)<" + s_short, (), POINTER(c_short)),
192+
(c_double * 4, "<d", (4,), (sizeof(c_double),), c_double),
193+
(c_float * 4 * 3 * 2, "<f", (2,3,4), (3*4*sizeof(c_float), 4*sizeof(c_float), sizeof(c_float)), c_float),
194+
(POINTER(c_short) * 2, "&<" + s_short, (2,), (sizeof(POINTER(c_short)),), POINTER(c_short)),
195+
(POINTER(c_short) * 2 * 3, "&<" + s_short, (3,2,), (2*sizeof(POINTER(c_short),), sizeof(POINTER(c_short))), POINTER(c_short)),
196+
(POINTER(c_short * 2), "&(2)<" + s_short, (), (), POINTER(c_short)),
183197

184198
## structures and unions
185199

186-
(Point, "T{<l:x:<l:y:}".replace('l', s_long), (), Point),
200+
(Point, "T{<l:x:<l:y:}".replace('l', s_long), (), (), Point),
187201
# packed structures do not implement the pep
188-
(PackedPoint, "B", (), PackedPoint),
189-
(Point2, "T{<l:x:<l:y:}".replace('l', s_long), (), Point2),
190-
(EmptyStruct, "T{}", (), EmptyStruct),
202+
(PackedPoint, "B", (), (), PackedPoint),
203+
(Point2, "T{<l:x:<l:y:}".replace('l', s_long), (), (), Point2),
204+
(EmptyStruct, "T{}", (), (), EmptyStruct),
191205
# the pep doesn't support unions
192-
(aUnion, "B", (), aUnion),
206+
(aUnion, "B", (), (), aUnion),
193207
# structure with sub-arrays
194-
(StructWithArrays, "T{(2,3)<l:x:(4)T{<l:x:<l:y:}:y:}".replace('l', s_long), (), StructWithArrays),
195-
(StructWithArrays * 3, "T{(2,3)<l:x:(4)T{<l:x:<l:y:}:y:}".replace('l', s_long), (3,), StructWithArrays),
208+
(StructWithArrays, "T{(2,3)<l:x:(4)T{<l:x:<l:y:}:y:}".replace('l', s_long), (), (), StructWithArrays),
209+
(StructWithArrays * 3, "T{(2,3)<l:x:(4)T{<l:x:<l:y:}:y:}".replace('l', s_long), (3,), (sizeof(StructWithArrays),), StructWithArrays),
196210

197211
## pointer to incomplete structure
198-
(Incomplete, "B", (), Incomplete),
199-
(POINTER(Incomplete), "&B", (), POINTER(Incomplete)),
212+
(Incomplete, "B", (), (), Incomplete),
213+
(POINTER(Incomplete), "&B", (), (), POINTER(Incomplete)),
200214

201215
# 'Complete' is a structure that starts incomplete, but is completed after the
202216
# pointer type to it has been created.
203-
(Complete, "T{<l:a:}".replace('l', s_long), (), Complete),
217+
(Complete, "T{<l:a:}".replace('l', s_long), (), (), Complete),
204218
# Unfortunately the pointer format string is not fixed...
205-
(POINTER(Complete), "&B", (), POINTER(Complete)),
219+
(POINTER(Complete), "&B", (), (), POINTER(Complete)),
206220

207221
## other
208222

209223
# function signatures are not implemented
210-
(CFUNCTYPE(None), "X{}", (), CFUNCTYPE(None)),
224+
(CFUNCTYPE(None), "X{}", (), (), CFUNCTYPE(None)),
211225

212226
]
213227

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fixed ctypes Arrays Py_buffer implimentation to provide strides if
2+
requested.

Modules/_ctypes/_ctypes.c

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1495,7 +1495,7 @@ PyCArrayType_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
14951495
if (stgdict->format == NULL)
14961496
goto error;
14971497
stgdict->ndim = itemdict->ndim + 1;
1498-
stgdict->shape = PyMem_Malloc(sizeof(Py_ssize_t) * stgdict->ndim);
1498+
stgdict->shape = PyMem_Malloc(sizeof(Py_ssize_t) * stgdict->ndim * 2);
14991499
if (stgdict->shape == NULL) {
15001500
PyErr_NoMemory();
15011501
goto error;
@@ -1505,6 +1505,14 @@ PyCArrayType_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
15051505
memmove(&stgdict->shape[1], itemdict->shape,
15061506
sizeof(Py_ssize_t) * (stgdict->ndim - 1));
15071507
}
1508+
stgdict->strides = stgdict->shape + stgdict->ndim;
1509+
if (stgdict->ndim > 1) {
1510+
memmove(&stgdict->strides[1], itemdict->strides,
1511+
sizeof(Py_ssize_t) * (stgdict->ndim - 1));
1512+
stgdict->strides[0] = stgdict->strides[1] * stgdict->shape[1];
1513+
} else {
1514+
stgdict->strides[0] = itemdict->size;
1515+
}
15081516

15091517
itemsize = itemdict->size;
15101518
if (length * itemsize < 0) {
@@ -2681,26 +2689,34 @@ static int PyCData_NewGetBuffer(PyObject *myself, Py_buffer *view, int flags)
26812689
{
26822690
CDataObject *self = (CDataObject *)myself;
26832691
StgDictObject *dict = PyObject_stgdict(myself);
2684-
Py_ssize_t i;
26852692

26862693
if (view == NULL) return 0;
26872694

2695+
if ((flags & PyBUF_F_CONTIGUOUS) == PyBUF_F_CONTIGUOUS) {
2696+
view->obj = NULL;
2697+
PyErr_Format(PyExc_TypeError, "Fortran contiguous buffer is not supported");
2698+
return -1;
2699+
}
2700+
26882701
view->buf = self->b_ptr;
26892702
view->obj = myself;
26902703
Py_INCREF(myself);
26912704
view->len = self->b_size;
26922705
view->readonly = 0;
2693-
/* use default format character if not set */
2694-
view->format = dict->format ? dict->format : "B";
2706+
if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT) {
2707+
/* use default format character if not set */
2708+
view->format = dict->format ? dict->format : "B";
2709+
} else {
2710+
view->format = NULL;
2711+
}
26952712
view->ndim = dict->ndim;
2696-
view->shape = dict->shape;
2697-
view->itemsize = self->b_size;
2698-
if (view->itemsize) {
2699-
for (i = 0; i < view->ndim; ++i) {
2700-
view->itemsize /= dict->shape[i];
2701-
}
2713+
view->shape = ((flags & PyBUF_ND) == PyBUF_ND) ? dict->shape : NULL;
2714+
if (dict->strides) {
2715+
view->itemsize = dict->strides[dict->ndim - 1];
2716+
} else {
2717+
view->itemsize = self->b_size;
27022718
}
2703-
view->strides = NULL;
2719+
view->strides = ((flags & PyBUF_STRIDES) == PyBUF_STRIDES) ? dict->strides : NULL;
27042720
view->suboffsets = NULL;
27052721
view->internal = NULL;
27062722
return 0;

Modules/_ctypes/_ctypes_test.c

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,12 +475,121 @@ EXPORT(int) unpack_bitfields(struct BITS *bits, char name)
475475
return 999;
476476
}
477477

478+
PyObject *py_getBufferInfo(PyObject *self, PyObject *obj)
479+
{
480+
int flags = PyBUF_FULL;
481+
int ii;
482+
Py_buffer view;
483+
PyObject *v;
484+
PyObject *x;
485+
PyObject *d;
486+
int buffer_err;
487+
488+
d = PyDict_New();
489+
if (NULL == d) Py_RETURN_NONE;
490+
buffer_err = PyObject_GetBuffer(obj, &view, flags);
491+
v = PyLong_FromLong(buffer_err);
492+
if (NULL != v){
493+
PyDict_SetItemString(d, "err", v);
494+
Py_DECREF(v);
495+
}
496+
497+
if (0 == buffer_err) {
498+
v = PyLong_FromSsize_t(view.len);
499+
if (NULL != v){
500+
PyDict_SetItemString(d, "len", v);
501+
Py_DECREF(v);
502+
}
503+
v = PyBool_FromLong(view.readonly);
504+
if (NULL != v){
505+
PyDict_SetItemString(d, "readonly", v);
506+
Py_DECREF(v);
507+
}
508+
v = PyLong_FromSsize_t(view.itemsize);
509+
if (NULL != v){
510+
PyDict_SetItemString(d, "itemsize", v);
511+
Py_DECREF(v);
512+
}
513+
if(NULL == view.format) {
514+
v = PyUnicode_New(0, 0);
515+
} else {
516+
v = PyUnicode_FromString(view.format);
517+
}
518+
if (NULL != v){
519+
PyDict_SetItemString(d, "format", v);
520+
Py_DECREF(v);
521+
}
522+
v = PyLong_FromLong(view.ndim);
523+
if (NULL != v){
524+
PyDict_SetItemString(d, "ndim", v);
525+
Py_DECREF(v);
526+
}
527+
if (NULL == view.shape) {
528+
PyDict_SetItemString(d, "shape", Py_None);
529+
} else {
530+
v = PyTuple_New(view.ndim);
531+
if (NULL != v){
532+
for (ii=0; ii<view.ndim; ++ii) {
533+
x = PyLong_FromSsize_t(view.shape[ii]);
534+
if (NULL == x) {
535+
Py_DECREF(v);
536+
Py_DECREF(d);
537+
return NULL;
538+
}
539+
PyTuple_SetItem(v, ii, x);
540+
}
541+
PyDict_SetItemString(d, "shape", v);
542+
Py_DECREF(v);
543+
}
544+
}
545+
if (NULL == view.strides) {
546+
PyDict_SetItemString(d, "strides", Py_None);
547+
} else {
548+
v = PyTuple_New(view.ndim);
549+
if (NULL != v){
550+
for (ii=0; ii<view.ndim; ++ii) {
551+
x = PyLong_FromSsize_t(view.strides[ii]);
552+
if (NULL == x) {
553+
Py_DECREF(v);
554+
Py_DECREF(d);
555+
return NULL;
556+
}
557+
PyTuple_SetItem(v, ii, x);
558+
}
559+
PyDict_SetItemString(d, "strides", v);
560+
Py_DECREF(v);
561+
}
562+
}
563+
if (NULL == view.suboffsets) {
564+
PyDict_SetItemString(d, "suboffsets", Py_None);
565+
} else {
566+
v = PyTuple_New(view.ndim);
567+
if (NULL != v){
568+
for (ii=0; ii<view.ndim; ++ii) {
569+
x = PyLong_FromSsize_t(view.suboffsets[ii]);
570+
if (NULL == x) {
571+
Py_DECREF(v);
572+
Py_DECREF(d);
573+
return NULL;
574+
}
575+
PyTuple_SetItem(v, ii, x);
576+
}
577+
PyDict_SetItemString(d, "suboffsets", v);
578+
Py_DECREF(v);
579+
}
580+
}
581+
PyBuffer_Release(&view);
582+
}
583+
return d;
584+
}
585+
478586
static PyMethodDef module_methods[] = {
479587
/* {"get_last_tf_arg_s", get_last_tf_arg_s, METH_NOARGS},
480588
{"get_last_tf_arg_u", get_last_tf_arg_u, METH_NOARGS},
481589
*/
482590
{"func_si", py_func_si, METH_VARARGS},
483591
{"func", py_func, METH_NOARGS},
592+
{"buffer_info", py_getBufferInfo, METH_O},
484593
{ NULL, NULL, 0, NULL},
485594
};
486595

Modules/_ctypes/ctypes.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,11 +208,11 @@ typedef struct {
208208
PyObject *checker;
209209
int flags; /* calling convention and such */
210210

211-
/* pep3118 fields, pointers neeed PyMem_Free */
211+
/* pep3118 fields, pointers need PyMem_Free */
212212
char *format;
213213
int ndim;
214214
Py_ssize_t *shape;
215-
/* Py_ssize_t *strides; */ /* unused in ctypes */
215+
Py_ssize_t *strides; /* offset from *shape, not necessary to PyMem_Free */
216216
/* Py_ssize_t *suboffsets; */ /* unused in ctypes */
217217

218218
} StgDictObject;

0 commit comments

Comments
 (0)