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 Unverified
4AEE18F83AFDEB23The key is unknown
3 files · +127 -12
M objc/objc_runtime_darwin.go +52 -11
| @@ -7,6 +7,7 @@ package objc | |||
| 7 | 7 | ||
| 8 | 8 | import ( | |
| 9 | 9 | "fmt" | |
| 10 | + | "math" | |
| 10 | 11 | "reflect" | |
| 11 | 12 | "runtime" | |
| 12 | 13 | "unsafe" | |
| @@ -21,15 +22,18 @@ import ( | |||
| 21 | 22 | var ( | |
| 22 | 23 | objc = purego.Dlopen("/usr/lib/libobjc.A.dylib", purego.RTLD_GLOBAL) | |
| 23 | 24 | ||
| 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") | |
| 33 | 37 | ) | |
| 34 | 38 | ||
| 35 | 39 | // 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 | 156 | 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 | 185 | // It can now be used to make objects by sending it either alloc and init or new. | |
| 157 | 186 | func (c Class) Register() { | |
| 158 | 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 | 202 | // _IMP is unexported so that the only way to make this type is by providing a Go function and casting | |
| 162 | 203 | // it with the IMP function | |
| 163 | 204 | type _IMP uintptr | |
| 164 | 205 | ||
| 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 | |
| 166 | 207 | // pointer that can be called by Objective-C code. The function pointer is never deallocated. | |
| 167 | 208 | func IMP(fn interface{}) _IMP { | |
| 168 | 209 | // 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 object | 39 | // 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) != 0 | 156 | 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 casting | 202 | // _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 function | 203 | // it with the IMP function | ||
| 163 | type _IMP uintptr | 204 | type _IMP uintptr | ||
| 164 | 205 | ||||
| 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 |
| 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) { | |||
| 116 | 116 | } | |
| 117 | 117 | ret := fn.Call(args) | |
| 118 | 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 | ||
| @@ -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 | ||||