Skip to content

Commit 7d1bbae

Browse files
committed
Issue #20160: Handled passing of large structs to callbacks correctly.
1 parent add7731 commit 7d1bbae

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
import _ctypes_test
@@ -243,6 +244,40 @@ def callback(a, b, c, d, e):
243244
self.assertEqual(result,
244245
callback(1.1*1.1, 2.2*2.2, 3.3*3.3, 4.4*4.4, 5.5*5.5))
245246

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

247282
################################################################
248283

Modules/_ctypes/_ctypes_test.c

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

29+
/*
30+
* This structure should be the same as in test_callbacks.py and the
31+
* method test_callback_large_struct. See issues 17310 and 20160: the
32+
* structure must be larger than 8 bytes long.
33+
*/
34+
35+
typedef struct {
36+
unsigned long first;
37+
unsigned long second;
38+
unsigned long third;
39+
} Test;
40+
41+
EXPORT(void)
42+
_testfunc_cbk_large_struct(Test in, void (*func)(Test))
43+
{
44+
func(in);
45+
}
46+
2947
EXPORT(void)testfunc_array(int values[4])
3048
{
3149
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
@@ -340,7 +340,7 @@ ffi_prep_incoming_args_SYSV(char *stack, void **rvalue,
340340

341341
if ( cif->rtype->type == FFI_TYPE_STRUCT ) {
342342
*rvalue = *(void **) argp;
343-
argp += 4;
343+
argp += sizeof(void *);
344344
}
345345

346346
p_argv = avalue;
@@ -351,13 +351,23 @@ ffi_prep_incoming_args_SYSV(char *stack, void **rvalue,
351351

352352
/* Align if necessary */
353353
if ((sizeof(char *) - 1) & (size_t) argp) {
354-
argp = (char *) ALIGN(argp, sizeof(char*));
354+
argp = (char *) ALIGN(argp, sizeof(char*));
355355
}
356356

357357
z = (*p_arg)->size;
358358

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

361+
#ifdef _WIN64
362+
if (z > 8) {
363+
/* On Win64, if a single argument takes more than 8 bytes,
364+
* then it is always passed by reference.
365+
*/
366+
*p_argv = *((void**) argp);
367+
z = 8;
368+
}
369+
else
370+
#endif
361371
*p_argv = (void*) argp;
362372

363373
p_argv++;

0 commit comments

Comments
 (0)