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