detached 8c4da10

objc: add Ivar support and Objc Tests (#15)

commit
8c4da10ed8047992d54be93b4c25a9af73ac8d88
author
TotallyGamerJet · · 2022-08-11 02:21 UTC
committer
GitHub · · 2022-08-11 02:21 UTC
parent
0f3e4f8
signature
GPG Unverified4AEE18F83AFDEB23 The key is unknown
3 files · +127 -12
M objc/objc_runtime_darwin.go +52 -11
@@ -7,6 +7,7 @@ package objc
77
88 import (
99 "fmt"
10+ "math"
1011 "reflect"
1112 "runtime"
1213 "unsafe"
@@ -21,15 +22,18 @@ import (
2122 var (
2223 objc = purego.Dlopen("/usr/lib/libobjc.A.dylib", purego.RTLD_GLOBAL)
2324
24- objc_msgSend = purego.Dlsym(objc, "objc_msgSend")
25- objc_msgSendSuper2 = purego.Dlsym(objc, "objc_msgSendSuper2")
26- objc_getClass = purego.Dlsym(objc, "objc_getClass")
27- objc_allocateClassPair = purego.Dlsym(objc, "objc_allocateClassPair")
28- objc_registerClassPair = purego.Dlsym(objc, "objc_registerClassPair")
29- sel_registerName = purego.Dlsym(objc, "sel_registerName")
30- class_getSuperclass = purego.Dlsym(objc, "class_getSuperclass")
31- class_addMethod = purego.Dlsym(objc, "class_addMethod")
32- object_getClass = purego.Dlsym(objc, "object_getClass")
25+ objc_msgSend = purego.Dlsym(objc, "objc_msgSend")
26+ objc_msgSendSuper2 = purego.Dlsym(objc, "objc_msgSendSuper2")
27+ objc_getClass = purego.Dlsym(objc, "objc_getClass")
28+ objc_allocateClassPair = purego.Dlsym(objc, "objc_allocateClassPair")
29+ objc_registerClassPair = purego.Dlsym(objc, "objc_registerClassPair")
30+ sel_registerName = purego.Dlsym(objc, "sel_registerName")
31+ class_getSuperclass = purego.Dlsym(objc, "class_getSuperclass")
32+ class_getInstanceVariable = purego.Dlsym(objc, "class_getInstanceVariable")
33+ class_addMethod = purego.Dlsym(objc, "class_addMethod")
34+ class_addIvar = purego.Dlsym(objc, "class_addIvar")
35+ ivar_getOffset = purego.Dlsym(objc, "ivar_getOffset")
36+ object_getClass = purego.Dlsym(objc, "object_getClass")
3337 )
3438
3539 // ID is an opaque pointer to some Objective-C object
@@ -152,17 +156,54 @@ func (c Class) AddMethod(name SEL, imp _IMP, types string) bool {
152156 return byte(ret) != 0
153157 }
154158
155-// Register registers a class that was allocated using objc_allocateClassPair.
159+// AddIvar adds a new instance variable to a class.
160+// It may only be called after AllocateClassPair and before Register.
161+// Adding an instance variable to an existing class is not supported.
162+// The class must not be a metaclass. Adding an instance variable to a metaclass is not supported.
163+// It takes the instance of the type of the Ivar and a string representing the type.
164+func (c Class) AddIvar(name string, ty interface{}, types string) bool {
165+ n := strings.CString(name)
166+ t := strings.CString(types)
167+ typeOf := reflect.TypeOf(ty)
168+ size := typeOf.Size()
169+ alignment := uint8(math.Log2(float64(typeOf.Align())))
170+ ret, _, _ := purego.SyscallN(class_addIvar, uintptr(c), uintptr(unsafe.Pointer(n)), size, uintptr(alignment), uintptr(unsafe.Pointer(t)))
171+ runtime.KeepAlive(n)
172+ runtime.KeepAlive(t)
173+ return byte(ret) != 0
174+}
175+
176+// InstanceVariable returns an Ivar data structure containing information about the instance variable specified by name.
177+func (c Class) InstanceVariable(name string) Ivar {
178+ n := strings.CString(name)
179+ ret, _, _ := purego.SyscallN(class_getInstanceVariable, uintptr(c), uintptr(unsafe.Pointer(n)))
180+ runtime.KeepAlive(n)
181+ return Ivar(ret)
182+}
183+
184+// Register registers a class that was allocated using AllocateClassPair.
156185 // It can now be used to make objects by sending it either alloc and init or new.
157186 func (c Class) Register() {
158187 purego.SyscallN(objc_registerClassPair, uintptr(c))
159188 }
160189
190+// Ivar an opaque type that represents an instance variable.
191+type Ivar uintptr
192+
193+// Offset returns the offset of an instance variable that can be used to assign and read the Ivar's value.
194+//
195+// For instance variables of type id or other object types, call Ivar and SetIvar instead
196+// of using this offset to access the instance variable data directly.
197+func (i Ivar) Offset() uintptr {
198+ ret, _, _ := purego.SyscallN(ivar_getOffset, uintptr(i))
199+ return ret
200+}
201+
161202 // _IMP is unexported so that the only way to make this type is by providing a Go function and casting
162203 // it with the IMP function
163204 type _IMP uintptr
164205
165-// IMP takes a Go function that takes (id, SEL) as its first two arguments. It returns an _IMP function
206+// IMP takes a Go function that takes (ID, SEL) as its first two arguments. It returns an _IMP function
166207 // pointer that can be called by Objective-C code. The function pointer is never deallocated.
167208 func IMP(fn interface{}) _IMP {
168209 // this is only here so that it is easier to port C code to Go.
@@ -7,6 +7,7 @@ package objc
7 7
8 import (8 import (
9 "fmt"9 "fmt"
10+ "math"
10 "reflect"11 "reflect"
11 "runtime"12 "runtime"
12 "unsafe"13 "unsafe"
@@ -21,15 +22,18 @@ import (
21 var (22 var (
22 objc = purego.Dlopen("/usr/lib/libobjc.A.dylib", purego.RTLD_GLOBAL)23 objc = purego.Dlopen("/usr/lib/libobjc.A.dylib", purego.RTLD_GLOBAL)
23 24
24- objc_msgSend = purego.Dlsym(objc, "objc_msgSend")25+ objc_msgSend = purego.Dlsym(objc, "objc_msgSend")
25- objc_msgSendSuper2 = purego.Dlsym(objc, "objc_msgSendSuper2")26+ objc_msgSendSuper2 = purego.Dlsym(objc, "objc_msgSendSuper2")
26- objc_getClass = purego.Dlsym(objc, "objc_getClass")27+ objc_getClass = purego.Dlsym(objc, "objc_getClass")
27- objc_allocateClassPair = purego.Dlsym(objc, "objc_allocateClassPair")28+ objc_allocateClassPair = purego.Dlsym(objc, "objc_allocateClassPair")
28- objc_registerClassPair = purego.Dlsym(objc, "objc_registerClassPair")29+ objc_registerClassPair = purego.Dlsym(objc, "objc_registerClassPair")
29- sel_registerName = purego.Dlsym(objc, "sel_registerName")30+ sel_registerName = purego.Dlsym(objc, "sel_registerName")
30- class_getSuperclass = purego.Dlsym(objc, "class_getSuperclass")31+ class_getSuperclass = purego.Dlsym(objc, "class_getSuperclass")
31- class_addMethod = purego.Dlsym(objc, "class_addMethod")32+ class_getInstanceVariable = purego.Dlsym(objc, "class_getInstanceVariable")
32- object_getClass = purego.Dlsym(objc, "object_getClass")33+ class_addMethod = purego.Dlsym(objc, "class_addMethod")
34+ class_addIvar = purego.Dlsym(objc, "class_addIvar")
35+ ivar_getOffset = purego.Dlsym(objc, "ivar_getOffset")
36+ object_getClass = purego.Dlsym(objc, "object_getClass")
33 )37 )
34 38
35 // ID is an opaque pointer to some Objective-C object39 // ID is an opaque pointer to some Objective-C object
@@ -152,17 +156,54 @@ func (c Class) AddMethod(name SEL, imp _IMP, types string) bool {
152 return byte(ret) != 0156 return byte(ret) != 0
153 }157 }
154 158
155-// Register registers a class that was allocated using objc_allocateClassPair.159+// AddIvar adds a new instance variable to a class.
160+// It may only be called after AllocateClassPair and before Register.
161+// Adding an instance variable to an existing class is not supported.
162+// The class must not be a metaclass. Adding an instance variable to a metaclass is not supported.
163+// It takes the instance of the type of the Ivar and a string representing the type.
164+func (c Class) AddIvar(name string, ty interface{}, types string) bool {
165+ n := strings.CString(name)
166+ t := strings.CString(types)
167+ typeOf := reflect.TypeOf(ty)
168+ size := typeOf.Size()
169+ alignment := uint8(math.Log2(float64(typeOf.Align())))
170+ ret, _, _ := purego.SyscallN(class_addIvar, uintptr(c), uintptr(unsafe.Pointer(n)), size, uintptr(alignment), uintptr(unsafe.Pointer(t)))
171+ runtime.KeepAlive(n)
172+ runtime.KeepAlive(t)
173+ return byte(ret) != 0
174+}
175+
176+// InstanceVariable returns an Ivar data structure containing information about the instance variable specified by name.
177+func (c Class) InstanceVariable(name string) Ivar {
178+ n := strings.CString(name)
179+ ret, _, _ := purego.SyscallN(class_getInstanceVariable, uintptr(c), uintptr(unsafe.Pointer(n)))
180+ runtime.KeepAlive(n)
181+ return Ivar(ret)
182+}
183+
184+// Register registers a class that was allocated using AllocateClassPair.
156 // It can now be used to make objects by sending it either alloc and init or new.185 // It can now be used to make objects by sending it either alloc and init or new.
157 func (c Class) Register() {186 func (c Class) Register() {
158 purego.SyscallN(objc_registerClassPair, uintptr(c))187 purego.SyscallN(objc_registerClassPair, uintptr(c))
159 }188 }
160 189
190+// Ivar an opaque type that represents an instance variable.
191+type Ivar uintptr
192+
193+// Offset returns the offset of an instance variable that can be used to assign and read the Ivar's value.
194+//
195+// For instance variables of type id or other object types, call Ivar and SetIvar instead
196+// of using this offset to access the instance variable data directly.
197+func (i Ivar) Offset() uintptr {
198+ ret, _, _ := purego.SyscallN(ivar_getOffset, uintptr(i))
199+ return ret
200+}
201+
161 // _IMP is unexported so that the only way to make this type is by providing a Go function and casting202 // _IMP is unexported so that the only way to make this type is by providing a Go function and casting
162 // it with the IMP function203 // it with the IMP function
163 type _IMP uintptr204 type _IMP uintptr
164 205
165-// IMP takes a Go function that takes (id, SEL) as its first two arguments. It returns an _IMP function206+// IMP takes a Go function that takes (ID, SEL) as its first two arguments. It returns an _IMP function
166 // pointer that can be called by Objective-C code. The function pointer is never deallocated.207 // pointer that can be called by Objective-C code. The function pointer is never deallocated.
167 func IMP(fn interface{}) _IMP {208 func IMP(fn interface{}) _IMP {
168 // this is only here so that it is easier to port C code to Go.209 // this is only here so that it is easier to port C code to Go.
A objc/objc_runtime_darwin_test.go +61 -0
@@ -0,0 +1,61 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3+
4+package objc
5+
6+import (
7+ "fmt"
8+ "runtime"
9+ "unsafe"
10+
11+ "github.com/ebitengine/purego"
12+)
13+
14+func init() {
15+ // this is here so that the program doesn't crash with:
16+ // fatal error: schedule: in cgo
17+ // this can be removed after purego#12 is solved.
18+ // this is due to moving goroutines to different threads while in C code
19+ runtime.LockOSThread()
20+}
21+
22+func ExampleAllocateClassPair() {
23+ var class = AllocateClassPair(GetClass("NSObject\x00"), "FooObject\x00", 0)
24+ class.AddMethod(RegisterName("run\x00"), IMP(func(self ID, _cmd SEL) {
25+ fmt.Println("Hello World!")
26+ }), "v@:\x00")
27+ class.Register()
28+
29+ var fooObject = ID(class).Send(RegisterName("new\x00"))
30+ fooObject.Send(RegisterName("run\x00"))
31+ // Output: Hello World!
32+}
33+
34+func ExampleClass_AddIvar() {
35+ var class = AllocateClassPair(GetClass("NSObject\x00"), "BarObject\x00", 0)
36+ class.AddIvar("bar\x00", int(0), "q\x00")
37+ var barOffset = class.InstanceVariable("bar\x00").Offset()
38+ class.AddMethod(RegisterName("bar\x00"), IMP(func(self ID, _cmd SEL) int {
39+ return *(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(self)) + barOffset))
40+ }), "q@:\x00")
41+ class.AddMethod(RegisterName("setBar:\x00"), IMP(func(self ID, _cmd SEL, bar int) {
42+ *(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(self)) + barOffset)) = bar
43+ }), "v@:q\x00")
44+ class.Register()
45+
46+ var barObject = ID(class).Send(RegisterName("new\x00"))
47+ barObject.Send(RegisterName("setBar:\x00"), 123)
48+ var bar = int(barObject.Send(RegisterName("bar\x00")))
49+ fmt.Println(bar)
50+ // Output: 123
51+}
52+
53+func ExampleIMP() {
54+ imp := IMP(func(self ID, _cmd SEL) {
55+ fmt.Println("IMP:", self, _cmd)
56+ })
57+
58+ purego.SyscallN(uintptr(imp), 105, 567)
59+
60+ // Output: IMP: 105 567
61+}
@@ -0,0 +1,61 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3+
4+package objc
5+
6+import (
7+ "fmt"
8+ "runtime"
9+ "unsafe"
10+
11+ "github.com/ebitengine/purego"
12+)
13+
14+func init() {
15+ // this is here so that the program doesn't crash with:
16+ // fatal error: schedule: in cgo
17+ // this can be removed after purego#12 is solved.
18+ // this is due to moving goroutines to different threads while in C code
19+ runtime.LockOSThread()
20+}
21+
22+func ExampleAllocateClassPair() {
23+ var class = AllocateClassPair(GetClass("NSObject\x00"), "FooObject\x00", 0)
24+ class.AddMethod(RegisterName("run\x00"), IMP(func(self ID, _cmd SEL) {
25+ fmt.Println("Hello World!")
26+ }), "v@:\x00")
27+ class.Register()
28+
29+ var fooObject = ID(class).Send(RegisterName("new\x00"))
30+ fooObject.Send(RegisterName("run\x00"))
31+ // Output: Hello World!
32+}
33+
34+func ExampleClass_AddIvar() {
35+ var class = AllocateClassPair(GetClass("NSObject\x00"), "BarObject\x00", 0)
36+ class.AddIvar("bar\x00", int(0), "q\x00")
37+ var barOffset = class.InstanceVariable("bar\x00").Offset()
38+ class.AddMethod(RegisterName("bar\x00"), IMP(func(self ID, _cmd SEL) int {
39+ return *(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(self)) + barOffset))
40+ }), "q@:\x00")
41+ class.AddMethod(RegisterName("setBar:\x00"), IMP(func(self ID, _cmd SEL, bar int) {
42+ *(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(self)) + barOffset)) = bar
43+ }), "v@:q\x00")
44+ class.Register()
45+
46+ var barObject = ID(class).Send(RegisterName("new\x00"))
47+ barObject.Send(RegisterName("setBar:\x00"), 123)
48+ var bar = int(barObject.Send(RegisterName("bar\x00")))
49+ fmt.Println(bar)
50+ // Output: 123
51+}
52+
53+func ExampleIMP() {
54+ imp := IMP(func(self ID, _cmd SEL) {
55+ fmt.Println("IMP:", self, _cmd)
56+ })
57+
58+ purego.SyscallN(uintptr(imp), 105, 567)
59+
60+ // Output: IMP: 105 567
61+}
M syscall_darwin.go +14 -1
@@ -116,7 +116,20 @@ func callbackWrap(a *callbackArgs) {
116116 }
117117 ret := fn.Call(args)
118118 if len(ret) > 0 {
119- a.result = uintptr(ret[0].Uint())
119+ switch k := ret[0].Kind(); k {
120+ case reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uintptr:
121+ a.result = uintptr(ret[0].Uint())
122+ case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8:
123+ a.result = uintptr(ret[0].Int())
124+ case reflect.Bool:
125+ if ret[0].Bool() {
126+ a.result = 1
127+ } else {
128+ a.result = 0
129+ }
130+ default:
131+ panic("purego: unsupported kind: " + k.String())
132+ }
120133 }
121134 }
122135
@@ -116,7 +116,20 @@ func callbackWrap(a *callbackArgs) {
116 }116 }
117 ret := fn.Call(args)117 ret := fn.Call(args)
118 if len(ret) > 0 {118 if len(ret) > 0 {
119- a.result = uintptr(ret[0].Uint())119+ switch k := ret[0].Kind(); k {
120+ case reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uintptr:
121+ a.result = uintptr(ret[0].Uint())
122+ case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8:
123+ a.result = uintptr(ret[0].Int())
124+ case reflect.Bool:
125+ if ret[0].Bool() {
126+ a.result = 1
127+ } else {
128+ a.result = 0
129+ }
130+ default:
131+ panic("purego: unsupported kind: " + k.String())
132+ }
120 }133 }
121 }134 }
122 135

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 102

↑↓ moveenter openesc close