Skip to content

Commit e1f3afb

Browse files
committed
Issue #20160: Handled passing of large structs to callbacks correctly.
1 parent 3d36f0f commit e1f3afb

File tree

3 files changed

+65
-2
lines changed

3 files changed

+65
-2
lines changed

Lib/ctypes/test/test_callbacks.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import functools
12
import unittest
23
from ctypes import *
34
from ctypes.test import need_symbol
@@ -246,6 +247,40 @@ def callback(a, b, c, d, e):
246247
self.assertEqual(result,
247248
callback(1.1*1.1, 2.2*2.2, 3.3*3.3, 4.4*4.4, 5.5*5.5))
248249

250+
def test_callback_large_struct(self):
251+
class Check: pass
252+
253+
class X(Structure):
254+
_fields_ = [
255+
('first', c_ulong),
256+
('second', c_ulong),
257+
('third', c_ulong),
258+
]
259+
260+
def callback(check, s):
261+
check.first = s.first
262+
check.second = s.second
263+
check.third = s.third
264+
265+
check = Check()
266+
s = X()
267+
s.first = 0xdeadbeef
268+
s.second = 0xcafebabe
269+
s.third = 0x0bad1dea
270+
271+
CALLBACK = CFUNCTYPE(None, X)
272+
dll = CDLL(_ctypes_test.__file__)
273+
func = dll._testfunc_cbk_large_struct
274+
func.argtypes = (X, CALLBACK)
275+
func.restype = None
276+
# the function just calls the callback with the passed structure
277+
func(s, CALLBACK(functools.partial(callback, check)))
278+
self.assertEqual(check.first, s.first)
279+
self.assertEqual(check.second, s.second)
280+
self.assertEqual(check.third, s.third)
281+
self.assertEqual(check.first, 0xdeadbeef)
282+
self.assertEqual(check.second, 0xcafebabe)
283+
self.assertEqual(check.third, 0x0bad1dea)
249284

250285
################################################################
251286

Modules/_ctypes/_ctypes_test.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,24 @@ _testfunc_cbk_reg_double(double a, double b, double c, double d, double e,
3434
return func(a*a, b*b, c*c, d*d, e*e);
3535
}
3636

37+
/*
38+
* This structure should be the same as in test_callbacks.py and the
39+
* method test_callback_large_struct. See issues 17310 and 20160: the
40+
* structure must be larger than 8 bytes long.
41+
*/
42+
43+
typedef struct {
44+
unsigned long first;
45+
unsigned long second;
46+
unsigned long third;
47+
} Test;
48+
49+
EXPORT(void)
50+
_testfunc_cbk_large_struct(Test in, void (*func)(Test))
51+
{
52+
func(in);
53+
}
54+
3755
EXPORT(void)testfunc_array(int values[4])
3856
{
3957
printf("testfunc_array %d %d %d %d\n",

Modules/_ctypes/libffi_msvc/ffi.c

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ ffi_prep_incoming_args_SYSV(char *stack, void **rvalue,
359359

360360
if ( cif->rtype->type == FFI_TYPE_STRUCT ) {
361361
*rvalue = *(void **) argp;
362-
argp += 4;
362+
argp += sizeof(void *);
363363
}
364364

365365
p_argv = avalue;
@@ -370,13 +370,23 @@ ffi_prep_incoming_args_SYSV(char *stack, void **rvalue,
370370

371371
/* Align if necessary */
372372
if ((sizeof(char *) - 1) & (size_t) argp) {
373-
argp = (char *) ALIGN(argp, sizeof(char*));
373+
argp = (char *) ALIGN(argp, sizeof(char*));
374374
}
375375

376376
z = (*p_arg)->size;
377377

378378
/* because we're little endian, this is what it turns into. */
379379

380+
#ifdef _WIN64
381+
if (z > 8) {
382+
/* On Win64, if a single argument takes more than 8 bytes,
383+
* then it is always passed by reference.
384+
*/
385+
*p_argv = *((void**) argp);
386+
z = 8;
387+
}
388+
else
389+
#endif
380390
*p_argv = (void*) argp;
381391

382392
p_argv++;

0 commit comments

Comments
 (0)