objc: generic Send and SendSuper (#90)
ID.Send and ID.SendSuper can only return integer types (such as ID). These two new functions can take any return type as long as it is a type that RegisterFunc supports. Closes #87
- commit
- 9246c5c809fa87320d2791d4f85e4fa870afc535
- author
- TotallyGamerJet · · 2023-01-09 04:51 UTC
- committer
- GitHub · · 2023-01-09 04:51 UTC
- parent
- 6d5fb77
- signature
- GPG Unverified
4AEE18F83AFDEB23The key is unknown
2 files · +45 -3
M objc/objc_runtime_darwin.go +31 -3
| @@ -18,7 +18,9 @@ import ( | |||
| 18 | 18 | //https://stackoverflow.com/questions/7062599/example-of-how-objective-cs-try-catch-implementation-is-executed-at-runtime | |
| 19 | 19 | ||
| 20 | 20 | var ( | |
| 21 | + | objc_msgSend_fn uintptr | |
| 21 | 22 | objc_msgSend func(obj ID, cmd SEL, args ...interface{}) ID | |
| 23 | + | objc_msgSendSuper2_fn uintptr | |
| 22 | 24 | objc_msgSendSuper2 func(super *objc_super, cmd SEL, args ...interface{}) ID | |
| 23 | 25 | objc_getClass func(name string) Class | |
| 24 | 26 | objc_getProtocol func(name string) *Protocol | |
| @@ -37,9 +39,13 @@ var ( | |||
| 37 | 39 | ||
| 38 | 40 | func init() { | |
| 39 | 41 | objc := purego.Dlopen("/usr/lib/libobjc.A.dylib", purego.RTLD_GLOBAL) | |
| 40 | - | ||
| 41 | - | purego.RegisterLibFunc(&objc_msgSend, objc, "objc_msgSend") | |
| 42 | - | purego.RegisterLibFunc(&objc_msgSendSuper2, objc, "objc_msgSendSuper2") | |
| 42 | + | if err := purego.Dlerror(); err != "" { | |
| 43 | + | panic("objc: " + err) | |
| 44 | + | } | |
| 45 | + | objc_msgSend_fn = purego.Dlsym(objc, "objc_msgSend") | |
| 46 | + | purego.RegisterFunc(&objc_msgSend, objc_msgSend_fn) | |
| 47 | + | objc_msgSendSuper2_fn = purego.Dlsym(objc, "objc_msgSendSuper2") | |
| 48 | + | purego.RegisterFunc(&objc_msgSendSuper2, objc_msgSendSuper2_fn) | |
| 43 | 49 | purego.RegisterLibFunc(&object_getClass, objc, "object_getClass") | |
| 44 | 50 | purego.RegisterLibFunc(&objc_getClass, objc, "objc_getClass") | |
| 45 | 51 | purego.RegisterLibFunc(&objc_getProtocol, objc, "objc_getProtocol") | |
| @@ -70,6 +76,15 @@ func (id ID) Send(sel SEL, args ...interface{}) ID { | |||
| 70 | 76 | return objc_msgSend(id, sel, args...) | |
| 71 | 77 | } | |
| 72 | 78 | ||
| 79 | + | // Send is a convenience method for sending messages to objects that can return any type. | |
| 80 | + | // This function takes a SEL instead of a string since RegisterName grabs the global Objective-C lock. | |
| 81 | + | // It is best to cache the result of RegisterName. | |
| 82 | + | func Send[T any](id ID, sel SEL, args ...any) T { | |
| 83 | + | var fn func(id ID, sel SEL, args ...any) T | |
| 84 | + | purego.RegisterFunc(&fn, objc_msgSend_fn) | |
| 85 | + | return fn(id, sel, args...) | |
| 86 | + | } | |
| 87 | + | ||
| 73 | 88 | // objc_super data structure is generated by the Objective-C compiler when it encounters the super keyword | |
| 74 | 89 | // as the receiver of a message. It specifies the class definition of the particular superclass that should | |
| 75 | 90 | // be messaged. | |
| @@ -89,6 +104,19 @@ func (id ID) SendSuper(sel SEL, args ...interface{}) ID { | |||
| 89 | 104 | return objc_msgSendSuper2(super, sel, args...) | |
| 90 | 105 | } | |
| 91 | 106 | ||
| 107 | + | // SendSuper is a convenience method for sending message to object's super that can return any type. | |
| 108 | + | // This function takes a SEL instead of a string since RegisterName grabs the global Objective-C lock. | |
| 109 | + | // It is best to cache the result of RegisterName. | |
| 110 | + | func SendSuper[T any](id ID, sel SEL, args ...any) T { | |
| 111 | + | var super = &objc_super{ | |
| 112 | + | receiver: id, | |
| 113 | + | superClass: id.Class(), | |
| 114 | + | } | |
| 115 | + | var fn func(objcSuper *objc_super, sel SEL, args ...any) T | |
| 116 | + | purego.RegisterFunc(&fn, objc_msgSendSuper2_fn) | |
| 117 | + | return fn(super, sel, args...) | |
| 118 | + | } | |
| 119 | + | ||
| 92 | 120 | // SEL is an opaque type that represents a method selector | |
| 93 | 121 | type SEL uintptr | |
| 94 | 122 | ||
| @@ -18,7 +18,9 @@ import ( | |||||
| 18 | //https://stackoverflow.com/questions/7062599/example-of-how-objective-cs-try-catch-implementation-is-executed-at-runtime | 18 | //https://stackoverflow.com/questions/7062599/example-of-how-objective-cs-try-catch-implementation-is-executed-at-runtime | ||
| 19 | 19 | ||||
| 20 | var ( | 20 | var ( | ||
| 21 | + | objc_msgSend_fn uintptr | |||
| 21 | objc_msgSend func(obj ID, cmd SEL, args ...interface{}) ID | 22 | objc_msgSend func(obj ID, cmd SEL, args ...interface{}) ID | ||
| 23 | + | objc_msgSendSuper2_fn uintptr | |||
| 22 | objc_msgSendSuper2 func(super *objc_super, cmd SEL, args ...interface{}) ID | 24 | objc_msgSendSuper2 func(super *objc_super, cmd SEL, args ...interface{}) ID | ||
| 23 | objc_getClass func(name string) Class | 25 | objc_getClass func(name string) Class | ||
| 24 | objc_getProtocol func(name string) *Protocol | 26 | objc_getProtocol func(name string) *Protocol | ||
| @@ -37,9 +39,13 @@ var ( | |||||
| 37 | 39 | ||||
| 38 | func init() { | 40 | func init() { | ||
| 39 | objc := purego.Dlopen("/usr/lib/libobjc.A.dylib", purego.RTLD_GLOBAL) | 41 | objc := purego.Dlopen("/usr/lib/libobjc.A.dylib", purego.RTLD_GLOBAL) | ||
| 40 | - | 42 | + | if err := purego.Dlerror(); err != "" { | |
| 41 | - | purego.RegisterLibFunc(&objc_msgSend, objc, "objc_msgSend") | 43 | + | panic("objc: " + err) |
| 42 | - | purego.RegisterLibFunc(&objc_msgSendSuper2, objc, "objc_msgSendSuper2") | 44 | + | } |
| 45 | + | objc_msgSend_fn = purego.Dlsym(objc, "objc_msgSend") | |||
| 46 | + | purego.RegisterFunc(&objc_msgSend, objc_msgSend_fn) | |||
| 47 | + | objc_msgSendSuper2_fn = purego.Dlsym(objc, "objc_msgSendSuper2") | |||
| 48 | + | purego.RegisterFunc(&objc_msgSendSuper2, objc_msgSendSuper2_fn) | |||
| 43 | purego.RegisterLibFunc(&object_getClass, objc, "object_getClass") | 49 | purego.RegisterLibFunc(&object_getClass, objc, "object_getClass") | ||
| 44 | purego.RegisterLibFunc(&objc_getClass, objc, "objc_getClass") | 50 | purego.RegisterLibFunc(&objc_getClass, objc, "objc_getClass") | ||
| 45 | purego.RegisterLibFunc(&objc_getProtocol, objc, "objc_getProtocol") | 51 | purego.RegisterLibFunc(&objc_getProtocol, objc, "objc_getProtocol") | ||
| @@ -70,6 +76,15 @@ func (id ID) Send(sel SEL, args ...interface{}) ID { | |||||
| 70 | return objc_msgSend(id, sel, args...) | 76 | return objc_msgSend(id, sel, args...) | ||
| 71 | } | 77 | } | ||
| 72 | 78 | ||||
| 79 | + | // Send is a convenience method for sending messages to objects that can return any type. | |||
| 80 | + | // This function takes a SEL instead of a string since RegisterName grabs the global Objective-C lock. | |||
| 81 | + | // It is best to cache the result of RegisterName. | |||
| 82 | + | func Send[T any](id ID, sel SEL, args ...any) T { | |||
| 83 | + | var fn func(id ID, sel SEL, args ...any) T | |||
| 84 | + | purego.RegisterFunc(&fn, objc_msgSend_fn) | |||
| 85 | + | return fn(id, sel, args...) | |||
| 86 | + | } | |||
| 87 | + | ||||
| 73 | // objc_super data structure is generated by the Objective-C compiler when it encounters the super keyword | 88 | // objc_super data structure is generated by the Objective-C compiler when it encounters the super keyword | ||
| 74 | // as the receiver of a message. It specifies the class definition of the particular superclass that should | 89 | // as the receiver of a message. It specifies the class definition of the particular superclass that should | ||
| 75 | // be messaged. | 90 | // be messaged. | ||
| @@ -89,6 +104,19 @@ func (id ID) SendSuper(sel SEL, args ...interface{}) ID { | |||||
| 89 | return objc_msgSendSuper2(super, sel, args...) | 104 | return objc_msgSendSuper2(super, sel, args...) | ||
| 90 | } | 105 | } | ||
| 91 | 106 | ||||
| 107 | + | // SendSuper is a convenience method for sending message to object's super that can return any type. | |||
| 108 | + | // This function takes a SEL instead of a string since RegisterName grabs the global Objective-C lock. | |||
| 109 | + | // It is best to cache the result of RegisterName. | |||
| 110 | + | func SendSuper[T any](id ID, sel SEL, args ...any) T { | |||
| 111 | + | var super = &objc_super{ | |||
| 112 | + | receiver: id, | |||
| 113 | + | superClass: id.Class(), | |||
| 114 | + | } | |||
| 115 | + | var fn func(objcSuper *objc_super, sel SEL, args ...any) T | |||
| 116 | + | purego.RegisterFunc(&fn, objc_msgSendSuper2_fn) | |||
| 117 | + | return fn(super, sel, args...) | |||
| 118 | + | } | |||
| 119 | + | ||||
| 92 | // SEL is an opaque type that represents a method selector | 120 | // SEL is an opaque type that represents a method selector | ||
| 93 | type SEL uintptr | 121 | type SEL uintptr | ||
| 94 | 122 | ||||
M objc/objc_runtime_darwin_test.go +14 -0
| @@ -5,6 +5,7 @@ package objc_test | |||
| 5 | 5 | ||
| 6 | 6 | import ( | |
| 7 | 7 | "fmt" | |
| 8 | + | "testing" | |
| 8 | 9 | ||
| 9 | 10 | "github.com/ebitengine/purego" | |
| 10 | 11 | "github.com/ebitengine/purego/objc" | |
| @@ -73,3 +74,16 @@ func ExampleID_SendSuper() { | |||
| 73 | 74 | // Output: In Child | |
| 74 | 75 | // In Super! | |
| 75 | 76 | } | |
| 77 | + | ||
| 78 | + | func TestSend(t *testing.T) { | |
| 79 | + | // NSNumber comes from Foundation so make sure we have linked to that framework. | |
| 80 | + | _ = purego.Dlopen("Foundation.framework/Foundation", purego.RTLD_GLOBAL) | |
| 81 | + | const double = float64(2.34) | |
| 82 | + | // Initialize a NSNumber | |
| 83 | + | NSNumber := objc.ID(objc.GetClass("NSNumber")).Send(objc.RegisterName("numberWithDouble:"), double) | |
| 84 | + | // Then get that number back using the generic Send function. | |
| 85 | + | var number = objc.Send[float64](NSNumber, objc.RegisterName("doubleValue")) | |
| 86 | + | if double != number { | |
| 87 | + | t.Failed() | |
| 88 | + | } | |
| 89 | + | } | |
| @@ -5,6 +5,7 @@ package objc_test | |||||
| 5 | 5 | ||||
| 6 | import ( | 6 | import ( | ||
| 7 | "fmt" | 7 | "fmt" | ||
| 8 | + | "testing" | |||
| 8 | 9 | ||||
| 9 | "github.com/ebitengine/purego" | 10 | "github.com/ebitengine/purego" | ||
| 10 | "github.com/ebitengine/purego/objc" | 11 | "github.com/ebitengine/purego/objc" | ||
| @@ -73,3 +74,16 @@ func ExampleID_SendSuper() { | |||||
| 73 | // Output: In Child | 74 | // Output: In Child | ||
| 74 | // In Super! | 75 | // In Super! | ||
| 75 | } | 76 | } | ||
| 77 | + | ||||
| 78 | + | func TestSend(t *testing.T) { | |||
| 79 | + | // NSNumber comes from Foundation so make sure we have linked to that framework. | |||
| 80 | + | _ = purego.Dlopen("Foundation.framework/Foundation", purego.RTLD_GLOBAL) | |||
| 81 | + | const double = float64(2.34) | |||
| 82 | + | // Initialize a NSNumber | |||
| 83 | + | NSNumber := objc.ID(objc.GetClass("NSNumber")).Send(objc.RegisterName("numberWithDouble:"), double) | |||
| 84 | + | // Then get that number back using the generic Send function. | |||
| 85 | + | var number = objc.Send[float64](NSNumber, objc.RegisterName("doubleValue")) | |||
| 86 | + | if double != number { | |||
| 87 | + | t.Failed() | |||
| 88 | + | } | |||
| 89 | + | } | |||