//export hookListPropertyAppend func hookListPropertyAppend(foldp unsafe.Pointer, reflectIndex, setIndex C.intptr_t, objp unsafe.Pointer) { fold := (*valueFold)(foldp) slice := listSlice(fold, reflectIndex) var objdv C.DataValue objdv.dataType = C.DTObject *(*unsafe.Pointer)(unsafe.Pointer(&objdv.data)) = objp newslice := append(*slice, unpackDataValue(&objdv, fold.engine).(Object)) if setIndex >= 0 { reflect.ValueOf(fold.gvalue).Method(int(setIndex)).Call([]reflect.Value{reflect.ValueOf(newslice)}) } else { *slice = newslice } }
//export hookListPropertyAppend func hookListPropertyAppend(foldp unsafe.Pointer, reflectIndex, onChangedIndex C.intptr_t, objp unsafe.Pointer) { fold := (*valueFold)(foldp) field := fold.gfield(int(reflectIndex)) slice := field.Addr().Interface().(*[]Object) var objdv C.DataValue objdv.dataType = C.DTObject *(*unsafe.Pointer)(unsafe.Pointer(&objdv.data)) = objp *slice = append(*slice, unpackDataValue(&objdv, fold.engine).(Object)) if onChangedIndex != -1 { // TODO Must probably dereference the ptr here. Test it. reflect.ValueOf(fold.gvalue).Method(int(onChangedIndex)).Call(nil) } }
// packDataValue packs the provided Go value into a C.DataValue for // shiping into C++ land. // // For simple types (bool, int, etc) value is converted into a // native C++ value. For anything else, including cases when value // has a type that has an underlying simple type, the Go value itself // is encapsulated into a C++ wrapper so that field access and method // calls work. // // This must be run from the main GUI thread due to the cases where // calling wrapGoValue is necessary. func packDataValue(value interface{}, dvalue *C.DataValue, engine *Engine, owner valueOwner) { datap := unsafe.Pointer(&dvalue.data) if value == nil { dvalue.dataType = C.DTInvalid return } switch value := value.(type) { case string: dvalue.dataType = C.DTString cstr, cstrlen := unsafeStringData(value) *(**C.char)(datap) = cstr dvalue.len = cstrlen case bool: dvalue.dataType = C.DTBool *(*bool)(datap) = value case int: dvalue.dataType = intDT *(*int)(datap) = value case int64: dvalue.dataType = C.DTInt64 *(*int64)(datap) = value case int32: dvalue.dataType = C.DTInt32 *(*int32)(datap) = value case float64: dvalue.dataType = C.DTFloat64 *(*float64)(datap) = value case float32: dvalue.dataType = C.DTFloat32 *(*float32)(datap) = value case *Object: dvalue.dataType = C.DTObject *(*unsafe.Pointer)(datap) = value.addr default: dvalue.dataType = C.DTObject *(*unsafe.Pointer)(datap) = wrapGoValue(engine, value, owner) } }
//export hookGoValueCallMethod func hookGoValueCallMethod(enginep, foldp unsafe.Pointer, reflectIndex C.int, args *C.DataValue) { fold := ensureEngine(enginep, foldp) v := reflect.ValueOf(fold.gvalue) // TODO Must assert that v is necessarily a pointer here, but we shouldn't have to manipulate // gvalue here for that. This should happen in a sensible place in the wrapping functions // that can still error out to the user in due time. method := v.Method(int(reflectIndex)) methodt := method.Type() methodName := v.Type().Method(int(reflectIndex)).Name // TODO Ensure methods with more parameters than this are not registered. var params [C.MaxParams]reflect.Value var err error numIn := methodt.NumIn() for i := 0; i < numIn; i++ { paramdv := (*C.DataValue)(unsafe.Pointer(uintptr(unsafe.Pointer(args)) + (uintptr(i)+1)*dataValueSize)) param := reflect.ValueOf(unpackDataValue(paramdv, fold.engine)) if argt := methodt.In(i); param.Type() != argt { param, err = convertParam(methodName, i, param, argt) if err != nil { panic(err.Error()) } } params[i] = param } result := method.Call(params[:numIn]) if len(result) == 1 { packDataValue(result[0].Interface(), args, fold.engine, jsOwner) } else if len(result) > 1 { if len(result) > len(dataValueArray) { panic("function has too many results") } for i, v := range result { packDataValue(v.Interface(), &dataValueArray[i], fold.engine, jsOwner) } args.dataType = C.DTList *(*unsafe.Pointer)(unsafe.Pointer(&args.data)) = C.newVariantList(&dataValueArray[0], C.int(len(result))) } }
//export hookGoValueReadField func hookGoValueReadField(enginep, foldp unsafe.Pointer, reflectIndex, getIndex, setIndex C.int, resultdv *C.DataValue) { fold := ensureEngine(enginep, foldp) var field reflect.Value if getIndex >= 0 { field = reflect.ValueOf(fold.gvalue).Method(int(getIndex)).Call(nil)[0] } else { field = deref(reflect.ValueOf(fold.gvalue)).Field(int(reflectIndex)) } field = deref(field) // Cannot compare Type directly as field may be invalid (nil). if field.Kind() == reflect.Slice && field.Type() == typeObjSlice { // TODO Handle getters that return []qml.Object. // TODO Handle other GoValue slices (!= []qml.Object). resultdv.dataType = C.DTListProperty *(*unsafe.Pointer)(unsafe.Pointer(&resultdv.data)) = C.newListProperty(foldp, C.intptr_t(reflectIndex), C.intptr_t(setIndex)) return } fieldk := field.Kind() if fieldk == reflect.Slice || fieldk == reflect.Struct && field.Type() != typeRGBA { if field.CanAddr() { field = field.Addr() } else if !hashable(field.Interface()) { t := reflect.ValueOf(fold.gvalue).Type() for t.Kind() == reflect.Ptr { t = t.Elem() } panic(fmt.Sprintf("cannot access unaddressable and unhashable struct value on interface field %s.%s; value: %#v", t.Name(), t.Field(int(reflectIndex)).Name, field.Interface())) } } var gvalue interface{} if field.IsValid() { gvalue = field.Interface() } // TODO Strings are being passed in an unsafe manner here. There is a // small chance that the field is changed and the garbage collector is run // before C++ has a chance to look at the data. We can solve this problem // by queuing up values in a stack, and cleaning the stack when the // idle timer fires next. packDataValue(gvalue, resultdv, fold.engine, jsOwner) }
//export hookGoValueCallMethod func hookGoValueCallMethod(enginep, foldp unsafe.Pointer, reflectIndex C.int, args *C.DataValue) { fold := ensureEngine(enginep, foldp) v := reflect.ValueOf(fold.gvalue) // TODO Must assert that v is necessarily a pointer here, but we shouldn't have to manipulate // gvalue here for that. This should happen in a sensible place in the wrapping functions // that can still error out to the user in due time. method := v.Method(int(reflectIndex)) // TODO Ensure methods with more parameters than this are not registered. var params [C.MaximumParamCount - 1]reflect.Value numIn := uintptr(method.Type().NumIn()) for i := uintptr(0); i < numIn; i++ { // TODO Convert the arguments when possible (int32 => int, etc). // TODO Type checking to avoid explosions (or catch the explosion) paramdv := (*C.DataValue)(unsafe.Pointer(uintptr(unsafe.Pointer(args)) + (i+1)*dataValueSize)) params[i] = reflect.ValueOf(unpackDataValue(paramdv, fold.engine)) } result := method.Call(params[:numIn]) if len(result) == 1 { packDataValue(result[0].Interface(), args, fold.engine, jsOwner) } else if len(result) > 1 { if len(result) > len(dataValueArray) { panic("function has too many results") } for i, v := range result { packDataValue(v.Interface(), &dataValueArray[i], fold.engine, jsOwner) } args.dataType = C.DTList *(*unsafe.Pointer)(unsafe.Pointer(&args.data)) = C.newVariantList(&dataValueArray[0], C.int(len(result))) } }
// packDataValue packs the provided Go value into a C.DataValue for // shiping into C++ land. // // For simple types (bool, int, etc) value is converted into a // native C++ value. For anything else, including cases when value // has a type that has an underlying simple type, the Go value itself // is encapsulated into a C++ wrapper so that field access and method // calls work. // // This must be run from the main GUI thread due to the cases where // calling wrapGoValue is necessary. func packDataValue(value interface{}, dvalue *C.DataValue, engine *Engine, owner valueOwner) { datap := unsafe.Pointer(&dvalue.data) if value == nil { dvalue.dataType = C.DTInvalid return } switch value := value.(type) { case string: dvalue.dataType = C.DTString cstr, cstrlen := unsafeStringData(value) *(**C.char)(datap) = cstr dvalue.len = cstrlen case bool: dvalue.dataType = C.DTBool *(*bool)(datap) = value case int: if value > 1<<31-1 { dvalue.dataType = C.DTInt64 *(*int64)(datap) = int64(value) } else { dvalue.dataType = C.DTInt32 *(*int32)(datap) = int32(value) } case int64: dvalue.dataType = C.DTInt64 *(*int64)(datap) = value case int32: dvalue.dataType = C.DTInt32 *(*int32)(datap) = value case float64: dvalue.dataType = C.DTFloat64 *(*float64)(datap) = value case float32: dvalue.dataType = C.DTFloat32 *(*float32)(datap) = value case *Common: dvalue.dataType = C.DTObject *(*unsafe.Pointer)(datap) = value.addr case color.RGBA: dvalue.dataType = C.DTColor *(*uint32)(datap) = uint32(value.A)<<24 | uint32(value.R)<<16 | uint32(value.G)<<8 | uint32(value.B) default: dvalue.dataType = C.DTObject if obj, ok := value.(Object); ok { *(*unsafe.Pointer)(datap) = obj.Common().addr } else { *(*unsafe.Pointer)(datap) = wrapGoValue(engine, value, owner) } } }
// packDataValue packs the provided Go value into a C.DataValue for // shiping into C++ land. // // For simple types (bool, int, etc) value is converted into a // native C++ value. For anything else, including cases when value // has a type that has an underlying simple type, the Go value itself // is encapsulated into a C++ wrapper so that field access and method // calls work. // // This must be run from the main GUI thread due to the cases where // calling wrapGoValue is necessary. func packDataValue(value interface{}, dvalue *C.DataValue, engine *Engine, owner valueOwner) { datap := unsafe.Pointer(&dvalue.data) if value == nil { dvalue.dataType = C.DTInvalid return } switch value := value.(type) { case string: dvalue.dataType = C.DTString cstr, cstrlen := unsafeStringData(value) *(**C.char)(datap) = cstr dvalue.len = cstrlen case bool: dvalue.dataType = C.DTBool *(*bool)(datap) = value case int: if value > 1<<31-1 { dvalue.dataType = C.DTInt64 *(*int64)(datap) = int64(value) } else { dvalue.dataType = C.DTInt32 *(*int32)(datap) = int32(value) } case int64: dvalue.dataType = C.DTInt64 *(*int64)(datap) = value case int32: dvalue.dataType = C.DTInt32 *(*int32)(datap) = value case uint64: dvalue.dataType = C.DTUint64 *(*uint64)(datap) = value case uint32: dvalue.dataType = C.DTUint32 *(*uint32)(datap) = value case float64: dvalue.dataType = C.DTFloat64 *(*float64)(datap) = value case float32: dvalue.dataType = C.DTFloat32 *(*float32)(datap) = value case *Common: dvalue.dataType = C.DTObject *(*unsafe.Pointer)(datap) = value.addr case color.RGBA: dvalue.dataType = C.DTColor *(*uint32)(datap) = uint32(value.A)<<24 | uint32(value.R)<<16 | uint32(value.G)<<8 | uint32(value.B) default: rv := reflect.ValueOf(value) switch rv.Type().Kind() { case reflect.Ptr: rv = rv.Elem() if rv.Kind() != reflect.Array && rv.Kind() != reflect.Slice { break } fallthrough case reflect.Array, reflect.Slice: dvalue.dataType = C.DTVariantList dataValues := make([]C.DataValue, rv.Len()) for i := range dataValues { packDataValue(rv.Index(i).Interface(), &dataValues[i], engine, owner) } switch len(dataValues) { case 0: *(*unsafe.Pointer)(unsafe.Pointer(&dvalue.data)) = C.newVariantList(nil, 0) default: *(*unsafe.Pointer)(unsafe.Pointer(&dvalue.data)) = C.newVariantList(&dataValues[0], C.int(len(dataValues))) } return } dvalue.dataType = C.DTObject if obj, ok := value.(Object); ok { *(*unsafe.Pointer)(datap) = obj.Common().addr } else { *(*unsafe.Pointer)(datap) = wrapGoValue(engine, value, owner) } } }