objc: implement ability to create Protocols at runtime (#320)
Closes #321
- commit
- 1a51bed53afae7417fa8f512a1e96b4cc43e938e
- author
- TotallyGamerJet · · 2025-06-08 18:18 UTC
- committer
- GitHub · · 2025-06-08 18:18 UTC
- parent
- 660f0c6
- signature
- GPG Unverified
B5690EEEBB952194The key is unknown - refs
- tag: v0.9.0-alpha.7
3 files · +363 -25
A examples/protocol-dumper/main_darwin.go +176 -0
| @@ -0,0 +1,176 @@ | |||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | + | // SPDX-FileCopyrightText: 2025 The Ebitengine Authors | |
| 3 | + | ||
| 4 | + | package main | |
| 5 | + | ||
| 6 | + | // #cgo CFLAGS: -x objective-c | |
| 7 | + | // #cgo LDFLAGS: -framework AppKit | |
| 8 | + | // | |
| 9 | + | // #import <AppKit/AppKit.h> | |
| 10 | + | // | |
| 11 | + | // __attribute__((used)) | |
| 12 | + | // static void __force_protocol_load() { | |
| 13 | + | // id o = NULL; | |
| 14 | + | // o = @protocol(NSAccessibility); | |
| 15 | + | // o = @protocol(NSApplicationDelegate); | |
| 16 | + | // } | |
| 17 | + | import "C" | |
| 18 | + | ||
| 19 | + | import ( | |
| 20 | + | "fmt" | |
| 21 | + | "io" | |
| 22 | + | "os" | |
| 23 | + | "strconv" | |
| 24 | + | "strings" | |
| 25 | + | "text/template" | |
| 26 | + | ||
| 27 | + | "github.com/ebitengine/purego/objc" | |
| 28 | + | ) | |
| 29 | + | ||
| 30 | + | var protocols = []string{ | |
| 31 | + | "NSAccessibility", | |
| 32 | + | "NSApplicationDelegate", | |
| 33 | + | } | |
| 34 | + | ||
| 35 | + | type ProtocolImpl struct { | |
| 36 | + | Name string | |
| 37 | + | ||
| 38 | + | RequiredInstanceMethods []objc.MethodDescription | |
| 39 | + | RequiredClassMethods []objc.MethodDescription | |
| 40 | + | OptionalInstanceMethods []objc.MethodDescription | |
| 41 | + | OptionalClassMethods []objc.MethodDescription | |
| 42 | + | ||
| 43 | + | AdoptedProtocols []*objc.Protocol | |
| 44 | + | ||
| 45 | + | RequiredInstanceProperties []objc.Property | |
| 46 | + | RequiredClassProperties []objc.Property | |
| 47 | + | OptionalInstanceProperties []objc.Property | |
| 48 | + | OptionalClassProperties []objc.Property | |
| 49 | + | } | |
| 50 | + | ||
| 51 | + | func readProtocols(names []string) (imps []ProtocolImpl, err error) { | |
| 52 | + | for _, name := range names { | |
| 53 | + | p := objc.GetProtocol(name) | |
| 54 | + | if p == nil { | |
| 55 | + | return nil, fmt.Errorf("protocol '%s' does not exist", name) | |
| 56 | + | } | |
| 57 | + | imp := ProtocolImpl{} | |
| 58 | + | imp.Name = name | |
| 59 | + | imp.RequiredInstanceMethods = p.CopyMethodDescriptionList(true, true) | |
| 60 | + | imp.RequiredClassMethods = p.CopyMethodDescriptionList(true, false) | |
| 61 | + | imp.OptionalInstanceMethods = p.CopyMethodDescriptionList(false, true) | |
| 62 | + | imp.OptionalClassMethods = p.CopyMethodDescriptionList(false, false) | |
| 63 | + | ||
| 64 | + | imp.AdoptedProtocols = p.CopyProtocolList() | |
| 65 | + | ||
| 66 | + | imp.RequiredInstanceProperties = p.CopyPropertyList(true, true) | |
| 67 | + | imp.RequiredClassProperties = p.CopyPropertyList(true, false) | |
| 68 | + | imp.OptionalInstanceProperties = p.CopyPropertyList(false, true) | |
| 69 | + | imp.OptionalClassProperties = p.CopyPropertyList(false, false) | |
| 70 | + | imps = append(imps, imp) | |
| 71 | + | } | |
| 72 | + | return imps, nil | |
| 73 | + | } | |
| 74 | + | ||
| 75 | + | const templ = `// Code generated by protocol-dumper; DO NOT EDIT. | |
| 76 | + | ||
| 77 | + | package main | |
| 78 | + | ||
| 79 | + | import ( | |
| 80 | + | "log" | |
| 81 | + | ||
| 82 | + | "github.com/ebitengine/purego/objc" | |
| 83 | + | ) | |
| 84 | + | ||
| 85 | + | func init() { | |
| 86 | + | var p *objc.Protocol | |
| 87 | + | {{- range . }} | |
| 88 | + | {{- $protocolName := .Name }} | |
| 89 | + | if p = objc.AllocateProtocol("{{$protocolName}}"); p != nil { | |
| 90 | + | {{- range .RequiredInstanceMethods }} | |
| 91 | + | p.AddMethodDescription(objc.RegisterName("{{ .Name }}"), "{{ .Types }}", true, true) | |
| 92 | + | {{- end }} | |
| 93 | + | ||
| 94 | + | {{- range .RequiredClassMethods }} | |
| 95 | + | p.AddMethodDescription(objc.RegisterName("{{ .Name }}"), "{{ .Types }}", true, false) | |
| 96 | + | {{- end }} | |
| 97 | + | ||
| 98 | + | {{- range .OptionalInstanceMethods }} | |
| 99 | + | p.AddMethodDescription(objc.RegisterName("{{ .Name }}"), "{{ .Types }}", false, true) | |
| 100 | + | {{- end }} | |
| 101 | + | ||
| 102 | + | {{- range .OptionalClassMethods }} | |
| 103 | + | p.AddMethodDescription(objc.RegisterName("{{ .Name }}"), "{{ .Types }}", false, false) | |
| 104 | + | {{- end }} | |
| 105 | + | var adoptedProtocol *objc.Protocol | |
| 106 | + | {{- range .AdoptedProtocols }} | |
| 107 | + | adoptedProtocol = objc.GetProtocol("{{ .Name }}") | |
| 108 | + | if adoptedProtocol == nil { | |
| 109 | + | log.Fatalln("protocol '{{ .Name }}' does not exist") | |
| 110 | + | } | |
| 111 | + | p.AddProtocol(adoptedProtocol) | |
| 112 | + | {{- end }} | |
| 113 | + | ||
| 114 | + | {{- range .RequiredInstanceProperties }} | |
| 115 | + | p.AddProperty("{{ .Name }}", {{ attributeToStructString . }}, true, true) | |
| 116 | + | {{- end }} | |
| 117 | + | ||
| 118 | + | ||
| 119 | + | {{- range .RequiredClassProperties }} | |
| 120 | + | p.AddProperty("{{ .Name }}", {{ attributeToStructString . }}, true, false) | |
| 121 | + | {{- end }} | |
| 122 | + | ||
| 123 | + | {{- range .OptionalInstanceProperties }} | |
| 124 | + | p.AddProperty("{{ .Name }}", {{ attributeToStructString . }}, false, true) | |
| 125 | + | {{- end }} | |
| 126 | + | ||
| 127 | + | {{- range .OptionalClassProperties }} | |
| 128 | + | p.AddProperty("{{ .Name }}", {{ attributeToStructString . }}, false, false) | |
| 129 | + | {{- end }} | |
| 130 | + | p.Register() | |
| 131 | + | } // Finished protocol: {{$protocolName}} | |
| 132 | + | {{- end }} | |
| 133 | + | } | |
| 134 | + | ` | |
| 135 | + | ||
| 136 | + | func printProtocols(impls []ProtocolImpl, w io.Writer) error { | |
| 137 | + | tmpl, err := template.New("protocol.tmpl").Funcs(template.FuncMap{ | |
| 138 | + | "attributeToStructString": attributeToStructString, | |
| 139 | + | }).Parse(templ) | |
| 140 | + | if err != nil { | |
| 141 | + | return err | |
| 142 | + | } | |
| 143 | + | err = tmpl.Execute(w, impls) | |
| 144 | + | if err != nil { | |
| 145 | + | return err | |
| 146 | + | } | |
| 147 | + | return nil | |
| 148 | + | } | |
| 149 | + | ||
| 150 | + | func attributeToStructString(p objc.Property) string { | |
| 151 | + | attribs := strings.Split(p.Attributes(), ",") | |
| 152 | + | var b strings.Builder | |
| 153 | + | b.WriteString("[]objc.PropertyAttribute{") | |
| 154 | + | for i, attrib := range attribs { | |
| 155 | + | b.WriteString(fmt.Sprintf(`{Name: &[]byte("%s\x00")[0], Value: &[]byte(%s)[0]}`, string(attrib[0]), strconv.Quote(attrib[1:]+"\x00"))) | |
| 156 | + | if i != len(attribs)-1 { | |
| 157 | + | b.WriteString(", ") | |
| 158 | + | } | |
| 159 | + | } | |
| 160 | + | b.WriteString("}") | |
| 161 | + | return b.String() | |
| 162 | + | } | |
| 163 | + | ||
| 164 | + | // This program prints out the Go code to generate the OBJC protocols list. | |
| 165 | + | // To use it add the @protocol(name) to the C import section and then add it to the protocols list. | |
| 166 | + | // You'll want to run it as amd64 and arm64 since the types are slightly different for each architecture. | |
| 167 | + | func main() { | |
| 168 | + | var imps []ProtocolImpl | |
| 169 | + | var err error | |
| 170 | + | if imps, err = readProtocols(protocols); err != nil { | |
| 171 | + | panic(err) | |
| 172 | + | } | |
| 173 | + | if err = printProtocols(imps, os.Stdout); err != nil { | |
| 174 | + | panic(err) | |
| 175 | + | } | |
| 176 | + | } | |
| @@ -0,0 +1,176 @@ | |||||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |||
| 2 | + | // SPDX-FileCopyrightText: 2025 The Ebitengine Authors | |||
| 3 | + | ||||
| 4 | + | package main | |||
| 5 | + | ||||
| 6 | + | // #cgo CFLAGS: -x objective-c | |||
| 7 | + | // #cgo LDFLAGS: -framework AppKit | |||
| 8 | + | // | |||
| 9 | + | // #import <AppKit/AppKit.h> | |||
| 10 | + | // | |||
| 11 | + | // __attribute__((used)) | |||
| 12 | + | // static void __force_protocol_load() { | |||
| 13 | + | // id o = NULL; | |||
| 14 | + | // o = @protocol(NSAccessibility); | |||
| 15 | + | // o = @protocol(NSApplicationDelegate); | |||
| 16 | + | // } | |||
| 17 | + | import "C" | |||
| 18 | + | ||||
| 19 | + | import ( | |||
| 20 | + | "fmt" | |||
| 21 | + | "io" | |||
| 22 | + | "os" | |||
| 23 | + | "strconv" | |||
| 24 | + | "strings" | |||
| 25 | + | "text/template" | |||
| 26 | + | ||||
| 27 | + | "github.com/ebitengine/purego/objc" | |||
| 28 | + | ) | |||
| 29 | + | ||||
| 30 | + | var protocols = []string{ | |||
| 31 | + | "NSAccessibility", | |||
| 32 | + | "NSApplicationDelegate", | |||
| 33 | + | } | |||
| 34 | + | ||||
| 35 | + | type ProtocolImpl struct { | |||
| 36 | + | Name string | |||
| 37 | + | ||||
| 38 | + | RequiredInstanceMethods []objc.MethodDescription | |||
| 39 | + | RequiredClassMethods []objc.MethodDescription | |||
| 40 | + | OptionalInstanceMethods []objc.MethodDescription | |||
| 41 | + | OptionalClassMethods []objc.MethodDescription | |||
| 42 | + | ||||
| 43 | + | AdoptedProtocols []*objc.Protocol | |||
| 44 | + | ||||
| 45 | + | RequiredInstanceProperties []objc.Property | |||
| 46 | + | RequiredClassProperties []objc.Property | |||
| 47 | + | OptionalInstanceProperties []objc.Property | |||
| 48 | + | OptionalClassProperties []objc.Property | |||
| 49 | + | } | |||
| 50 | + | ||||
| 51 | + | func readProtocols(names []string) (imps []ProtocolImpl, err error) { | |||
| 52 | + | for _, name := range names { | |||
| 53 | + | p := objc.GetProtocol(name) | |||
| 54 | + | if p == nil { | |||
| 55 | + | return nil, fmt.Errorf("protocol '%s' does not exist", name) | |||
| 56 | + | } | |||
| 57 | + | imp := ProtocolImpl{} | |||
| 58 | + | imp.Name = name | |||
| 59 | + | imp.RequiredInstanceMethods = p.CopyMethodDescriptionList(true, true) | |||
| 60 | + | imp.RequiredClassMethods = p.CopyMethodDescriptionList(true, false) | |||
| 61 | + | imp.OptionalInstanceMethods = p.CopyMethodDescriptionList(false, true) | |||
| 62 | + | imp.OptionalClassMethods = p.CopyMethodDescriptionList(false, false) | |||
| 63 | + | ||||
| 64 | + | imp.AdoptedProtocols = p.CopyProtocolList() | |||
| 65 | + | ||||
| 66 | + | imp.RequiredInstanceProperties = p.CopyPropertyList(true, true) | |||
| 67 | + | imp.RequiredClassProperties = p.CopyPropertyList(true, false) | |||
| 68 | + | imp.OptionalInstanceProperties = p.CopyPropertyList(false, true) | |||
| 69 | + | imp.OptionalClassProperties = p.CopyPropertyList(false, false) | |||
| 70 | + | imps = append(imps, imp) | |||
| 71 | + | } | |||
| 72 | + | return imps, nil | |||
| 73 | + | } | |||
| 74 | + | ||||
| 75 | + | const templ = `// Code generated by protocol-dumper; DO NOT EDIT. | |||
| 76 | + | ||||
| 77 | + | package main | |||
| 78 | + | ||||
| 79 | + | import ( | |||
| 80 | + | "log" | |||
| 81 | + | ||||
| 82 | + | "github.com/ebitengine/purego/objc" | |||
| 83 | + | ) | |||
| 84 | + | ||||
| 85 | + | func init() { | |||
| 86 | + | var p *objc.Protocol | |||
| 87 | + | {{- range . }} | |||
| 88 | + | {{- $protocolName := .Name }} | |||
| 89 | + | if p = objc.AllocateProtocol("{{$protocolName}}"); p != nil { | |||
| 90 | + | {{- range .RequiredInstanceMethods }} | |||
| 91 | + | p.AddMethodDescription(objc.RegisterName("{{ .Name }}"), "{{ .Types }}", true, true) | |||
| 92 | + | {{- end }} | |||
| 93 | + | ||||
| 94 | + | {{- range .RequiredClassMethods }} | |||
| 95 | + | p.AddMethodDescription(objc.RegisterName("{{ .Name }}"), "{{ .Types }}", true, false) | |||
| 96 | + | {{- end }} | |||
| 97 | + | ||||
| 98 | + | {{- range .OptionalInstanceMethods }} | |||
| 99 | + | p.AddMethodDescription(objc.RegisterName("{{ .Name }}"), "{{ .Types }}", false, true) | |||
| 100 | + | {{- end }} | |||
| 101 | + | ||||
| 102 | + | {{- range .OptionalClassMethods }} | |||
| 103 | + | p.AddMethodDescription(objc.RegisterName("{{ .Name }}"), "{{ .Types }}", false, false) | |||
| 104 | + | {{- end }} | |||
| 105 | + | var adoptedProtocol *objc.Protocol | |||
| 106 | + | {{- range .AdoptedProtocols }} | |||
| 107 | + | adoptedProtocol = objc.GetProtocol("{{ .Name }}") | |||
| 108 | + | if adoptedProtocol == nil { | |||
| 109 | + | log.Fatalln("protocol '{{ .Name }}' does not exist") | |||
| 110 | + | } | |||
| 111 | + | p.AddProtocol(adoptedProtocol) | |||
| 112 | + | {{- end }} | |||
| 113 | + | ||||
| 114 | + | {{- range .RequiredInstanceProperties }} | |||
| 115 | + | p.AddProperty("{{ .Name }}", {{ attributeToStructString . }}, true, true) | |||
| 116 | + | {{- end }} | |||
| 117 | + | ||||
| 118 | + | ||||
| 119 | + | {{- range .RequiredClassProperties }} | |||
| 120 | + | p.AddProperty("{{ .Name }}", {{ attributeToStructString . }}, true, false) | |||
| 121 | + | {{- end }} | |||
| 122 | + | ||||
| 123 | + | {{- range .OptionalInstanceProperties }} | |||
| 124 | + | p.AddProperty("{{ .Name }}", {{ attributeToStructString . }}, false, true) | |||
| 125 | + | {{- end }} | |||
| 126 | + | ||||
| 127 | + | {{- range .OptionalClassProperties }} | |||
| 128 | + | p.AddProperty("{{ .Name }}", {{ attributeToStructString . }}, false, false) | |||
| 129 | + | {{- end }} | |||
| 130 | + | p.Register() | |||
| 131 | + | } // Finished protocol: {{$protocolName}} | |||
| 132 | + | {{- end }} | |||
| 133 | + | } | |||
| 134 | + | ` | |||
| 135 | + | ||||
| 136 | + | func printProtocols(impls []ProtocolImpl, w io.Writer) error { | |||
| 137 | + | tmpl, err := template.New("protocol.tmpl").Funcs(template.FuncMap{ | |||
| 138 | + | "attributeToStructString": attributeToStructString, | |||
| 139 | + | }).Parse(templ) | |||
| 140 | + | if err != nil { | |||
| 141 | + | return err | |||
| 142 | + | } | |||
| 143 | + | err = tmpl.Execute(w, impls) | |||
| 144 | + | if err != nil { | |||
| 145 | + | return err | |||
| 146 | + | } | |||
| 147 | + | return nil | |||
| 148 | + | } | |||
| 149 | + | ||||
| 150 | + | func attributeToStructString(p objc.Property) string { | |||
| 151 | + | attribs := strings.Split(p.Attributes(), ",") | |||
| 152 | + | var b strings.Builder | |||
| 153 | + | b.WriteString("[]objc.PropertyAttribute{") | |||
| 154 | + | for i, attrib := range attribs { | |||
| 155 | + | b.WriteString(fmt.Sprintf(`{Name: &[]byte("%s\x00")[0], Value: &[]byte(%s)[0]}`, string(attrib[0]), strconv.Quote(attrib[1:]+"\x00"))) | |||
| 156 | + | if i != len(attribs)-1 { | |||
| 157 | + | b.WriteString(", ") | |||
| 158 | + | } | |||
| 159 | + | } | |||
| 160 | + | b.WriteString("}") | |||
| 161 | + | return b.String() | |||
| 162 | + | } | |||
| 163 | + | ||||
| 164 | + | // This program prints out the Go code to generate the OBJC protocols list. | |||
| 165 | + | // To use it add the @protocol(name) to the C import section and then add it to the protocols list. | |||
| 166 | + | // You'll want to run it as amd64 and arm64 since the types are slightly different for each architecture. | |||
| 167 | + | func main() { | |||
| 168 | + | var imps []ProtocolImpl | |||
| 169 | + | var err error | |||
| 170 | + | if imps, err = readProtocols(protocols); err != nil { | |||
| 171 | + | panic(err) | |||
| 172 | + | } | |||
| 173 | + | if err = printProtocols(imps, os.Stdout); err != nil { | |||
| 174 | + | panic(err) | |||
| 175 | + | } | |||
| 176 | + | } | |||
M objc/objc_runtime_darwin.go +151 -25
| @@ -16,39 +16,52 @@ import ( | |||
| 16 | 16 | "unsafe" | |
| 17 | 17 | ||
| 18 | 18 | "github.com/ebitengine/purego" | |
| 19 | + | "github.com/ebitengine/purego/internal/strings" | |
| 19 | 20 | ) | |
| 20 | 21 | ||
| 21 | 22 | // TODO: support try/catch? | |
| 22 | 23 | // https://stackoverflow.com/questions/7062599/example-of-how-objective-cs-try-catch-implementation-is-executed-at-runtime | |
| 23 | 24 | var ( | |
| 24 | - | objc_msgSend_fn uintptr | |
| 25 | - | objc_msgSend_stret_fn uintptr | |
| 26 | - | objc_msgSend func(obj ID, cmd SEL, args ...any) ID | |
| 27 | - | objc_msgSendSuper2_fn uintptr | |
| 28 | - | objc_msgSendSuper2_stret_fn uintptr | |
| 29 | - | objc_msgSendSuper2 func(super *objc_super, cmd SEL, args ...any) ID | |
| 30 | - | objc_getClass func(name string) Class | |
| 31 | - | objc_getProtocol func(name string) *Protocol | |
| 32 | - | objc_allocateClassPair func(super Class, name string, extraBytes uintptr) Class | |
| 33 | - | objc_registerClassPair func(class Class) | |
| 34 | - | sel_registerName func(name string) SEL | |
| 35 | - | class_getSuperclass func(class Class) Class | |
| 36 | - | class_getInstanceVariable func(class Class, name string) Ivar | |
| 37 | - | class_getInstanceSize func(class Class) uintptr | |
| 38 | - | class_addMethod func(class Class, name SEL, imp IMP, types string) bool | |
| 39 | - | class_addIvar func(class Class, name string, size uintptr, alignment uint8, types string) bool | |
| 40 | - | class_addProtocol func(class Class, protocol *Protocol) bool | |
| 41 | - | ivar_getOffset func(ivar Ivar) uintptr | |
| 42 | - | ivar_getName func(ivar Ivar) string | |
| 43 | - | object_getClass func(obj ID) Class | |
| 44 | - | object_getIvar func(obj ID, ivar Ivar) ID | |
| 45 | - | object_setIvar func(obj ID, ivar Ivar, value ID) | |
| 46 | - | protocol_getName func(protocol *Protocol) string | |
| 47 | - | protocol_isEqual func(p *Protocol, p2 *Protocol) bool | |
| 25 | + | objc_msgSend_fn uintptr | |
| 26 | + | objc_msgSend_stret_fn uintptr | |
| 27 | + | objc_msgSend func(obj ID, cmd SEL, args ...any) ID | |
| 28 | + | objc_msgSendSuper2_fn uintptr | |
| 29 | + | objc_msgSendSuper2_stret_fn uintptr | |
| 30 | + | objc_msgSendSuper2 func(super *objc_super, cmd SEL, args ...any) ID | |
| 31 | + | objc_getClass func(name string) Class | |
| 32 | + | objc_getProtocol func(name string) *Protocol | |
| 33 | + | objc_allocateProtocol func(name string) *Protocol | |
| 34 | + | objc_registerProtocol func(protocol *Protocol) | |
| 35 | + | objc_allocateClassPair func(super Class, name string, extraBytes uintptr) Class | |
| 36 | + | objc_registerClassPair func(class Class) | |
| 37 | + | sel_registerName func(name string) SEL | |
| 38 | + | class_getSuperclass func(class Class) Class | |
| 39 | + | class_getInstanceVariable func(class Class, name string) Ivar | |
| 40 | + | class_getInstanceSize func(class Class) uintptr | |
| 41 | + | class_addMethod func(class Class, name SEL, imp IMP, types string) bool | |
| 42 | + | class_addIvar func(class Class, name string, size uintptr, alignment uint8, types string) bool | |
| 43 | + | class_addProtocol func(class Class, protocol *Protocol) bool | |
| 44 | + | ivar_getOffset func(ivar Ivar) uintptr | |
| 45 | + | ivar_getName func(ivar Ivar) string | |
| 46 | + | object_getClass func(obj ID) Class | |
| 47 | + | object_getIvar func(obj ID, ivar Ivar) ID | |
| 48 | + | object_setIvar func(obj ID, ivar Ivar, value ID) | |
| 49 | + | protocol_getName func(protocol *Protocol) string | |
| 50 | + | protocol_isEqual func(p *Protocol, p2 *Protocol) bool | |
| 51 | + | protocol_addMethodDescription func(p *Protocol, name SEL, types string, isRequiredMethod bool, isInstanceMethod bool) | |
| 52 | + | protocol_copyMethodDescriptionList func(p *Protocol, isRequiredMethod bool, isInstanceMethod bool, outCount *uint32) *MethodDescription | |
| 53 | + | protocol_copyProtocolList func(p *Protocol, outCount *uint32) **Protocol | |
| 54 | + | protocol_copyPropertyList2 func(p *Protocol, outCount *uint32, isRequiredProperty, isInstanceProperty bool) *Property | |
| 55 | + | protocol_addProtocol func(p *Protocol, p2 *Protocol) | |
| 56 | + | protocol_addProperty func(p *Protocol, name string, attributes []PropertyAttribute, attributeCount uint32, isRequiredProperty bool, isInstanceProperty bool) | |
| 57 | + | property_getName func(p Property) string | |
| 58 | + | property_getAttributes func(p Property) string | |
| 59 | + | ||
| 60 | + | free func(ptr unsafe.Pointer) | |
| 48 | 61 | ) | |
| 49 | 62 | ||
| 50 | 63 | func init() { | |
| 51 | - | objc, err := purego.Dlopen("/usr/lib/libobjc.A.dylib", purego.RTLD_GLOBAL) | |
| 64 | + | objc, err := purego.Dlopen("/usr/lib/libobjc.A.dylib", purego.RTLD_GLOBAL|purego.RTLD_NOW) | |
| 52 | 65 | if err != nil { | |
| 53 | 66 | panic(fmt.Errorf("objc: %w", err)) | |
| 54 | 67 | } | |
| @@ -75,6 +88,8 @@ func init() { | |||
| 75 | 88 | purego.RegisterLibFunc(&object_getClass, objc, "object_getClass") | |
| 76 | 89 | purego.RegisterLibFunc(&objc_getClass, objc, "objc_getClass") | |
| 77 | 90 | purego.RegisterLibFunc(&objc_getProtocol, objc, "objc_getProtocol") | |
| 91 | + | purego.RegisterLibFunc(&objc_allocateProtocol, objc, "objc_allocateProtocol") | |
| 92 | + | purego.RegisterLibFunc(&objc_registerProtocol, objc, "objc_registerProtocol") | |
| 78 | 93 | purego.RegisterLibFunc(&objc_allocateClassPair, objc, "objc_allocateClassPair") | |
| 79 | 94 | purego.RegisterLibFunc(&objc_registerClassPair, objc, "objc_registerClassPair") | |
| 80 | 95 | purego.RegisterLibFunc(&sel_registerName, objc, "sel_registerName") | |
| @@ -88,8 +103,17 @@ func init() { | |||
| 88 | 103 | purego.RegisterLibFunc(&ivar_getName, objc, "ivar_getName") | |
| 89 | 104 | purego.RegisterLibFunc(&protocol_getName, objc, "protocol_getName") | |
| 90 | 105 | purego.RegisterLibFunc(&protocol_isEqual, objc, "protocol_isEqual") | |
| 106 | + | purego.RegisterLibFunc(&protocol_addMethodDescription, objc, "protocol_addMethodDescription") | |
| 107 | + | purego.RegisterLibFunc(&protocol_copyMethodDescriptionList, objc, "protocol_copyMethodDescriptionList") | |
| 108 | + | purego.RegisterLibFunc(&protocol_copyProtocolList, objc, "protocol_copyProtocolList") | |
| 109 | + | purego.RegisterLibFunc(&protocol_addProtocol, objc, "protocol_addProtocol") | |
| 110 | + | purego.RegisterLibFunc(&protocol_addProperty, objc, "protocol_addProperty") | |
| 111 | + | purego.RegisterLibFunc(&protocol_copyPropertyList2, objc, "protocol_copyPropertyList2") | |
| 112 | + | purego.RegisterLibFunc(&property_getName, objc, "property_getName") | |
| 113 | + | purego.RegisterLibFunc(&property_getAttributes, objc, "property_getAttributes") | |
| 91 | 114 | purego.RegisterLibFunc(&object_getIvar, objc, "object_getIvar") | |
| 92 | 115 | purego.RegisterLibFunc(&object_setIvar, objc, "object_setIvar") | |
| 116 | + | purego.RegisterLibFunc(&free, purego.RTLD_DEFAULT, "free") | |
| 93 | 117 | } | |
| 94 | 118 | ||
| 95 | 119 | // ID is an opaque pointer to some Objective-C object | |
| @@ -531,6 +555,39 @@ func (i Ivar) Name() string { | |||
| 531 | 555 | return ivar_getName(i) | |
| 532 | 556 | } | |
| 533 | 557 | ||
| 558 | + | // MethodDescription holds the name and type definition of a method. | |
| 559 | + | type MethodDescription struct { | |
| 560 | + | name, types uintptr | |
| 561 | + | } | |
| 562 | + | ||
| 563 | + | // Name returns the name of this method. | |
| 564 | + | func (m MethodDescription) Name() string { | |
| 565 | + | return strings.GoString(m.name) | |
| 566 | + | } | |
| 567 | + | ||
| 568 | + | // Types returns the OBJC runtime encoded type description. | |
| 569 | + | func (m MethodDescription) Types() string { | |
| 570 | + | return strings.GoString(m.types) | |
| 571 | + | } | |
| 572 | + | ||
| 573 | + | // PropertyAttribute contains the null-terminated Name and Value pair of a Properties internal description. | |
| 574 | + | type PropertyAttribute struct { | |
| 575 | + | Name, Value *byte | |
| 576 | + | } | |
| 577 | + | ||
| 578 | + | // Property is an opaque type for Objective-C property metadata. | |
| 579 | + | type Property uintptr | |
| 580 | + | ||
| 581 | + | // Name returns the name of this property. | |
| 582 | + | func (p Property) Name() string { | |
| 583 | + | return property_getName(p) | |
| 584 | + | } | |
| 585 | + | ||
| 586 | + | // Attributes returns a comma separated list of PropertyAttribute | |
| 587 | + | func (p Property) Attributes() string { | |
| 588 | + | return property_getAttributes(p) | |
| 589 | + | } | |
| 590 | + | ||
| 534 | 591 | // Protocol is a type that declares methods that can be implemented by any class. | |
| 535 | 592 | type Protocol [0]func() | |
| 536 | 593 | ||
| @@ -539,11 +596,69 @@ func GetProtocol(name string) *Protocol { | |||
| 539 | 596 | return objc_getProtocol(name) | |
| 540 | 597 | } | |
| 541 | 598 | ||
| 599 | + | // AllocateProtocol creates a new protocol in the OBJC runtime or nil if the protocol already exists. | |
| 600 | + | func AllocateProtocol(name string) *Protocol { | |
| 601 | + | return objc_allocateProtocol(name) | |
| 602 | + | } | |
| 603 | + | ||
| 604 | + | // Register registers the protocol created using AllocateProtocol with the runtime. This must be done | |
| 605 | + | // before it is used anywhere and can only be called once. | |
| 606 | + | func (p *Protocol) Register() { | |
| 607 | + | objc_registerProtocol(p) | |
| 608 | + | } | |
| 609 | + | ||
| 610 | + | // CopyMethodDescriptionList returns a list of methods that this protocol has given it isRequiredMethod and isInstanceMethod. | |
| 611 | + | func (p *Protocol) CopyMethodDescriptionList(isRequiredMethod, isInstanceMethod bool) []MethodDescription { | |
| 612 | + | count := uint32(0) | |
| 613 | + | desc := protocol_copyMethodDescriptionList(p, isRequiredMethod, isInstanceMethod, &count) | |
| 614 | + | methods := clone(unsafe.Slice(desc, count)) | |
| 615 | + | free(unsafe.Pointer(desc)) | |
| 616 | + | return methods | |
| 617 | + | } | |
| 618 | + | ||
| 619 | + | // CopyProtocolList returns a list of the protocols that this protocol inherits from. | |
| 620 | + | func (p *Protocol) CopyProtocolList() []*Protocol { | |
| 621 | + | count := uint32(0) | |
| 622 | + | desc := protocol_copyProtocolList(p, &count) | |
| 623 | + | protocols := clone(unsafe.Slice(desc, count)) | |
| 624 | + | free(unsafe.Pointer(desc)) | |
| 625 | + | return protocols | |
| 626 | + | } | |
| 627 | + | ||
| 628 | + | // CopyPropertyList returns a list of properties that this protocol has given it isRequiredProperty and isInstanceProperty. | |
| 629 | + | func (p *Protocol) CopyPropertyList(isRequiredProperty, isInstanceProperty bool) []Property { | |
| 630 | + | count := uint32(0) | |
| 631 | + | desc := protocol_copyPropertyList2(p, &count, isRequiredProperty, isInstanceProperty) | |
| 632 | + | protocols := clone(unsafe.Slice(desc, count)) | |
| 633 | + | free(unsafe.Pointer(desc)) | |
| 634 | + | return protocols | |
| 635 | + | } | |
| 636 | + | ||
| 637 | + | // Name returns the name of this protocol. | |
| 638 | + | func (p *Protocol) Name() string { | |
| 639 | + | return protocol_getName(p) | |
| 640 | + | } | |
| 641 | + | ||
| 542 | 642 | // Equals return true if the two protocols are the same. | |
| 543 | 643 | func (p *Protocol) Equals(p2 *Protocol) bool { | |
| 544 | 644 | return protocol_isEqual(p, p2) | |
| 545 | 645 | } | |
| 546 | 646 | ||
| 647 | + | // AddMethodDescription adds a method to a protocol. This can only be called between AllocateProtocol and Protocol.Register. | |
| 648 | + | func (p *Protocol) AddMethodDescription(name SEL, types string, isRequiredMethod, isInstanceMethod bool) { | |
| 649 | + | protocol_addMethodDescription(p, name, types, isRequiredMethod, isInstanceMethod) | |
| 650 | + | } | |
| 651 | + | ||
| 652 | + | // AddProtocol marks the protocol as inheriting from another. This can only be called between AllocateProtocol and Protocol.Register. | |
| 653 | + | func (p *Protocol) AddProtocol(protocol *Protocol) { | |
| 654 | + | protocol_addProtocol(p, protocol) | |
| 655 | + | } | |
| 656 | + | ||
| 657 | + | // AddProperty adds a property to the protocol. This can only be called between AllocateProtocol and Protocol.Register. | |
| 658 | + | func (p *Protocol) AddProperty(name string, attributes []PropertyAttribute, isRequiredProperty, isInstanceProperty bool) { | |
| 659 | + | protocol_addProperty(p, name, attributes, uint32(len(attributes)), isRequiredProperty, isInstanceProperty) | |
| 660 | + | } | |
| 661 | + | ||
| 547 | 662 | // IMP is a function pointer that can be called by Objective-C code. | |
| 548 | 663 | type IMP uintptr | |
| 549 | 664 | ||
| @@ -568,3 +683,14 @@ func NewIMP(fn any) IMP { | |||
| 568 | 683 | } | |
| 569 | 684 | return IMP(purego.NewCallback(fn)) | |
| 570 | 685 | } | |
| 686 | + | ||
| 687 | + | // TODO: remove and use slices.Clone when minimum version for purego is 1.21 | |
| 688 | + | func clone[S ~[]E, E any](s S) S { | |
| 689 | + | // Preserve nilness in case it matters. | |
| 690 | + | if s == nil { | |
| 691 | + | return nil | |
| 692 | + | } | |
| 693 | + | // Avoid s[:0:0] as it leads to unwanted liveness when cloning a | |
| 694 | + | // zero-length slice of a large array; see https://go.dev/issue/68488. | |
| 695 | + | return append(S{}, s...) | |
| 696 | + | } | |
| @@ -16,39 +16,52 @@ import ( | |||||
| 16 | "unsafe" | 16 | "unsafe" | ||
| 17 | 17 | ||||
| 18 | "github.com/ebitengine/purego" | 18 | "github.com/ebitengine/purego" | ||
| 19 | + | "github.com/ebitengine/purego/internal/strings" | |||
| 19 | ) | 20 | ) | ||
| 20 | 21 | ||||
| 21 | // TODO: support try/catch? | 22 | // TODO: support try/catch? | ||
| 22 | // https://stackoverflow.com/questions/7062599/example-of-how-objective-cs-try-catch-implementation-is-executed-at-runtime | 23 | // https://stackoverflow.com/questions/7062599/example-of-how-objective-cs-try-catch-implementation-is-executed-at-runtime | ||
| 23 | var ( | 24 | var ( | ||
| 24 | - | objc_msgSend_fn uintptr | 25 | + | objc_msgSend_fn uintptr |
| 25 | - | objc_msgSend_stret_fn uintptr | 26 | + | objc_msgSend_stret_fn uintptr |
| 26 | - | objc_msgSend func(obj ID, cmd SEL, args ...any) ID | 27 | + | objc_msgSend func(obj ID, cmd SEL, args ...any) ID |
| 27 | - | objc_msgSendSuper2_fn uintptr | 28 | + | objc_msgSendSuper2_fn uintptr |
| 28 | - | objc_msgSendSuper2_stret_fn uintptr | 29 | + | objc_msgSendSuper2_stret_fn uintptr |
| 29 | - | objc_msgSendSuper2 func(super *objc_super, cmd SEL, args ...any) ID | 30 | + | objc_msgSendSuper2 func(super *objc_super, cmd SEL, args ...any) ID |
| 30 | - | objc_getClass func(name string) Class | 31 | + | objc_getClass func(name string) Class |
| 31 | - | objc_getProtocol func(name string) *Protocol | 32 | + | objc_getProtocol func(name string) *Protocol |
| 32 | - | objc_allocateClassPair func(super Class, name string, extraBytes uintptr) Class | 33 | + | objc_allocateProtocol func(name string) *Protocol |
| 33 | - | objc_registerClassPair func(class Class) | 34 | + | objc_registerProtocol func(protocol *Protocol) |
| 34 | - | sel_registerName func(name string) SEL | 35 | + | objc_allocateClassPair func(super Class, name string, extraBytes uintptr) Class |
| 35 | - | class_getSuperclass func(class Class) Class | 36 | + | objc_registerClassPair func(class Class) |
| 36 | - | class_getInstanceVariable func(class Class, name string) Ivar | 37 | + | sel_registerName func(name string) SEL |
| 37 | - | class_getInstanceSize func(class Class) uintptr | 38 | + | class_getSuperclass func(class Class) Class |
| 38 | - | class_addMethod func(class Class, name SEL, imp IMP, types string) bool | 39 | + | class_getInstanceVariable func(class Class, name string) Ivar |
| 39 | - | class_addIvar func(class Class, name string, size uintptr, alignment uint8, types string) bool | 40 | + | class_getInstanceSize func(class Class) uintptr |
| 40 | - | class_addProtocol func(class Class, protocol *Protocol) bool | 41 | + | class_addMethod func(class Class, name SEL, imp IMP, types string) bool |
| 41 | - | ivar_getOffset func(ivar Ivar) uintptr | 42 | + | class_addIvar func(class Class, name string, size uintptr, alignment uint8, types string) bool |
| 42 | - | ivar_getName func(ivar Ivar) string | 43 | + | class_addProtocol func(class Class, protocol *Protocol) bool |
| 43 | - | object_getClass func(obj ID) Class | 44 | + | ivar_getOffset func(ivar Ivar) uintptr |
| 44 | - | object_getIvar func(obj ID, ivar Ivar) ID | 45 | + | ivar_getName func(ivar Ivar) string |
| 45 | - | object_setIvar func(obj ID, ivar Ivar, value ID) | 46 | + | object_getClass func(obj ID) Class |
| 46 | - | protocol_getName func(protocol *Protocol) string | 47 | + | object_getIvar func(obj ID, ivar Ivar) ID |
| 47 | - | protocol_isEqual func(p *Protocol, p2 *Protocol) bool | 48 | + | object_setIvar func(obj ID, ivar Ivar, value ID) |
| 49 | + | protocol_getName func(protocol *Protocol) string | |||
| 50 | + | protocol_isEqual func(p *Protocol, p2 *Protocol) bool | |||
| 51 | + | protocol_addMethodDescription func(p *Protocol, name SEL, types string, isRequiredMethod bool, isInstanceMethod bool) | |||
| 52 | + | protocol_copyMethodDescriptionList func(p *Protocol, isRequiredMethod bool, isInstanceMethod bool, outCount *uint32) *MethodDescription | |||
| 53 | + | protocol_copyProtocolList func(p *Protocol, outCount *uint32) **Protocol | |||
| 54 | + | protocol_copyPropertyList2 func(p *Protocol, outCount *uint32, isRequiredProperty, isInstanceProperty bool) *Property | |||
| 55 | + | protocol_addProtocol func(p *Protocol, p2 *Protocol) | |||
| 56 | + | protocol_addProperty func(p *Protocol, name string, attributes []PropertyAttribute, attributeCount uint32, isRequiredProperty bool, isInstanceProperty bool) | |||
| 57 | + | property_getName func(p Property) string | |||
| 58 | + | property_getAttributes func(p Property) string | |||
| 59 | + | ||||
| 60 | + | free func(ptr unsafe.Pointer) | |||
| 48 | ) | 61 | ) | ||
| 49 | 62 | ||||
| 50 | func init() { | 63 | func init() { | ||
| 51 | - | objc, err := purego.Dlopen("/usr/lib/libobjc.A.dylib", purego.RTLD_GLOBAL) | 64 | + | objc, err := purego.Dlopen("/usr/lib/libobjc.A.dylib", purego.RTLD_GLOBAL|purego.RTLD_NOW) |
| 52 | if err != nil { | 65 | if err != nil { | ||
| 53 | panic(fmt.Errorf("objc: %w", err)) | 66 | panic(fmt.Errorf("objc: %w", err)) | ||
| 54 | } | 67 | } | ||
| @@ -75,6 +88,8 @@ func init() { | |||||
| 75 | purego.RegisterLibFunc(&object_getClass, objc, "object_getClass") | 88 | purego.RegisterLibFunc(&object_getClass, objc, "object_getClass") | ||
| 76 | purego.RegisterLibFunc(&objc_getClass, objc, "objc_getClass") | 89 | purego.RegisterLibFunc(&objc_getClass, objc, "objc_getClass") | ||
| 77 | purego.RegisterLibFunc(&objc_getProtocol, objc, "objc_getProtocol") | 90 | purego.RegisterLibFunc(&objc_getProtocol, objc, "objc_getProtocol") | ||
| 91 | + | purego.RegisterLibFunc(&objc_allocateProtocol, objc, "objc_allocateProtocol") | |||
| 92 | + | purego.RegisterLibFunc(&objc_registerProtocol, objc, "objc_registerProtocol") | |||
| 78 | purego.RegisterLibFunc(&objc_allocateClassPair, objc, "objc_allocateClassPair") | 93 | purego.RegisterLibFunc(&objc_allocateClassPair, objc, "objc_allocateClassPair") | ||
| 79 | purego.RegisterLibFunc(&objc_registerClassPair, objc, "objc_registerClassPair") | 94 | purego.RegisterLibFunc(&objc_registerClassPair, objc, "objc_registerClassPair") | ||
| 80 | purego.RegisterLibFunc(&sel_registerName, objc, "sel_registerName") | 95 | purego.RegisterLibFunc(&sel_registerName, objc, "sel_registerName") | ||
| @@ -88,8 +103,17 @@ func init() { | |||||
| 88 | purego.RegisterLibFunc(&ivar_getName, objc, "ivar_getName") | 103 | purego.RegisterLibFunc(&ivar_getName, objc, "ivar_getName") | ||
| 89 | purego.RegisterLibFunc(&protocol_getName, objc, "protocol_getName") | 104 | purego.RegisterLibFunc(&protocol_getName, objc, "protocol_getName") | ||
| 90 | purego.RegisterLibFunc(&protocol_isEqual, objc, "protocol_isEqual") | 105 | purego.RegisterLibFunc(&protocol_isEqual, objc, "protocol_isEqual") | ||
| 106 | + | purego.RegisterLibFunc(&protocol_addMethodDescription, objc, "protocol_addMethodDescription") | |||
| 107 | + | purego.RegisterLibFunc(&protocol_copyMethodDescriptionList, objc, "protocol_copyMethodDescriptionList") | |||
| 108 | + | purego.RegisterLibFunc(&protocol_copyProtocolList, objc, "protocol_copyProtocolList") | |||
| 109 | + | purego.RegisterLibFunc(&protocol_addProtocol, objc, "protocol_addProtocol") | |||
| 110 | + | purego.RegisterLibFunc(&protocol_addProperty, objc, "protocol_addProperty") | |||
| 111 | + | purego.RegisterLibFunc(&protocol_copyPropertyList2, objc, "protocol_copyPropertyList2") | |||
| 112 | + | purego.RegisterLibFunc(&property_getName, objc, "property_getName") | |||
| 113 | + | purego.RegisterLibFunc(&property_getAttributes, objc, "property_getAttributes") | |||
| 91 | purego.RegisterLibFunc(&object_getIvar, objc, "object_getIvar") | 114 | purego.RegisterLibFunc(&object_getIvar, objc, "object_getIvar") | ||
| 92 | purego.RegisterLibFunc(&object_setIvar, objc, "object_setIvar") | 115 | purego.RegisterLibFunc(&object_setIvar, objc, "object_setIvar") | ||
| 116 | + | purego.RegisterLibFunc(&free, purego.RTLD_DEFAULT, "free") | |||
| 93 | } | 117 | } | ||
| 94 | 118 | ||||
| 95 | // ID is an opaque pointer to some Objective-C object | 119 | // ID is an opaque pointer to some Objective-C object | ||
| @@ -531,6 +555,39 @@ func (i Ivar) Name() string { | |||||
| 531 | return ivar_getName(i) | 555 | return ivar_getName(i) | ||
| 532 | } | 556 | } | ||
| 533 | 557 | ||||
| 558 | + | // MethodDescription holds the name and type definition of a method. | |||
| 559 | + | type MethodDescription struct { | |||
| 560 | + | name, types uintptr | |||
| 561 | + | } | |||
| 562 | + | ||||
| 563 | + | // Name returns the name of this method. | |||
| 564 | + | func (m MethodDescription) Name() string { | |||
| 565 | + | return strings.GoString(m.name) | |||
| 566 | + | } | |||
| 567 | + | ||||
| 568 | + | // Types returns the OBJC runtime encoded type description. | |||
| 569 | + | func (m MethodDescription) Types() string { | |||
| 570 | + | return strings.GoString(m.types) | |||
| 571 | + | } | |||
| 572 | + | ||||
| 573 | + | // PropertyAttribute contains the null-terminated Name and Value pair of a Properties internal description. | |||
| 574 | + | type PropertyAttribute struct { | |||
| 575 | + | Name, Value *byte | |||
| 576 | + | } | |||
| 577 | + | ||||
| 578 | + | // Property is an opaque type for Objective-C property metadata. | |||
| 579 | + | type Property uintptr | |||
| 580 | + | ||||
| 581 | + | // Name returns the name of this property. | |||
| 582 | + | func (p Property) Name() string { | |||
| 583 | + | return property_getName(p) | |||
| 584 | + | } | |||
| 585 | + | ||||
| 586 | + | // Attributes returns a comma separated list of PropertyAttribute | |||
| 587 | + | func (p Property) Attributes() string { | |||
| 588 | + | return property_getAttributes(p) | |||
| 589 | + | } | |||
| 590 | + | ||||
| 534 | // Protocol is a type that declares methods that can be implemented by any class. | 591 | // Protocol is a type that declares methods that can be implemented by any class. | ||
| 535 | type Protocol [0]func() | 592 | type Protocol [0]func() | ||
| 536 | 593 | ||||
| @@ -539,11 +596,69 @@ func GetProtocol(name string) *Protocol { | |||||
| 539 | return objc_getProtocol(name) | 596 | return objc_getProtocol(name) | ||
| 540 | } | 597 | } | ||
| 541 | 598 | ||||
| 599 | + | // AllocateProtocol creates a new protocol in the OBJC runtime or nil if the protocol already exists. | |||
| 600 | + | func AllocateProtocol(name string) *Protocol { | |||
| 601 | + | return objc_allocateProtocol(name) | |||
| 602 | + | } | |||
| 603 | + | ||||
| 604 | + | // Register registers the protocol created using AllocateProtocol with the runtime. This must be done | |||
| 605 | + | // before it is used anywhere and can only be called once. | |||
| 606 | + | func (p *Protocol) Register() { | |||
| 607 | + | objc_registerProtocol(p) | |||
| 608 | + | } | |||
| 609 | + | ||||
| 610 | + | // CopyMethodDescriptionList returns a list of methods that this protocol has given it isRequiredMethod and isInstanceMethod. | |||
| 611 | + | func (p *Protocol) CopyMethodDescriptionList(isRequiredMethod, isInstanceMethod bool) []MethodDescription { | |||
| 612 | + | count := uint32(0) | |||
| 613 | + | desc := protocol_copyMethodDescriptionList(p, isRequiredMethod, isInstanceMethod, &count) | |||
| 614 | + | methods := clone(unsafe.Slice(desc, count)) | |||
| 615 | + | free(unsafe.Pointer(desc)) | |||
| 616 | + | return methods | |||
| 617 | + | } | |||
| 618 | + | ||||
| 619 | + | // CopyProtocolList returns a list of the protocols that this protocol inherits from. | |||
| 620 | + | func (p *Protocol) CopyProtocolList() []*Protocol { | |||
| 621 | + | count := uint32(0) | |||
| 622 | + | desc := protocol_copyProtocolList(p, &count) | |||
| 623 | + | protocols := clone(unsafe.Slice(desc, count)) | |||
| 624 | + | free(unsafe.Pointer(desc)) | |||
| 625 | + | return protocols | |||
| 626 | + | } | |||
| 627 | + | ||||
| 628 | + | // CopyPropertyList returns a list of properties that this protocol has given it isRequiredProperty and isInstanceProperty. | |||
| 629 | + | func (p *Protocol) CopyPropertyList(isRequiredProperty, isInstanceProperty bool) []Property { | |||
| 630 | + | count := uint32(0) | |||
| 631 | + | desc := protocol_copyPropertyList2(p, &count, isRequiredProperty, isInstanceProperty) | |||
| 632 | + | protocols := clone(unsafe.Slice(desc, count)) | |||
| 633 | + | free(unsafe.Pointer(desc)) | |||
| 634 | + | return protocols | |||
| 635 | + | } | |||
| 636 | + | ||||
| 637 | + | // Name returns the name of this protocol. | |||
| 638 | + | func (p *Protocol) Name() string { | |||
| 639 | + | return protocol_getName(p) | |||
| 640 | + | } | |||
| 641 | + | ||||
| 542 | // Equals return true if the two protocols are the same. | 642 | // Equals return true if the two protocols are the same. | ||
| 543 | func (p *Protocol) Equals(p2 *Protocol) bool { | 643 | func (p *Protocol) Equals(p2 *Protocol) bool { | ||
| 544 | return protocol_isEqual(p, p2) | 644 | return protocol_isEqual(p, p2) | ||
| 545 | } | 645 | } | ||
| 546 | 646 | ||||
| 647 | + | // AddMethodDescription adds a method to a protocol. This can only be called between AllocateProtocol and Protocol.Register. | |||
| 648 | + | func (p *Protocol) AddMethodDescription(name SEL, types string, isRequiredMethod, isInstanceMethod bool) { | |||
| 649 | + | protocol_addMethodDescription(p, name, types, isRequiredMethod, isInstanceMethod) | |||
| 650 | + | } | |||
| 651 | + | ||||
| 652 | + | // AddProtocol marks the protocol as inheriting from another. This can only be called between AllocateProtocol and Protocol.Register. | |||
| 653 | + | func (p *Protocol) AddProtocol(protocol *Protocol) { | |||
| 654 | + | protocol_addProtocol(p, protocol) | |||
| 655 | + | } | |||
| 656 | + | ||||
| 657 | + | // AddProperty adds a property to the protocol. This can only be called between AllocateProtocol and Protocol.Register. | |||
| 658 | + | func (p *Protocol) AddProperty(name string, attributes []PropertyAttribute, isRequiredProperty, isInstanceProperty bool) { | |||
| 659 | + | protocol_addProperty(p, name, attributes, uint32(len(attributes)), isRequiredProperty, isInstanceProperty) | |||
| 660 | + | } | |||
| 661 | + | ||||
| 547 | // IMP is a function pointer that can be called by Objective-C code. | 662 | // IMP is a function pointer that can be called by Objective-C code. | ||
| 548 | type IMP uintptr | 663 | type IMP uintptr | ||
| 549 | 664 | ||||
| @@ -568,3 +683,14 @@ func NewIMP(fn any) IMP { | |||||
| 568 | } | 683 | } | ||
| 569 | return IMP(purego.NewCallback(fn)) | 684 | return IMP(purego.NewCallback(fn)) | ||
| 570 | } | 685 | } | ||
| 686 | + | ||||
| 687 | + | // TODO: remove and use slices.Clone when minimum version for purego is 1.21 | |||
| 688 | + | func clone[S ~[]E, E any](s S) S { | |||
| 689 | + | // Preserve nilness in case it matters. | |||
| 690 | + | if s == nil { | |||
| 691 | + | return nil | |||
| 692 | + | } | |||
| 693 | + | // Avoid s[:0:0] as it leads to unwanted liveness when cloning a | |||
| 694 | + | // zero-length slice of a large array; see https://go.dev/issue/68488. | |||
| 695 | + | return append(S{}, s...) | |||
| 696 | + | } | |||
M objc/objc_runtime_darwin_test.go +36 -0
| @@ -5,6 +5,7 @@ package objc_test | |||
| 5 | 5 | ||
| 6 | 6 | import ( | |
| 7 | 7 | "fmt" | |
| 8 | + | "log" | |
| 8 | 9 | "reflect" | |
| 9 | 10 | "testing" | |
| 10 | 11 | ||
| @@ -208,3 +209,38 @@ func ExampleSendSuper() { | |||
| 208 | 209 | fmt.Println(res) | |
| 209 | 210 | // Output: 16 | |
| 210 | 211 | } | |
| 212 | + | ||
| 213 | + | func ExampleAllocateProtocol() { | |
| 214 | + | var p *objc.Protocol | |
| 215 | + | if p = objc.AllocateProtocol("MyCustomProtocol"); p != nil { | |
| 216 | + | p.AddMethodDescription(objc.RegisterName("isFoo"), "B16@0:8", true, true) | |
| 217 | + | var adoptedProtocol *objc.Protocol | |
| 218 | + | adoptedProtocol = objc.GetProtocol("NSObject") | |
| 219 | + | if adoptedProtocol == nil { | |
| 220 | + | log.Fatalln("protocol 'NSObject' does not exist") | |
| 221 | + | } | |
| 222 | + | p.AddProtocol(adoptedProtocol) | |
| 223 | + | p.AddProperty("accessibilityElement", []objc.PropertyAttribute{ | |
| 224 | + | {Name: &[]byte("T\x00")[0], Value: &[]byte("B\x00")[0]}, | |
| 225 | + | {Name: &[]byte("G\x00")[0], Value: &[]byte("isBar\x00")[0]}, | |
| 226 | + | }, true, true) | |
| 227 | + | p.Register() | |
| 228 | + | } | |
| 229 | + | ||
| 230 | + | p = objc.GetProtocol("MyCustomProtocol") | |
| 231 | + | ||
| 232 | + | for _, protocol := range p.CopyProtocolList() { | |
| 233 | + | fmt.Println(protocol.Name()) | |
| 234 | + | } | |
| 235 | + | for _, property := range p.CopyPropertyList(true, true) { | |
| 236 | + | fmt.Println(property.Name(), property.Attributes()) | |
| 237 | + | } | |
| 238 | + | for _, method := range p.CopyMethodDescriptionList(true, true) { | |
| 239 | + | fmt.Println(method.Name(), method.Types()) | |
| 240 | + | } | |
| 241 | + | ||
| 242 | + | // Output: | |
| 243 | + | // NSObject | |
| 244 | + | // accessibilityElement TB,GisBar | |
| 245 | + | // isFoo B16@0:8 | |
| 246 | + | } | |
| @@ -5,6 +5,7 @@ package objc_test | |||||
| 5 | 5 | ||||
| 6 | import ( | 6 | import ( | ||
| 7 | "fmt" | 7 | "fmt" | ||
| 8 | + | "log" | |||
| 8 | "reflect" | 9 | "reflect" | ||
| 9 | "testing" | 10 | "testing" | ||
| 10 | 11 | ||||
| @@ -208,3 +209,38 @@ func ExampleSendSuper() { | |||||
| 208 | fmt.Println(res) | 209 | fmt.Println(res) | ||
| 209 | // Output: 16 | 210 | // Output: 16 | ||
| 210 | } | 211 | } | ||
| 212 | + | ||||
| 213 | + | func ExampleAllocateProtocol() { | |||
| 214 | + | var p *objc.Protocol | |||
| 215 | + | if p = objc.AllocateProtocol("MyCustomProtocol"); p != nil { | |||
| 216 | + | p.AddMethodDescription(objc.RegisterName("isFoo"), "B16@0:8", true, true) | |||
| 217 | + | var adoptedProtocol *objc.Protocol | |||
| 218 | + | adoptedProtocol = objc.GetProtocol("NSObject") | |||
| 219 | + | if adoptedProtocol == nil { | |||
| 220 | + | log.Fatalln("protocol 'NSObject' does not exist") | |||
| 221 | + | } | |||
| 222 | + | p.AddProtocol(adoptedProtocol) | |||
| 223 | + | p.AddProperty("accessibilityElement", []objc.PropertyAttribute{ | |||
| 224 | + | {Name: &[]byte("T\x00")[0], Value: &[]byte("B\x00")[0]}, | |||
| 225 | + | {Name: &[]byte("G\x00")[0], Value: &[]byte("isBar\x00")[0]}, | |||
| 226 | + | }, true, true) | |||
| 227 | + | p.Register() | |||
| 228 | + | } | |||
| 229 | + | ||||
| 230 | + | p = objc.GetProtocol("MyCustomProtocol") | |||
| 231 | + | ||||
| 232 | + | for _, protocol := range p.CopyProtocolList() { | |||
| 233 | + | fmt.Println(protocol.Name()) | |||
| 234 | + | } | |||
| 235 | + | for _, property := range p.CopyPropertyList(true, true) { | |||
| 236 | + | fmt.Println(property.Name(), property.Attributes()) | |||
| 237 | + | } | |||
| 238 | + | for _, method := range p.CopyMethodDescriptionList(true, true) { | |||
| 239 | + | fmt.Println(method.Name(), method.Types()) | |||
| 240 | + | } | |||
| 241 | + | ||||
| 242 | + | // Output: | |||
| 243 | + | // NSObject | |||
| 244 | + | // accessibilityElement TB,GisBar | |||
| 245 | + | // isFoo B16@0:8 | |||
| 246 | + | } | |||