diff --git a/examples/app/main.go b/examples/app/main.go index 94ce012..b6f88ca 100644 --- a/examples/app/main.go +++ b/examples/app/main.go @@ -6,6 +6,7 @@ import ( "fmt" "git.wow.st/gmp/nswrap/examples/app/ns" "runtime" + "time" ) //Shortcut for literal NSStrings @@ -31,8 +32,9 @@ func didFinishLaunching(n *ns.NSNotification) { ns.NSBackingStoreBuffered, 0, ) - // retain win since we called Alloc and did not add it to a collection - win.Retain() + // We do not need to retain this because we are in garbage collection mode + // and have assigned it to a global variable. + //win.Retain() win.SetTitle(nst("Hi World")) win.MakeKeyAndOrderFront(win) @@ -119,12 +121,12 @@ var ( ) func app() { - //Lock OS thread because Cocoa uses thread-local storage + // Lock OS thread because Cocoa uses thread-local storage runtime.LockOSThread() a = ns.NSApplicationSharedApplication() a.SetActivationPolicy(ns.NSApplicationActivationPolicyRegular) - //Set up an AppDelegate + // Set up an AppDelegate del := ns.AppDelegateAlloc() del.ApplicationDidFinishLaunchingCallback(didFinishLaunching) del.ApplicationShouldTerminateAfterLastWindowClosedCallback(shouldTerminateAfterLastWindowClosed) @@ -133,12 +135,20 @@ func app() { a.SetDelegate(del) - //Run the app + // Run the app a.Run() } func main() { - //Run our app in an autorelease pool just for fun + // Run GC every second to ensure pointers are not being prematurely released. + go func() { + for { + runtime.GC() + time.Sleep(time.Second) + } + }() + + // Run our app in an autorelease pool just for fun go ns.Autoreleasepool(app) select {} } diff --git a/examples/app/nswrap.yaml b/examples/app/nswrap.yaml index b90b36b..db31016 100644 --- a/examples/app/nswrap.yaml +++ b/examples/app/nswrap.yaml @@ -44,4 +44,5 @@ subclasses: frameworks: [ Foundation, AppKit ] pragma: [ clang diagnostic ignored "-Wformat-security" ] -autorelease: true +#autorelease: true +gogc: true diff --git a/examples/bluetooth/main.go b/examples/bluetooth/main.go index 5e44097..373d3db 100644 --- a/examples/bluetooth/main.go +++ b/examples/bluetooth/main.go @@ -5,6 +5,8 @@ import "C" import ( "encoding/binary" "fmt" + "runtime" + "time" "git.wow.st/gmp/nswrap/examples/bluetooth/ns" ) @@ -25,34 +27,53 @@ func updateState(c *ns.CBCentralManager) { fmt.Printf(" powered on\n") cm.ScanForPeripheralsWithServices(ns.NSArrayWithObjects(hrm_uuid), nil) } + fmt.Printf("Go: updateState returning\n") } func discoverPeripheral(c *ns.CBCentralManager, p *ns.CBPeripheral, d *ns.NSDictionary, rssi *ns.NSNumber) { fmt.Printf("Did discover peripheral\n") c.StopScan() - if peripheral.Ptr() != nil { + + // if we already have a pointer to a peripheral, check that this one is different, + // and if so release the old one. Be careful to check the Objective-C pointers + // here as the Go pointers will differ. + + if peripheral != nil && p.Ptr() != peripheral.Ptr() { peripheral.Release() } + peripheral = p + + // we need to take ownership of this peripheral so CoreBluetooth doesn't + // dealloc it. + peripheral.Retain() c.ConnectPeripheral(peripheral, nil) } func connectPeripheral(c *ns.CBCentralManager, p *ns.CBPeripheral) { fmt.Printf("Did connect peripheral\n") + + // set ourselves up as a peripheral delegate + p.SetDelegate(cd) + + // discover all services on this device + p.DiscoverServices(nil) + fmt.Printf("Go: discoverPeripheral returning\n") } func discoverServices(p *ns.CBPeripheral, e *ns.NSError) { fmt.Printf("Did discover services\n") p.Services().ObjectEnumerator().ForIn(func(o *ns.Id) bool { serv := o.CBService() + uuid := serv.UUID() switch { - case serv.UUID().IsEqualTo(hrm_uuid): + case uuid.IsEqualTo(hrm_uuid): fmt.Printf("--heart rate monitor service\n") p.DiscoverCharacteristics(nil, serv) - case serv.UUID().IsEqualTo(ns.CBUUIDWithGoString("180A")): + case uuid.IsEqualTo(info_uuid): fmt.Printf("--device information service\n") p.DiscoverCharacteristics(nil, serv) default: @@ -60,6 +81,7 @@ func discoverServices(p *ns.CBPeripheral, e *ns.NSError) { } return true }) + fmt.Printf("Go: discoverServices returning\n") } func hr(d *ns.NSData) int { @@ -77,12 +99,14 @@ func hr(d *ns.NSData) int { func discoverCharacteristics(p *ns.CBPeripheral, s *ns.CBService, e *ns.NSError) { fmt.Printf("Did discover characteristics\n") - fmt.Printf("----%s\n", s.UUID().UUIDString().UTF8String()) - if s.UUID().IsEqualTo(hrm_uuid) { + uuid := s.UUID() + fmt.Printf("----%s\n", uuid.UUIDString().UTF8String()) + if uuid.IsEqualTo(hrm_uuid) { s.Characteristics().ObjectEnumerator().ForIn(func(o *ns.Id) bool { chr := o.CBCharacteristic() - fmt.Printf("------%s\n", chr.UUID().UUIDString().UTF8String()) - if chr.UUID().IsEqualTo(hrv_uuid) { + chuuid := chr.UUID() + fmt.Printf("------%s\n", chuuid.UUIDString().UTF8String()) + if chuuid.IsEqualTo(hrv_uuid) { p.SetNotifyValue(1, chr) v := chr.Value() fmt.Println(hr(v)) @@ -90,6 +114,7 @@ func discoverCharacteristics(p *ns.CBPeripheral, s *ns.CBService, e *ns.NSError) return true }) } + fmt.Printf("Go: discoverCharacteristics returning\n") } func updateValue(p *ns.CBPeripheral, chr *ns.CBCharacteristic, e *ns.NSError) { @@ -97,11 +122,13 @@ func updateValue(p *ns.CBPeripheral, chr *ns.CBCharacteristic, e *ns.NSError) { v := chr.Value() fmt.Printf("Heart rate: %d\n", hr(v)) } + fmt.Printf("Go: updateValue returning\n") } var ( hrm_uuid *ns.CBUUID hrv_uuid *ns.CBUUID + info_uuid *ns.CBUUID cd *ns.CBDelegate cm *ns.CBCentralManager peripheral *ns.CBPeripheral @@ -121,9 +148,18 @@ func main() { hrm_uuid = ns.CBUUIDWithGoString("180D") hrv_uuid = ns.CBUUIDWithGoString("2A37") + info_uuid = ns.CBUUIDWithGoString("180A") - //We defined our own queue because this won't work on the main queue. + // We defined our own queue because this won't work on the main queue. cm = ns.CBCentralManagerAlloc().InitWithDelegateQueue(cd, queue) + // For debugging purposes, run GC every second to make sure things are not + // over-released. + go func() { + for { + runtime.GC() + time.Sleep(time.Second) + } + }() select {} } diff --git a/examples/bluetooth/nswrap.yaml b/examples/bluetooth/nswrap.yaml index e3fb835..6f2df9b 100644 --- a/examples/bluetooth/nswrap.yaml +++ b/examples/bluetooth/nswrap.yaml @@ -22,6 +22,7 @@ classes: - NSDictionary - NSEnumerator - NSString + - NSAutoreleasePool functions: [ NSMakeRange, dispatch_queue_create ] enums: [ CB.* ] @@ -38,4 +39,4 @@ delegates: - peripheralDidUpdateValueForCharacteristic pragma: [ clang diagnostic ignored "-Wformat-security" ] -#gogc: true +gogc: true diff --git a/examples/gc/main.go b/examples/gc/main.go index d963973..3754e14 100644 --- a/examples/gc/main.go +++ b/examples/gc/main.go @@ -15,6 +15,7 @@ func releaseX(x int) func (ns.MyClassSupermethods) { super.Release() // comment out for leak } } + func memtest1() { fmt.Println("memtest1 started") for { @@ -33,7 +34,7 @@ func memtest1() { // in a real program. runtime.GC() time.Sleep(time.Second/50) - fmt.Printf("Loop complete\n") + //fmt.Printf("Loop complete\n") } } @@ -52,18 +53,35 @@ func memtest2() { o4 := ns.NSStringAlloc() _ = o4 - //arr := ns.NSArrayAlloc().InitWithObjects(o1,o1) - arr := ns.NSArrayWithObjects(o1,o2,o3,o4) - _ = arr + a1 := ns.NSArrayAlloc() + + // init methods in Objective-C always return a retained object. + // init may or may not return the same object that was sent in. + + a1 = a1.InitWithObjects(o1,o2,o3,o4) + + a2 := ns.NSArrayWithObjects(o1,o2,o3,o4) + + // you can always nest alloc and init. + + a3 := ns.NSMutableArrayAlloc().Init() + a3.AddObject(o1) + a3.AddObject(o2) + a3.AddObject(o3) + a3.AddObject(o4) + _ = a1 + _ = a2 + _ = a3 - //o1.Release() - //o1.Release() runtime.GC() time.Sleep(time.Second/50) } } func addStr(arr *ns.NSMutableArray) { + // temporary strings made by the 'WithGoString' methods should be released + // automatically by the GC. + s1 := ns.NSStringAlloc().InitWithGoString("a string") arr.AddObject(s1) @@ -75,24 +93,35 @@ func addStr(arr *ns.NSMutableArray) { func memtest3() { fmt.Println("memtest3 started") - arr := ns.NSMutableArrayAlloc().Init() // arr will be garbage collected by Go - addStr(arr) - runtime.GC() - time.Sleep(time.Second) - s1 := arr.ObjectAtIndex(0) - fmt.Println(s1.NSString().UTF8String()) - fmt.Println("memtest3 done") + for { + // arr will be garbage collected by Go + arr := ns.NSMutableArrayAlloc().Init() + addStr(arr) + runtime.GC() + time.Sleep(time.Second) + + // check that our string was retained. + + s1 := arr.ObjectAtIndex(0) + gstr := s1.NSString().UTF8String().String() + _ = gstr + } } func memtest4() { fmt.Println("memtest4 started") for { - o1 := ns.NSStringAlloc().InitWithGoString("four string") + o1 := ns.NSStringAlloc().InitWithGoString("red string") + + // conversions to UTF8String internally create autoreleased strings + // in the Objective-C runtime. NSWrap runs these in a mini- + // @autoreleasepool block. + c1 := o1.UTF8String() _ = o1 _ = c1 runtime.GC() - time.Sleep(time.Second/10) + time.Sleep(time.Second/50) } } @@ -100,12 +129,21 @@ func memtest5() { fmt.Println("memtest5 started") i := 0 for { - str := ns.NSStringWithGoString(fmt.Sprintf("five string %d",i)) - _ = str + // by incrementing i we can ensure that Objective-C needs to create + // a new NSString object at each loop iteration and cannot be reusing + // the same string object. + + str := ns.NSStringWithGoString(fmt.Sprintf("blue string %d",i)) + + // SubstringFromIndex should be returning a newly allocated NSString, + // which is getting retained by NSWrap and released by a Go GC + // finalizer. + sub := str.SubstringFromIndex(5) _ = sub - fmt.Printf("sub = %s\n",sub) - time.Sleep(time.Second/10) + u := sub.UTF8String() + _ = u + time.Sleep(time.Second/50) runtime.GC() i++ } @@ -117,5 +155,12 @@ func main() { go memtest3() go memtest4() go memtest5() + go func() { + for { + // print a progress indicator + fmt.Printf("t = %s\n",time.Now()) + time.Sleep(time.Second * 10) + } + }() select {} } diff --git a/types/convert.go b/types/convert.go index 7eca8a9..df10cb9 100644 --- a/types/convert.go +++ b/types/convert.go @@ -246,10 +246,10 @@ func (t *Type) _CType(attrib bool) string { return ct } -func (t *Type) GoTypeDecl() string { +func (t *Type) GoTypeDecl(fin bool) string { gt := t.GoType() if wrapped[gt] { - return t.GoInterfaceDecl() + return t.GoInterfaceDecl(fin) } if t.Node.IsId() { return "" @@ -286,7 +286,7 @@ type %s %s } } -func (t *Type) GoInterfaceDecl() string { +func (t *Type) GoInterfaceDecl(fin bool) string { ct := t.CType() gt := t.GoType() if Debug { @@ -384,7 +384,7 @@ func (t *Type) CToGo(cval string) string { } // Call a C function from Go with a given return type and parameter types -func GoToC(name string, pnames, snames []string, rtype *Type, ptypes []*Type, fun, fin bool) string { +func GoToC(name string, pnames, snames []string, rtype *Type, ptypes []*Type, fun, fin bool, cm bool) string { if rtype == nil { //fmt.Println("nil sent to GoToC") return "" @@ -495,10 +495,24 @@ func GoToC(name string, pnames, snames []string, rtype *Type, ptypes []*Type, fu } if rt != "void" { if fin { + cmp := "" + if !cm { + cmp = fmt.Sprintf(`if ret.ptr == o.ptr { return (%s)(unsafe.Pointer(o)) } + `,rtgt) + } + dbg := "" + dbg2 := "" + if Debug { + dbg = fmt.Sprintf(`fmt.Printf("Setting finalizer (%s): %%p -> %%p\n", ret, ret.ptr) + `, rtgt) + dbg2 = fmt.Sprintf(`fmt.Printf("Finalizer (%s): release %%p -> %%p\n", o, o.ptr) + `, rtgt) + } ret.WriteString(fmt.Sprintf(` - runtime.SetFinalizer(ret, func(o %s) { - o.Release() - })`,rtgt)) + if ret.ptr == nil { return ret } + %s%sruntime.SetFinalizer(ret, func(o %s) { + %so.Release() + })`, cmp, dbg, rtgt, dbg2)) } ret.WriteString(` return ret`) diff --git a/types/convert_test.go b/types/convert_test.go index 69b2453..b1bc97a 100644 --- a/types/convert_test.go +++ b/types/convert_test.go @@ -177,14 +177,14 @@ func TestType(t *testing.T) { chk(tp.PointsTo(), tp2) chk(tp2.PointsTo(), nil) - chk(tp.GoTypeDecl(), ` + chk(tp.GoTypeDecl(false), ` type MyTypedef **C.NSObject `) str = "void" n = &Node{"TypeName", "", []*Node{ &Node{"TypeSpecifier", "void", []*Node{}}}} chk_newtype() - chk(tp.GoTypeDecl(), "") + chk(tp.GoTypeDecl(false), "") void := tp str = "BOOL" @@ -206,14 +206,14 @@ type MyTypedef **C.NSObject chk(tp.GoType(), "*NSObject") Wrap("NSString") - chk(nso.GoTypeDecl(), ` + chk(nso.GoTypeDecl(false), ` type NSObject interface { Ptr() unsafe.Pointer } `) chk(nso.GoType(), "NSObject") - chk(nst.GoTypeDecl(), ` + chk(nst.GoTypeDecl(false), ` type NSString struct { Id } func (o *NSString) Ptr() unsafe.Pointer { if o == nil { return nil }; return o.ptr } func (o *Id) NSString() *NSString { @@ -276,8 +276,7 @@ func (o *Id) NSString() *NSString { snames := []string{"", "", "", ""} chk_gotoc := func(expected string) { - fmt.Printf("chk_gotoc\n") - chk(GoToC("myFun", pnames, snames, rtype, ptypes, false, false), expected) + chk(GoToC("myFun", pnames, snames, rtype, ptypes, false, false, false), expected) } chk_gotoc("") @@ -352,7 +351,7 @@ func (o *Id) NSString() *NSString { } return ret`) - chk(GoToC("myFun", pnames, snames, rtype, ptypes, true, false), + chk(GoToC("myFun", pnames, snames, rtype, ptypes, true, false, false), `ret := &Id{} ret.ptr = unsafe.Pointer(C.myFun(p1.Ptr(), p2.Ptr(), unsafe.Pointer(&p3p[0]), p4)) (*p3) = (*p3)[:cap(*p3)] diff --git a/wrap/main.go b/wrap/main.go index e34cd61..975148a 100644 --- a/wrap/main.go +++ b/wrap/main.go @@ -52,6 +52,7 @@ type Wrapper struct { func NewWrapper(debug bool) *Wrapper { Debug = debug + types.Debug = Debug if Debug { fmt.Println("// Debug mode") } @@ -133,8 +134,9 @@ type Subclass struct { } type Property struct { - Name, Attr string - Type *types.Type + Name, Attr string + Type *types.Type + retained, notretained bool } type Parameter struct { @@ -155,9 +157,34 @@ func (m *Method) ShouldFinalize() bool { } func shouldFinalize (grtype, name string) bool { - return Gogc && grtype != "NSAutoreleasePool" && (types.ShouldWrap(grtype) || grtype == "Id") && (len(name) < 4 || name[:4] != "init") + return Gogc && grtype != "*NSAutoreleasePool" && + (types.PtrShouldWrap(grtype) || grtype == "*Id") && + (len(name) < 6 || name != "retain") } +// IsRetained returns true if the name matches a retained property for the +// given interface. +func (i *Interface) IsRetained(name string) bool { + if p, ok := i.Properties[name]; ok { + if p.retained { + return true + } + if p.notretained { + return false + } + attrs := strings.Split(p.Attr," ") + for _, a := range attrs { + if a == "retain" { + p.retained = true // cache this result + return true + } + } + p.notretained = true // cache this result + } + return false +} + + //Fully disambiguated method name (m.GoName + all parameter names) func (m *Method) LongName() string { ret := m.GoName @@ -184,13 +211,14 @@ type Protocol struct { } type MethodCollection struct { - Class string + Class, GoClass string Methods []*Method } func NewMethodCollection(class string) *MethodCollection { ret := &MethodCollection{ Class: class, + GoClass: strings.Title(class), Methods: []*Method{}, } return ret @@ -564,7 +592,7 @@ func (w *Wrapper) AddEnum(n *ast.EnumDecl, rs []string) { func (w *Wrapper) addIntCat(name string, ns []ast.Node) { var i *Interface var ok bool - goname := strings.Title(types.NewTypeFromString(name, name).GoType()) + goname := strings.Title(name) types.Wrap(goname) if i, ok = w.Interfaces[name]; !ok { i = &Interface{} @@ -572,6 +600,7 @@ func (w *Wrapper) addIntCat(name string, ns []ast.Node) { i.GoName = goname i.InstanceMethods = NewMethodCollection(name) i.ClassMethods = NewMethodCollection(name) + i.Properties = map[string]*Property{} i.Protocols = []string{} i.ProcessedInstanceMethods = map[string]bool{} } @@ -582,19 +611,15 @@ func (w *Wrapper) addIntCat(name string, ns []ast.Node) { for _, c := range ns { switch x := c.(type) { case *ast.ObjCPropertyDecl: - //FIXME: Properties are not supported, typically there - //will be setter/getter methods you can use instead if Debug { fmt.Printf("ObjCPropertyDecl: %s\n", x.Name) } - //p := &Property{ - // Name: x.Name, - // Type: types.NewTypeFromString(x.Type,name), - //} - //_,avail = w.GetParms(x,name) // TODO - //if avail { - // i.Properties[p.Name] = p - //} + p := &Property{ + Name: x.Name, + Type: types.NewTypeFromString(x.Type,name), + Attr: x.Attr, + } + i.Properties[x.Name] = p case *ast.ObjCMethodDecl: if Debug { fmt.Printf("ObjCMethodDecl: %s (%s) %s\n", x.Type, name, x.Name) @@ -765,7 +790,6 @@ func (w *Wrapper) AddTypedef(n, t string) { } types.Wrap(n) types.SetSuper(n, gt) - //w._processType(tp, n) w._processType(tp) } else { cgt := tp.CGoType() @@ -785,8 +809,6 @@ func (w *Wrapper) processTypes(tps []*types.Type) { func (w *Wrapper) processType(tp *types.Type) { bt := tp.BaseType() - //gt := bt.GoType() - //w._processType(bt, gt) w._processType(bt) } @@ -804,8 +826,10 @@ func (w *Wrapper) _processType(bt *types.Type) { return } if w.ProcessedTypes[gt] { + if Debug { fmt.Printf(" -- already seen\n") } return } + if Debug { fmt.Printf(" -- not yet seen\n") } w.ProcessedTypes[gt] = true if gt == "Char" { w.CharHelpers() @@ -828,7 +852,7 @@ func (w *Wrapper) _processType(bt *types.Type) { if Debug { fmt.Printf("Writing go type for %s -> %s\n",bt.CType(),gt) } - w.goTypes.WriteString(bt.GoTypeDecl()) + w.goTypes.WriteString(bt.GoTypeDecl(Gogc)) } func (w *Wrapper) CharHelpers() { @@ -860,13 +884,19 @@ func (o *NSString) String() string { } func (w *Wrapper) EnumeratorHelpers() { - w.goHelpers.WriteString(` + var re1, re2 string + if Gogc && false { // FIXME: don't need this + re1 = "o.Release(); " + re2 = ` + o.Release()` + } + w.goHelpers.WriteString(fmt.Sprintf(` func (e *NSEnumerator) ForIn(f func(*Id) bool) { for o := e.NextObject(); o.Ptr() != nil; o = e.NextObject() { - if !f(o) { break } + if !f(o) { %sbreak }%s } } -`) +`, re1, re2)) } func (w *Wrapper) AutoreleaseHelpers() { @@ -898,12 +928,13 @@ func (w *Wrapper) ProcessMethod(m *Method) { } func (w *Wrapper) ProcessMethodForClass(m *Method, class string) { - goclass := strings.Title(types.NewTypeFromString(class, class).GoType()) + goclass := strings.Title(class) m2 := &Method{ Name: m.Name, GoName: m.GoName, Class: class, GoClass: goclass, Type: m.Type.CloneToClass(class), ClassMethod: m.ClassMethod, Parameters: make([]*Parameter, len(m.Parameters)), + Unavailable: m.Unavailable, } for i, p := range m.Parameters { m2.Parameters[i] = &Parameter{ @@ -1004,21 +1035,23 @@ func (w *Wrapper) _processMethod(m *Method, fun bool) { if gname == grtype { // avoid name conflicts between methods and types gname = "Get" + gname } + var inter *Interface if m.ClassMethod { if w.ProcessedClassMethods[gname] { return } w.ProcessedClassMethods[gname] = true } else { - i, ok := w.Interfaces[m.Class] + var ok bool + inter,ok = w.Interfaces[m.Class] if !ok { fmt.Printf("Can't find interface %s for method %s\n", m.Class, m.Name) os.Exit(-1) } - if i.ProcessedInstanceMethods[gname] { + if inter.ProcessedInstanceMethods[gname] { return } - i.ProcessedInstanceMethods[gname] = true + inter.ProcessedInstanceMethods[gname] = true } w.goCode.WriteString(fmt.Sprintf(` @@ -1048,7 +1081,7 @@ func %s%s(%s) %s { `, snames[i], n, n, snames[i], n)) } w.goCode.WriteString(` ` + - types.GoToC(cname, ns, snames, m.Type, tps, fun, m.ShouldFinalize()) + "\n}\n") + types.GoToC(cname, ns, snames, m.Type, tps, fun, m.ShouldFinalize() && (m.ClassMethod || !inter.IsRetained(m.Name)), m.ClassMethod) + "\n}\n") cret := "" if !m.isVoid() { @@ -1088,13 +1121,25 @@ func %s%s(%s) %s { default: if Gogc && !m.isVoid() { rtn := "" - //if m.ClassMethod && types.ShouldWrap(m.Type.GoType()) { - if types.ShouldWrap(m.Type.GoType()) { - if m.ClassMethod { - rtn = ` + if types.PtrShouldWrap(m.Type.GoType()) { + switch { + case m.ClassMethod: + if grtype != "*NSAutoreleasePool" { + // retain objects returned by class methods + rtn = ` [ret retain];` - } else { - rtn = ` + } + + case len(m.Name) >= 4 && m.Name[:4] == "init": + // init always returns a retained object + + case inter.IsRetained(m.Name): + // some methods relate to retained properties + // do not retain these again + + default: + // by default, retain if returning a new object + rtn = ` if (o != ret) { [ret retain]; }` } } @@ -1111,6 +1156,28 @@ func %s%s(%s) %s { } } + // create SetFinalizer methods when we see an alloc function: + if Gogc && m.Name == "alloc" { + cls := m.GoClass + if types.IsGoInterface(cls) { + cls = "Id" + } + dbg := "" + dbg2 := "" + if Debug { + dbg = fmt.Sprintf(`fmt.Printf("Setting GC finalizer (%s): %%p -> %%p\n", o, o.ptr) + `,cls) + dbg2 = fmt.Sprintf(`fmt.Printf("GC finalizer (%s): release %%p -> %%p\n", o, o.ptr)`, cls) + } + w.goCode.WriteString(fmt.Sprintf(` +func (o *%s) GC() { + if o.ptr == nil { return } + %sruntime.SetFinalizer(o, func(o *%s) { + %so.Release() + }) +} +`, cls, dbg, cls, dbg2)) + } // create GoString helper method if ok, _ := regexp.MatchString("WithString$", m.Name); ok { if Debug { @@ -1143,21 +1210,12 @@ func %s%s(%s) %s { obj = "o." ns = ns[1:] } - finalizer := "" - if m.ShouldFinalize() && false { - w.goImports["runtime"] = true - finalizer = fmt.Sprintf( - `runtime.SetFinalizer(ret, func(o *%s) { - o.Release() - }) - `, grtype) - } w.goCode.WriteString(fmt.Sprintf(` func %s%s(%s) %s { %sret := %s%s(%s) - %sreturn ret + return ret } -`, receiver, gname2, gplist, grtype, cvts, obj, gname, strings.Join(ns, ", "), finalizer)) +`, receiver, gname2, gplist, grtype, cvts, obj, gname, strings.Join(ns, ", "))) } } @@ -1246,7 +1304,40 @@ func (w *Wrapper) MethodFromSig(sig, class string) *Method { return ret } +func (mc *MethodCollection) AddMethod(m *Method) { + m2 := &Method{ + Name: m.Name, + GoName: m.GoName, + Class: mc.Class, + GoClass: mc.GoClass, + Type: m.Type.CloneToClass(mc.Class), + ClassMethod: m.ClassMethod, + Parameters: []*Parameter{}, + Unavailable: m.Unavailable, + } + for _, p := range m.Parameters { + p2 := &Parameter{ + Pname: p.Pname, + Vname: p.Vname, + Type: p.Type.CloneToClass(mc.Class), + } + m2.Parameters = append(m2.Parameters, p2) + } + mc.Methods = append(mc.Methods, m2) +} + +func (mc *MethodCollection) AddMethods(smc *MethodCollection) { + for _, m := range smc.Methods { + mc.AddMethod(m) + } +} + func (w *Wrapper) ProcessSubclass(sname string, sc *Subclass) { + i := &Interface{ + ProcessedInstanceMethods: map[string]bool{}, + Properties: map[string]*Property{}, + } + w.Interfaces[sname] = i gname := strings.Title(sname) types.Wrap(gname) types.SetSuper(gname, sc.Super) @@ -1257,12 +1348,17 @@ func (w *Wrapper) ProcessSubclass(sname string, sc *Subclass) { nms[i] = w.MethodFromSig(sig, sname) } if Debug { - fmt.Println("ProcessSubclass(%s)\n",sname) + fmt.Printf("ProcessSubclass(%s)\n",sname) } w._ProcessDelSub(sname, ps, nms, true) } func (w *Wrapper) ProcessDelegate(dname string, ps map[string][]string) { + i := &Interface{ + ProcessedInstanceMethods: map[string]bool{}, + Properties: map[string]*Property{}, + } + w.Interfaces[dname] = i w._ProcessDelSub(dname, ps, nil, false) } @@ -1276,6 +1372,7 @@ func (w *Wrapper) _ProcessDelSub(dname string, ps map[string][]string, nms []*Me //5. Go constructor //6. Go dispatch database for callbacks //7. Go superclass dispatch function + //8. Methods inherited from parent class //To create (per method): //1. ObjC function prototypes for go exports //2. Go callback registration functions @@ -1290,6 +1387,7 @@ func (w *Wrapper) _ProcessDelSub(dname string, ps map[string][]string, nms []*Me sms := 0 // the number of methods that have super-methods gnames := []string{} // go names for methods pnames := make([]string, len(ps)) + supmeths := []*Method{} var supr string i := 0 for pname, pats := range ps { @@ -1364,6 +1462,8 @@ func (w *Wrapper) _ProcessDelSub(dname string, ps map[string][]string, nms []*Me fmt.Printf("--Method: %s\n", m.Name) } if !matches(string(m.Name[0])+m.GoName[1:], pats) { + //methods from superclass that we are not overriding + supmeths = append(supmeths,m) continue } if m.HasUnsupportedType() { @@ -1606,27 +1706,49 @@ void* } //4. Go type - - gotypes.WriteString( - types.NewTypeFromString(gname, supr).GoInterfaceDecl()) + if !w.ProcessedTypes[gname] { + gotypes.WriteString( + types.NewTypeFromString(gname, supr).GoInterfaceDecl(Gogc)) //5. Go constructor - var finalizer string - if shouldFinalize(gname,"") { - w.goImports["runtime"] = true - finalizer = fmt.Sprintf( -`runtime.SetFinalizer(ret,func(o *%s) { - o.Release() + var finalizer string + dbg := "" + dbg2 := "" + if Debug { + dbg = fmt.Sprintf(`fmt.Printf("Setting finalizer (%s): %%p -> %%p\n", ret, ret.ptr) + `, gname) + dbg2 = fmt.Sprintf(`fmt.Printf("Finalizer (%s): release %%p -> %%p\n", o, o.ptr) + `, gname) + } + if Gogc { + w.goImports["runtime"] = true + if Debug { w.goImports["fmt"] = true } + finalizer = fmt.Sprintf( +`if ret.ptr == nil { return ret } + %sruntime.SetFinalizer(ret,func(o *%s) { + %so.Release() }) - `,gname) - } - gocode.WriteString(fmt.Sprintf(` + `, dbg, gname, dbg2) + } + gocode.WriteString(fmt.Sprintf(` func %sAlloc() *%s { ret := &%s{} ret.ptr = unsafe.Pointer(C.%sAlloc()) %sreturn ret } `, gname, gname, gname, dname, finalizer)) + if Gogc { + gocode.WriteString(fmt.Sprintf(` +func (o *%s) GC() { + if o.ptr == nil { return } + %sruntime.SetFinalizer(o,func(o *%s) { + %so.Release() + }) +} +`, gname, dbg, gname, dbg2)) + } + } + w.ProcessedTypes[gname] = true //6. Go dispatch database for callbacks dispitems := make([]string, len(gnames)) @@ -1790,7 +1912,7 @@ func (o *%s) Super%s(%s) %s { } `, vn, w.Vaargs, vn, vn)) } - gocode.WriteString("\t" + types.GoToC(dname+"_super_"+m.Name, ns, snames, m.Type, tps, false, m.ShouldFinalize()) + "\n}\n") + gocode.WriteString("\t" + types.GoToC(dname+"_super_"+m.Name, ns, snames, m.Type, tps, false, m.ShouldFinalize(), m.ClassMethod) + "\n}\n") } } w.cCode.WriteString(cprotos.String()) @@ -1798,6 +1920,11 @@ func (o *%s) Super%s(%s) %s { w.goTypes.WriteString(gotypes.String()) w.goCode.WriteString(gocode.String()) w.goExports.WriteString(goexports.String()) + + // add methods from parent class that we are not overriding + for _,m := range supmeths { + w.ProcessMethodForClass(m,dname) + } } //Add class and instance methods from super class @@ -1808,8 +1935,8 @@ func (w *Wrapper) AddSupermethods(i *Interface) { m2 := &Method{ Name: m.Name, GoName: m.GoName, - Class: i.Name, - GoClass: i.GoName, + Class: mc.Class, + GoClass: mc.GoClass, Type: m.Type.CloneToClass(i.Name), ClassMethod: m.ClassMethod, Parameters: []*Parameter{},