Skip to content

Commit d1206d0

Browse files
committed
Add interface{} args & results in cffi callbacks
1 parent 36c319c commit d1206d0

7 files changed

Lines changed: 228 additions & 66 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,8 @@ jobs:
136136
- name: Build
137137
run: go build -v ./...
138138

139-
# The skipped tests use features the cffi backend does not support yet:
140-
# Python callbacks that take an interface{} or return a value
141-
# (TestBindFuncs; see cffi_callback.go for the ones that work), and complex
142-
# numbers as a slice element rather than a plain function arg/return
143-
# (TestBuiltinSlices; see genFuncComplexCFFI in gen_func.go for the case that works).
139+
# The skipped test uses a feature the cffi backend does not support yet:
140+
# complex numbers as a slice element rather than a plain function
141+
# arg/return (see genFuncComplexCFFI in gen_func.go for the case that works).
144142
- name: Test
145-
run: go test -v -skip '^(TestBindFuncs|TestBuiltinSlices)$' ./...
143+
run: go test -v -skip '^TestBuiltinSlices$' ./...

‎_examples/callbacks/callbacks.go‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package callbacks
99
import (
1010
"fmt"
1111
"sync"
12+
"time"
1213
)
1314

1415
// Each calls fun for i in 0..n-1, with a label made from i.
@@ -30,6 +31,49 @@ func Twice(fun func()) {
3031
fun()
3132
}
3233

34+
// Describe calls fun with a string and a fmt.Stringer, as interface{} values.
35+
// They arrive as strings, made by fmt.Sprintf("%s", v).
36+
func Describe(fun func(v interface{})) {
37+
fun("a string")
38+
fun(1500 * time.Millisecond)
39+
}
40+
41+
// Count returns how many of 0..n-1 keep says yes to.
42+
func Count(n int, keep func(i int) bool) int {
43+
total := 0
44+
for i := 0; i < n; i++ {
45+
if keep(i) {
46+
total++
47+
}
48+
}
49+
return total
50+
}
51+
52+
// Sum adds up what val returns for 0..n-1.
53+
func Sum(n int, val func(i int) int) int {
54+
total := 0
55+
for i := 0; i < n; i++ {
56+
total += val(i)
57+
}
58+
return total
59+
}
60+
61+
// Widest returns the largest of what size returns for 0..n-1.
62+
func Widest(n int, size func(i int) uint) uint {
63+
var widest uint
64+
for i := 0; i < n; i++ {
65+
if w := size(i); w > widest {
66+
widest = w
67+
}
68+
}
69+
return widest
70+
}
71+
72+
// Apply returns f(x).
73+
func Apply(x float64, f func(x float64) float64) float64 {
74+
return f(x)
75+
}
76+
3377
// Counter counts how many times it has been visited.
3478
type Counter struct {
3579
N int
@@ -43,6 +87,11 @@ func (c *Counter) Visit(times int, fun func(c *Counter, n int)) {
4387
}
4488
}
4589

90+
// Check calls fun with the counter itself, and reports what it answered.
91+
func (c *Counter) Check(fun func(c *Counter, n int) bool) bool {
92+
return fun(c, c.N)
93+
}
94+
4695
// InGoroutine calls fun from another goroutine, and waits for it.
4796
func InGoroutine(fun func(i int)) {
4897
var wg sync.WaitGroup

‎_examples/callbacks/test.py‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,24 @@ def visit(handle, n):
3131
c.Visit(2, visit)
3232
print("counter:", c.N)
3333

34+
print("--- Describe: an interface{} arrives as a string")
35+
callbacks.Describe(lambda v: print("describe:", repr(v)))
36+
37+
print("--- Count: a bool result")
38+
print("count:", callbacks.Count(10, lambda i: i % 3 == 0))
39+
40+
print("--- Sum: an int result")
41+
print("sum:", callbacks.Sum(5, lambda i: i * i))
42+
43+
print("--- Widest: a uint result")
44+
print("widest:", callbacks.Widest(4, lambda i: i * 10))
45+
46+
print("--- Apply: a float result")
47+
print("apply:", callbacks.Apply(1.5, lambda x: x * 2))
48+
49+
print("--- Counter.Check: a handle argument and a bool result")
50+
print("check:", c.Check(lambda handle, n: callbacks.Counter(handle=handle).N == n))
51+
3452
print("--- a bound method")
3553

3654
class Box(object):

‎bind/cffi_build.py‎

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -160,13 +160,16 @@ def wrapper(name, ret, params, exported):
160160

161161
def callback_setup(pname, ctype, argn):
162162
"""Returns the python source that wraps the callable pname in an
163-
ffi.callback, for the C signature in ctype ("callback:void(int64_t,char*)",
164-
see cffi_callback.go). The wrapper passes it on to Go as _cb_<pname>,
165-
which is kept referenced by this local variable until the Go call
166-
returns: cffi frees a callback as soon as nothing refers to it.
163+
ffi.callback, for the C signature in ctype
164+
("callback:<result or void>(<parameter types>)", see cffi_callback.go).
165+
The wrapper passes it on to Go as _cb_<pname>, which is kept referenced by
166+
this local variable until the Go call returns: cffi frees a callback as
167+
soon as nothing refers to it.
168+
169+
If the callable raises, cffi prints the traceback and returns 0 to Go.
167170
"""
168-
cargs = ctype[len("callback:void("):-1]
169-
ctypes = cargs.split(",") if cargs else []
171+
ret, _, rest = ctype[len("callback:"):].partition("(")
172+
ctypes = [t for t in rest[:-1].split(",") if t]
170173
names = ["a%d" % i for i in range(len(ctypes))]
171174
conv = []
172175
for n, t in zip(names, ctypes):
@@ -176,12 +179,23 @@ def callback_setup(pname, ctype, argn):
176179
conv.append("bool(%s)" % n)
177180
else:
178181
conv.append(n)
179-
cdecl = "void(%s)" % ", ".join("unsigned char" if t == "bool" else t for t in ctypes)
182+
call = "%s(%s)" % (pname, ", ".join(conv))
183+
if ret == "void":
184+
body = call
185+
elif ret == "bool":
186+
body = "return 1 if %s else 0" % call
187+
else:
188+
body = "return %s" % call
189+
190+
def cdecl_type(t):
191+
return "unsigned char" if t == "bool" else t
192+
193+
cdecl = "%s(%s)" % (cdecl_type(ret), ", ".join(cdecl_type(t) for t in ctypes))
180194
return "\n".join([
181195
" if not callable(%s):" % pname,
182196
" raise TypeError('argument %d must be callable, not %%s' %% type(%s).__name__)" % (argn, pname),
183197
" def _cbfn_%s(%s):" % (pname, ", ".join(names)),
184-
" %s(%s)" % (pname, ", ".join(conv)),
198+
" %s" % body,
185199
" _cb_%s = _ffi.callback(%r, _cbfn_%s)" % (pname, cdecl, pname),
186200
])
187201

0 commit comments

Comments
 (0)