genbindings/slots: emit per-slot callback bindings

This commit is contained in:
mappu 2024-09-14 19:31:39 +12:00
parent 69767d7d04
commit 7f56b4147c
5 changed files with 87 additions and 60 deletions

View File

@ -7,6 +7,7 @@ import (
)
func (p CppParameter) RenderTypeCabi() string {
ret := p.ParameterType
switch p.ParameterType {
case "uchar":
@ -334,6 +335,12 @@ func emitAssignCppToCabi(assignExpression string, p CppParameter, rvalue string)
} else if !t.QtClassType() || (t.QtClassType() && t.Pointer) { // QList<int>, QList<QFoo*>
// In some cases rvalue is a function call and the temporary
// is necessary; in some cases it's a literal and the temporary is
// elided; but in some cases it's a Qt class and the temporary goes
// through a copy constructor
// TODO Detect safe cases where this can be optimized
shouldReturn = p.RenderTypeQtCpp() + " " + namePrefix + "_ret = "
afterCall += indent + "// Convert QList<> from C++ memory to manually-managed C memory\n"
@ -695,14 +702,32 @@ func emitBindingCpp(src *CppParsedHeader, filename string) (string, error) {
}
if m.IsSignal {
bindingFunc := "miqt_exec_callback_" + cabiClassName(c.ClassName) + "_" + m.SafeMethodName()
// If there are hidden parameters, the type of the signal itself
// needs to include them
exactSignal := `static_cast<void (` + c.ClassName + `::*)(` + emitParameterTypesCpp(m, true) + `)>(&` + c.ClassName + `::` + m.CppCallTarget() + `)`
paramArgs := []string{"slot"}
paramArgDefs := []string{"void* cb"}
var signalCode string
for i, p := range m.Parameters {
signalCode += emitAssignCppToCabi(fmt.Sprintf("\t\t%s sigval%d = ", emitReturnTypeCabi(p), i+1), p, p.ParameterName)
paramArgs = append(paramArgs, fmt.Sprintf("sigval%d", i+1))
paramArgDefs = append(paramArgDefs, emitReturnTypeCabi(p)+" "+p.ParameterName)
}
signalCode += "\t\t" + bindingFunc + "(" + strings.Join(paramArgs, `, `) + ");\n"
ret.WriteString(
`void ` + cClassName + `_connect_` + m.SafeMethodName() + `(` + cClassName + `* self, void* slot) {` + "\n" +
"void " + bindingFunc + "(" + strings.Join(paramArgDefs, `, `) + ");\n" +
"\n" +
`void ` + cClassName + `_connect_` + m.SafeMethodName() + `(` + cClassName + `* self, void* slot) {` + "\n" +
"\t" + c.ClassName + `::connect(self, ` + exactSignal + `, self, [=](` + emitParametersCpp(m) + `) {` + "\n" +
"\t\t" + `miqt_exec_callback(slot, 0, nullptr);` + "\n" +
signalCode +
"\t});\n" +
"}\n" +
"\n",

View File

@ -106,8 +106,13 @@ func (p CppParameter) RenderTypeGo() string {
func (p CppParameter) parameterTypeCgo() string {
if p.ParameterType == "QString" {
return "C.char"
return "*C.struct_miqt_string"
}
if _, ok := p.QListOf(); ok {
return "*C.struct_miqt_array"
}
tmp := strings.Replace(p.RenderTypeCabi(), `*`, "", -1)
if strings.HasPrefix(tmp, "const ") {
tmp = tmp[6:] // Constness doesn't survive the CABI boundary
@ -119,7 +124,13 @@ func (p CppParameter) parameterTypeCgo() string {
tmp = "s" + tmp[7:] // Cgo uses schar
}
tmp = strings.Replace(tmp, `long long`, `longlong`, -1)
return "C." + strings.Replace(tmp, " ", "_", -1)
tmp = "C." + strings.Replace(tmp, " ", "_", -1)
if p.QtClassType() || p.Pointer || p.ByRef {
return "*" + tmp
} else {
return tmp
}
}
func emitParametersGo(params []CppParameter) string {
@ -194,12 +205,14 @@ func (gfs *goFileState) emitParametersGo2CABIForwarding(m CppMethod) (preamble s
// Go: convert T[] -> t* and len
// CABI: create a real QList<>
gfs.imports["runtime"] = struct{}{}
gfs.imports["unsafe"] = struct{}{}
if listType.ParameterType == "QString" {
// Combo
gfs.imports["unsafe"] = struct{}{}
preamble += "// For the C ABI, malloc two C arrays; raw char* pointers and their lengths\n"
preamble += p.ParameterName + "_CArray := (*[0xffff]*" + listType.parameterTypeCgo() + ")(C.malloc(C.size_t(8 * len(" + p.ParameterName + "))))\n"
preamble += p.ParameterName + "_CArray := (*[0xffff]" + listType.parameterTypeCgo() + ")(C.malloc(C.size_t(8 * len(" + p.ParameterName + "))))\n"
preamble += "defer C.free(unsafe.Pointer(" + p.ParameterName + "_CArray))\n"
preamble += "for i := range " + p.ParameterName + "{\n"
@ -211,16 +224,12 @@ func (gfs *goFileState) emitParametersGo2CABIForwarding(m CppMethod) (preamble s
preamble += p.ParameterName + "_ma := &C.struct_miqt_array{len: C.size_t(len(" + p.ParameterName + ")), data: unsafe.Pointer(" + p.ParameterName + "_CArray)}\n"
preamble += "defer runtime.KeepAlive(unsafe.Pointer(" + p.ParameterName + "_ma))\n"
tmp = append(tmp, p.ParameterName)
tmp = append(tmp, p.ParameterName+"_ma")
} else {
preamble += "// For the C ABI, malloc a C array of raw pointers\n"
if listType.QtClassType() {
preamble += p.ParameterName + "_CArray := (*[0xffff]*" + listType.parameterTypeCgo() + ")(C.malloc(C.size_t(8 * len(" + p.ParameterName + "))))\n"
} else {
preamble += p.ParameterName + "_CArray := (*[0xffff]" + listType.parameterTypeCgo() + ")(C.malloc(C.size_t(8 * len(" + p.ParameterName + "))))\n"
}
preamble += p.ParameterName + "_CArray := (*[0xffff]" + listType.parameterTypeCgo() + ")(C.malloc(C.size_t(8 * len(" + p.ParameterName + "))))\n"
preamble += "defer C.free(unsafe.Pointer(" + p.ParameterName + "_CArray))\n"
preamble += "for i := range " + p.ParameterName + "{\n"
@ -234,7 +243,7 @@ func (gfs *goFileState) emitParametersGo2CABIForwarding(m CppMethod) (preamble s
preamble += p.ParameterName + "_ma := &C.struct_miqt_array{len: C.size_t(len(" + p.ParameterName + ")), data: unsafe.Pointer(" + p.ParameterName + "_CArray)}\n"
preamble += "defer runtime.KeepAlive(unsafe.Pointer(" + p.ParameterName + "_ma))\n"
tmp = append(tmp, p.ParameterName)
tmp = append(tmp, p.ParameterName+"_ma")
}
} else if p.Pointer && p.ParameterType == "char" {
@ -252,7 +261,7 @@ func (gfs *goFileState) emitParametersGo2CABIForwarding(m CppMethod) (preamble s
} else if p.IntType() || p.ParameterType == "bool" {
if p.Pointer || p.ByRef {
gfs.imports["unsafe"] = struct{}{}
tmp = append(tmp, "(*"+p.parameterTypeCgo()+")(unsafe.Pointer("+p.ParameterName+"))") // n.b. This may not work if the integer type conversion was wrong
tmp = append(tmp, "("+p.parameterTypeCgo()+")(unsafe.Pointer("+p.ParameterName+"))") // n.b. This may not work if the integer type conversion was wrong
} else {
tmp = append(tmp, "("+p.parameterTypeCgo()+")("+p.ParameterName+")")
}
@ -526,13 +535,34 @@ import "C"
if m.IsSignal {
gfs.imports["unsafe"] = struct{}{}
gfs.imports["runtime/cgo"] = struct{}{}
ret.WriteString(`func (this *` + goClassName + `) On` + m.SafeMethodName() + `(slot func()) {
var slotWrapper miqtCallbackFunc = func(argc C.int, args *C.void) {
var cgoNamedParams []string
var paramNames []string
for _, pp := range m.Parameters {
cgoNamedParams = append(cgoNamedParams, pp.ParameterName+" "+pp.parameterTypeCgo())
paramNames = append(paramNames, pp.ParameterName)
}
ret.WriteString(`func (this *` + goClassName + `) On` + m.SafeMethodName() + `(slot func(` + emitParametersGo(m.Parameters) + `)) {
slotWrapper := func(` + strings.Join(cgoNamedParams, `, `) + `) {
// TODO convert CABI parameters to Go parameters here
slot()
}
C.` + goClassName + `_connect_` + m.SafeMethodName() + `(this.h, unsafe.Pointer(uintptr(cgo.NewHandle(slotWrapper))))
}
//export miqt_exec_callback_` + goClassName + `_` + m.SafeMethodName() + `
func miqt_exec_callback_` + goClassName + `_` + m.SafeMethodName() + `(cb *C.void` + ifv(len(m.Parameters) > 0, ", ", "") + strings.Join(cgoNamedParams, `, `) + `) {
cfunc, ok := (cgo.Handle(uintptr(unsafe.Pointer(cb))).Value()).(func(` + strings.Join(cgoNamedParams, `, `) + `))
if !ok {
panic("miqt: callback of non-callback type (heap corruption?)")
}
cfunc(` + strings.Join(paramNames, `, `) + ` )
}
`)
}
}

View File

@ -2,23 +2,24 @@ package qt
// SPDX-License-Identifier: MIT
/*
#include "binding.h"
struct miqt_string* miqt_strdupg(_GoString_ gs) {
return miqt_strdup(_GoStringPtr(gs), _GoStringLen(gs));
}
*/
import "C"
import (
"C"
"runtime/cgo"
"unsafe"
)
type miqtCallbackFunc func(argc C.int, args *C.void)
//export miqt_exec_callback
func miqt_exec_callback(cb *C.void, argc C.int, args *C.void) {
// Our CABI for all callbacks is void(int, void*).
// Our Go ABI is CallbackFunc
// Then the Go bindings can unmarshal the arguments and C.free() them as necessary
cfunc, ok := (cgo.Handle(uintptr(unsafe.Pointer(cb))).Value()).(miqtCallbackFunc)
if !ok {
panic("miqt: callback of non-callback type (heap corruption?)")
}
cfunc(argc, args)
// miqt_strdupg will strdup a Go string into a miqt_string*.
// It is typed as returning an unsafe.Pointer because Cgo types cannot be shared
// across Go file boundaries.
func miqt_strdupg(s string) unsafe.Pointer {
return unsafe.Pointer(C.miqt_strdupg(s))
}

View File

@ -5,10 +5,6 @@
extern "C" {
#endif
// miqt_exec_callback calls a Go function pointer for a connect() slot.
// The function is defined in Go.
void miqt_exec_callback(void* cb, int argc, void* argv);
struct miqt_string {
size_t len;
char data; // Data continues after this element, all in the same allocation

View File

@ -1,25 +0,0 @@
package qt
// SPDX-License-Identifier: MIT
/*
#include "binding.h"
struct miqt_string* miqt_strdupg(_GoString_ gs) {
return miqt_strdup(_GoStringPtr(gs), _GoStringLen(gs));
}
*/
import "C"
import (
"unsafe"
)
// miqt_strdupg will strdup a Go string into a miqt_string*.
// It is typed as returning an unsafe.Pointer because Cgo types cannot be shared
// across Go file boundaries.
func miqt_strdupg(s string) unsafe.Pointer {
return unsafe.Pointer(C.miqt_strdupg(s))
}