detached 7128aea

purego: new mechanism for calling C functions (#49)

This commit adds purego.RegisterLibFunc and purego.RegisterFunc which combines C functions with their Go counterpart. This is a better way to call into C because it is type safe from the caller's perspective. It will also eventually allow for float and struct arguments and returns.
commit
7128aea44c774c29b29ab5e079938f36d88264c6
author
TotallyGamerJet · · 2022-10-19 15:39 UTC
committer
GitHub · · 2022-10-19 15:39 UTC
parent
09060e5
signature
GPG Unverified4AEE18F83AFDEB23 The key is unknown
4 files · +222 -142
M dlfcn.go +1 -21
@@ -7,7 +7,6 @@
77 package purego
88
99 import (
10- "reflect"
1110 "runtime"
1211 "unsafe"
1312
@@ -52,26 +51,7 @@ func Dlerror() string {
5251 // msg is only valid until the next call to Dlerror
5352 // which is why it gets copied into a Go string
5453 msg, _, _ := SyscallN(dlerrorABI0)
55- if msg == 0 {
56- return ""
57- }
58- var length int
59- for {
60- // use unsafe.Add once we reach 1.17
61- if *(*byte)(unsafe.Pointer(msg + uintptr(length))) == '\x00' {
62- break
63- }
64- length++
65- }
66- // use unsafe.Slice once we reach 1.17
67- s := make([]byte, length)
68- var src []byte
69- h := (*reflect.SliceHeader)(unsafe.Pointer(&src))
70- h.Data = msg
71- h.Len = length
72- h.Cap = length
73- copy(s, src)
74- return string(s)
54+ return strings.GoString(msg)
7555 }
7656
7757 // Dlclose decrements the reference count on the dynamic library handle.
@@ -7,7 +7,6 @@
7 package purego7 package purego
8 8
9 import (9 import (
10- "reflect"
11 "runtime"10 "runtime"
12 "unsafe"11 "unsafe"
13 12
@@ -52,26 +51,7 @@ func Dlerror() string {
52 // msg is only valid until the next call to Dlerror51 // msg is only valid until the next call to Dlerror
53 // which is why it gets copied into a Go string52 // which is why it gets copied into a Go string
54 msg, _, _ := SyscallN(dlerrorABI0)53 msg, _, _ := SyscallN(dlerrorABI0)
55- if msg == 0 {54+ return strings.GoString(msg)
56- return ""
57- }
58- var length int
59- for {
60- // use unsafe.Add once we reach 1.17
61- if *(*byte)(unsafe.Pointer(msg + uintptr(length))) == '\x00' {
62- break
63- }
64- length++
65- }
66- // use unsafe.Slice once we reach 1.17
67- s := make([]byte, length)
68- var src []byte
69- h := (*reflect.SliceHeader)(unsafe.Pointer(&src))
70- h.Data = msg
71- h.Len = length
72- h.Cap = length
73- copy(s, src)
74- return string(s)
75 }55 }
76 56
77 // Dlclose decrements the reference count on the dynamic library handle.57 // Dlclose decrements the reference count on the dynamic library handle.
A func_darwin.go +143 -0
@@ -0,0 +1,143 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3+
4+package purego
5+
6+import (
7+ "reflect"
8+ "runtime"
9+ "unsafe"
10+
11+ "github.com/ebitengine/purego/internal/strings"
12+)
13+
14+// RegisterLibFunc is a wrapper around RegisterFunc that uses the C function returned from Dlsym(handle, name).
15+// It panics if Dlsym fails.
16+func RegisterLibFunc(fptr interface{}, handle uintptr, name string) {
17+ sym := Dlsym(handle, name)
18+ if sym == 0 {
19+ panic("purego: couldn't find symbol: " + Dlerror())
20+ }
21+ RegisterFunc(fptr, sym)
22+}
23+
24+// RegisterFunc takes a pointer to a Go function representing the calling convention of the C function.
25+// fptr will be set to a function that when called will call the C function given by cfn with the
26+// parameters passed in the correct registers and stack.
27+//
28+// A panic is produced if the type is not a function pointer or if the function returns more than 1 value.
29+//
30+// These conversions describe how a Go type in the fptr will be used to call
31+// the C function. It is important to note that there is no way to verify that fptr
32+// matches the C function. This also holds true for struct types where the padding
33+// needs to be ensured to match that of C; RegisterFunc does not verify this.
34+//
35+// Type Conversions (Go => C)
36+//
37+// string <=> char*
38+// bool <=> _Bool
39+// uintptr <=> uintptr_t
40+// uint <=> uint32_t or uint64_t
41+// uint8 <=> uint8_t
42+// uint16 <=> uint16_t
43+// uint32 <=> uint32_t
44+// uint64 <=> uint64_t
45+// int <=> int32_t or int64_t
46+// int8 <=> int8_t
47+// int16 <=> int16_t
48+// int32 <=> int32_t
49+// int64 <=> int64_t
50+// float32 <=> float (WIP)
51+// float64 <=> double (WIP)
52+// struct <=> struct (WIP)
53+// func <=> C function
54+// unsafe.Pointer, *T <=> void*
55+// []T => void*
56+//
57+// There is a special case when the last argument of fptr is a variadic interface (or []interface}
58+// it will be expanded into a call to the C function as if it had the arguments in that slice.
59+// This means that using arg ...interface{} is like a cast to the function with the arguments inside arg.
60+// This is not the same as C variadic.
61+func RegisterFunc(fptr interface{}, cfn uintptr) {
62+ fn := reflect.ValueOf(fptr).Elem()
63+ ty := fn.Type()
64+ if ty.Kind() != reflect.Func {
65+ panic("purego: fptr must be a function pointer")
66+ }
67+ if ty.NumOut() > 1 {
68+ panic("purego: function can only return zero or one values")
69+ }
70+ v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) {
71+ if len(args) > 0 {
72+ if variadic, ok := args[len(args)-1].Interface().([]interface{}); ok {
73+ // subtract one from args bc the last argument in args is []interface{}
74+ // which we are currently expanding
75+ tmp := make([]reflect.Value, len(args)-1+len(variadic))
76+ n := copy(tmp, args[:len(args)-1])
77+ for i, v := range variadic {
78+ tmp[n+i] = reflect.ValueOf(v)
79+ }
80+ args = tmp
81+ }
82+ }
83+ var sysargs = make([]uintptr, len(args))
84+ var keepAlive []interface{}
85+ defer func() {
86+ runtime.KeepAlive(keepAlive)
87+ }()
88+ for i, v := range args {
89+ switch v.Kind() {
90+ case reflect.String:
91+ ptr := strings.CString(v.String())
92+ keepAlive = append(keepAlive, ptr)
93+ sysargs[i] = uintptr(unsafe.Pointer(ptr))
94+ case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
95+ sysargs[i] = uintptr(v.Uint())
96+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
97+ sysargs[i] = uintptr(v.Int())
98+ case reflect.Ptr, reflect.UnsafePointer, reflect.Slice:
99+ keepAlive = append(keepAlive, v.Pointer())
100+ sysargs[i] = v.Pointer()
101+ case reflect.Func:
102+ sysargs[i] = NewCallback(v.Interface())
103+ case reflect.Bool:
104+ if v.Bool() {
105+ sysargs[i] = 1
106+ } else {
107+ sysargs[i] = 0
108+ }
109+ default:
110+ panic("purego: unsupported kind: " + v.Kind().String())
111+ }
112+ }
113+ r1, _, _ := SyscallN(cfn, sysargs...) //TODO: handle float32/64 and struct types
114+ if ty.NumOut() == 0 {
115+ return nil
116+ }
117+ outType := ty.Out(0)
118+ v := reflect.New(outType).Elem()
119+ switch outType.Kind() {
120+ case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
121+ v.SetUint(uint64(r1))
122+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
123+ v.SetInt(int64(r1))
124+ case reflect.Bool:
125+ v.SetBool(r1 != 0)
126+ case reflect.UnsafePointer:
127+ // We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer
128+ v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&r1)))
129+ case reflect.Ptr:
130+ v = reflect.NewAt(outType, unsafe.Pointer(&r1)).Elem()
131+ case reflect.Func:
132+ // wrap this C function in a nicely typed Go function
133+ v = reflect.New(outType)
134+ RegisterFunc(v.Interface(), r1)
135+ case reflect.String:
136+ v.SetString(strings.GoString(r1))
137+ default:
138+ panic("purego: unsupported return kind: " + outType.Kind().String())
139+ }
140+ return []reflect.Value{v}
141+ })
142+ fn.Set(v)
143+}
@@ -0,0 +1,143 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3+
4+package purego
5+
6+import (
7+ "reflect"
8+ "runtime"
9+ "unsafe"
10+
11+ "github.com/ebitengine/purego/internal/strings"
12+)
13+
14+// RegisterLibFunc is a wrapper around RegisterFunc that uses the C function returned from Dlsym(handle, name).
15+// It panics if Dlsym fails.
16+func RegisterLibFunc(fptr interface{}, handle uintptr, name string) {
17+ sym := Dlsym(handle, name)
18+ if sym == 0 {
19+ panic("purego: couldn't find symbol: " + Dlerror())
20+ }
21+ RegisterFunc(fptr, sym)
22+}
23+
24+// RegisterFunc takes a pointer to a Go function representing the calling convention of the C function.
25+// fptr will be set to a function that when called will call the C function given by cfn with the
26+// parameters passed in the correct registers and stack.
27+//
28+// A panic is produced if the type is not a function pointer or if the function returns more than 1 value.
29+//
30+// These conversions describe how a Go type in the fptr will be used to call
31+// the C function. It is important to note that there is no way to verify that fptr
32+// matches the C function. This also holds true for struct types where the padding
33+// needs to be ensured to match that of C; RegisterFunc does not verify this.
34+//
35+// Type Conversions (Go => C)
36+//
37+// string <=> char*
38+// bool <=> _Bool
39+// uintptr <=> uintptr_t
40+// uint <=> uint32_t or uint64_t
41+// uint8 <=> uint8_t
42+// uint16 <=> uint16_t
43+// uint32 <=> uint32_t
44+// uint64 <=> uint64_t
45+// int <=> int32_t or int64_t
46+// int8 <=> int8_t
47+// int16 <=> int16_t
48+// int32 <=> int32_t
49+// int64 <=> int64_t
50+// float32 <=> float (WIP)
51+// float64 <=> double (WIP)
52+// struct <=> struct (WIP)
53+// func <=> C function
54+// unsafe.Pointer, *T <=> void*
55+// []T => void*
56+//
57+// There is a special case when the last argument of fptr is a variadic interface (or []interface}
58+// it will be expanded into a call to the C function as if it had the arguments in that slice.
59+// This means that using arg ...interface{} is like a cast to the function with the arguments inside arg.
60+// This is not the same as C variadic.
61+func RegisterFunc(fptr interface{}, cfn uintptr) {
62+ fn := reflect.ValueOf(fptr).Elem()
63+ ty := fn.Type()
64+ if ty.Kind() != reflect.Func {
65+ panic("purego: fptr must be a function pointer")
66+ }
67+ if ty.NumOut() > 1 {
68+ panic("purego: function can only return zero or one values")
69+ }
70+ v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) {
71+ if len(args) > 0 {
72+ if variadic, ok := args[len(args)-1].Interface().([]interface{}); ok {
73+ // subtract one from args bc the last argument in args is []interface{}
74+ // which we are currently expanding
75+ tmp := make([]reflect.Value, len(args)-1+len(variadic))
76+ n := copy(tmp, args[:len(args)-1])
77+ for i, v := range variadic {
78+ tmp[n+i] = reflect.ValueOf(v)
79+ }
80+ args = tmp
81+ }
82+ }
83+ var sysargs = make([]uintptr, len(args))
84+ var keepAlive []interface{}
85+ defer func() {
86+ runtime.KeepAlive(keepAlive)
87+ }()
88+ for i, v := range args {
89+ switch v.Kind() {
90+ case reflect.String:
91+ ptr := strings.CString(v.String())
92+ keepAlive = append(keepAlive, ptr)
93+ sysargs[i] = uintptr(unsafe.Pointer(ptr))
94+ case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
95+ sysargs[i] = uintptr(v.Uint())
96+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
97+ sysargs[i] = uintptr(v.Int())
98+ case reflect.Ptr, reflect.UnsafePointer, reflect.Slice:
99+ keepAlive = append(keepAlive, v.Pointer())
100+ sysargs[i] = v.Pointer()
101+ case reflect.Func:
102+ sysargs[i] = NewCallback(v.Interface())
103+ case reflect.Bool:
104+ if v.Bool() {
105+ sysargs[i] = 1
106+ } else {
107+ sysargs[i] = 0
108+ }
109+ default:
110+ panic("purego: unsupported kind: " + v.Kind().String())
111+ }
112+ }
113+ r1, _, _ := SyscallN(cfn, sysargs...) //TODO: handle float32/64 and struct types
114+ if ty.NumOut() == 0 {
115+ return nil
116+ }
117+ outType := ty.Out(0)
118+ v := reflect.New(outType).Elem()
119+ switch outType.Kind() {
120+ case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
121+ v.SetUint(uint64(r1))
122+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
123+ v.SetInt(int64(r1))
124+ case reflect.Bool:
125+ v.SetBool(r1 != 0)
126+ case reflect.UnsafePointer:
127+ // We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer
128+ v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&r1)))
129+ case reflect.Ptr:
130+ v = reflect.NewAt(outType, unsafe.Pointer(&r1)).Elem()
131+ case reflect.Func:
132+ // wrap this C function in a nicely typed Go function
133+ v = reflect.New(outType)
134+ RegisterFunc(v.Interface(), r1)
135+ case reflect.String:
136+ v.SetString(strings.GoString(r1))
137+ default:
138+ panic("purego: unsupported return kind: " + outType.Kind().String())
139+ }
140+ return []reflect.Value{v}
141+ })
142+ fn.Set(v)
143+}
M internal/strings/strings.go +30 -1
@@ -3,7 +3,10 @@
33
44 package strings
55
6-import "unsafe"
6+import (
7+ "reflect"
8+ "unsafe"
9+)
710
811 // hasSuffix tests whether the string s ends with suffix.
912 func hasSuffix(s, suffix string) bool {
@@ -19,3 +22,29 @@ func CString(name string) *byte {
1922 copy(b, name)
2023 return &b[0]
2124 }
25+
26+// GoString copies a char* to a Go string.
27+func GoString(c uintptr) string {
28+ // We take the address and then dereference it to trick go vet from creating a possible misuse of unsafe.Pointer
29+ ptr := *(*unsafe.Pointer)(unsafe.Pointer(&c))
30+ if ptr == nil {
31+ return ""
32+ }
33+ var length int
34+ for {
35+ // use unsafe.Add once we reach 1.17
36+ if *(*byte)(unsafe.Pointer(uintptr(ptr) + uintptr(length))) == '\x00' {
37+ break
38+ }
39+ length++
40+ }
41+ // use unsafe.Slice once we reach 1.17
42+ s := make([]byte, length)
43+ var src []byte
44+ h := (*reflect.SliceHeader)(unsafe.Pointer(&src))
45+ h.Data = uintptr(ptr)
46+ h.Len = length
47+ h.Cap = length
48+ copy(s, src)
49+ return string(s)
50+}
@@ -3,7 +3,10 @@
3 3
4 package strings4 package strings
5 5
6-import "unsafe"6+import (
7+ "reflect"
8+ "unsafe"
9+)
7 10
8 // hasSuffix tests whether the string s ends with suffix.11 // hasSuffix tests whether the string s ends with suffix.
9 func hasSuffix(s, suffix string) bool {12 func hasSuffix(s, suffix string) bool {
@@ -19,3 +22,29 @@ func CString(name string) *byte {
19 copy(b, name)22 copy(b, name)
20 return &b[0]23 return &b[0]
21 }24 }
25+
26+// GoString copies a char* to a Go string.
27+func GoString(c uintptr) string {
28+ // We take the address and then dereference it to trick go vet from creating a possible misuse of unsafe.Pointer
29+ ptr := *(*unsafe.Pointer)(unsafe.Pointer(&c))
30+ if ptr == nil {
31+ return ""
32+ }
33+ var length int
34+ for {
35+ // use unsafe.Add once we reach 1.17
36+ if *(*byte)(unsafe.Pointer(uintptr(ptr) + uintptr(length))) == '\x00' {
37+ break
38+ }
39+ length++
40+ }
41+ // use unsafe.Slice once we reach 1.17
42+ s := make([]byte, length)
43+ var src []byte
44+ h := (*reflect.SliceHeader)(unsafe.Pointer(&src))
45+ h.Data = uintptr(ptr)
46+ h.Len = length
47+ h.Cap = length
48+ copy(s, src)
49+ return string(s)
50+}
M objc/objc_runtime_darwin.go +48 -120
@@ -6,53 +6,61 @@
66 package objc
77
88 import (
9- "fmt"
9+ "github.com/ebitengine/purego"
1010 "math"
1111 "reflect"
12- "runtime"
13- "unsafe"
14-
15- "github.com/ebitengine/purego"
16- "github.com/ebitengine/purego/internal/strings"
1712 )
1813
1914 //TODO: support try/catch?
2015 //https://stackoverflow.com/questions/7062599/example-of-how-objective-cs-try-catch-implementation-is-executed-at-runtime
2116
2217 var (
23- objc = purego.Dlopen("/usr/lib/libobjc.A.dylib", purego.RTLD_GLOBAL)
24-
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_getProtocol = purego.Dlsym(objc, "objc_getProtocol")
29- objc_allocateClassPair = purego.Dlsym(objc, "objc_allocateClassPair")
30- objc_registerClassPair = purego.Dlsym(objc, "objc_registerClassPair")
31- sel_registerName = purego.Dlsym(objc, "sel_registerName")
32- class_getSuperclass = purego.Dlsym(objc, "class_getSuperclass")
33- class_getInstanceVariable = purego.Dlsym(objc, "class_getInstanceVariable")
34- class_addMethod = purego.Dlsym(objc, "class_addMethod")
35- class_addIvar = purego.Dlsym(objc, "class_addIvar")
36- class_addProtocol = purego.Dlsym(objc, "class_addProtocol")
37- ivar_getOffset = purego.Dlsym(objc, "ivar_getOffset")
38- object_getClass = purego.Dlsym(objc, "object_getClass")
18+ objc_msgSend func(obj ID, cmd SEL, args ...interface{}) ID
19+ objc_msgSendSuper2 func(super *objc_super, cmd SEL, args ...interface{}) ID
20+ objc_getClass func(name string) Class
21+ objc_getProtocol func(name string) *Protocol
22+ objc_allocateClassPair func(super Class, name string, extraBytes uintptr) Class
23+ objc_registerClassPair func(class Class)
24+ sel_registerName func(name string) SEL
25+ class_getSuperclass func(class Class) Class
26+ class_getInstanceVariable func(class Class, name string) Ivar
27+ class_addMethod func(class Class, name SEL, imp IMP, types string) bool
28+ class_addIvar func(class Class, name string, size uintptr, alignment uint8, types string) bool
29+ class_addProtocol func(class Class, protocol *Protocol) bool
30+ ivar_getOffset func(ivar Ivar) uintptr
31+ object_getClass func(obj ID) Class
3932 )
4033
34+func init() {
35+ objc := purego.Dlopen("/usr/lib/libobjc.A.dylib", purego.RTLD_GLOBAL)
36+
37+ purego.RegisterLibFunc(&objc_msgSend, objc, "objc_msgSend")
38+ purego.RegisterLibFunc(&objc_msgSendSuper2, objc, "objc_msgSendSuper2")
39+ purego.RegisterLibFunc(&object_getClass, objc, "object_getClass")
40+ purego.RegisterLibFunc(&objc_getClass, objc, "objc_getClass")
41+ purego.RegisterLibFunc(&objc_getProtocol, objc, "objc_getProtocol")
42+ purego.RegisterLibFunc(&objc_allocateClassPair, objc, "objc_allocateClassPair")
43+ purego.RegisterLibFunc(&objc_registerClassPair, objc, "objc_registerClassPair")
44+ purego.RegisterLibFunc(&sel_registerName, objc, "sel_registerName")
45+ purego.RegisterLibFunc(&class_getSuperclass, objc, "class_getSuperclass")
46+ purego.RegisterLibFunc(&class_getInstanceVariable, objc, "class_getInstanceVariable")
47+ purego.RegisterLibFunc(&class_addMethod, objc, "class_addMethod")
48+ purego.RegisterLibFunc(&class_addIvar, objc, "class_addIvar")
49+ purego.RegisterLibFunc(&class_addProtocol, objc, "class_addProtocol")
50+ purego.RegisterLibFunc(&ivar_getOffset, objc, "ivar_getOffset")
51+}
52+
4153 // ID is an opaque pointer to some Objective-C object
4254 type ID uintptr
4355
4456 // Class returns the class of the object.
4557 func (id ID) Class() Class {
46- ret, _, _ := purego.SyscallN(object_getClass, uintptr(id))
47- return Class(ret)
58+ return object_getClass(id)
4859 }
4960
5061 // Send is a convenience method for sending messages to objects.
5162 func (id ID) Send(sel SEL, args ...interface{}) ID {
52- tmp, keepAlive := createArgs(id, sel, args...)
53- defer keepAlive()
54- ret, _, _ := purego.SyscallN(objc_msgSend, tmp...)
55- return ID(ret)
63+ return objc_msgSend(id, sel, args...)
5664 }
5765
5866 // objc_super data structure is generated by the Objective-C compiler when it encounters the super keyword
@@ -69,60 +77,7 @@ func (id ID) SendSuper(sel SEL, args ...interface{}) ID {
6977 receiver: id,
7078 superClass: id.Class(),
7179 }
72- tmp, keepAlive := createArgs(0, sel, args...)
73- defer keepAlive()
74- tmp[0] = uintptr(unsafe.Pointer(super)) // if createArgs splits the stack the pointer would be wrong
75- ret, _, _ := purego.SyscallN(objc_msgSendSuper2, tmp...)
76- return ID(ret)
77-}
78-
79-func createArgs(cls ID, sel SEL, args ...interface{}) (out []uintptr, keepAlive func()) {
80- out = make([]uintptr, 2, len(args)+2)
81- out[0] = uintptr(cls)
82- out[1] = uintptr(sel)
83- var alive []interface{}
84- for _, a := range args {
85- switch v := a.(type) {
86- case ID:
87- out = append(out, uintptr(v))
88- case Class:
89- out = append(out, uintptr(v))
90- case SEL:
91- out = append(out, uintptr(v))
92- case IMP:
93- out = append(out, uintptr(v))
94- case bool:
95- if v {
96- out = append(out, uintptr(1))
97- } else {
98- out = append(out, uintptr(0))
99- }
100- case string:
101- c := strings.CString(v)
102- alive = append(alive, c)
103- // NOTE: that although the pointer will be kept alive
104- // if there are any stack splits before this uintptr
105- // is no longer needed the pointer will point to random memory.
106- out = append(out, uintptr(unsafe.Pointer(c)))
107- case unsafe.Pointer:
108- alive = append(alive, v)
109- // NOTE: that although the pointer will be kept alive
110- // if there are any stack splits before this uintptr
111- // is no longer needed the pointer will point to random memory.
112- out = append(out, uintptr(v))
113- case uintptr:
114- out = append(out, v)
115- case int:
116- out = append(out, uintptr(v))
117- case uint:
118- out = append(out, uintptr(v))
119- default:
120- panic(fmt.Sprintf("objc: unknown type %T", v))
121- }
122- }
123- return out, func() {
124- runtime.KeepAlive(alive)
125- }
80+ return objc_msgSendSuper2(super, sel, args...)
12681 }
12782
12883 // SEL is an opaque type that represents a method selector
@@ -131,10 +86,7 @@ type SEL uintptr
13186 // RegisterName registers a method with the Objective-C runtime system, maps the method name to a selector,
13287 // and returns the selector value.
13388 func RegisterName(name string) SEL {
134- n := strings.CString(name)
135- ret, _, _ := purego.SyscallN(sel_registerName, uintptr(unsafe.Pointer(n)))
136- runtime.KeepAlive(n)
137- return SEL(ret)
89+ return sel_registerName(name)
13890 }
13991
14092 // Class is an opaque type that represents an Objective-C class.
@@ -142,25 +94,18 @@ type Class uintptr
14294
14395 // GetClass returns the Class object for the named class, or nil if the class is not registered with the Objective-C runtime.
14496 func GetClass(name string) Class {
145- n := strings.CString(name)
146- ret, _, _ := purego.SyscallN(objc_getClass, uintptr(unsafe.Pointer(n)))
147- runtime.KeepAlive(n)
148- return Class(ret)
97+ return objc_getClass(name)
14998 }
15099
151100 // AllocateClassPair creates a new class and metaclass. Then returns the new class, or Nil if the class could not be created
152101 func AllocateClassPair(super Class, name string, extraBytes uintptr) Class {
153- n := strings.CString(name)
154- ret, _, _ := purego.SyscallN(objc_allocateClassPair, uintptr(super), uintptr(unsafe.Pointer(n)), extraBytes)
155- runtime.KeepAlive(n)
156- return Class(ret)
102+ return objc_allocateClassPair(super, name, extraBytes)
157103 }
158104
159105 // SuperClass returns the superclass of a class.
160106 // You should usually use NSObject‘s superclass method instead of this function.
161107 func (c Class) SuperClass() Class {
162- ret, _, _ := purego.SyscallN(class_getSuperclass, uintptr(c))
163- return Class(ret)
108+ return class_getSuperclass(c)
164109 }
165110
166111 // AddMethod adds a new method to a class with a given name and implementation.
@@ -168,10 +113,7 @@ func (c Class) SuperClass() Class {
168113 // Since the function must take at least two arguments—self and _cmd, the second and third
169114 // characters must be “@:” (the first character is the return type).
170115 func (c Class) AddMethod(name SEL, imp IMP, types string) bool {
171- t := strings.CString(types)
172- ret, _, _ := purego.SyscallN(class_addMethod, uintptr(c), uintptr(name), uintptr(imp), uintptr(unsafe.Pointer(t)))
173- runtime.KeepAlive(t)
174- return byte(ret) != 0
116+ return class_addMethod(c, name, imp, types)
175117 }
176118
177119 // AddIvar adds a new instance variable to a class.
@@ -180,37 +122,28 @@ func (c Class) AddMethod(name SEL, imp IMP, types string) bool {
180122 // The class must not be a metaclass. Adding an instance variable to a metaclass is not supported.
181123 // It takes the instance of the type of the Ivar and a string representing the type.
182124 func (c Class) AddIvar(name string, ty interface{}, types string) bool {
183- n := strings.CString(name)
184- t := strings.CString(types)
185125 typeOf := reflect.TypeOf(ty)
186126 size := typeOf.Size()
187127 alignment := uint8(math.Log2(float64(typeOf.Align())))
188- ret, _, _ := purego.SyscallN(class_addIvar, uintptr(c), uintptr(unsafe.Pointer(n)), size, uintptr(alignment), uintptr(unsafe.Pointer(t)))
189- runtime.KeepAlive(n)
190- runtime.KeepAlive(t)
191- return byte(ret) != 0
128+ return class_addIvar(c, name, size, alignment, types)
192129 }
193130
194131 // AddProtocol adds a protocol to a class.
195132 // Returns true if the protocol was added successfully, otherwise false (for example,
196133 // the class already conforms to that protocol).
197134 func (c Class) AddProtocol(protocol *Protocol) bool {
198- ret, _, _ := purego.SyscallN(class_addProtocol, uintptr(c), uintptr(unsafe.Pointer(protocol)))
199- return byte(ret) != 0
135+ return class_addProtocol(c, protocol)
200136 }
201137
202138 // InstanceVariable returns an Ivar data structure containing information about the instance variable specified by name.
203139 func (c Class) InstanceVariable(name string) Ivar {
204- n := strings.CString(name)
205- ret, _, _ := purego.SyscallN(class_getInstanceVariable, uintptr(c), uintptr(unsafe.Pointer(n)))
206- runtime.KeepAlive(n)
207- return Ivar(ret)
140+ return class_getInstanceVariable(c, name)
208141 }
209142
210143 // Register registers a class that was allocated using AllocateClassPair.
211144 // It can now be used to make objects by sending it either alloc and init or new.
212145 func (c Class) Register() {
213- purego.SyscallN(objc_registerClassPair, uintptr(c))
146+ objc_registerClassPair(c)
214147 }
215148
216149 // Ivar an opaque type that represents an instance variable.
@@ -221,8 +154,7 @@ type Ivar uintptr
221154 // For instance variables of type ID or other object types, call Ivar and SetIvar instead
222155 // of using this offset to access the instance variable data directly.
223156 func (i Ivar) Offset() uintptr {
224- ret, _, _ := purego.SyscallN(ivar_getOffset, uintptr(i))
225- return ret
157+ return ivar_getOffset(i)
226158 }
227159
228160 // Protocol is a type that declares methods that can be implemented by any class.
@@ -230,11 +162,7 @@ type Protocol uintptr
230162
231163 // GetProtocol returns the protocol for the given name or nil if there is no protocol by that name.
232164 func GetProtocol(name string) *Protocol {
233- n := strings.CString(name)
234- p, _, _ := purego.SyscallN(objc_getProtocol, uintptr(unsafe.Pointer(n)))
235- runtime.KeepAlive(n)
236- // We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer
237- return *(**Protocol)(unsafe.Pointer(&p))
165+ return objc_getProtocol(name)
238166 }
239167
240168 // IMP is a function pointer that can be called by Objective-C code.
@@ -6,53 +6,61 @@
6 package objc6 package objc
7 7
8 import (8 import (
9- "fmt"9+ "github.com/ebitengine/purego"
10 "math"10 "math"
11 "reflect"11 "reflect"
12- "runtime"
13- "unsafe"
14-
15- "github.com/ebitengine/purego"
16- "github.com/ebitengine/purego/internal/strings"
17 )12 )
18 13
19 //TODO: support try/catch?14 //TODO: support try/catch?
20 //https://stackoverflow.com/questions/7062599/example-of-how-objective-cs-try-catch-implementation-is-executed-at-runtime15 //https://stackoverflow.com/questions/7062599/example-of-how-objective-cs-try-catch-implementation-is-executed-at-runtime
21 16
22 var (17 var (
23- objc = purego.Dlopen("/usr/lib/libobjc.A.dylib", purego.RTLD_GLOBAL)18+ objc_msgSend func(obj ID, cmd SEL, args ...interface{}) ID
24-19+ objc_msgSendSuper2 func(super *objc_super, cmd SEL, args ...interface{}) ID
25- objc_msgSend = purego.Dlsym(objc, "objc_msgSend")20+ objc_getClass func(name string) Class
26- objc_msgSendSuper2 = purego.Dlsym(objc, "objc_msgSendSuper2")21+ objc_getProtocol func(name string) *Protocol
27- objc_getClass = purego.Dlsym(objc, "objc_getClass")22+ objc_allocateClassPair func(super Class, name string, extraBytes uintptr) Class
28- objc_getProtocol = purego.Dlsym(objc, "objc_getProtocol")23+ objc_registerClassPair func(class Class)
29- objc_allocateClassPair = purego.Dlsym(objc, "objc_allocateClassPair")24+ sel_registerName func(name string) SEL
30- objc_registerClassPair = purego.Dlsym(objc, "objc_registerClassPair")25+ class_getSuperclass func(class Class) Class
31- sel_registerName = purego.Dlsym(objc, "sel_registerName")26+ class_getInstanceVariable func(class Class, name string) Ivar
32- class_getSuperclass = purego.Dlsym(objc, "class_getSuperclass")27+ class_addMethod func(class Class, name SEL, imp IMP, types string) bool
33- class_getInstanceVariable = purego.Dlsym(objc, "class_getInstanceVariable")28+ class_addIvar func(class Class, name string, size uintptr, alignment uint8, types string) bool
34- class_addMethod = purego.Dlsym(objc, "class_addMethod")29+ class_addProtocol func(class Class, protocol *Protocol) bool
35- class_addIvar = purego.Dlsym(objc, "class_addIvar")30+ ivar_getOffset func(ivar Ivar) uintptr
36- class_addProtocol = purego.Dlsym(objc, "class_addProtocol")31+ object_getClass func(obj ID) Class
37- ivar_getOffset = purego.Dlsym(objc, "ivar_getOffset")
38- object_getClass = purego.Dlsym(objc, "object_getClass")
39 )32 )
40 33
34+func init() {
35+ objc := purego.Dlopen("/usr/lib/libobjc.A.dylib", purego.RTLD_GLOBAL)
36+
37+ purego.RegisterLibFunc(&objc_msgSend, objc, "objc_msgSend")
38+ purego.RegisterLibFunc(&objc_msgSendSuper2, objc, "objc_msgSendSuper2")
39+ purego.RegisterLibFunc(&object_getClass, objc, "object_getClass")
40+ purego.RegisterLibFunc(&objc_getClass, objc, "objc_getClass")
41+ purego.RegisterLibFunc(&objc_getProtocol, objc, "objc_getProtocol")
42+ purego.RegisterLibFunc(&objc_allocateClassPair, objc, "objc_allocateClassPair")
43+ purego.RegisterLibFunc(&objc_registerClassPair, objc, "objc_registerClassPair")
44+ purego.RegisterLibFunc(&sel_registerName, objc, "sel_registerName")
45+ purego.RegisterLibFunc(&class_getSuperclass, objc, "class_getSuperclass")
46+ purego.RegisterLibFunc(&class_getInstanceVariable, objc, "class_getInstanceVariable")
47+ purego.RegisterLibFunc(&class_addMethod, objc, "class_addMethod")
48+ purego.RegisterLibFunc(&class_addIvar, objc, "class_addIvar")
49+ purego.RegisterLibFunc(&class_addProtocol, objc, "class_addProtocol")
50+ purego.RegisterLibFunc(&ivar_getOffset, objc, "ivar_getOffset")
51+}
52+
41 // ID is an opaque pointer to some Objective-C object53 // ID is an opaque pointer to some Objective-C object
42 type ID uintptr54 type ID uintptr
43 55
44 // Class returns the class of the object.56 // Class returns the class of the object.
45 func (id ID) Class() Class {57 func (id ID) Class() Class {
46- ret, _, _ := purego.SyscallN(object_getClass, uintptr(id))58+ return object_getClass(id)
47- return Class(ret)
48 }59 }
49 60
50 // Send is a convenience method for sending messages to objects.61 // Send is a convenience method for sending messages to objects.
51 func (id ID) Send(sel SEL, args ...interface{}) ID {62 func (id ID) Send(sel SEL, args ...interface{}) ID {
52- tmp, keepAlive := createArgs(id, sel, args...)63+ return objc_msgSend(id, sel, args...)
53- defer keepAlive()
54- ret, _, _ := purego.SyscallN(objc_msgSend, tmp...)
55- return ID(ret)
56 }64 }
57 65
58 // objc_super data structure is generated by the Objective-C compiler when it encounters the super keyword66 // objc_super data structure is generated by the Objective-C compiler when it encounters the super keyword
@@ -69,60 +77,7 @@ func (id ID) SendSuper(sel SEL, args ...interface{}) ID {
69 receiver: id,77 receiver: id,
70 superClass: id.Class(),78 superClass: id.Class(),
71 }79 }
72- tmp, keepAlive := createArgs(0, sel, args...)80+ return objc_msgSendSuper2(super, sel, args...)
73- defer keepAlive()
74- tmp[0] = uintptr(unsafe.Pointer(super)) // if createArgs splits the stack the pointer would be wrong
75- ret, _, _ := purego.SyscallN(objc_msgSendSuper2, tmp...)
76- return ID(ret)
77-}
78-
79-func createArgs(cls ID, sel SEL, args ...interface{}) (out []uintptr, keepAlive func()) {
80- out = make([]uintptr, 2, len(args)+2)
81- out[0] = uintptr(cls)
82- out[1] = uintptr(sel)
83- var alive []interface{}
84- for _, a := range args {
85- switch v := a.(type) {
86- case ID:
87- out = append(out, uintptr(v))
88- case Class:
89- out = append(out, uintptr(v))
90- case SEL:
91- out = append(out, uintptr(v))
92- case IMP:
93- out = append(out, uintptr(v))
94- case bool:
95- if v {
96- out = append(out, uintptr(1))
97- } else {
98- out = append(out, uintptr(0))
99- }
100- case string:
101- c := strings.CString(v)
102- alive = append(alive, c)
103- // NOTE: that although the pointer will be kept alive
104- // if there are any stack splits before this uintptr
105- // is no longer needed the pointer will point to random memory.
106- out = append(out, uintptr(unsafe.Pointer(c)))
107- case unsafe.Pointer:
108- alive = append(alive, v)
109- // NOTE: that although the pointer will be kept alive
110- // if there are any stack splits before this uintptr
111- // is no longer needed the pointer will point to random memory.
112- out = append(out, uintptr(v))
113- case uintptr:
114- out = append(out, v)
115- case int:
116- out = append(out, uintptr(v))
117- case uint:
118- out = append(out, uintptr(v))
119- default:
120- panic(fmt.Sprintf("objc: unknown type %T", v))
121- }
122- }
123- return out, func() {
124- runtime.KeepAlive(alive)
125- }
126 }81 }
127 82
128 // SEL is an opaque type that represents a method selector83 // SEL is an opaque type that represents a method selector
@@ -131,10 +86,7 @@ type SEL uintptr
131 // RegisterName registers a method with the Objective-C runtime system, maps the method name to a selector,86 // RegisterName registers a method with the Objective-C runtime system, maps the method name to a selector,
132 // and returns the selector value.87 // and returns the selector value.
133 func RegisterName(name string) SEL {88 func RegisterName(name string) SEL {
134- n := strings.CString(name)89+ return sel_registerName(name)
135- ret, _, _ := purego.SyscallN(sel_registerName, uintptr(unsafe.Pointer(n)))
136- runtime.KeepAlive(n)
137- return SEL(ret)
138 }90 }
139 91
140 // Class is an opaque type that represents an Objective-C class.92 // Class is an opaque type that represents an Objective-C class.
@@ -142,25 +94,18 @@ type Class uintptr
142 94
143 // GetClass returns the Class object for the named class, or nil if the class is not registered with the Objective-C runtime.95 // GetClass returns the Class object for the named class, or nil if the class is not registered with the Objective-C runtime.
144 func GetClass(name string) Class {96 func GetClass(name string) Class {
145- n := strings.CString(name)97+ return objc_getClass(name)
146- ret, _, _ := purego.SyscallN(objc_getClass, uintptr(unsafe.Pointer(n)))
147- runtime.KeepAlive(n)
148- return Class(ret)
149 }98 }
150 99
151 // AllocateClassPair creates a new class and metaclass. Then returns the new class, or Nil if the class could not be created100 // AllocateClassPair creates a new class and metaclass. Then returns the new class, or Nil if the class could not be created
152 func AllocateClassPair(super Class, name string, extraBytes uintptr) Class {101 func AllocateClassPair(super Class, name string, extraBytes uintptr) Class {
153- n := strings.CString(name)102+ return objc_allocateClassPair(super, name, extraBytes)
154- ret, _, _ := purego.SyscallN(objc_allocateClassPair, uintptr(super), uintptr(unsafe.Pointer(n)), extraBytes)
155- runtime.KeepAlive(n)
156- return Class(ret)
157 }103 }
158 104
159 // SuperClass returns the superclass of a class.105 // SuperClass returns the superclass of a class.
160 // You should usually use NSObject‘s superclass method instead of this function.106 // You should usually use NSObject‘s superclass method instead of this function.
161 func (c Class) SuperClass() Class {107 func (c Class) SuperClass() Class {
162- ret, _, _ := purego.SyscallN(class_getSuperclass, uintptr(c))108+ return class_getSuperclass(c)
163- return Class(ret)
164 }109 }
165 110
166 // AddMethod adds a new method to a class with a given name and implementation.111 // AddMethod adds a new method to a class with a given name and implementation.
@@ -168,10 +113,7 @@ func (c Class) SuperClass() Class {
168 // Since the function must take at least two arguments—self and _cmd, the second and third113 // Since the function must take at least two arguments—self and _cmd, the second and third
169 // characters must be “@:” (the first character is the return type).114 // characters must be “@:” (the first character is the return type).
170 func (c Class) AddMethod(name SEL, imp IMP, types string) bool {115 func (c Class) AddMethod(name SEL, imp IMP, types string) bool {
171- t := strings.CString(types)116+ return class_addMethod(c, name, imp, types)
172- ret, _, _ := purego.SyscallN(class_addMethod, uintptr(c), uintptr(name), uintptr(imp), uintptr(unsafe.Pointer(t)))
173- runtime.KeepAlive(t)
174- return byte(ret) != 0
175 }117 }
176 118
177 // AddIvar adds a new instance variable to a class.119 // AddIvar adds a new instance variable to a class.
@@ -180,37 +122,28 @@ func (c Class) AddMethod(name SEL, imp IMP, types string) bool {
180 // The class must not be a metaclass. Adding an instance variable to a metaclass is not supported.122 // The class must not be a metaclass. Adding an instance variable to a metaclass is not supported.
181 // It takes the instance of the type of the Ivar and a string representing the type.123 // It takes the instance of the type of the Ivar and a string representing the type.
182 func (c Class) AddIvar(name string, ty interface{}, types string) bool {124 func (c Class) AddIvar(name string, ty interface{}, types string) bool {
183- n := strings.CString(name)
184- t := strings.CString(types)
185 typeOf := reflect.TypeOf(ty)125 typeOf := reflect.TypeOf(ty)
186 size := typeOf.Size()126 size := typeOf.Size()
187 alignment := uint8(math.Log2(float64(typeOf.Align())))127 alignment := uint8(math.Log2(float64(typeOf.Align())))
188- ret, _, _ := purego.SyscallN(class_addIvar, uintptr(c), uintptr(unsafe.Pointer(n)), size, uintptr(alignment), uintptr(unsafe.Pointer(t)))128+ return class_addIvar(c, name, size, alignment, types)
189- runtime.KeepAlive(n)
190- runtime.KeepAlive(t)
191- return byte(ret) != 0
192 }129 }
193 130
194 // AddProtocol adds a protocol to a class.131 // AddProtocol adds a protocol to a class.
195 // Returns true if the protocol was added successfully, otherwise false (for example,132 // Returns true if the protocol was added successfully, otherwise false (for example,
196 // the class already conforms to that protocol).133 // the class already conforms to that protocol).
197 func (c Class) AddProtocol(protocol *Protocol) bool {134 func (c Class) AddProtocol(protocol *Protocol) bool {
198- ret, _, _ := purego.SyscallN(class_addProtocol, uintptr(c), uintptr(unsafe.Pointer(protocol)))135+ return class_addProtocol(c, protocol)
199- return byte(ret) != 0
200 }136 }
201 137
202 // InstanceVariable returns an Ivar data structure containing information about the instance variable specified by name.138 // InstanceVariable returns an Ivar data structure containing information about the instance variable specified by name.
203 func (c Class) InstanceVariable(name string) Ivar {139 func (c Class) InstanceVariable(name string) Ivar {
204- n := strings.CString(name)140+ return class_getInstanceVariable(c, name)
205- ret, _, _ := purego.SyscallN(class_getInstanceVariable, uintptr(c), uintptr(unsafe.Pointer(n)))
206- runtime.KeepAlive(n)
207- return Ivar(ret)
208 }141 }
209 142
210 // Register registers a class that was allocated using AllocateClassPair.143 // Register registers a class that was allocated using AllocateClassPair.
211 // It can now be used to make objects by sending it either alloc and init or new.144 // It can now be used to make objects by sending it either alloc and init or new.
212 func (c Class) Register() {145 func (c Class) Register() {
213- purego.SyscallN(objc_registerClassPair, uintptr(c))146+ objc_registerClassPair(c)
214 }147 }
215 148
216 // Ivar an opaque type that represents an instance variable.149 // Ivar an opaque type that represents an instance variable.
@@ -221,8 +154,7 @@ type Ivar uintptr
221 // For instance variables of type ID or other object types, call Ivar and SetIvar instead154 // For instance variables of type ID or other object types, call Ivar and SetIvar instead
222 // of using this offset to access the instance variable data directly.155 // of using this offset to access the instance variable data directly.
223 func (i Ivar) Offset() uintptr {156 func (i Ivar) Offset() uintptr {
224- ret, _, _ := purego.SyscallN(ivar_getOffset, uintptr(i))157+ return ivar_getOffset(i)
225- return ret
226 }158 }
227 159
228 // Protocol is a type that declares methods that can be implemented by any class.160 // Protocol is a type that declares methods that can be implemented by any class.
@@ -230,11 +162,7 @@ type Protocol uintptr
230 162
231 // GetProtocol returns the protocol for the given name or nil if there is no protocol by that name.163 // GetProtocol returns the protocol for the given name or nil if there is no protocol by that name.
232 func GetProtocol(name string) *Protocol {164 func GetProtocol(name string) *Protocol {
233- n := strings.CString(name)165+ return objc_getProtocol(name)
234- p, _, _ := purego.SyscallN(objc_getProtocol, uintptr(unsafe.Pointer(n)))
235- runtime.KeepAlive(n)
236- // We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer
237- return *(**Protocol)(unsafe.Pointer(&p))
238 }166 }
239 167
240 // IMP is a function pointer that can be called by Objective-C code.168 // IMP is a function pointer that can be called by Objective-C code.

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