Fix various memory management bugs in GC mode.

This commit is contained in:
Greg 2019-06-27 11:41:58 -04:00
parent b5773b5525
commit acc8cab583
19 changed files with 696 additions and 223 deletions

2
.gitignore vendored
View File

@ -16,3 +16,5 @@ examples/gc/ns
examples/functions/functions examples/functions/functions
examples/functions/ns examples/functions/ns
ns-old ns-old
examples/strings/strings
examples/strings/ns

401
README.md
View File

@ -62,6 +62,8 @@ pragma [ clang diagnostic ignored "-Wformat-security" ]
Regular expressions are permitted in the names of classes, functions, Regular expressions are permitted in the names of classes, functions,
protocols and protocol methods, overridden superclass methods, and enums. protocols and protocol methods, overridden superclass methods, and enums.
Since the `NSObject` class is necessary for memory management, NSWrap will
automatically include it if it is encountered in an input header file.
When invoked, NSWrap creates a subdirectory with the name of the package When invoked, NSWrap creates a subdirectory with the name of the package
as specified in `nswrap.yaml` or, by default, `ns` if a package name is not as specified in `nswrap.yaml` or, by default, `ns` if a package name is not
@ -131,14 +133,14 @@ For example, `NSString` provides the folowing `compare` methods:
These are translated into Go as: These are translated into Go as:
```go ```go
func (o NSString) Compare(string NSString) NSComparisonResult { } func (o *NSString) Compare(string *NSString) NSComparisonResult { }
func (o NSString) CompareOptions(string NSString, mask NSStringCompareOptions) NSComparisonResult { } func (o *NSString) CompareOptions(string *NSString, mask NSStringCompareOptions) NSComparisonResult { }
func (o NSString) CompareOptionsRange(string NSString, mask NSStringCompareOptions, func (o *NSString) CompareOptionsRange(string *NSString, mask NSStringCompareOptions,
rangeOfReceiverToCompare NSRange) NSComparisonResult { } rangeOfReceiverToCompare NSRange) NSComparisonResult { }
func (o NSString) CompareOptionsRangeLocale(string NSString, mask NSStringCompareOptions, func (o *NSString) CompareOptionsRangeLocale(string *NSString, mask NSStringCompareOptions,
rangeOfReceiverToCompare NSRange, locale NSObject) NSComparisonResult { } rangeOfReceiverToCompare NSRange, locale NSObject) NSComparisonResult { }
``` ```
@ -156,11 +158,45 @@ fmt.Printf("%s\n",str)
NSWrap creates a `Char` Go type that is equivalent to a C `char`. A pointer to NSWrap creates a `Char` Go type that is equivalent to a C `char`. A pointer to
`Char` in Go code can therefore be used with Objective-C functions and methods `Char` in Go code can therefore be used with Objective-C functions and methods
that take a `char*` parameter. that take a `char*` parameter.
When NSWrap binds a method that returns `*Char` (and is in garbage collected mode,
the default), it first calls `strdup`
on the output of the underlying Objective-C method. Therefore, the returned
pointer is manually allocated and will need to be freed later from Go. NSWrap
creates a
`(*Char).Free()` method for use when these pointers are no longer needed.
This copying is necessary because the Objective-C runtime will sometimes
return pointers to internal objects that are impossible to manage from the
Go side. NSWrap aims to cause any internal objects to be deallocated as soon
as possible so they do not cause memory leaks. This means that any returned
C strings need to be copied and memory managed manually from the Go side.
NSWrap provides the helper functions `CharWithGoString` and `CharWithBytes` NSWrap provides the helper functions `CharWithGoString` and `CharWithBytes`
that take, respectively, Go strings and Go byte arrays (`[]byte`) and return that take, respectively, Go strings and Go byte arrays (`[]byte`) and return
`*Char` in Go. As demonstrated above, NSWrap also provides a `String()` `*Char` in Go. As demonstrated above, NSWrap also provides `String()`
methods so that the `*Char` and `NSString` types implement the `Stringer` methods so that the `*Char` and `*NSString` types implement the `Stringer`
Go interface. Go interface and therefore can be sent directly to functions like `fmt.Printf`.
The `String()` method on `*NSString` creates a temporary `*Char` internally
but frees it for you before returning. Since methods returning
`*Char` return a pointer that needs to be manually freed, it is important
to use these properly in order to avoid leaks:
```go
nst := ns.NSStringWithGoString("one string")
// NO: the next line leaks a *Char (UTF8String)
//mygostring := nst.UTF8String().String()
// OK: NSWrap creates a temporary *Char and frees it for you:
mygostring := nst.String()
// ALSO OK: manually free your own temporary *Char:
mytmpchar := nst.UTF8String()
mygostring = mytmpchar.String()
mytmpchar.Free()
```
In most cases it will be more convenient to convert directly to Go strings instead
of `*Char`.
## Working With NSObject and its Descendants ## Working With NSObject and its Descendants
@ -171,14 +207,23 @@ follows:
type Id struct { type Id struct {
ptr unsafe.Pointer ptr unsafe.Pointer
} }
func (o Id) Ptr() unsafe.Pointer { return o.ptr } func (o *Id) Ptr() unsafe.Pointer { if o == nil { return nil }; return o.ptr }
type NSObject interface { type NSObject interface {
Ptr() unsafe.Pointer Ptr() unsafe.Pointer
} }
``` ```
Other object types in Go are structs that directly or indirectly embed `Id` Other object types in Go are structs that directly or indirectly embed `Id`
and therefore implement `NSObject`. and therefore contain an `unsafe.Pointer` to an Objective-C object, and
implement `NSObject` by inheriting the `Ptr()` method.
Because of this implementation, you will note that every Objective-C object
is represented by at least two pointers -- an underlying pointer to the
Objective-C object
in CGo memory (allocated by the Objective-C runtime), as well as a pointer
allocated by the Go runtime to an `Id` type, or to another type that directly
or indirectly embeds `Id`. This "dual pointer" approach is necessary to ensure
that memory management can be made to work correctly (see below for details).
* The NSObject Interface * The NSObject Interface
@ -190,7 +235,7 @@ embeds `Id` to be used with generic Objective-C functions. For example:
```go ```go
o1 := ns.NSStringWithGoString("my string") o1 := ns.NSStringWithGoString("my string")
s1 := ns.NSSetWithOBjects(o1) s1 := ns.NSSetWithObjects(o1)
a := ns.NSMutableArrayWithObjects(o1,s1) a := ns.NSMutableArrayWithObjects(o1,s1)
``` ```
Since `NSString` and `NSSet` in Go both implement the `NSObject` interface, Since `NSString` and `NSSet` in Go both implement the `NSObject` interface,
@ -204,70 +249,73 @@ implements the required delegate protocol.
* Inheritance * Inheritance
Objective-C only provides single inheritance. In Go, this is modeled using Objective-C allows single inheritance. NSWrap automatically adds
embedding. Top level objects that inherit from `NSObject` in Objective-C inherited methods to classes that are includled in your binding.
Types created by NSWrap also "embed" their parent class. For example, top
level objects that inherit from `NSObject` in Objective-C
embed the Go type `Id` and therefore implement the `NSObject` Go interface. embed the Go type `Id` and therefore implement the `NSObject` Go interface.
Other objects embed their superclass. For example: Other objects embed their direct superclass. For example:
```go ```go
type NSArray struct { Id } type NSArray struct { Id }
func (o NSArray) Ptr() unsafe.Pointer { return o.ptr } func (o *NSArray) Ptr() unsafe.Pointer { if o == nil { return nil }; return o.ptr }
func (o Id) NSArray() NSArray { func (o *Id) NSArray() *NSArray {
ret := NSArray{} return (*NSArray)(unsafe.Pointer(o))
ret.ptr = o.ptr
return ret
} }
type NSMutableArray struct { NSArray } type NSMutableArray struct { NSArray }
func (o NSMutableArray) Ptr() unsafe.Pointer { return o.ptr } func (o *NSMutableArray) Ptr() unsafe.Pointer { if o == nil { return nil }; return o.ptr }
func (o Id) NSMutableArray() NSMutableArray {...} func (o *Id) NSMutableArray() *NSMutableArray {
return (*NSMutableArray)(unsafe.Pointer(o))
}
``` ```
Observe: Observe:
```go ```go
b := ns.NSButtonAlloc() // NSButton > NSControl > NSView > NSResponder > NSObject b := ns.NSButtonAlloc() // NSButton > NSControl > NSView > NSResponder > NSObject
b.InitWithFrame(ns.NSMakeRect(100,100,200,200)) // Method of NSView b.InitWithFrame(ns.NSMakeRect(100,100,200,200))
b.SetTitle(nst("PUSH")) // Method of NSButton b.SetTitle(nst("PUSH"))
vw := win.ContentView() vw := win.ContentView()
vw.AddSubview(b.NSView) // Pass the button's embedded NSView vw.AddSubview(&b.NSView) // Pass the button's embedded NSView
``` ```
In Go, `NSButtonAlloc` returns a Go object of type `ns.NSButton`. However, In Go, `NSButtonAlloc` returns a Go object of type `ns.NSButton`. However,
there is no `InitWithFrame` method for receivers of this type. This is the `initWithFrame` method is defined in AppKit for `NSView`. NSWrap will find this
not necessary because `NSButton` embeds `NSControl` which in turn embeds method and add it to the Go `NSButton` type when creating your wrapper because
`NSView`. The `InitWithFrame` method only needs to be implemented for `NSView` `NSButton` inherits from `NSControl` which inherits from `NSView`.
receivers. Go will automatically find the indirectly embedded `NSView` and
call the right method.
Note that, since `InitWithFrame()` is defined only for `NSView` and returns As of this
an `NSView` type, writing, on MacOS 10.13.6, NSWrap binds 115 instance methods for `NSObject`,
the following will not work. Look out for this if you like to chain your so things like `Hash()`, `IsEqualTo()`, `ClassName()`, `RespondsToSelector()`
`Alloc` and `Init` methods and are getting type errors:
```go
//DO NOT DO THIS -- InitWithFrame returns NSView, not NSButton
b := ns.NSButtonAlloc().InitWithFrame(ns.MakeRect(100,100,200,200))
```
Go has no trouble finding embedded methods for your `NSButton` and will
happily search up the chain through `NSControl`, `NSView`, `NSResponder` and
`NSObject` and all of their associated protocols and categories. As of this
writing, on MacOS 10.13.6, NSWrap binds 90 instance methods for `NSObject`,
so things like `Hash()`, `IsEqualTo()`, `ClassName()`, `RespondsToSelector`
and many many others are available and can be called on any object directly and many many others are available and can be called on any object directly
from Go. from Go.
Go does not perform the same type All objects implement the `NSObject` interface, but from time to time you
magic when you use variables as function or method parameters. will encounter a method that takes a parameter of a different type that may
If you want to pass your `NSButton` as a parameter to a method that accepts not exactly match the type you have. For example, if you want to pass your
an `NSView` type, you need to explicitly pass the embedded `NSView` `NSButton` as a parameter to a method that accepts an `NSView` type, you need
(`b.NSView` in the example above). to explicitly pass its embedded `NSView` (`&b.NSView` in the example above).
This approach is safer than "converting" the button to an `NSView` (see below)
because it will only work on objects that directly or indirectly embed an
`NSView` Go type.
NSWrap creates a method for `Id` allowing objects to be converted NSWrap creates a method for `Id` allowing objects to be converted
at run-time to any other class. You will need this for Enumerators, which at run-time to any other class. You will need this for Enumerators and
always return `Id`. See below under Enumerators for an example, but make functions like `NSArray`'s `GetObjects`, for example,
which always return `*Id`. Make
sure you know (or test) what type your objects are before converting them. sure you know (or test) what type your objects are before converting them.
You can You can implement a version of a Go type switch this way:
implement a somewhat less convenient version of a Go type switch this way.
```go
switch {
case o.IsKindOfClass(ns.NSStringClass()):
// do something with o.NSString()
case o.IsKindOfClass(ns.NSSetClass()):
// do something with o.NSSet()
default:
...
}
```
Because `Id` can be converted to any type, and every object in the Foundation Because `Id` can be converted to any type, and every object in the Foundation
classes inherits from `Id`, it is possible to send any message to any classes inherits from `Id`, it is possible to send any message to any
@ -314,11 +362,12 @@ functions.
Pointers to pointers are sometimes passed to Objective-C methods or functions Pointers to pointers are sometimes passed to Objective-C methods or functions
as a way of receiving output from those functions, especially because as a way of receiving output from those functions, especially because
Objective-C does not allow for multiple return values. In those cases, after Objective-C does not allow for multiple return values. In those cases, after
the CGo call, the method parameter will be treated as a nil-terminated array of the CGo call, the method parameter will be treated as an array of
object pointers that may have been modified by the Objective-C function or object pointers that may have been modified by the Objective-C function or
method. NSWrap will copy the object pointers back into the input Go slice, up method. NSWrap will copy the object pointers back into the input Go slice, up
to its capacity (which will never be changed). The input Go slice is then to its capacity (which will never be changed). The input Go slice is then
truncated to the appropriate length. truncated to the appropriate length. If there is no output, the length will
be set to 0.
An example in Core Foundation is the `getObjects:andKeys:count` method for An example in Core Foundation is the `getObjects:andKeys:count` method for
`NSDictionary`: `NSDictionary`:
@ -329,28 +378,31 @@ An example in Core Foundation is the `getObjects:andKeys:count` method for
ns.NSArrayWithObjects(nst("obj1"),nst("obj2")), ns.NSArrayWithObjects(nst("obj1"),nst("obj2")),
ns.NSArrayWithObjects(nst("key1"),nst("key2")), ns.NSArrayWithObjects(nst("key1"),nst("key2")),
) )
os,ks := make([]ns.Id,0,5), make([]ns.Id,0,5) // length 0, capacity 5 slices va,ka := make([]*ns.Id,0,5), make([]*ns.Id,0,5) // length 0, capacity 5 slices
dict.GetObjects(&os,&ks,5) dict.GetObjects(&va,&ka,5)
// last parameter is the count, must be less than or equal to the input slice capacity // last parameter to GetObjects is the count, must be less than or equal to the input slice capacity
fmt.Printf("Length of os is now %d\n",len(os)) // os and ks slices are now length = 2 fmt.Printf("Length of va is now %d\n",len(va)) // va and ka slices are now length = 2
for i,k := range ks { for i,k := range ka {
fmt.Printf("-- %s -> %s\n",k.NSString(),os[i].NSString()) fmt.Printf("-- %s -> %s\n",k.NSString(),va[i].NSString())
} }
``` ```
NSWrap will never check the "count" parameter, so the user will always need NSWrap will not check the "count" parameter, so the user will always need
to make sure it is less than or equal to the capacity of the input to make sure it is less than or equal to the capacity of the input
Go slices. Go slices.
Using pointers to pointers is necessary in many Core Foundation situations Using pointers to pointers is necessary in many Core Foundation situations
where you need to get an error message out of a function or method. where you need to get an error message out of a function or method, or in other
cases where an Objective-C method wants to provide multiple return values.
Here is an example using `[NSString stringWithContentsOfURL...]`: Here is an example using `[NSString stringWithContentsOfURL...]`:
```go ```go
err := make([]ns.NSError,1) err := make([]*ns.NSError,1)
n1 = ns.NSStringWithContentsOfURLEncoding(ns.NSURLWithGoString("htttypo://example.com"),0,&err) n1 = ns.NSStringWithContentsOfURLEncoding(ns.NSURLWithGoString("htttypo://example.com"), 0, &err)
fmt.Printf("err: %s\n",err[0].LocalizedDescription()) if len(err) > 0 {
fmt.Printf("err: %s\n",err[0].LocalizedDescription())
//err: The file couldnt be opened because URL type htttypo isnt supported. //err: The file couldnt be opened because URL type htttypo isnt supported.
}
``` ```
## Selectors ## Selectors
@ -370,15 +422,17 @@ appMenu.AddItemWithTitle(
## Enumerators ## Enumerators
NSWrap provides a `ForIn` method for the `NSEnumerator` type. Call it with a NSWrap provides a `ForIn` method for the `NSEnumerator` type. Call it with a
`func(ns.Id) bool` parameter that returns `true` to continue and `false` to `func(*ns.Id) bool` parameter that returns `true` to continue and `false` to
stop the enumeration. stop the enumeration.
```go ```go
a := ns.NSArrayWithObjects(o1,o2,o3) a := ns.NSArrayWithObjects(o1,o2,o3)
a.ObjectEnumerator().ForIn(func (o ns.Id) bool { i := 0
a.ObjectEnumerator().ForIn(func (o *ns.Id) bool {
switch { switch {
case o.IsKindOfClass(ns.NSStringClass()): case o.IsKindOfClass(ns.NSStringClass()):
fmt.Println(o.NSString().UTF8String()) fmt.Printf("%d: %s\n", i, o.NSString())
i++
return true // continue enumeration return true // continue enumeration
default: default:
fmt.Println("Unknown class") fmt.Println("Unknown class")
@ -392,7 +446,7 @@ identification.
## Enum Definitions ## Enum Definitions
NSWrap translates C `enum` values into Go constants. The enums you need are NSWrap translates C `enum` values into Go constants. The enums you want are
specified in `nswrap.yaml` by regular expression, which, in the case of named specified in `nswrap.yaml` by regular expression, which, in the case of named
enums, must match the name of the `enum` itself, or in the case of anonymous enums, must match the name of the `enum` itself, or in the case of anonymous
enums, must match the name of the constant(s) you are looking for as declared enums, must match the name of the constant(s) you are looking for as declared
@ -426,70 +480,6 @@ const _CLOCK_MONOTONIC_RAW = C._CLOCK_MONOTONIC_RAW
... ...
``` ```
## Memory management
Objective-C objects are always allocated and returned from CGo code, and
therefore these pointers are not garbage collected by Go. You can use any
of the standard Objective-C memory management techniques for those pointers,
which seem to work but have not been extensively tested.
Since everything inherits methods from `NSObject`, you can call `Retain()`,
`Release()` and `Autorelease()` on any object.
If the autorelease configuration directive is set to "true", all allocation functions
created by NSWrap (i.e. those ending in `Alloc`) will call `autorelease` before they
return an object. Alternately, objects can be manually sent an autorelease message.
If you are not working in an environment (such as an
Application Delegate callback) that provides an autorelease pool, you can
create your own:
* Work directly with `NSAutoreleasePool` objects
```go
swamp := ns.NSAutoreleasePoolAlloc().Init()
del := ns.AppDelegateAlloc()
//del.Autorelease() // if autorelease: true is not set in nswrap.yaml
menu := ns.NSMenuAlloc().InitWithTitle(nst("Main"))
//menu.Autorelease()
str := ns.NSStringWithGoString("these objects will be automatically deallocated when swamp is drained.")
...
swamp.Drain()
```
* ...or use the `AutoreleasePool()` helper function
NSWrap provides a helper function that can be passed a `func()` with no
parameters or return value. It is conventient to give it an anonymous function
and write your code in line, just like you would if you were using an
`@autoreleasepool { }` block.
```go
ns.AutoreleasePool(func() {
a := MyObjectAlloc().Init()
b := MyOtherObjectAlloc().Init()
...
})
```
You will need to make sure `NSAutoreleasePool` is included in the `classes`
directive in your configuration file before working with
`NSAutoreleasePool` objects or the `AutoreleasePool` helper function.
* Pitfalls
Go concurrency does not play well with Objective-C memory management. In particular,
an AutoreleasePool needs to be allocated and drained from the same thread, and
only objects allocated within that thread will be drained. Objects allocated and
autoreleased from a different goroutine in the same thread are at risk of being
prematurely drained. Therefore, you should only work with one AutoreleasePool at
a time, and only within a thread that is locked to the OS thread
(by calling `runtime.LockOSThread()`). If you will be allocating Objective-C objects
from multiple goroutines, it is best not to use the `autorelease: true` directive
as that will cause all objects to receive an `autorelease` message even if they are
created outside the thread where are using your autorelease pool.
See `examples/memory` for some basic tests and read the comments to larn what to avoid.
## Delegates ## Delegates
The `delegates` directive in `nswrap.yaml` creates a new Objective-C The `delegates` directive in `nswrap.yaml` creates a new Objective-C
@ -531,7 +521,8 @@ registered,
it will be called with all of its parameters converted to their Go type it will be called with all of its parameters converted to their Go type
equivalents. User-defined callbacks are registered by calling a function equivalents. User-defined callbacks are registered by calling a function
with the method name in TitleCase + `Callback`, so in the example above, with the method name in TitleCase + `Callback`, so in the example above,
you would call `ns.CentralManagerDidUpdateStateCallback(...)` with the name of if your delegate was named `del`, you would call
`del.CentralManagerDidUpdateStateCallback(...)` with the name of
your callback function to register to receive notifications when your central your callback function to register to receive notifications when your central
manager updates its state. manager updates its state.
@ -547,11 +538,16 @@ func cb(c ns.CBCentralManager) {
... ...
} }
var (
del *ns.CBDelegate // use global variables so these don't get garbage collected
cm *ns.CBCentralManager
)
func main() { func main() {
... ...
del := ns.CBDelegateAlloc() del = ns.CBDelegateAlloc()
del.CentralManagerDidUpdateStateCallback(cb) del.CentralManagerDidUpdateStateCallback(cb)
cm := ns.CBCentralManagerAlloc().InitWithDelegateQueue(del,queue) cm = ns.CBCentralManagerAlloc().InitWithDelegateQueue(del,queue)
``` ```
When you provide user-defined callback functions, you will need to specify When you provide user-defined callback functions, you will need to specify
@ -562,12 +558,12 @@ messages will point you in the right direction.
``` ```
$ go build $ go build
./main.go:127:43: cannot use didFinishLaunching (type func(ns.NSNotification, bool)) as type ./main.go:127:43: cannot use didFinishLaunching (type func(*ns.NSNotification, bool)) as type
func(ns.NSNotification) in argument to del.ApplicationDidFinishLaunchingCallback func(*ns.NSNotification) in argument to del.ApplicationDidFinishLaunchingCallback
``` ```
In the above example, the build failed because an extra `bool` parameter was In the above example, the build failed because an extra `bool` parameter was
included in the callback function. The compiler is telling you that the right included in the callback function. The compiler is telling you that the right
type for the callback is `func(ns.NSNotification)` with no return value. type for the callback is `func(*ns.NSNotification)` with no return value.
## Working with AppKit ## Working with AppKit
@ -576,8 +572,7 @@ Delegate. This allows you to build a Cocoa application entirely in Go.
Because AppKit uses thread local storage, you will need to make sure all Because AppKit uses thread local storage, you will need to make sure all
calls into it are done from the main OS thread. This can be a challenge in calls into it are done from the main OS thread. This can be a challenge in
Go even though runtime.LockOSThread() is supposed to provide Go and you will want to make use of `runtime.LockOSThread()`.
this functionality.
This is actually a full working Cocoa application: This is actually a full working Cocoa application:
@ -618,24 +613,30 @@ import (
"git.wow.st/gmp/nswrap/examples/app/ns" // point to your own NSWrap output directory "git.wow.st/gmp/nswrap/examples/app/ns" // point to your own NSWrap output directory
) )
func didFinishLaunching(n ns.NSNotification) { func didFinishLaunching(n *ns.NSNotification) {
fmt.Println("Go: did finish launching!") fmt.Println("Go: did finish launching!")
} }
func shouldTerminate(s ns.NSApplication) ns.BOOL { func shouldTerminate(s *ns.NSApplication) ns.BOOL {
return 1 return 1
} }
var (
a *ns.NSApplication // global vars so these are not garbage collected
del *ns.AppDelegate
win *ns.NSWindow
)
func main() { func main() {
runtime.LockOSThread() runtime.LockOSThread()
a := ns.NSApplicationSharedApplication() a = ns.NSApplicationSharedApplication()
a.SetActivationPolicy(ns.NSApplicationActivationPolicyRegular) a.SetActivationPolicy(ns.NSApplicationActivationPolicyRegular)
del := ns.AppDelegateAlloc() del = ns.AppDelegateAlloc()
del.ApplicationDidFinishLaunchingCallback(didFinishLaunching) del.ApplicationDidFinishLaunchingCallback(didFinishLaunching)
del.ApplicationShouldTerminateAfterLastWindowClosedCallback(shouldTerminate) del.ApplicationShouldTerminateAfterLastWindowClosedCallback(shouldTerminate)
a.SetDelegate(del) a.SetDelegate(del)
win := ns.NSWindowAlloc().InitWithContentRectStyleMask( win = ns.NSWindowAlloc().InitWithContentRectStyleMask(
ns.NSMakeRect(200,200,600,600), ns.NSMakeRect(200,200,600,600),
ns.NSWindowStyleMaskTitled | ns.NSWindowStyleMaskClosable, ns.NSWindowStyleMaskTitled | ns.NSWindowStyleMaskClosable,
ns.NSBackingStoreBuffered, ns.NSBackingStoreBuffered,
@ -647,7 +648,7 @@ func main() {
} }
``` ```
Pretty simple right? Not really, NSWrap just generated almost 15,000 lines of Pretty simple right? Not really, NSWrap just generated over 39,000 lines of
code. See `examples/app` for a slightly more complex example with working code. See `examples/app` for a slightly more complex example with working
menus, visual format-based auto layout, and a custom button class. menus, visual format-based auto layout, and a custom button class.
@ -667,7 +668,7 @@ subclasses:
yourClass: # the superclass to inherit from yourClass: # the superclass to inherit from
- init.* # what methods to override - init.* # what methods to override
- -(void)hi_there:(int)x # Objective-C prototype of your new method(s) - -(void)hi_there:(int)x # Objective-C prototype of your new method(s)
# |--this hyphen indicates that this is an instance method # \--the initial hyphen indicates that this is an instance method
``` ```
In the example above, your new class will be named `myClass` in Objective-C In the example above, your new class will be named `myClass` in Objective-C
@ -728,9 +729,109 @@ Later on you can add your new button to a view and tell Cocoa where to lay
it out. It's all a little verbose, but that's because for some reason you it out. It's all a little verbose, but that's because for some reason you
decided to write Objective-C code in Go. decided to write Objective-C code in Go.
## Memory management
As mentioned above, NSWrap is designed for there to be at least one Go pointer
associated with each underlying Objective-C object pointer.
Since Objective-C memory
is always allocated by the Objective-C runtime, it is not possible for the Go
runtime to have visibility into these memory regions or to directly manage memory
used by the CGo code. However, Go will keep track of the associated Go pointer
that was created the first time the corresponding Objective-C object was passed
over to the Go side and an `Id` or other NSWrap struct type was allocated.
Because of this,
it is possible to hook into the Go garbage collection system in an attempt to
manage Objective-C memory strictly from the Go side. When there are no remaining Go
pointers to an NSWrap `Id` struct, it will be deallocated by the Go garbage collector
and a finalizer will be called that `release`es the corresponding Objective-C
object.
The memory management rules work as follows:
* Objects in Go are represented by pointers to types that implement the `NSObject`
interface
* NSObject has one method, `Ptr()`, which returns an `unsafe.Pointer` to an
Objective-C object.
* All methods that return objects to Go call `retain` except for `new`, `init`,
`alloc`, `copy` and `mutableCopy`, which already return retained objects from the
Objective-C runtime.
* Go wrappers for Objective-C methods call `runtime.SetFinalizer()`, which calls
`release` when the associated Go struct is garbage collected.
* All Objective-C methods are run inside an `@autoreleasepool {}` block to prevent
internal memory leaks within the Objective-C libraries and frameworks.
* Objects sent to you in callback functions are not memory managed by Go
and must be manually
managed using `Retain()` and `Release()` methods if you need to take ownership of them.
A rule of thumb is that if you assign such an object to a persistent Go variable for
use outside of the callback, call `Retain()`.
Because of the linkage with the Go garbage collector described above, there should be
no need for any memory management code to be written from the Go side, except in the
case mentioned above where your Go delegate receives objects that need to be kept
around outside of the callback.
Since everything in Objective C inherits methods from `NSObject`, you can call
`Retain()`, `Release()` and `Autorelease()` on any object. You can technically bind
the `NSAutoreleasePool` class and create and drain instances of it from the Go side,
but this is not recommended in the default, garbage collected mode and can run into
problems because the Go runtime is inherently multithreaded. See `examples/memory`
for an example of manual memory management, which should be possible to do reliably
but I'm not sure why you would go through the trouble.
NSWrap is doing a number of things behind the scenes to make garbage collection work.
As mentioned,
all Objective-C methods are called within an `@autorelease {}` block. This is
necessary because some foundation classes (notably `NSString`) create internal
objects that are `autoreleased` but never returned to the caller. These objects can
never be deallocated unless the method in question was called within an autorelease
pool.
NSWrap assumes you
are going to take ownership of every Objective-C object returned by a method, either
directly as a return value or through a pointer to a pointer given as a parameter.
Therefore, NSWrap calls `retain` on all of these objects before going back to the
Go side, unless the object is either `nil` or equivalent to the input object.
NSWrap also will not call `retain` on the return values of `init`, `new`, `copy`,
`mutableCopy` or `alloc` methods. If you do not want ownership of the object,
simply assign it to a local varable and the garbage collector will take care of
releasing it.
In
order for this to work on a pointer to a pointer parameter, NSWrap treats the
input
parameter as an array with a length specified by either a `range` parameter (of
type `NSRange`) or a `count` parameter of an integer type. If there is neither a
`range` or `count` parameter, NSWrap assumes the array is length 1.
As an example, in Objective-C, if you were to take an object out of an `NSArray`
and the array was later deallocated, there is no guarantee that the object you
obtained is still around unless you called `retain` on it. This is not necessary
with NSWrap, which automatically retains objects returned by methods like
`objectAtIndex:` and `getObjects:range` and manages them with the Go garbage
collector.
The methods described above work for methods that return Objective-C
objects, which can be `retain`ed, but not with methods that return other types of
pointers such as C strings. NSWrap has a special case for C strings (`*Char`
in Go), calling
`strdup` on the return value within the `@autoreleasepool` block. This
ensures that the string is preserved even if it points to a termporary
autoreleased
object. Since this behavior results in newly allocated memory, these pointers will
need to be freed from Go later on. Since these are pointers to C memory,
it is not possible to set a finalizer on these pointers for garbage collection
by Go.
Note that the Go garbage collector is lazy and will not activate unless your
application is running low on heap space. That means in practice that Objective-C
objects are going to stick around a lot longer than they might in a pure
Objective-C application. If this is an issue, simply run the Go GC manually with
`runtime.GC()`.
# Limitations # Limitations
## Blocks ## Blocks and Function Pointers
NSWrap does not support methods or functions that take C functions or blocks NSWrap does not support methods or functions that take C functions or blocks
as parameters or return values. as parameters or return values.
@ -742,7 +843,7 @@ and got carried away.
# Acknowledgements # Acknowledgements
This work was inspired by Maxim's excellent This work was inspired by Maxim Kupriianov's excellent
[c-for-go](https://github.com/xlab/c-for-go). Much of the [c-for-go](https://github.com/xlab/c-for-go). Much of the
infrastructure was lifted from Elliot Chance's equally excellent infrastructure was lifted from Elliot Chance's equally excellent
[c2go](https://github.com/elliotchance/c2go). Kiyoshi Murata's [c2go](https://github.com/elliotchance/c2go). Kiyoshi Murata's

View File

@ -21,6 +21,10 @@ func pb2() {
a.Terminate(a) a.Terminate(a)
} }
func db() {
fmt.Println("button deallocated")
}
func didFinishLaunching(n *ns.NSNotification) { func didFinishLaunching(n *ns.NSNotification) {
fmt.Println("Go: did finish launching") fmt.Println("Go: did finish launching")
fmt.Printf("Notification: %s\n", n.Name().UTF8String()) fmt.Printf("Notification: %s\n", n.Name().UTF8String())
@ -68,12 +72,14 @@ func didFinishLaunching(n *ns.NSNotification) {
b1.Init() b1.Init()
b1.PressedCallback(pb1) b1.PressedCallback(pb1)
b1.DeallocCallback(db)
b1.SetAction(ns.Selector("pressed")) b1.SetAction(ns.Selector("pressed"))
b1.SetTarget(b1) b1.SetTarget(b1)
b1.SetTitle(nst("PUSH")) b1.SetTitle(nst("PUSH"))
b2.Init() b2.Init()
b2.PressedCallback(pb2) b2.PressedCallback(pb2)
b2.DeallocCallback(db)
b2.SetTarget(b2) b2.SetTarget(b2)
b2.SetAction(ns.Selector("pressed")) b2.SetAction(ns.Selector("pressed"))
b2.SetTitle(nst("QUIT")) b2.SetTitle(nst("QUIT"))
@ -103,6 +109,7 @@ func didFinishLaunching(n *ns.NSNotification) {
} }
func shouldTerminateAfterLastWindowClosed(s *ns.NSApplication) ns.BOOL { func shouldTerminateAfterLastWindowClosed(s *ns.NSApplication) ns.BOOL {
fmt.Println("Go: should terminate after last window closed")
return 1 return 1
} }
@ -117,6 +124,7 @@ func didBecomeActive(n *ns.NSNotification) {
var ( var (
a *ns.NSApplication a *ns.NSApplication
del *ns.AppDelegate
win *ns.NSWindow win *ns.NSWindow
) )
@ -127,7 +135,8 @@ func app() {
a.SetActivationPolicy(ns.NSApplicationActivationPolicyRegular) a.SetActivationPolicy(ns.NSApplicationActivationPolicyRegular)
// Set up an AppDelegate // Set up an AppDelegate
del := ns.AppDelegateAlloc() // assign it to a global variable so it doesn't get garbage collected
del = ns.AppDelegateAlloc()
del.ApplicationDidFinishLaunchingCallback(didFinishLaunching) del.ApplicationDidFinishLaunchingCallback(didFinishLaunching)
del.ApplicationShouldTerminateAfterLastWindowClosedCallback(shouldTerminateAfterLastWindowClosed) del.ApplicationShouldTerminateAfterLastWindowClosedCallback(shouldTerminateAfterLastWindowClosed)
del.ApplicationWillTerminateCallback(willTerminate) del.ApplicationWillTerminateCallback(willTerminate)
@ -149,6 +158,6 @@ func main() {
}() }()
// Run our app in an autorelease pool just for fun // Run our app in an autorelease pool just for fun
go ns.Autoreleasepool(app) go app()
select {} select {}
} }

View File

@ -41,6 +41,7 @@ subclasses:
GButton: GButton:
NSButton: NSButton:
- -(void)pressed - -(void)pressed
- dealloc
frameworks: [ Foundation, AppKit ] frameworks: [ Foundation, AppKit ]
pragma: [ clang diagnostic ignored "-Wformat-security" ] pragma: [ clang diagnostic ignored "-Wformat-security" ]

View File

@ -100,12 +100,12 @@ func hr(d *ns.NSData) int {
func discoverCharacteristics(p *ns.CBPeripheral, s *ns.CBService, e *ns.NSError) { func discoverCharacteristics(p *ns.CBPeripheral, s *ns.CBService, e *ns.NSError) {
fmt.Printf("Did discover characteristics\n") fmt.Printf("Did discover characteristics\n")
uuid := s.UUID() uuid := s.UUID()
fmt.Printf("----%s\n", uuid.UUIDString().UTF8String()) fmt.Printf("----%s\n", uuid.UUIDString())
if uuid.IsEqualTo(hrm_uuid) { if uuid.IsEqualTo(hrm_uuid) {
s.Characteristics().ObjectEnumerator().ForIn(func(o *ns.Id) bool { s.Characteristics().ObjectEnumerator().ForIn(func(o *ns.Id) bool {
chr := o.CBCharacteristic() chr := o.CBCharacteristic()
chuuid := chr.UUID() chuuid := chr.UUID()
fmt.Printf("------%s\n", chuuid.UUIDString().UTF8String()) fmt.Printf("------%s\n", chuuid.UUIDString())
if chuuid.IsEqualTo(hrv_uuid) { if chuuid.IsEqualTo(hrv_uuid) {
p.SetNotifyValue(1, chr) p.SetNotifyValue(1, chr)
v := chr.Value() v := chr.Value()

View File

@ -4,6 +4,7 @@ package main
import ( import (
"fmt" "fmt"
"os"
"git.wow.st/gmp/nswrap/examples/foundation/ns" "git.wow.st/gmp/nswrap/examples/foundation/ns"
) )
@ -43,8 +44,10 @@ func main() {
fmt.Printf("i1 = %@\n", i1) fmt.Printf("i1 = %@\n", i1)
fmt.Printf("i1.Ptr() = %p\n", i1.Ptr()) fmt.Printf("i1.Ptr() = %p\n", i1.Ptr())
fmt.Printf("\nNSArray.ObjectEnumerator().ForIn():\n") fmt.Printf("\nNSArray.ObjectEnumerator().ForIn():\n")
x := 0
a.ObjectEnumerator().ForIn(func(o *ns.Id) bool { a.ObjectEnumerator().ForIn(func(o *ns.Id) bool {
fmt.Println(o.NSString()) fmt.Printf("%d: %s\n",x,o.NSString())
x++
return true return true
}) })
fmt.Printf("\nNSSetWithObjectsCount():\n") fmt.Printf("\nNSSetWithObjectsCount():\n")
@ -69,6 +72,7 @@ func main() {
} }
return true return true
}) })
fmt.Printf("a = %p a.NSArray = %p\n",a,&a.NSArray)
fmt.Printf("\nNSArrayWithObjects() (length 1)\n") fmt.Printf("\nNSArrayWithObjects() (length 1)\n")
a2 = ns.NSArrayWithObjects(n1) a2 = ns.NSArrayWithObjects(n1)
a2.ObjectEnumerator().ForIn(func(o *ns.Id) bool { a2.ObjectEnumerator().ForIn(func(o *ns.Id) bool {
@ -102,19 +106,26 @@ func main() {
ns.NSArrayWithObjects(nst("obj1"), nst("obj2")), ns.NSArrayWithObjects(nst("obj1"), nst("obj2")),
ns.NSArrayWithObjects(nst("key1"), nst("key2")), ns.NSArrayWithObjects(nst("key1"), nst("key2")),
) )
os := make([]*ns.Id, 0, 5) oarr := make([]*ns.Id, 0, 5)
fmt.Printf("Length of os is %d\n", len(os)) fmt.Printf("Length of oarr is %d\n", len(oarr))
ks := make([]*ns.Id, 0, 5) karr := make([]*ns.Id, 0, 5)
fmt.Printf("\nGetObjects()\n") fmt.Printf("\nGetObjects()\n")
d.GetObjects(&os, &ks, 4) d.GetObjects(&oarr, &karr, 4)
fmt.Printf("Length of os is now %d\n", len(os)) fmt.Printf("Length of oarr is now %d\n", len(oarr))
for i, k := range ks { for i, k := range karr {
fmt.Printf("-- %s -> %s\n", k.NSString(), os[i].NSString()) fmt.Printf("-- %s -> %s\n", k.NSString(), oarr[i].NSString())
} }
fmt.Printf("\nNSStringWithContentsOfURLEncoding()\n") fmt.Printf("\nNSStringWithContentsOfURLEncoding()\n")
err := make([]*ns.NSError, 1) err := make([]*ns.NSError, 1)
n1 = ns.NSStringWithContentsOfURLEncoding(ns.NSURLWithGoString("htttypo://example.com"), 0, &err) n1 = ns.NSStringWithContentsOfURLEncoding(ns.NSURLWithGoString("http://captive.apple.com"), ns.NSUTF8StringEncoding, &err)
fmt.Printf("err: %s\n", err[0].LocalizedDescription()) if len(err) == 0 {
fmt.Printf("n1 = %s\n",n1)
}
n1 = ns.NSStringWithContentsOfURLEncoding(ns.NSURLWithGoString("htttypo://example.com"), ns.NSUTF8StringEncoding, &err)
if len(err) > 0 {
fmt.Printf("err[0] = %p -> %p\n",err[0],err[0].Ptr())
fmt.Printf("err: %s\n", err[0].LocalizedDescription())
}
fmt.Printf("\nNSStringWithFormat()\n") fmt.Printf("\nNSStringWithFormat()\n")
str := ns.NSStringWithFormat(nst("(%@) (%@)\n(%@)\n"), n2, n3, s1) str := ns.NSStringWithFormat(nst("(%@) (%@)\n(%@)\n"), n2, n3, s1)
@ -129,4 +140,24 @@ func main() {
fmt.Printf("--%s\n",o.NSString()) fmt.Printf("--%s\n",o.NSString())
return true return true
}) })
dir,e := os.Getwd()
if e != nil {
fmt.Printf("Failed to get current working directory. %s\n",err)
os.Exit(-1)
}
path := nst(dir)
filter := ns.NSArrayWithObjects(nst("ast"),nst("yaml"))
ost := make([]*ns.NSString,0,1)
oar:= make([]*ns.NSArray,0,1)
fmt.Printf("\nCompletePathIntoString()\n")
i := path.CompletePathIntoString(&ost,0,&oar,filter)
fmt.Printf("%d matches\n",i)
if i > 0 {
fmt.Printf("ost = %s\n",ost[0])
fmt.Printf("oar =\n")
oar[0].ObjectEnumerator().ForIn(func(o *ns.Id) bool {
fmt.Printf("--%s\n",o.NSString())
return true
})
}
} }

View File

@ -19,7 +19,7 @@ classes:
- NSFileManager - NSFileManager
- NSObject - NSObject
functions: [ NSMakeRange ] functions: [ NSMakeRange ]
enums: [ P_ALL, CF.* ] enums: [ P_ALL, CF.*, .*StringEncoding ]
frameworks: [ Foundation ] frameworks: [ Foundation ]
pragma: [ clang diagnostic ignored "-Wformat-security" ] pragma: [ clang diagnostic ignored "-Wformat-security" ]
vaargs: 32 vaargs: 32

View File

@ -103,7 +103,7 @@ func memtest3() {
// check that our string was retained. // check that our string was retained.
s1 := arr.ObjectAtIndex(0) s1 := arr.ObjectAtIndex(0)
gstr := s1.NSString().UTF8String().String() gstr := s1.NSString().String()
_ = gstr _ = gstr
} }
} }
@ -122,6 +122,7 @@ func memtest4() {
_ = c1 _ = c1
runtime.GC() runtime.GC()
time.Sleep(time.Second/50) time.Sleep(time.Second/50)
c1.Free() // you need to manually free UTF8Strings
} }
} }
@ -145,22 +146,78 @@ func memtest5() {
u := sub.UTF8String() u := sub.UTF8String()
u2 := sub2.UTF8String() u2 := sub2.UTF8String()
u3 := sub3.UTF8String() u3 := sub3.UTF8String()
_ = u
_ = u2
_ = u3
time.Sleep(time.Second/50) time.Sleep(time.Second/50)
runtime.GC() runtime.GC()
i++ i++
u.Free()
u2.Free()
u3.Free()
_ = u
_ = u2
_ = u3
//fmt.Printf("loop completed\n") //fmt.Printf("loop completed\n")
} }
} }
func tmpdict(i int) *ns.NSString {
o1 := ns.NSStringWithGoString(fmt.Sprintf("temp string 1-%d",i))
o2 := ns.NSStringWithGoString(fmt.Sprintf("temp string 2-%d",i))
k1 := ns.NSStringWithGoString(fmt.Sprintf("temp key 1-%d",i))
k2 := ns.NSStringWithGoString(fmt.Sprintf("temp key 2-%d",i))
dict := ns.NSDictionaryWithObjectsAndKeys(o1,k1,o2,k2)
ret := dict.ValueForKey(k1)
//fmt.Printf("tmpdict(): string = %s\n",ret.NSString())
defer runtime.GC() // o1, o2, k1, k2, and dict can be released after we return
return ret.NSString() // should be retained by NSDictionary.ValueForKey()
}
func tmparr(i int) *ns.NSString {
o1 := ns.NSStringWithGoString(fmt.Sprintf("temp string 3-%d",i))
o2 := ns.NSStringWithGoString(fmt.Sprintf("temp string 4-%d",i))
arr := ns.NSArrayWithObjects(o1,o2)
os := make([]*ns.Id,0,2)
arr.GetObjects(&os, ns.NSMakeRange(0,2))
defer runtime.GC() // collect o1, o2 and arr
return os[1].NSString() // should have been retained by NSArray.GetObjects()
}
func memtest6() {
fmt.Println("memtest6 started")
i := 0
for {
s1 := tmpdict(i)
s2 := tmparr(i)
time.Sleep(time.Second / 5)
u1 := s1.String() // make sure s1 and s2 are still available
u2 := s2.String()
e1 := fmt.Sprintf("temp string 1-%d",i)
if u1 != e1 {
fmt.Printf("tmpdict() error: %s != %s\n",u1,e1)
}
e2 := fmt.Sprintf("temp string 4-%d",i)
if u2 != e2 {
fmt.Printf("tmparr() error: %s != %s\n",u2,e2)
}
i++
}
}
func main() { func main() {
fmt.Printf("MultiThreaded? %t\n", ns.NSThreadIsMultiThreaded())
th := ns.NSThreadNew()
th.Start()
fmt.Printf("MultiThreaded? %t\n", ns.NSThreadIsMultiThreaded())
go memtest1() go memtest1()
go memtest2() go memtest2()
go memtest3() go memtest3()
go memtest4() go memtest4()
go memtest5() go memtest5()
go memtest6()
go func() { go func() {
for { for {
// print a progress indicator // print a progress indicator

View File

@ -5,7 +5,8 @@ classes:
- NSArray - NSArray
- NSMutableArray - NSMutableArray
- NSString - NSString
- NSObject - NSDictionary
- NSThread
subclasses: subclasses:
MyClass: MyClass:
NSObject: NSObject:
@ -16,3 +17,4 @@ functions:
frameworks: frameworks:
- Foundation - Foundation
pragma: [ clang diagnostic ignored "-Wformat-security" ] pragma: [ clang diagnostic ignored "-Wformat-security" ]
#nogc: true

View File

@ -1,3 +1,4 @@
// An example of manual memory management (nogc directive in nswrap.yaml)
package main package main
import "C" import "C"
@ -38,8 +39,6 @@ func memtest1() {
for { for {
pool := ns.NSAutoreleasePoolAlloc().Init() pool := ns.NSAutoreleasePoolAlloc().Init()
o1 := ns.MyClassAlloc() o1 := ns.MyClassAlloc()
//If autorelease: true is set in nswrap.yaml, the manual calls to
//autorelease are not necessary.
o1.Autorelease() o1.Autorelease()
o1.DeallocCallback(dealloc) o1.DeallocCallback(dealloc)
o1.ReleaseCallback(release) o1.ReleaseCallback(release)
@ -133,6 +132,9 @@ func memtest4() {
go memtest1() go memtest1()
go memtest2() go memtest2()
go memtest3() go memtest3()
go memtest1()
go memtest2()
go memtest3()
} }
func memtest4a() { func memtest4a() {
@ -196,9 +198,6 @@ func main() {
//Within an autorelease pool, do not do anything that can result in a //Within an autorelease pool, do not do anything that can result in a
//switch to a different thread. //switch to a different thread.
//go memtest1()
//go memtest2()
//go memtest3()
go memtest4() go memtest4()
select {} select {}
} }

View File

@ -14,3 +14,4 @@ subclasses:
frameworks: frameworks:
- Foundation - Foundation
pragma: [ clang diagnostic ignored "-Wformat-security" ] pragma: [ clang diagnostic ignored "-Wformat-security" ]
nogc: true

View File

@ -1,6 +1,7 @@
package: ClassOne package: ClassOne
inputfiles: [ ClassOne/simple.h ] inputfiles: [ ClassOne/simple.h ]
classes: classes:
- NSObject
- ClassOne - ClassOne
- ClassTwo - ClassTwo
subclasses: subclasses:
@ -8,8 +9,7 @@ subclasses:
ClassTwo: ClassTwo:
- geti1 - geti1
- -(void)myMethod1:(int)a - -(void)myMethod1:(int)a
- +(const NSObject* _Nonnull)myMethod2:(int)a options:(NSDictionary<NSString*,NSObject*> *)opt - -(const NSObject* _Nonnull)myMethod2:(int)a options:(NSDictionary<NSString*,NSObject*> *)opt
imports: [ simple.h ] imports: [ simple.h ]
frameworks: [ Foundation ] frameworks: [ Foundation ]
#arc: true

157
examples/strings/main.go Normal file
View File

@ -0,0 +1,157 @@
package main
import (
"fmt"
"os"
"runtime"
"sync"
"time"
"unsafe"
"git.wow.st/gmp/nswrap/examples/strings/ns"
)
func incr() func(bool) (int, float64) {
i := 0
b := 0.0
var mx sync.Mutex
return func(bad bool) (int, float64) {
mx.Lock()
if bad {
b++
defer mx.Unlock()
} else {
defer func() { i++; mx.Unlock() }()
}
if b == 0 {
return i, 0.0
} else {
return i, (b/float64(i)) * 100
}
}
}
type tracker struct {
add, drop func(*ns.Id)
check func()
i func(bool) (int, float64)
}
type record struct {
ptr unsafe.Pointer
goPtr *ns.Id
when time.Time
}
func newTracker() (func(*ns.Id), func(*ns.Id), func()) {
addch := make(chan *ns.Id)
dropch := make(chan *ns.Id)
data := []record{}
var mux sync.Mutex
go func() {
for {
select {
case x := <-addch:
mux.Lock()
data = append(data,record{
x.Ptr(),
x,
time.Now(),
})
mux.Unlock()
case x := <-dropch:
mux.Lock()
data = append(data,record{
nil,
x,
time.Now(),
})
mux.Unlock()
}
}
}()
add := func(x *ns.Id) {
addch<- x
}
drop := func(x *ns.Id) {
dropch<- x
}
check := func() {
live := map[unsafe.Pointer]*ns.Id{}
bad := false
mux.Lock()
for _,r := range data {
if r.ptr != nil {
if live[r.ptr] != nil {
fmt.Printf("COLLISION: %p & %p -> %p\n", r.goPtr, live[r.ptr], r.ptr)
bad = true
}
live[r.ptr] = r.goPtr
} else {
delete(live,r.ptr)
}
}
fmt.Printf("Checked %d records -- ",len(data))
if bad {
fmt.Printf("failed\n")
} else {
fmt.Printf("ok\n")
}
mux.Unlock()
}
return add,drop,check
}
func mkstrings(t tracker) {
for {
//fmt.Printf("main thread: %t\n",ns.NSThreadIsMainThread())
x,b := t.i(false)
str := fmt.Sprintf("string %d",x)
s := ns.NSStringWithGoString(str)
//t.add(&s.Id)
for j := 0; j < 10; j++ {
sout := s.String()
if str != sout {
_,b = t.i(true)
fmt.Printf("%3.2f%% -- %d: '%s' '%s'\n", b, x, str, sout)
}
time.Sleep(time.Second/1000)
}
if x % 1000 == 0 {
fmt.Printf("%3.2f%% -- %s\n", b, time.Now().Format("03:04:05.000"))
}
//t.drop(&s.Id)
}
}
func main() {
runtime.GOMAXPROCS(4)
fmt.Printf("Starting\n")
//ns.NSThreadNew().Start()
fmt.Printf("multithreaded: %t\n", ns.NSThreadIsMultiThreaded())
//pool := ns.NSAutoreleasePoolAlloc()
add, drop, check := newTracker()
i := tracker{add, drop, check, incr()}
go mkstrings(i)
go mkstrings(i)
go mkstrings(i)
go mkstrings(i)
go mkstrings(i)
go mkstrings(i)
go mkstrings(i)
go mkstrings(i)
go func() {
for {
runtime.GC()
time.Sleep(time.Second/100)
}
}()
time.Sleep(time.Second * 600)
i.check()
//pool.Drain()
os.Exit(0)
}

View File

@ -0,0 +1,9 @@
inputfiles:
- /System/Library/Frameworks/Foundation.framework/Headers/Foundation.h
classes:
- NSString
- NSThread
frameworks:
- Foundation
pragma: [ clang diagnostic ignored "-Wformat-security" ]
#nogc: true

13
main.go
View File

@ -19,6 +19,12 @@ import (
var Debug = false var Debug = false
var Profile = false var Profile = false
//automatically add interfaces if they are found in the input interface
//declarations
var autoadd = []string{
"NSObject",
}
type conf struct { type conf struct {
Positions bool Positions bool
Package string Package string
@ -39,6 +45,7 @@ type conf struct {
//Arc flag for debugging only, builds will break //Arc flag for debugging only, builds will break
Arc bool Arc bool
Autorelease bool Autorelease bool
Nogc bool
} }
var Config conf var Config conf
@ -260,6 +267,9 @@ func Start() (err error) {
wrap.Autorelease = true wrap.Autorelease = true
wrap.Gogc = false wrap.Gogc = false
} }
if Config.Nogc {
wrap.Gogc = false
}
//NOTE: converting in parallel is slower on my system //NOTE: converting in parallel is slower on my system
//nodes := convertLinesToNodesParallel(lines) //nodes := convertLinesToNodesParallel(lines)
@ -294,6 +304,9 @@ func Start() (err error) {
} }
} }
} }
if matches(x.Name, autoadd) {
Config.Classes = append(Config.Classes,x.Name)
}
case *ast.ObjCCategoryDecl: case *ast.ObjCCategoryDecl:
w.AddCategory(x) w.AddCategory(x)
case *ast.TypedefDecl: case *ast.TypedefDecl:

View File

@ -7,6 +7,8 @@ import (
"strings" "strings"
) )
var Gogc bool
//super is a map recording which class is the parent of each other class //super is a map recording which class is the parent of each other class
var super map[string]string var super map[string]string
@ -442,7 +444,7 @@ func GoToC(sname, name string, pnames, snames []string, rtype *Type, ptypes []*T
case TypedefShouldWrap(ptgt) && !pt.Variadic && !fun: case TypedefShouldWrap(ptgt) && !pt.Variadic && !fun:
p = pn + ".Ptr()" p = pn + ".Ptr()"
case snames[i] != "": case snames[i] != "":
p = "unsafe.Pointer(&" + snames[i] + "[0])" p = "(*unsafe.Pointer)(unsafe.Pointer(&" + snames[i] + "[0]))"
case pt.Variadic: case pt.Variadic:
p = "unsafe.Pointer(&" + p + ")" p = "unsafe.Pointer(&" + p + ")"
case pt.IsPointer() && !fun: case pt.IsPointer() && !fun:
@ -478,6 +480,13 @@ func GoToC(sname, name string, pnames, snames []string, rtype *Type, ptypes []*T
if IsGoInterface(ptgt) { if IsGoInterface(ptgt) {
ptgt = "Id" ptgt = "Id"
} }
dogc := ""
if Gogc {
dogc = fmt.Sprintf(`
runtime.SetFinalizer((*%s)[i], func(o *%s) {
o.Release()
})`, pnames[i], ptgt)
}
ret.WriteString(fmt.Sprintf(` ret.WriteString(fmt.Sprintf(`
(*%s) = (*%s)[:cap(*%s)] (*%s) = (*%s)[:cap(*%s)]
for i := 0; i < len(*%s); i++ { for i := 0; i < len(*%s); i++ {
@ -486,10 +495,10 @@ func GoToC(sname, name string, pnames, snames []string, rtype *Type, ptypes []*T
break break
} }
if (*%s)[i] == nil { if (*%s)[i] == nil {
(*%s)[i] = &%s{} (*%s)[i] = &%s{}%s
} }
(*%s)[i].ptr = %s[i] (*%s)[i].ptr = %s[i]
}`, pnames[i], pnames[i], pnames[i], pnames[i], sname, pnames[i], pnames[i], pnames[i], pnames[i], ptgt, pnames[i], sname)) }`, pnames[i], pnames[i], pnames[i], pnames[i], sname, pnames[i], pnames[i], pnames[i], pnames[i], ptgt, dogc, pnames[i], sname))
} }
if rt != "void" { if rt != "void" {
cmp := "" cmp := ""

View File

@ -217,9 +217,7 @@ type NSObject interface {
type NSString struct { Id } type NSString struct { Id }
func (o *NSString) Ptr() unsafe.Pointer { if o == nil { return nil }; return o.ptr } func (o *NSString) Ptr() unsafe.Pointer { if o == nil { return nil }; return o.ptr }
func (o *Id) NSString() *NSString { func (o *Id) NSString() *NSString {
ret := &NSString{} return (*NSString)(unsafe.Pointer(o))
ret.ptr = o.ptr
return ret
} }
`) `)
str = "int(void)" str = "int(void)"
@ -276,7 +274,7 @@ func (o *Id) NSString() *NSString {
snames := []string{"", "", "", ""} snames := []string{"", "", "", ""}
chk_gotoc := func(expected string) { chk_gotoc := func(expected string) {
chk(GoToC("myFun", pnames, snames, rtype, ptypes, false, false, false), expected) chk(GoToC("myFun", "myFun", pnames, snames, rtype, ptypes, false, false, false), expected)
} }
chk_gotoc("") chk_gotoc("")
@ -298,12 +296,16 @@ func (o *Id) NSString() *NSString {
chk_gotoc( chk_gotoc(
`ret := &NSString{} `ret := &NSString{}
ret.ptr = unsafe.Pointer(C.myFun(p1.Ptr(), p2.Ptr(), (C.int)(p3), unsafe.Pointer(p4))) ret.ptr = unsafe.Pointer(C.myFun(p1.Ptr(), p2.Ptr(), (C.int)(p3), unsafe.Pointer(p4)))
if ret.ptr == nil { return ret }
if ret.ptr == o.ptr { return (*NSString)(unsafe.Pointer(o)) }
return ret`) return ret`)
rtype = nsop rtype = nsop
chk_gotoc( chk_gotoc(
`ret := &Id{} `ret := &Id{}
ret.ptr = unsafe.Pointer(C.myFun(p1.Ptr(), p2.Ptr(), (C.int)(p3), unsafe.Pointer(p4))) ret.ptr = unsafe.Pointer(C.myFun(p1.Ptr(), p2.Ptr(), (C.int)(p3), unsafe.Pointer(p4)))
if ret.ptr == nil { return ret }
if ret.ptr == o.ptr { return (*Id)(unsafe.Pointer(o)) }
return ret`) return ret`)
ptypes[1].Variadic = true ptypes[1].Variadic = true
@ -311,6 +313,8 @@ func (o *Id) NSString() *NSString {
chk_gotoc( chk_gotoc(
`ret := &Id{} `ret := &Id{}
ret.ptr = unsafe.Pointer(C.myFun(p1.Ptr(), unsafe.Pointer(&p2), (C.int)(p3), unsafe.Pointer(p4))) ret.ptr = unsafe.Pointer(C.myFun(p1.Ptr(), unsafe.Pointer(&p2), (C.int)(p3), unsafe.Pointer(p4)))
if ret.ptr == nil { return ret }
if ret.ptr == o.ptr { return (*Id)(unsafe.Pointer(o)) }
return ret`) return ret`)
ptypes[1].Variadic = false ptypes[1].Variadic = false
@ -318,7 +322,7 @@ func (o *Id) NSString() *NSString {
ptypes[1] = nsopp ptypes[1] = nsopp
chk_gotoc( chk_gotoc(
`ret := &Id{} `ret := &Id{}
ret.ptr = unsafe.Pointer(C.myFun(p1.Ptr(), unsafe.Pointer(&p2p[0]), (C.int)(p3), unsafe.Pointer(p4))) ret.ptr = unsafe.Pointer(C.myFun(p1.Ptr(), (*unsafe.Pointer)(unsafe.Pointer(&p2p[0])), (C.int)(p3), unsafe.Pointer(p4)))
(*p2) = (*p2)[:cap(*p2)] (*p2) = (*p2)[:cap(*p2)]
for i := 0; i < len(*p2); i++ { for i := 0; i < len(*p2); i++ {
if p2p[i] == nil { if p2p[i] == nil {
@ -330,6 +334,8 @@ func (o *Id) NSString() *NSString {
} }
(*p2)[i].ptr = p2p[i] (*p2)[i].ptr = p2p[i]
} }
if ret.ptr == nil { return ret }
if ret.ptr == o.ptr { return (*Id)(unsafe.Pointer(o)) }
return ret`) return ret`)
snames[1] = "" snames[1] = ""
snames[2] = "p3p" snames[2] = "p3p"
@ -337,7 +343,7 @@ func (o *Id) NSString() *NSString {
ptypes[2] = nstpp ptypes[2] = nstpp
chk_gotoc( chk_gotoc(
`ret := &Id{} `ret := &Id{}
ret.ptr = unsafe.Pointer(C.myFun(p1.Ptr(), p2.Ptr(), unsafe.Pointer(&p3p[0]), unsafe.Pointer(p4))) ret.ptr = unsafe.Pointer(C.myFun(p1.Ptr(), p2.Ptr(), (*unsafe.Pointer)(unsafe.Pointer(&p3p[0])), unsafe.Pointer(p4)))
(*p3) = (*p3)[:cap(*p3)] (*p3) = (*p3)[:cap(*p3)]
for i := 0; i < len(*p3); i++ { for i := 0; i < len(*p3); i++ {
if p3p[i] == nil { if p3p[i] == nil {
@ -349,11 +355,13 @@ func (o *Id) NSString() *NSString {
} }
(*p3)[i].ptr = p3p[i] (*p3)[i].ptr = p3p[i]
} }
if ret.ptr == nil { return ret }
if ret.ptr == o.ptr { return (*Id)(unsafe.Pointer(o)) }
return ret`) return ret`)
chk(GoToC("myFun", pnames, snames, rtype, ptypes, true, false, false), chk(GoToC("myFun", "myFun", pnames, snames, rtype, ptypes, true, false, false),
`ret := &Id{} `ret := &Id{}
ret.ptr = unsafe.Pointer(C.myFun(p1.Ptr(), p2.Ptr(), unsafe.Pointer(&p3p[0]), p4)) ret.ptr = unsafe.Pointer(C.myFun(p1.Ptr(), p2.Ptr(), (*unsafe.Pointer)(unsafe.Pointer(&p3p[0])), p4))
(*p3) = (*p3)[:cap(*p3)] (*p3) = (*p3)[:cap(*p3)]
for i := 0; i < len(*p3); i++ { for i := 0; i < len(*p3); i++ {
if p3p[i] == nil { if p3p[i] == nil {
@ -365,5 +373,7 @@ func (o *Id) NSString() *NSString {
} }
(*p3)[i].ptr = p3p[i] (*p3)[i].ptr = p3p[i]
} }
if ret.ptr == nil { return ret }
if ret.ptr == o.ptr { return (*Id)(unsafe.Pointer(o)) }
return ret`) return ret`)
} }

View File

@ -9,7 +9,7 @@ var (
) )
func dbg(f string, xs ...interface{}) { func dbg(f string, xs ...interface{}) {
if Debug && false { if Debug {
fmt.Printf(f, xs...) fmt.Printf(f, xs...)
} }
} }

View File

@ -70,7 +70,7 @@ func NewWrapper(debug bool) *Wrapper {
} }
ret.goImports["unsafe"] = true ret.goImports["unsafe"] = true
if Gogc { if Gogc {
ret.goImports["runtime"] = true types.Gogc = true
} }
ret.goTypes.WriteString(` ret.goTypes.WriteString(`
type Id struct { type Id struct {
@ -152,6 +152,8 @@ type Method struct {
Unavailable bool Unavailable bool
} }
// ShouldFinalize returns true on a method that returns an object that should
// have a GC finalizer.
func (m *Method) ShouldFinalize() bool { func (m *Method) ShouldFinalize() bool {
grtype := m.Type.GoType() grtype := m.Type.GoType()
return Gogc && grtype != "NSAutoreleasePool" && return Gogc && grtype != "NSAutoreleasePool" &&
@ -160,6 +162,8 @@ func (m *Method) ShouldFinalize() bool {
} }
// IsRetained returns true if a given instance method returns a retained object. // IsRetained returns true if a given instance method returns a retained object.
// NSWrap will not send a 'retain' message to these objects before returning
// them to Go.
func IsRetained(name string) bool { func IsRetained(name string) bool {
return ( return (
(len(name) >= 3 && name[:3] == "new") || (len(name) >= 3 && name[:3] == "new") ||
@ -300,10 +304,14 @@ func (w Wrapper) cparamlist(m *Method) (string, string, string) {
} }
for _, p := range m.Parameters { for _, p := range m.Parameters {
var tp string var tp string
wp := types.ShouldWrap(p.Type.GoType()) gt := p.Type.GoType()
if wp || p.Type.IsPointer() || p.Type.Variadic { wp := types.ShouldWrap(gt)
switch {
case len(gt) > 2 && gt[:1] == "*" && types.PtrShouldWrap(gt[1:]):
tp = "void**"
case wp || p.Type.IsPointer() || p.Type.Variadic:
tp = "void*" tp = "void*"
} else { default:
tp = p.Type.CType() tp = p.Type.CType()
} }
ns = append(ns, p.Vname) ns = append(ns, p.Vname)
@ -319,21 +327,28 @@ func (w Wrapper) objcparamlist(m *Method) string {
} }
first := true first := true
ret := []string{} ret := []string{}
pname := ""
for _, p := range m.Parameters { for _, p := range m.Parameters {
if first && !p.Type.Variadic { gt := p.Type.GoType()
ret = append(ret, m.Name+":"+p.Vname) if first {
first = false first = false
pname = m.Name
} else { } else {
if p.Type.Variadic { pname = p.Pname
str := []string{p.Pname + ", arr[0]"} }
for i := 1; i < w.Vaargs; i++ { switch {
str = append(str, "arr["+strconv.Itoa(i)+"]") case len(gt) > 2 && gt[:1] == "*" && types.PtrShouldWrap(gt[1:]):
} ret = append(ret, pname+":("+p.Type.Node.CType()+")"+p.Vname)
str = append(str, "nil") case !p.Type.Variadic:
ret = append(ret, strings.Join(str, ", ")) ret = append(ret, pname+":"+p.Vname)
} else { first = false
ret = append(ret, p.Pname+":"+p.Vname) case p.Type.Variadic:
str := []string{p.Pname + ", arr[0]"}
for i := 1; i < w.Vaargs; i++ {
str = append(str, "arr["+strconv.Itoa(i)+"]")
} }
str = append(str, "nil")
ret = append(ret, strings.Join(str, ", "))
} }
} }
return strings.Join(ret, " ") return strings.Join(ret, " ")
@ -347,11 +362,13 @@ var goreserved map[string]bool = map[string]bool{
"len": true, "len": true,
} }
func (w *Wrapper) gpntp(m *Method) ([]string, []string, []*types.Type, string) { func (w *Wrapper) gpntp(m *Method) ([]string, []string, []string, []*types.Type, string) {
ns := []string{} ns := []string{}
pnames := []string{}
tps := []*types.Type{} tps := []*types.Type{}
if !m.ClassMethod { if !m.ClassMethod {
ns = append(ns, "o") ns = append(ns, "o")
pnames = append(pnames, m.Name)
tps = append(tps, types.NewTypeFromString(m.Class+"*", "")) tps = append(tps, types.NewTypeFromString(m.Class+"*", ""))
} }
for i, p := range m.Parameters { for i, p := range m.Parameters {
@ -363,6 +380,7 @@ func (w *Wrapper) gpntp(m *Method) ([]string, []string, []*types.Type, string) {
gname = fmt.Sprintf("p%d",i) gname = fmt.Sprintf("p%d",i)
} }
ns = append(ns, gname) ns = append(ns, gname)
pnames = append(pnames, p.Pname)
tps = append(tps, p.Type) tps = append(tps, p.Type)
} }
w.processTypes(tps) w.processTypes(tps)
@ -394,7 +412,7 @@ func (w *Wrapper) gpntp(m *Method) ([]string, []string, []*types.Type, string) {
} }
ret = append(ret, ns[i]+" "+gt) ret = append(ret, ns[i]+" "+gt)
} }
return ns, snames, tps, strings.Join(ret, ", ") return ns, pnames, snames, tps, strings.Join(ret, ", ")
} }
type Interface struct { type Interface struct {
@ -883,11 +901,17 @@ func (c *Char) Free() {
} }
func (w *Wrapper) StringHelpers() { func (w *Wrapper) StringHelpers() {
w.goHelpers.WriteString(` ufree := ""
if Gogc {
ufree = "utf8.Free()\n\t"
}
w.goHelpers.WriteString(fmt.Sprintf(`
func (o *NSString) String() string { func (o *NSString) String() string {
return o.UTF8String().String() utf8 := o.UTF8String()
ret := utf8.String()
%sreturn ret
} }
`) `,ufree))
} }
func (w *Wrapper) EnumeratorHelpers() { func (w *Wrapper) EnumeratorHelpers() {
@ -1043,7 +1067,7 @@ func (w *Wrapper) _processMethod(m *Method, fun bool) {
} else { } else {
cmtype = m.Type.CTypeAttrib() cmtype = m.Type.CTypeAttrib()
} }
ns, snames, tps, gplist := w.gpntp(m) ns, pnames, snames, tps, gplist := w.gpntp(m)
if gname == grtype { // avoid name conflicts between methods and types if gname == grtype { // avoid name conflicts between methods and types
gname = "Get" + gname gname = "Get" + gname
} }
@ -1131,7 +1155,8 @@ func %s%s(%s) %s {
}`, cret, cobj, w.objcparamlist(m))) }`, cret, cobj, w.objcparamlist(m)))
} }
default: default:
if Gogc && !m.isVoid() { //if Gogc && !m.isVoid() {
if Gogc {
rtn := "" rtn := ""
if types.PtrShouldWrap(m.Type.GoType()) { if types.PtrShouldWrap(m.Type.GoType()) {
switch { switch {
@ -1139,7 +1164,7 @@ func %s%s(%s) %s {
if grtype != "*NSAutoreleasePool" && constructor { if grtype != "*NSAutoreleasePool" && constructor {
// retain objects returned by class constructor methods // retain objects returned by class constructor methods
rtn = ` rtn = `
[ret retain];` if(ret != nil) { [ret retain]; }`
} }
// do not retain new, alloc, init and copy methods // do not retain new, alloc, init and copy methods
@ -1149,19 +1174,48 @@ func %s%s(%s) %s {
// by default, for instance methods, retain // by default, for instance methods, retain
// if returning a new object // if returning a new object
rtn = ` rtn = `
if (o != ret) { [ret retain]; }` if (ret != nil && ret != o) { [ret retain]; }`
} }
} }
var ar1, ar2 string rtns := []string{}
if constructor || true { // for pointers to pointers, assume length 1 unless there is a
ar1 = "@autoreleasepool {\n\t\t" // parameter named "range" or "count".
ar2 = "\n }" rlength := "i<1"
for i,n := range pnames {
vn := strings.ReplaceAll(ns[i],"_","")
if n == "range" {
rlength = "i<" + vn + ".length"
}
if n == "count" {
rlength = "i<" + vn
}
}
for i,n := range ns {
if snames[i] == "" {
continue
}
rtns = append(rtns,fmt.Sprintf(`
for(int i=0;%s;i++) {
if(%s[i] == 0) { break; }
[(id)%s[i] retain];
}
`, rlength, n, n))
}
var retdecl, reteq, retretn, dup1, dup2 string
if !m.isVoid() {
retdecl = fmt.Sprintf("%s ret;\n\t", m.Type.CTypeAttrib())
reteq = "ret = "
retretn = "\n\treturn ret;\n"
if m.Type.CType() == "char*" {
dup1 = "strdup("
dup2 = ")"
}
} }
w.cCode.WriteString(fmt.Sprintf( w.cCode.WriteString(fmt.Sprintf(
` %s ret; ` %s@autoreleasepool {
%sret = [%s %s];%s%s %s%s[%s %s]%s;%s%s
return ret; }%s
}`, m.Type.CTypeAttrib(), ar1, cobj, w.objcparamlist(m), rtn, ar2)) }`, retdecl, reteq, dup1, cobj, w.objcparamlist(m), dup2, rtn, strings.Join(rtns,"\n\t"), retretn))
} else { } else {
w.cCode.WriteString(fmt.Sprintf(` %s[%s %s]; w.cCode.WriteString(fmt.Sprintf(` %s[%s %s];
}`, cret, cobj, w.objcparamlist(m))) }`, cret, cobj, w.objcparamlist(m)))
@ -1182,6 +1236,7 @@ func %s%s(%s) %s {
dbg2 = fmt.Sprintf(`fmt.Printf("GC finalizer (%s): release %%p -> %%p\n", o, o.ptr) dbg2 = fmt.Sprintf(`fmt.Printf("GC finalizer (%s): release %%p -> %%p\n", o, o.ptr)
`, cls) `, cls)
} }
w.goImports["runtime"] = true
w.goCode.WriteString(fmt.Sprintf(` w.goCode.WriteString(fmt.Sprintf(`
func (o *%s) GC() { func (o *%s) GC() {
if o.ptr == nil { return } if o.ptr == nil { return }
@ -1916,7 +1971,7 @@ func %s%s(%s)%s {
gocode.WriteString(fmt.Sprintf(` gocode.WriteString(fmt.Sprintf(`
func (o *%s) Super%s(%s) %s { func (o *%s) Super%s(%s) %s {
`, gname, gnames[i], strings.Join(earglist[1:], ", "), grtype)) `, gname, gnames[i], strings.Join(earglist[1:], ", "), grtype))
ns, snames, tps, _ := w.gpntp(m) ns, _, snames, tps, _ := w.gpntp(m)
lparm := len(tps) - 1 lparm := len(tps) - 1
if len(tps) > 0 && tps[lparm].Variadic { if len(tps) > 0 && tps[lparm].Variadic {
vn := ns[lparm] vn := ns[lparm]
@ -2157,14 +2212,31 @@ func (w *Wrapper) Wrap(toproc []string) {
for k := range w.goImports { for k := range w.goImports {
imports = append(imports,"\t\"" + k + "\"") imports = append(imports,"\t\"" + k + "\"")
} }
startThread := ""
goInit := ""
if w.Interfaces["NSThread"] != nil {
startThread = `
void
NSWrap_init() {
[[NSThread new] start]; // put the runtime into multi-threaded mode
}
`
goInit = `
func init() {
C.NSWrap_init()
}
`
}
of.WriteString(fmt.Sprintf(` of.WriteString(fmt.Sprintf(`
%s
*/ */
import "C" import "C"
import ( import (
%s %s
) )
`,strings.Join(imports,"\n"))) %s
`,startThread,strings.Join(imports,"\n"),goInit))
of.WriteString(w.goTypes.String()) of.WriteString(w.goTypes.String())
of.WriteString(w.goConst.String()) of.WriteString(w.goConst.String())
of.WriteString(w.goHelpers.String()) of.WriteString(w.goHelpers.String())