From d0c627e7a588e20a1c36e47299cfa7079c04dfc2 Mon Sep 17 00:00:00 2001 From: mappu Date: Sat, 10 Aug 2024 10:34:54 +1200 Subject: [PATCH] genbindings: support classes returned by value, with go finalizer --- cmd/genbindings/emitcabi.go | 32 ++++++++++++++++++++++++++------ cmd/genbindings/emitgo.go | 23 +++++++++++++++++++++++ 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/cmd/genbindings/emitcabi.go b/cmd/genbindings/emitcabi.go index bd45639..76fd648 100644 --- a/cmd/genbindings/emitcabi.go +++ b/cmd/genbindings/emitcabi.go @@ -21,6 +21,11 @@ func emitReturnTypeCabi(p CppParameter) string { } else if (p.Pointer || p.ByRef) && p.QtClassType() { return "P" + p.ParameterType // CABI type + } else if p.QtClassType() && !p.Pointer { + // Even if C++ returns by value, CABI is returning a heap copy (new'd, not malloc'd) + return "P" + p.ParameterType // CABI type + // return "void" // Handled separately with an _out pointer + } else { return p.RenderTypeCpp() } @@ -56,6 +61,10 @@ func emitParametersCabi(m CppMethod, selfType string) string { // Go: converted to native Go string if m.ReturnType.ParameterType == "QString" { tmp = append(tmp, "char** _out, size_t* _out_Strlen") + /* + } else if m.ReturnType.QtClassType() && !m.ReturnType.Pointer { + tmp = append(tmp, "P"+m.ReturnType.ParameterType+" _out") + */ } return strings.Join(tmp, ", ") @@ -64,21 +73,24 @@ func emitParametersCabi(m CppMethod, selfType string) string { func emitParametersCABI2CppForwarding(params []CppParameter, selfType string) (preamble string, forwarding string) { tmp := make([]string, 0, len(params)+1) - if selfType != "" { - tmp = append(tmp, "self") - } - for _, p := range params { if p.ParameterType == "QString" { // The CABI has accepted two parameters - need to convert to one real QString // Create it on the stack - preamble += "\tQString " + p.ParameterName + "_QString(" + p.ParameterName + ", " + p.ParameterName + "_Strlen);\n" + preamble += "\tQString " + p.ParameterName + "_QString = QString::fromUtf8(" + p.ParameterName + ", " + p.ParameterName + "_Strlen);\n" tmp = append(tmp, p.ParameterName+"_QString") } else if p.ByRef { // We changed RenderTypeCpp() to render this as a pointer // Need to dereference so we can pass as reference to the actual Qt C++ function - tmp = append(tmp, "*"+p.ParameterName) + //tmp = append(tmp, "*"+p.ParameterName) + tmp = append(tmp, "*static_cast<"+p.ParameterType+"*>("+p.ParameterName+")") + + } else if p.QtClassType() && !p.Pointer { + // CABI takes all Qt types by pointer, even if C++ wants them by value + // Dereference the passed-in pointer + tmp = append(tmp, "*static_cast<"+p.ParameterType+"*>("+p.ParameterName+")") + } else { tmp = append(tmp, p.ParameterName) } @@ -204,6 +216,14 @@ func emitBindingCpp(src *CppParsedHeader, filename string) (string, error) { afterCall += "\t*_out = static_cast(malloc(b.length()));\n" afterCall += "\tmemcpy(*_out, b.data(), b.length());\n" afterCall += "\t*_out_Strlen = b.length();\n" + + } else if m.ReturnType.QtClassType() && !m.ReturnType.Pointer { + shouldReturn = m.ReturnType.ParameterType + " ret = " + // afterCall = "\t// Copy-construct value returned type into Go-allocated POCO\n" + // afterCall += "\t_*out = ret;\n" + afterCall = "\t// Copy-construct value returned type into heap-allocated copy\n" + afterCall += "\treturn static_cast(new " + m.ReturnType.ParameterType + "(ret));\n" + } preamble, forwarding := emitParametersCABI2CppForwarding(m.Parameters, c.ClassName) diff --git a/cmd/genbindings/emitgo.go b/cmd/genbindings/emitgo.go index 4503b82..f236ef2 100644 --- a/cmd/genbindings/emitgo.go +++ b/cmd/genbindings/emitgo.go @@ -138,6 +138,29 @@ import "C" afterword += "ret := C.GoStringN(_out, _out_Strlen)\n" afterword += "C.free(_out)\n" afterword += "return ret" + + } else if m.ReturnType.QtClassType() { + // Construct our Go type based on this inner CABI type + shouldReturn = "ret := " + + if m.ReturnType.Pointer { + afterword = "return " + m.ReturnType.ParameterType + "{h: ret}" + } else { + // This is return by value, but CABI has new'd it into a + // heap type for us + // To preserve Qt's approximate semantics, add a runtime + // finalizer to automatically Delete once the type goes out + // of Go scope + imports["runtime"] = struct{}{} + afterword = "// Qt uses pass-by-value semantics for this type. Mimic with finalizer\n" + afterword += "ret1 := &" + m.ReturnType.ParameterType + "{h: ret}\n" + afterword += "runtime.SetFinalizer(ret1, func(ret2 *" + m.ReturnType.ParameterType + ") {\n" + afterword += "ret2.Delete()\n" + afterword += "runtime.KeepAlive(ret2.h)\n" + afterword += "})\n" + afterword += "return ret1" + } + } ret.WriteString(`