Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add QMap<> binding #56

Merged
merged 11 commits into from
Nov 4, 2024
  •  
  •  
  •  
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,12 @@ MIQT is a clean-room binding that does not use any code from other Qt bindings.

Most functions are implemented 1:1. [The Qt documentation](https://doc.qt.io/qt-5/classes.html) should be used.

The `QByteArray`, `QString`, `QList<T>`, and `QVector<T>` types are projected as plain Go `[]byte`, `string`, and `[]T`. Therefore, you can't call any of QByteArray/QString/QList/QVector's helper methods, you must use some Go equivalent method instead.
The `QByteArray`, `QString`, `QList<T>`, `QVector<T>`, `QMap<K,V>`, `QHash<K,V>` types are projected as plain Go `[]byte`, `string`, `[]T`, and `map[K]V`. Therefore, you can't call any of the Qt type's methods, you must use some Go equivalent method instead.

- Go strings are internally converted to QString using `QString::fromUtf8`. Therefore, the Go string must be UTF-8 to avoid [mojibake](https://en.wikipedia.org/wiki/Mojibake). If the Go string contains binary data, the conversion would corrupt such bytes into U+FFFD (�). On return to Go space, this becomes `\xEF\xBF\xBD`.

- The iteration order of a Qt QMap/QHash will differ from the Go map iteration order. QMap is iterated by key order, but Go maps and QHash iterate in an undefined internal order.

Where Qt returns a C++ object by value (e.g. `QSize`), the binding may have moved it to the heap, and in Go this may be represented as a pointer type. In such cases, a Go finalizer is added to automatically delete the heap object. This means code using MIQT can look basically similar to the Qt C++ equivalent code.

The `connect(sourceObject, sourceSignal, targetObject, targetSlot)` is projected as `targetObject.onSourceSignal(func()...)`.
Expand Down Expand Up @@ -189,7 +191,7 @@ pacman -S mingw-w64-ucrt-x86_64-qt6-base # For Qt 6
go build -ldflags "-s -w -H windowsgui"
```

- Note: the MSYS2 `qt5-base` package [links against `libicu`](https://github.com/msys2/MINGW-packages/blob/master/mingw-w64-qt5-base/PKGBUILD#L241), whereas the Fsu0413 Qt packages do not. When using MSYS2, your distribution size including `.dll` files will be larger.
- Note: the MSYS2 `qt5-base` package [is built to use `libicu`](https://github.com/msys2/MINGW-packages/blob/master/mingw-w64-qt5-base/PKGBUILD#L241), whereas the Fsu0413 Qt packages are not. [ICU is included by default with Windows 10 1703 and later](https://devblogs.microsoft.com/oldnewthing/20210527-00/?p=105255). If you are targeting older versions of Windows, then when using MSYS2, your distribution size including `.dll` files will be larger.

For static linking:

Expand Down Expand Up @@ -254,7 +256,7 @@ See FAQ Q3 for advice about docker performance.

*Tested with Raymii Qt 5.15 / Android SDK 31 / Android NDK 22*

Miqt supports compiling for Android. Some extra steps are required to bridge the Java, C++, Go worlds.
MIQT supports compiling for Android. Some extra steps are required to bridge the Java, C++, Go worlds.

![](doc/android-architecture.png)

Expand Down
20 changes: 17 additions & 3 deletions cmd/genbindings/config-allowlist.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ func AllowHeader(fullpath string) bool {
"qbytearray.h", // QByteArray does not exist in this binding
"qlist.h", // QList does not exist in this binding
"qvector.h", // QVector does not exist in this binding
"qhash.h", // QHash does not exist in this binding
"qmap.h", // QMap does not exist in this binding
"qtcoreexports.h", // Nothing bindable here and has Q_CORE_EXPORT definition issues
"q20algorithm.h", // Qt 6 unstable header
"q20functional.h", // Qt 6 unstable header
Expand Down Expand Up @@ -216,9 +218,6 @@ func AllowMethod(className string, mm CppMethod) error {
// Any type not permitted by AllowClass is also not permitted by this method.
func AllowType(p CppParameter, isReturnType bool) error {

if p.QMapOf() {
return ErrTooComplex // Example???
}
if p.QPairOf() {
return ErrTooComplex // e.g. QGradientStop
}
Expand All @@ -238,6 +237,15 @@ func AllowType(p CppParameter, isReturnType bool) error {
return ErrTooComplex
}
}
if kType, vType, ok := p.QMapOf(); ok {
if err := AllowType(kType, isReturnType); err != nil {
return err
}
if err := AllowType(vType, isReturnType); err != nil {
return err
}
}

if !AllowClass(p.ParameterType) {
return ErrTooComplex // This whole class type has been blocked, not only as a parameter/return type
}
Expand Down Expand Up @@ -294,6 +302,12 @@ func AllowType(p CppParameter, isReturnType bool) error {
if strings.Contains(p.ParameterType, `Iterator::value_type`) {
return ErrTooComplex // e.g. qcbormap
}
if strings.Contains(p.ParameterType, `>::iterator`) ||
strings.Contains(p.ParameterType, `>::const_iterator`) {
// qresultstore.h tries to create a
// NewQtPrivate__ResultIteratorBase2(_mapIterator QMap<int, ResultItem>__const_iterator)
return ErrTooComplex
}
if strings.Contains(p.ParameterType, `::QPrivate`) {
return ErrTooComplex // e.g. QAbstractItemModel::QPrivateSignal
}
Expand Down
149 changes: 103 additions & 46 deletions cmd/genbindings/emitcabi.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@ func (p CppParameter) RenderTypeCabi() string {
return "struct miqt_string"

} else if _, ok := p.QListOf(); ok {
return "struct miqt_array*"
return "struct miqt_array"

} else if _, ok := p.QSetOf(); ok {
return "struct miqt_array*"
return "struct miqt_array"

} else if _, _, ok := p.QMapOf(); ok {
return "struct miqt_map"

} else if (p.Pointer || p.ByRef) && p.QtClassType() {
return cabiClassName(p.ParameterType) + "*"
Expand Down Expand Up @@ -155,10 +158,10 @@ func emitParametersCabi(m CppMethod, selfType string) string {
tmp = append(tmp, "struct miqt_string "+p.ParameterName)

} else if t, ok := p.QListOf(); ok {
tmp = append(tmp, "struct miqt_array* /* of "+t.RenderTypeCabi()+" */ "+p.ParameterName)
tmp = append(tmp, "struct miqt_array /* of "+t.RenderTypeCabi()+" */ "+p.ParameterName)

} else if t, ok := p.QSetOf(); ok {
tmp = append(tmp, "struct miqt_array* /* Set of "+t.RenderTypeCabi()+" */ "+p.ParameterName)
tmp = append(tmp, "struct miqt_array /* Set of "+t.RenderTypeCabi()+" */ "+p.ParameterName)

} else if p.QtClassType() {
if p.ByRef || p.Pointer {
Expand Down Expand Up @@ -218,10 +221,10 @@ func emitCABI2CppForwarding(p CppParameter, indent string) (preamble string, for
} else if listType, ok := p.QListOf(); ok {

preamble += indent + p.GetQtCppType().ParameterType + " " + nameprefix + "_QList;\n"
preamble += indent + nameprefix + "_QList.reserve(" + p.ParameterName + "->len);\n"
preamble += indent + nameprefix + "_QList.reserve(" + p.ParameterName + ".len);\n"

preamble += indent + listType.RenderTypeCabi() + "* " + nameprefix + "_arr = static_cast<" + listType.RenderTypeCabi() + "*>(" + p.ParameterName + "->data);\n"
preamble += indent + "for(size_t i = 0; i < " + p.ParameterName + "->len; ++i) {\n"
preamble += indent + listType.RenderTypeCabi() + "* " + nameprefix + "_arr = static_cast<" + listType.RenderTypeCabi() + "*>(" + p.ParameterName + ".data);\n"
preamble += indent + "for(size_t i = 0; i < " + p.ParameterName + ".len; ++i) {\n"

listType.ParameterName = nameprefix + "_arr[i]"
addPre, addFwd := emitCABI2CppForwarding(listType, indent+"\t")
Expand All @@ -231,6 +234,32 @@ func emitCABI2CppForwarding(p CppParameter, indent string) (preamble string, for
preamble += indent + "}\n"
return preamble, nameprefix + "_QList"

} else if kType, vType, ok := p.QMapOf(); ok {
preamble += indent + p.GetQtCppType().ParameterType + " " + nameprefix + "_QMap;\n"

// This container may be a QMap or a QHash
// QHash supports .reserve(), but QMap doesn't
if strings.HasPrefix(p.ParameterType, "QHash<") {
preamble += indent + nameprefix + "_QMap.reserve(" + p.ParameterName + ".len);\n"
}

preamble += indent + kType.RenderTypeCabi() + "* " + nameprefix + "_karr = static_cast<" + kType.RenderTypeCabi() + "*>(" + p.ParameterName + ".keys);\n"
preamble += indent + vType.RenderTypeCabi() + "* " + nameprefix + "_varr = static_cast<" + vType.RenderTypeCabi() + "*>(" + p.ParameterName + ".values);\n"
preamble += indent + "for(size_t i = 0; i < " + p.ParameterName + ".len; ++i) {\n"

kType.ParameterName = nameprefix + "_karr[i]"
addPreK, addFwdK := emitCABI2CppForwarding(kType, indent+"\t")
preamble += addPreK

vType.ParameterName = nameprefix + "_varr[i]"
addPreV, addFwdV := emitCABI2CppForwarding(vType, indent+"\t")
preamble += addPreV

preamble += indent + "\t" + nameprefix + "_QMap[" + addFwdK + "] = " + addFwdV + ";\n"

preamble += indent + "}\n"
return preamble, nameprefix + "_QMap"

} else if p.IsFlagType() || p.IntType() || p.IsKnownEnum() {
castSrc := p.ParameterName
castType := p.RenderTypeQtCpp()
Expand Down Expand Up @@ -362,9 +391,9 @@ func emitAssignCppToCabi(assignExpression string, p CppParameter, rvalue string)
afterCall += emitAssignCppToCabi(indent+"\t"+namePrefix+"_arr[i] = ", t, namePrefix+"_ret[i]")
afterCall += indent + "}\n"

afterCall += indent + "struct miqt_array* " + namePrefix + "_out = static_cast<struct miqt_array*>(malloc(sizeof(struct miqt_array)));\n"
afterCall += indent + "" + namePrefix + "_out->len = " + namePrefix + "_ret.length();\n"
afterCall += indent + "" + namePrefix + "_out->data = static_cast<void*>(" + namePrefix + "_arr);\n"
afterCall += indent + "struct miqt_array " + namePrefix + "_out;\n"
afterCall += indent + "" + namePrefix + "_out.len = " + namePrefix + "_ret.length();\n"
afterCall += indent + "" + namePrefix + "_out.data = static_cast<void*>(" + namePrefix + "_arr);\n"

afterCall += indent + assignExpression + "" + namePrefix + "_out;\n"

Expand All @@ -380,9 +409,33 @@ func emitAssignCppToCabi(assignExpression string, p CppParameter, rvalue string)
afterCall += emitAssignCppToCabi(indent+"\t"+namePrefix+"_arr["+namePrefix+"_ctr++] = ", t, namePrefix+"_itr.next()")
afterCall += indent + "}\n"

afterCall += indent + "struct miqt_array* " + namePrefix + "_out = static_cast<struct miqt_array*>(malloc(sizeof(struct miqt_array)));\n"
afterCall += indent + "" + namePrefix + "_out->len = " + namePrefix + "_ret.size();\n"
afterCall += indent + "" + namePrefix + "_out->data = static_cast<void*>(" + namePrefix + "_arr);\n"
afterCall += indent + "struct miqt_array " + namePrefix + "_out;\n"
afterCall += indent + "" + namePrefix + "_out.len = " + namePrefix + "_ret.size();\n"
afterCall += indent + "" + namePrefix + "_out.data = static_cast<void*>(" + namePrefix + "_arr);\n"

afterCall += indent + assignExpression + "" + namePrefix + "_out;\n"

} else if kType, vType, ok := p.QMapOf(); ok {
// QMap<K,V>

shouldReturn = p.RenderTypeQtCpp() + " " + namePrefix + "_ret = "

afterCall += indent + "// Convert QMap<> from C++ memory to manually-managed C memory\n"
afterCall += indent + "" + kType.RenderTypeCabi() + "* " + namePrefix + "_karr = static_cast<" + kType.RenderTypeCabi() + "*>(malloc(sizeof(" + kType.RenderTypeCabi() + ") * " + namePrefix + "_ret.size()));\n"
afterCall += indent + "" + vType.RenderTypeCabi() + "* " + namePrefix + "_varr = static_cast<" + vType.RenderTypeCabi() + "*>(malloc(sizeof(" + vType.RenderTypeCabi() + ") * " + namePrefix + "_ret.size()));\n"

afterCall += indent + "int " + namePrefix + "_ctr = 0;\n"
afterCall += indent + "for (auto " + namePrefix + "_itr = " + namePrefix + "_ret.keyValueBegin(); " + namePrefix + "_itr != " + namePrefix + "_ret.keyValueEnd(); ++" + namePrefix + "_itr) {\n"
afterCall += emitAssignCppToCabi(indent+"\t"+namePrefix+"_karr["+namePrefix+"_ctr] = ", kType, namePrefix+"_itr->first")
afterCall += emitAssignCppToCabi(indent+"\t"+namePrefix+"_varr["+namePrefix+"_ctr] = ", vType, namePrefix+"_itr->second")
afterCall += indent + "\t" + namePrefix + "_ctr++;\n"

afterCall += indent + "}\n"

afterCall += indent + "struct miqt_map " + namePrefix + "_out;\n"
afterCall += indent + "" + namePrefix + "_out.len = " + namePrefix + "_ret.size();\n"
afterCall += indent + "" + namePrefix + "_out.keys = static_cast<void*>(" + namePrefix + "_karr);\n"
afterCall += indent + "" + namePrefix + "_out.values = static_cast<void*>(" + namePrefix + "_varr);\n"

afterCall += indent + assignExpression + "" + namePrefix + "_out;\n"

Expand Down Expand Up @@ -428,44 +481,42 @@ func emitAssignCppToCabi(assignExpression string, p CppParameter, rvalue string)
func getReferencedTypes(src *CppParsedHeader) []string {

foundTypes := map[string]struct{}{}

maybeAddType := func(p CppParameter) {
if p.QtClassType() {
foundTypes[p.ParameterType] = struct{}{}
}
if t, ok := p.QListOf(); ok {
foundTypes["QList"] = struct{}{} // FIXME or QVector?
if t.QtClassType() {
foundTypes[t.ParameterType] = struct{}{}
}
}
if kType, vType, ok := p.QMapOf(); ok {
foundTypes["QMap"] = struct{}{} // FIXME or QHash?
if kType.QtClassType() {
foundTypes[kType.ParameterType] = struct{}{}
}
if vType.QtClassType() {
foundTypes[vType.ParameterType] = struct{}{}
}
}
}

for _, c := range src.Classes {

foundTypes[c.ClassName] = struct{}{}

for _, ctor := range c.Ctors {
for _, p := range ctor.Parameters {
if p.QtClassType() {
foundTypes[p.ParameterType] = struct{}{}
}
if t, ok := p.QListOf(); ok {
foundTypes["QList"] = struct{}{} // FIXME or QVector?
if t.QtClassType() {
foundTypes[t.ParameterType] = struct{}{}
}
}
maybeAddType(p)
}
}
for _, m := range c.Methods {
for _, p := range m.Parameters {
if p.QtClassType() {
foundTypes[p.ParameterType] = struct{}{}
}
if t, ok := p.QListOf(); ok {
foundTypes["QList"] = struct{}{} // FIXME or QVector?
if t.QtClassType() {
foundTypes[t.ParameterType] = struct{}{}
}
}
}
if m.ReturnType.QtClassType() {
foundTypes[m.ReturnType.ParameterType] = struct{}{}
}
if t, ok := m.ReturnType.QListOf(); ok {
foundTypes["QList"] = struct{}{} // FIXME or QVector?
if t.QtClassType() {
foundTypes[t.ParameterType] = struct{}{}
}
maybeAddType(p)
}
maybeAddType(m.ReturnType)
}
}

Expand All @@ -477,10 +528,7 @@ func getReferencedTypes(src *CppParsedHeader) []string {
// Convert to sorted list
foundTypesList := make([]string, 0, len(foundTypes))
for ft := range foundTypes {
if strings.HasPrefix(ft, "QList<") || strings.HasPrefix(ft, "QVector<") { // TODO properly exclude via the QListOf() check above
continue
}
if strings.HasSuffix(ft, "Private") { // qbrush.h finds QGradientPrivate
if !AllowClass(ft) {
continue
}

Expand All @@ -504,6 +552,15 @@ func cabiClassName(className string) string {
return strings.Replace(className, `::`, `__`, -1)
}

func cabiPreventStructDeclaration(className string) bool {
switch className {
case "QList", "QString", "QSet", "QMap", "QHash":
return true // These types are reprojected
default:
return false
}
}

func emitBindingHeader(src *CppParsedHeader, filename string, packageName string) (string, error) {
ret := strings.Builder{}

Expand Down Expand Up @@ -537,7 +594,7 @@ extern "C" {
ret.WriteString("#ifdef __cplusplus\n")

for _, ft := range foundTypesList {
if ft == "QList" || ft == "QString" { // These types are reprojected
if cabiPreventStructDeclaration(ft) {
continue
}

Expand All @@ -559,7 +616,7 @@ extern "C" {
ret.WriteString("#else\n")

for _, ft := range foundTypesList {
if ft == "QList" || ft == "QString" { // These types are reprojected
if cabiPreventStructDeclaration(ft) {
continue
}
ret.WriteString(`typedef struct ` + cabiClassName(ft) + " " + cabiClassName(ft) + ";\n")
Expand Down
Loading