github.com/ebitengine/purego@v0.8.0-alpha.2.0.20240512170805-6cd12240d332/objc/objc_runtime_darwin.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors 3 4 // Package objc is a low-level pure Go objective-c runtime. This package is easy to use incorrectly, so it is best 5 // to use a wrapper that provides the functionality you need in a safer way. 6 package objc 7 8 import ( 9 "errors" 10 "fmt" 11 "math" 12 "reflect" 13 "regexp" 14 "runtime" 15 "unicode" 16 "unsafe" 17 18 "github.com/ebitengine/purego" 19 ) 20 21 // TODO: support try/catch? 22 // https://stackoverflow.com/questions/7062599/example-of-how-objective-cs-try-catch-implementation-is-executed-at-runtime 23 var ( 24 objc_msgSend_fn uintptr 25 objc_msgSend_stret_fn uintptr 26 objc_msgSend func(obj ID, cmd SEL, args ...interface{}) ID 27 objc_msgSendSuper2_fn uintptr 28 objc_msgSendSuper2_stret_fn uintptr 29 objc_msgSendSuper2 func(super *objc_super, cmd SEL, args ...interface{}) 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 48 ) 49 50 func init() { 51 objc, err := purego.Dlopen("/usr/lib/libobjc.A.dylib", purego.RTLD_GLOBAL) 52 if err != nil { 53 panic(fmt.Errorf("objc: %w", err)) 54 } 55 objc_msgSend_fn, err = purego.Dlsym(objc, "objc_msgSend") 56 if err != nil { 57 panic(fmt.Errorf("objc: %w", err)) 58 } 59 if runtime.GOARCH == "amd64" { 60 objc_msgSend_stret_fn, err = purego.Dlsym(objc, "objc_msgSend_stret") 61 if err != nil { 62 panic(fmt.Errorf("objc: %w", err)) 63 } 64 objc_msgSendSuper2_stret_fn, err = purego.Dlsym(objc, "objc_msgSendSuper2_stret") 65 if err != nil { 66 panic(fmt.Errorf("objc: %w", err)) 67 } 68 } 69 purego.RegisterFunc(&objc_msgSend, objc_msgSend_fn) 70 objc_msgSendSuper2_fn, err = purego.Dlsym(objc, "objc_msgSendSuper2") 71 if err != nil { 72 panic(fmt.Errorf("objc: %w", err)) 73 } 74 purego.RegisterFunc(&objc_msgSendSuper2, objc_msgSendSuper2_fn) 75 purego.RegisterLibFunc(&object_getClass, objc, "object_getClass") 76 purego.RegisterLibFunc(&objc_getClass, objc, "objc_getClass") 77 purego.RegisterLibFunc(&objc_getProtocol, objc, "objc_getProtocol") 78 purego.RegisterLibFunc(&objc_allocateClassPair, objc, "objc_allocateClassPair") 79 purego.RegisterLibFunc(&objc_registerClassPair, objc, "objc_registerClassPair") 80 purego.RegisterLibFunc(&sel_registerName, objc, "sel_registerName") 81 purego.RegisterLibFunc(&class_getSuperclass, objc, "class_getSuperclass") 82 purego.RegisterLibFunc(&class_getInstanceVariable, objc, "class_getInstanceVariable") 83 purego.RegisterLibFunc(&class_addMethod, objc, "class_addMethod") 84 purego.RegisterLibFunc(&class_addIvar, objc, "class_addIvar") 85 purego.RegisterLibFunc(&class_addProtocol, objc, "class_addProtocol") 86 purego.RegisterLibFunc(&class_getInstanceSize, objc, "class_getInstanceSize") 87 purego.RegisterLibFunc(&ivar_getOffset, objc, "ivar_getOffset") 88 purego.RegisterLibFunc(&ivar_getName, objc, "ivar_getName") 89 purego.RegisterLibFunc(&protocol_getName, objc, "protocol_getName") 90 purego.RegisterLibFunc(&protocol_isEqual, objc, "protocol_isEqual") 91 purego.RegisterLibFunc(&object_getIvar, objc, "object_getIvar") 92 purego.RegisterLibFunc(&object_setIvar, objc, "object_setIvar") 93 } 94 95 // ID is an opaque pointer to some Objective-C object 96 type ID uintptr 97 98 // Class returns the class of the object. 99 func (id ID) Class() Class { 100 return object_getClass(id) 101 } 102 103 // Send is a convenience method for sending messages to objects. This function takes a SEL 104 // instead of a string since RegisterName grabs the global Objective-C lock. It is best to cache the result 105 // of RegisterName. 106 func (id ID) Send(sel SEL, args ...interface{}) ID { 107 return objc_msgSend(id, sel, args...) 108 } 109 110 // GetIvar reads the value of an instance variable in an object. 111 func (id ID) GetIvar(ivar Ivar) ID { 112 return object_getIvar(id, ivar) 113 } 114 115 // SetIvar sets the value of an instance variable in an object. 116 func (id ID) SetIvar(ivar Ivar, value ID) { 117 object_setIvar(id, ivar, value) 118 } 119 120 // keep in sync with func.go 121 const maxRegAllocStructSize = 16 122 123 // Send is a convenience method for sending messages to objects that can return any type. 124 // This function takes a SEL instead of a string since RegisterName grabs the global Objective-C lock. 125 // It is best to cache the result of RegisterName. 126 func Send[T any](id ID, sel SEL, args ...any) T { 127 var fn func(id ID, sel SEL, args ...any) T 128 var zero T 129 if runtime.GOARCH == "amd64" && 130 reflect.ValueOf(zero).Kind() == reflect.Struct && 131 reflect.ValueOf(zero).Type().Size() > maxRegAllocStructSize { 132 purego.RegisterFunc(&fn, objc_msgSend_stret_fn) 133 } else { 134 purego.RegisterFunc(&fn, objc_msgSend_fn) 135 } 136 return fn(id, sel, args...) 137 } 138 139 // objc_super data structure is generated by the Objective-C compiler when it encounters the super keyword 140 // as the receiver of a message. It specifies the class definition of the particular superclass that should 141 // be messaged. 142 type objc_super struct { 143 receiver ID 144 superClass Class 145 } 146 147 // SendSuper is a convenience method for sending message to object's super. This function takes a SEL 148 // instead of a string since RegisterName grabs the global Objective-C lock. It is best to cache the result 149 // of RegisterName. 150 func (id ID) SendSuper(sel SEL, args ...interface{}) ID { 151 super := &objc_super{ 152 receiver: id, 153 superClass: id.Class(), 154 } 155 return objc_msgSendSuper2(super, sel, args...) 156 } 157 158 // SendSuper is a convenience method for sending message to object's super that can return any type. 159 // This function takes a SEL instead of a string since RegisterName grabs the global Objective-C lock. 160 // It is best to cache the result of RegisterName. 161 func SendSuper[T any](id ID, sel SEL, args ...any) T { 162 super := &objc_super{ 163 receiver: id, 164 superClass: id.Class(), 165 } 166 var fn func(objcSuper *objc_super, sel SEL, args ...any) T 167 var zero T 168 if runtime.GOARCH == "amd64" && 169 reflect.ValueOf(zero).Kind() == reflect.Struct && 170 reflect.ValueOf(zero).Type().Size() > maxRegAllocStructSize { 171 purego.RegisterFunc(&fn, objc_msgSendSuper2_stret_fn) 172 } else { 173 purego.RegisterFunc(&fn, objc_msgSendSuper2_fn) 174 } 175 return fn(super, sel, args...) 176 } 177 178 // SEL is an opaque type that represents a method selector 179 type SEL uintptr 180 181 // RegisterName registers a method with the Objective-C runtime system, maps the method name to a selector, 182 // and returns the selector value. This function grabs the global Objective-c lock. It is best the cache the 183 // result of this function. 184 func RegisterName(name string) SEL { 185 return sel_registerName(name) 186 } 187 188 // Class is an opaque type that represents an Objective-C class. 189 type Class uintptr 190 191 // GetClass returns the Class object for the named class, or nil if the class is not registered with the Objective-C runtime. 192 func GetClass(name string) Class { 193 return objc_getClass(name) 194 } 195 196 // MethodDef represents the Go function and the selector that ObjC uses to access that function. 197 type MethodDef struct { 198 Cmd SEL 199 Fn any 200 } 201 202 // IvarAttrib is the attribute that an ivar has. It affects if and which methods are automatically 203 // generated when creating a class with RegisterClass. See [Apple Docs] for an understanding of these attributes. 204 // The fields are still accessible using objc.GetIvar and objc.SetIvar regardless of the value of IvarAttrib. 205 // 206 // Take for example this Objective-C code: 207 // 208 // @property (readwrite) float value; 209 // 210 // In Go, the functions can be accessed as followed: 211 // 212 // var value = purego.Send[float32](id, purego.RegisterName("value")) 213 // id.Send(purego.RegisterName("setValue:"), 3.46) 214 // 215 // [Apple Docs]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocProperties.html 216 type IvarAttrib int 217 218 const ( 219 ReadOnly IvarAttrib = 1 << iota 220 ReadWrite 221 ) 222 223 // FieldDef is a definition of a field to add to an Objective-C class. 224 // The name of the field is what will be used to access it through the Ivar. If the type is bool 225 // the name cannot start with `is` since a getter will be generated with the name `isBoolName`. 226 // The name also cannot contain any spaces. 227 // The type is the Go equivalent type of the Ivar. 228 // Attribute determines if a getter and or setter method is generated for this field. 229 type FieldDef struct { 230 Name string 231 Type reflect.Type 232 Attribute IvarAttrib 233 } 234 235 // ivarRegex checks to make sure the Ivar is correctly formatted 236 var ivarRegex = regexp.MustCompile("[a-z_][a-zA-Z0-9_]*") 237 238 // RegisterClass takes the name of the class to create, the superclass, a list of protocols this class 239 // implements, a list of fields this class has and a list of methods. It returns the created class or an error 240 // describing what went wrong. 241 func RegisterClass(name string, superClass Class, protocols []*Protocol, ivars []FieldDef, methods []MethodDef) (Class, error) { 242 class := objc_allocateClassPair(superClass, name, 0) 243 if class == 0 { 244 return 0, fmt.Errorf("objc: failed to create class with name '%s'", name) 245 } 246 // Add Protocols 247 for _, p := range protocols { 248 if !class.AddProtocol(p) { 249 return 0, fmt.Errorf("objc: couldn't add Protocol %s", protocol_getName(p)) 250 } 251 } 252 // Add exported methods based on the selectors returned from ClassDef(string) SEL 253 for idx, def := range methods { 254 imp, err := func() (imp IMP, err error) { 255 defer func() { 256 if r := recover(); r != nil { 257 err = fmt.Errorf("objc: failed to create IMP: %s", r) 258 } 259 }() 260 return NewIMP(def.Fn), nil 261 }() 262 if err != nil { 263 return 0, fmt.Errorf("objc: couldn't add Method at index %d: %w", idx, err) 264 } 265 encoding, err := encodeFunc(def.Fn) 266 if err != nil { 267 return 0, fmt.Errorf("objc: couldn't add Method at index %d: %w", idx, err) 268 } 269 if !class.AddMethod(def.Cmd, imp, encoding) { 270 return 0, fmt.Errorf("objc: couldn't add Method at index %d", idx) 271 } 272 } 273 // Add Ivars 274 for _, instVar := range ivars { 275 ivar := instVar 276 if !ivarRegex.MatchString(ivar.Name) { 277 return 0, fmt.Errorf("objc: Ivar must start with a lowercase letter and only contain ASCII letters and numbers: '%s'", ivar.Name) 278 } 279 size := ivar.Type.Size() 280 alignment := uint8(math.Log2(float64(ivar.Type.Align()))) 281 enc, err := encodeType(ivar.Type, false) 282 if err != nil { 283 return 0, fmt.Errorf("objc: couldn't add Ivar %s: %w", ivar.Name, err) 284 } 285 if !class_addIvar(class, ivar.Name, size, alignment, enc) { 286 return 0, fmt.Errorf("objc: couldn't add Ivar %s", ivar.Name) 287 } 288 offset := class.InstanceVariable(ivar.Name).Offset() 289 switch ivar.Attribute { 290 case ReadWrite: 291 ty := reflect.FuncOf( 292 []reflect.Type{ 293 reflect.TypeOf(ID(0)), reflect.TypeOf(SEL(0)), ivar.Type, 294 }, 295 nil, false, 296 ) 297 var encoding string 298 if encoding, err = encodeFunc(reflect.New(ty).Elem().Interface()); err != nil { 299 return 0, fmt.Errorf("objc: failed to create read method for '%s': %w", ivar.Name, err) 300 } 301 val := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) { 302 // on entry the first and second arguments are ID and SEL followed by the value 303 if len(args) != 3 { 304 panic(fmt.Sprintf("objc: incorrect number of args. expected 3 got %d", len(args))) 305 } 306 // The following reflect code does the equivalent of this: 307 // 308 // ((*struct { 309 // Padding [offset]byte 310 // Value int 311 // })(unsafe.Pointer(args[0].Interface().(ID)))).v = 123 312 // 313 // However, since the type of the variable is unknown reflection is used to actually assign the value 314 id := args[0].Interface().(ID) 315 ptr := *(*unsafe.Pointer)(unsafe.Pointer(&id)) // circumvent go vet 316 reflect.NewAt(ivar.Type, unsafe.Add(ptr, offset)).Elem().Set(args[2]) 317 return nil 318 }).Interface() 319 // this code only works for ascii but that shouldn't be a problem 320 selector := "set" + string(unicode.ToUpper(rune(ivar.Name[0]))) + ivar.Name[1:] + ":\x00" 321 class.AddMethod(RegisterName(selector), NewIMP(val), encoding) 322 fallthrough // also implement the read method 323 case ReadOnly: 324 ty := reflect.FuncOf( 325 []reflect.Type{ 326 reflect.TypeOf(ID(0)), reflect.TypeOf(SEL(0)), 327 }, 328 []reflect.Type{ivar.Type}, false, 329 ) 330 var encoding string 331 if encoding, err = encodeFunc(reflect.New(ty).Elem().Interface()); err != nil { 332 return 0, fmt.Errorf("objc: failed to create read method for '%s': %w", ivar.Name, err) 333 } 334 val := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) { 335 // on entry the first and second arguments are ID and SEL 336 if len(args) != 2 { 337 panic(fmt.Sprintf("objc: incorrect number of args. expected 2 got %d", len(args))) 338 } 339 id := args[0].Interface().(ID) 340 ptr := *(*unsafe.Pointer)(unsafe.Pointer(&id)) // circumvent go vet 341 // the variable is located at an offset from the id 342 return []reflect.Value{reflect.NewAt(ivar.Type, unsafe.Add(ptr, offset)).Elem()} 343 }).Interface() 344 if ivar.Type.Kind() == reflect.Bool { 345 // this code only works for ascii but that shouldn't be a problem 346 ivar.Name = "is" + string(unicode.ToUpper(rune(ivar.Name[0]))) + ivar.Name[1:] 347 } 348 class.AddMethod(RegisterName(ivar.Name), NewIMP(val), encoding) 349 default: 350 return 0, fmt.Errorf("objc: unknown Ivar Attribute (%d)", ivar.Attribute) 351 } 352 } 353 objc_registerClassPair(class) 354 return class, nil 355 } 356 357 const ( 358 encId = "@" 359 encClass = "#" 360 encSelector = ":" 361 encChar = "c" 362 encUChar = "C" 363 encShort = "s" 364 encUShort = "S" 365 encInt = "i" 366 encUInt = "I" 367 encLong = "l" 368 encULong = "L" 369 encFloat = "f" 370 encDouble = "d" 371 encBool = "B" 372 encVoid = "v" 373 encPtr = "^" 374 encCharPtr = "*" 375 encStructBegin = "{" 376 encStructEnd = "}" 377 encUnsafePtr = "^v" 378 ) 379 380 // encodeType returns a string representing a type as if it was given to @encode(typ) 381 // Source: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100 382 func encodeType(typ reflect.Type, insidePtr bool) (string, error) { 383 switch typ { 384 case reflect.TypeOf(Class(0)): 385 return encClass, nil 386 case reflect.TypeOf(ID(0)): 387 return encId, nil 388 case reflect.TypeOf(SEL(0)): 389 return encSelector, nil 390 } 391 392 kind := typ.Kind() 393 switch kind { 394 case reflect.Bool: 395 return encBool, nil 396 case reflect.Int: 397 return encLong, nil 398 case reflect.Int8: 399 return encChar, nil 400 case reflect.Int16: 401 return encShort, nil 402 case reflect.Int32: 403 return encInt, nil 404 case reflect.Int64: 405 return encULong, nil 406 case reflect.Uint: 407 return encULong, nil 408 case reflect.Uint8: 409 return encUChar, nil 410 case reflect.Uint16: 411 return encUShort, nil 412 case reflect.Uint32: 413 return encUInt, nil 414 case reflect.Uint64: 415 return encULong, nil 416 case reflect.Uintptr: 417 return encPtr, nil 418 case reflect.Float32: 419 return encFloat, nil 420 case reflect.Float64: 421 return encDouble, nil 422 case reflect.Ptr: 423 enc, err := encodeType(typ.Elem(), true) 424 return encPtr + enc, err 425 case reflect.Struct: 426 if insidePtr { 427 return encStructBegin + typ.Name() + encStructEnd, nil 428 } 429 encoding := encStructBegin 430 encoding += typ.Name() 431 encoding += "=" 432 for i := 0; i < typ.NumField(); i++ { 433 f := typ.Field(i) 434 tmp, err := encodeType(f.Type, false) 435 if err != nil { 436 return "", err 437 } 438 encoding += tmp 439 } 440 encoding = encStructEnd 441 return encoding, nil 442 case reflect.UnsafePointer: 443 return encUnsafePtr, nil 444 case reflect.String: 445 return encCharPtr, nil 446 } 447 448 return "", errors.New(fmt.Sprintf("unhandled/invalid kind %v typed %v", kind, typ)) 449 } 450 451 // encodeFunc returns a functions type as if it was given to @encode(fn) 452 func encodeFunc(fn interface{}) (string, error) { 453 typ := reflect.TypeOf(fn) 454 if typ.Kind() != reflect.Func { 455 return "", errors.New("not a func") 456 } 457 458 encoding := "" 459 switch typ.NumOut() { 460 case 0: 461 encoding += encVoid 462 case 1: 463 tmp, err := encodeType(typ.Out(0), false) 464 if err != nil { 465 return "", err 466 } 467 encoding += tmp 468 default: 469 return "", errors.New("too many output parameters") 470 } 471 472 if typ.NumIn() < 2 { 473 return "", errors.New("func doesn't take ID and SEL as its first two parameters") 474 } 475 476 encoding += encId 477 478 for i := 1; i < typ.NumIn(); i++ { 479 tmp, err := encodeType(typ.In(i), false) 480 if err != nil { 481 return "", err 482 } 483 encoding += tmp 484 } 485 return encoding, nil 486 } 487 488 // SuperClass returns the superclass of a class. 489 // You should usually use NSObject‘s superclass method instead of this function. 490 func (c Class) SuperClass() Class { 491 return class_getSuperclass(c) 492 } 493 494 // AddMethod adds a new method to a class with a given name and implementation. 495 // The types argument is a string containing the mapping of parameters and return type. 496 // Since the function must take at least two arguments—self and _cmd, the second and third 497 // characters must be “@:” (the first character is the return type). 498 func (c Class) AddMethod(name SEL, imp IMP, types string) bool { 499 return class_addMethod(c, name, imp, types) 500 } 501 502 // AddProtocol adds a protocol to a class. 503 // Returns true if the protocol was added successfully, otherwise false (for example, 504 // the class already conforms to that protocol). 505 func (c Class) AddProtocol(protocol *Protocol) bool { 506 return class_addProtocol(c, protocol) 507 } 508 509 // InstanceSize returns the size in bytes of instances of the class or 0 if cls is nil 510 func (c Class) InstanceSize() uintptr { 511 return class_getInstanceSize(c) 512 } 513 514 // InstanceVariable returns an Ivar data structure containing information about the instance variable specified by name. 515 func (c Class) InstanceVariable(name string) Ivar { 516 return class_getInstanceVariable(c, name) 517 } 518 519 // Ivar an opaque type that represents an instance variable. 520 type Ivar uintptr 521 522 // Offset returns the offset of an instance variable that can be used to assign and read the Ivar's value. 523 // 524 // For instance variables of type ID or other object types, call Ivar and SetIvar instead 525 // of using this offset to access the instance variable data directly. 526 func (i Ivar) Offset() uintptr { 527 return ivar_getOffset(i) 528 } 529 530 func (i Ivar) Name() string { 531 return ivar_getName(i) 532 } 533 534 // Protocol is a type that declares methods that can be implemented by any class. 535 type Protocol [0]func() 536 537 // GetProtocol returns the protocol for the given name or nil if there is no protocol by that name. 538 func GetProtocol(name string) *Protocol { 539 return objc_getProtocol(name) 540 } 541 542 // Equals return true if the two protocols are the same. 543 func (p *Protocol) Equals(p2 *Protocol) bool { 544 return protocol_isEqual(p, p2) 545 } 546 547 // IMP is a function pointer that can be called by Objective-C code. 548 type IMP uintptr 549 550 // NewIMP takes a Go function that takes (ID, SEL) as its first two arguments. 551 // It returns an IMP function pointer that can be called by Objective-C code. 552 // The function panics if an error occurs. 553 // The function pointer is never deallocated. 554 func NewIMP(fn interface{}) IMP { 555 ty := reflect.TypeOf(fn) 556 if ty.Kind() != reflect.Func { 557 panic("objc: not a function") 558 } 559 // IMP is stricter than a normal callback 560 // id (*IMP)(id, SEL, ...) 561 switch { 562 case ty.NumIn() < 2: 563 fallthrough 564 case ty.In(0) != reflect.TypeOf(ID(0)): 565 fallthrough 566 case ty.In(1) != reflect.TypeOf(SEL(0)): 567 panic("objc: NewIMP must take a (id, SEL) as its first two arguments; got " + ty.String()) 568 } 569 return IMP(purego.NewCallback(fn)) 570 }