all: struct return fixes (#221)
On amd64, there was a crash when calling objc.Send with a struct return type. This was due to calling the wrong objc message send function. On arm64, if a struct return contained structs that only contained floats it would expect it to be in R8 instead of the expected float registers Closes #223
- commit
- ff2c2cce0d0b43e3ed9743fcd3408cbd9187f835
- author
- TotallyGamerJet · · 2024-04-04 02:43 UTC
- committer
- Hajime Hoshi · · 2024-04-04 02:52 UTC
- parent
- 4889c1a
- signature
- unsigned
- refs
- tag: v0.7.1
6 files · +145 -38
M func.go +23 -8
| @@ -250,7 +250,8 @@ func RegisterFunc(fptr interface{}, cfn uintptr) { | |||
| 250 | 250 | keepAlive = append(keepAlive, val) | |
| 251 | 251 | addInt(val.Pointer()) | |
| 252 | 252 | } else if runtime.GOARCH == "arm64" && outType.Size() > maxRegAllocStructSize { | |
| 253 | - | if !isAllSameFloat(outType) || outType.NumField() > 4 { | |
| 253 | + | isAllFloats, numFields := isAllSameFloat(outType) | |
| 254 | + | if !isAllFloats || numFields > 4 { | |
| 254 | 255 | val := reflect.New(outType) | |
| 255 | 256 | keepAlive = append(keepAlive, val) | |
| 256 | 257 | syscall.arm64_r8 = val.Pointer() | |
| @@ -351,20 +352,34 @@ func RegisterFunc(fptr interface{}, cfn uintptr) { | |||
| 351 | 352 | // maxRegAllocStructSize is the biggest a struct can be while still fitting in registers. | |
| 352 | 353 | // if it is bigger than this than enough space must be allocated on the heap and then passed into | |
| 353 | 354 | // the function as the first parameter on amd64 or in R8 on arm64. | |
| 355 | + | // | |
| 356 | + | // If you change this make sure to update it in objc_runtime_darwin.go | |
| 354 | 357 | const maxRegAllocStructSize = 16 | |
| 355 | 358 | ||
| 356 | - | func isAllSameFloat(ty reflect.Type) bool { | |
| 357 | - | first := ty.Field(0).Type.Kind() | |
| 359 | + | func isAllSameFloat(ty reflect.Type) (allFloats bool, numFields int) { | |
| 360 | + | allFloats = true | |
| 361 | + | root := ty.Field(0).Type | |
| 362 | + | for root.Kind() == reflect.Struct { | |
| 363 | + | root = root.Field(0).Type | |
| 364 | + | } | |
| 365 | + | first := root.Kind() | |
| 358 | 366 | if first != reflect.Float32 && first != reflect.Float64 { | |
| 359 | - | return false | |
| 367 | + | allFloats = false | |
| 360 | 368 | } | |
| 361 | 369 | for i := 0; i < ty.NumField(); i++ { | |
| 362 | - | f := ty.Field(i) | |
| 363 | - | if f.Type.Kind() != first { | |
| 364 | - | return false | |
| 370 | + | f := ty.Field(i).Type | |
| 371 | + | if f.Kind() == reflect.Struct { | |
| 372 | + | var structNumFields int | |
| 373 | + | allFloats, structNumFields = isAllSameFloat(f) | |
| 374 | + | numFields += structNumFields | |
| 375 | + | continue | |
| 376 | + | } | |
| 377 | + | numFields++ | |
| 378 | + | if f.Kind() != first { | |
| 379 | + | allFloats = false | |
| 365 | 380 | } | |
| 366 | 381 | } | |
| 367 | - | return true | |
| 382 | + | return allFloats, numFields | |
| 368 | 383 | } | |
| 369 | 384 | ||
| 370 | 385 | func checkStructFieldsSupported(ty reflect.Type) { | |
| @@ -250,7 +250,8 @@ func RegisterFunc(fptr interface{}, cfn uintptr) { | |||||
| 250 | keepAlive = append(keepAlive, val) | 250 | keepAlive = append(keepAlive, val) | ||
| 251 | addInt(val.Pointer()) | 251 | addInt(val.Pointer()) | ||
| 252 | } else if runtime.GOARCH == "arm64" && outType.Size() > maxRegAllocStructSize { | 252 | } else if runtime.GOARCH == "arm64" && outType.Size() > maxRegAllocStructSize { | ||
| 253 | - | if !isAllSameFloat(outType) || outType.NumField() > 4 { | 253 | + | isAllFloats, numFields := isAllSameFloat(outType) |
| 254 | + | if !isAllFloats || numFields > 4 { | |||
| 254 | val := reflect.New(outType) | 255 | val := reflect.New(outType) | ||
| 255 | keepAlive = append(keepAlive, val) | 256 | keepAlive = append(keepAlive, val) | ||
| 256 | syscall.arm64_r8 = val.Pointer() | 257 | syscall.arm64_r8 = val.Pointer() | ||
| @@ -351,20 +352,34 @@ func RegisterFunc(fptr interface{}, cfn uintptr) { | |||||
| 351 | // maxRegAllocStructSize is the biggest a struct can be while still fitting in registers. | 352 | // maxRegAllocStructSize is the biggest a struct can be while still fitting in registers. | ||
| 352 | // if it is bigger than this than enough space must be allocated on the heap and then passed into | 353 | // if it is bigger than this than enough space must be allocated on the heap and then passed into | ||
| 353 | // the function as the first parameter on amd64 or in R8 on arm64. | 354 | // the function as the first parameter on amd64 or in R8 on arm64. | ||
| 355 | + | // | |||
| 356 | + | // If you change this make sure to update it in objc_runtime_darwin.go | |||
| 354 | const maxRegAllocStructSize = 16 | 357 | const maxRegAllocStructSize = 16 | ||
| 355 | 358 | ||||
| 356 | - | func isAllSameFloat(ty reflect.Type) bool { | 359 | + | func isAllSameFloat(ty reflect.Type) (allFloats bool, numFields int) { |
| 357 | - | first := ty.Field(0).Type.Kind() | 360 | + | allFloats = true |
| 361 | + | root := ty.Field(0).Type | |||
| 362 | + | for root.Kind() == reflect.Struct { | |||
| 363 | + | root = root.Field(0).Type | |||
| 364 | + | } | |||
| 365 | + | first := root.Kind() | |||
| 358 | if first != reflect.Float32 && first != reflect.Float64 { | 366 | if first != reflect.Float32 && first != reflect.Float64 { | ||
| 359 | - | return false | 367 | + | allFloats = false |
| 360 | } | 368 | } | ||
| 361 | for i := 0; i < ty.NumField(); i++ { | 369 | for i := 0; i < ty.NumField(); i++ { | ||
| 362 | - | f := ty.Field(i) | 370 | + | f := ty.Field(i).Type |
| 363 | - | if f.Type.Kind() != first { | 371 | + | if f.Kind() == reflect.Struct { |
| 364 | - | return false | 372 | + | var structNumFields int |
| 373 | + | allFloats, structNumFields = isAllSameFloat(f) | |||
| 374 | + | numFields += structNumFields | |||
| 375 | + | continue | |||
| 376 | + | } | |||
| 377 | + | numFields++ | |||
| 378 | + | if f.Kind() != first { | |||
| 379 | + | allFloats = false | |||
| 365 | } | 380 | } | ||
| 366 | } | 381 | } | ||
| 367 | - | return true | 382 | + | return allFloats, numFields |
| 368 | } | 383 | } | ||
| 369 | 384 | ||||
| 370 | func checkStructFieldsSupported(ty reflect.Type) { | 385 | func checkStructFieldsSupported(ty reflect.Type) { | ||
M objc/objc_runtime_darwin.go +54 -24
| @@ -11,6 +11,7 @@ import ( | |||
| 11 | 11 | "math" | |
| 12 | 12 | "reflect" | |
| 13 | 13 | "regexp" | |
| 14 | + | "runtime" | |
| 14 | 15 | "unicode" | |
| 15 | 16 | "unsafe" | |
| 16 | 17 | ||
| @@ -20,28 +21,30 @@ import ( | |||
| 20 | 21 | // TODO: support try/catch? | |
| 21 | 22 | // https://stackoverflow.com/questions/7062599/example-of-how-objective-cs-try-catch-implementation-is-executed-at-runtime | |
| 22 | 23 | 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 | |
| 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 | |
| 45 | 48 | ) | |
| 46 | 49 | ||
| 47 | 50 | func init() { | |
| @@ -53,6 +56,16 @@ func init() { | |||
| 53 | 56 | if err != nil { | |
| 54 | 57 | panic(fmt.Errorf("objc: %w", err)) | |
| 55 | 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 | + | } | |
| 56 | 69 | purego.RegisterFunc(&objc_msgSend, objc_msgSend_fn) | |
| 57 | 70 | objc_msgSendSuper2_fn, err = purego.Dlsym(objc, "objc_msgSendSuper2") | |
| 58 | 71 | if err != nil { | |
| @@ -104,12 +117,22 @@ func (id ID) SetIvar(ivar Ivar, value ID) { | |||
| 104 | 117 | object_setIvar(id, ivar, value) | |
| 105 | 118 | } | |
| 106 | 119 | ||
| 120 | + | // keep in sync with func.go | |
| 121 | + | const maxRegAllocStructSize = 16 | |
| 122 | + | ||
| 107 | 123 | // Send is a convenience method for sending messages to objects that can return any type. | |
| 108 | 124 | // This function takes a SEL instead of a string since RegisterName grabs the global Objective-C lock. | |
| 109 | 125 | // It is best to cache the result of RegisterName. | |
| 110 | 126 | func Send[T any](id ID, sel SEL, args ...any) T { | |
| 111 | 127 | var fn func(id ID, sel SEL, args ...any) T | |
| 112 | - | purego.RegisterFunc(&fn, objc_msgSend_fn) | |
| 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 | + | } | |
| 113 | 136 | return fn(id, sel, args...) | |
| 114 | 137 | } | |
| 115 | 138 | ||
| @@ -141,7 +164,14 @@ func SendSuper[T any](id ID, sel SEL, args ...any) T { | |||
| 141 | 164 | superClass: id.Class(), | |
| 142 | 165 | } | |
| 143 | 166 | var fn func(objcSuper *objc_super, sel SEL, args ...any) T | |
| 144 | - | purego.RegisterFunc(&fn, objc_msgSendSuper2_fn) | |
| 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 | + | } | |
| 145 | 175 | return fn(super, sel, args...) | |
| 146 | 176 | } | |
| 147 | 177 | ||
| @@ -11,6 +11,7 @@ import ( | |||||
| 11 | "math" | 11 | "math" | ||
| 12 | "reflect" | 12 | "reflect" | ||
| 13 | "regexp" | 13 | "regexp" | ||
| 14 | + | "runtime" | |||
| 14 | "unicode" | 15 | "unicode" | ||
| 15 | "unsafe" | 16 | "unsafe" | ||
| 16 | 17 | ||||
| @@ -20,28 +21,30 @@ import ( | |||||
| 20 | // TODO: support try/catch? | 21 | // TODO: support try/catch? | ||
| 21 | // https://stackoverflow.com/questions/7062599/example-of-how-objective-cs-try-catch-implementation-is-executed-at-runtime | 22 | // https://stackoverflow.com/questions/7062599/example-of-how-objective-cs-try-catch-implementation-is-executed-at-runtime | ||
| 22 | var ( | 23 | var ( | ||
| 23 | - | objc_msgSend_fn uintptr | 24 | + | objc_msgSend_fn uintptr |
| 24 | - | objc_msgSend func(obj ID, cmd SEL, args ...interface{}) ID | 25 | + | objc_msgSend_stret_fn uintptr |
| 25 | - | objc_msgSendSuper2_fn uintptr | 26 | + | objc_msgSend func(obj ID, cmd SEL, args ...interface{}) ID |
| 26 | - | objc_msgSendSuper2 func(super *objc_super, cmd SEL, args ...interface{}) ID | 27 | + | objc_msgSendSuper2_fn uintptr |
| 27 | - | objc_getClass func(name string) Class | 28 | + | objc_msgSendSuper2_stret_fn uintptr |
| 28 | - | objc_getProtocol func(name string) *Protocol | 29 | + | objc_msgSendSuper2 func(super *objc_super, cmd SEL, args ...interface{}) ID |
| 29 | - | objc_allocateClassPair func(super Class, name string, extraBytes uintptr) Class | 30 | + | objc_getClass func(name string) Class |
| 30 | - | objc_registerClassPair func(class Class) | 31 | + | objc_getProtocol func(name string) *Protocol |
| 31 | - | sel_registerName func(name string) SEL | 32 | + | objc_allocateClassPair func(super Class, name string, extraBytes uintptr) Class |
| 32 | - | class_getSuperclass func(class Class) Class | 33 | + | objc_registerClassPair func(class Class) |
| 33 | - | class_getInstanceVariable func(class Class, name string) Ivar | 34 | + | sel_registerName func(name string) SEL |
| 34 | - | class_getInstanceSize func(class Class) uintptr | 35 | + | class_getSuperclass func(class Class) Class |
| 35 | - | class_addMethod func(class Class, name SEL, imp IMP, types string) bool | 36 | + | class_getInstanceVariable func(class Class, name string) Ivar |
| 36 | - | class_addIvar func(class Class, name string, size uintptr, alignment uint8, types string) bool | 37 | + | class_getInstanceSize func(class Class) uintptr |
| 37 | - | class_addProtocol func(class Class, protocol *Protocol) bool | 38 | + | class_addMethod func(class Class, name SEL, imp IMP, types string) bool |
| 38 | - | ivar_getOffset func(ivar Ivar) uintptr | 39 | + | class_addIvar func(class Class, name string, size uintptr, alignment uint8, types string) bool |
| 39 | - | ivar_getName func(ivar Ivar) string | 40 | + | class_addProtocol func(class Class, protocol *Protocol) bool |
| 40 | - | object_getClass func(obj ID) Class | 41 | + | ivar_getOffset func(ivar Ivar) uintptr |
| 41 | - | object_getIvar func(obj ID, ivar Ivar) ID | 42 | + | ivar_getName func(ivar Ivar) string |
| 42 | - | object_setIvar func(obj ID, ivar Ivar, value ID) | 43 | + | object_getClass func(obj ID) Class |
| 43 | - | protocol_getName func(protocol *Protocol) string | 44 | + | object_getIvar func(obj ID, ivar Ivar) ID |
| 44 | - | protocol_isEqual func(p *Protocol, p2 *Protocol) bool | 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 | |||
| 45 | ) | 48 | ) | ||
| 46 | 49 | ||||
| 47 | func init() { | 50 | func init() { | ||
| @@ -53,6 +56,16 @@ func init() { | |||||
| 53 | if err != nil { | 56 | if err != nil { | ||
| 54 | panic(fmt.Errorf("objc: %w", err)) | 57 | panic(fmt.Errorf("objc: %w", err)) | ||
| 55 | } | 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 | + | } | |||
| 56 | purego.RegisterFunc(&objc_msgSend, objc_msgSend_fn) | 69 | purego.RegisterFunc(&objc_msgSend, objc_msgSend_fn) | ||
| 57 | objc_msgSendSuper2_fn, err = purego.Dlsym(objc, "objc_msgSendSuper2") | 70 | objc_msgSendSuper2_fn, err = purego.Dlsym(objc, "objc_msgSendSuper2") | ||
| 58 | if err != nil { | 71 | if err != nil { | ||
| @@ -104,12 +117,22 @@ func (id ID) SetIvar(ivar Ivar, value ID) { | |||||
| 104 | object_setIvar(id, ivar, value) | 117 | object_setIvar(id, ivar, value) | ||
| 105 | } | 118 | } | ||
| 106 | 119 | ||||
| 120 | + | // keep in sync with func.go | |||
| 121 | + | const maxRegAllocStructSize = 16 | |||
| 122 | + | ||||
| 107 | // Send is a convenience method for sending messages to objects that can return any type. | 123 | // 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. | 124 | // 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. | 125 | // It is best to cache the result of RegisterName. | ||
| 110 | func Send[T any](id ID, sel SEL, args ...any) T { | 126 | func Send[T any](id ID, sel SEL, args ...any) T { | ||
| 111 | var fn func(id ID, sel SEL, args ...any) T | 127 | var fn func(id ID, sel SEL, args ...any) T | ||
| 112 | - | purego.RegisterFunc(&fn, objc_msgSend_fn) | 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 | + | } | |||
| 113 | return fn(id, sel, args...) | 136 | return fn(id, sel, args...) | ||
| 114 | } | 137 | } | ||
| 115 | 138 | ||||
| @@ -141,7 +164,14 @@ func SendSuper[T any](id ID, sel SEL, args ...any) T { | |||||
| 141 | superClass: id.Class(), | 164 | superClass: id.Class(), | ||
| 142 | } | 165 | } | ||
| 143 | var fn func(objcSuper *objc_super, sel SEL, args ...any) T | 166 | var fn func(objcSuper *objc_super, sel SEL, args ...any) T | ||
| 144 | - | purego.RegisterFunc(&fn, objc_msgSendSuper2_fn) | 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 | + | } | |||
| 145 | return fn(super, sel, args...) | 175 | return fn(super, sel, args...) | ||
| 146 | } | 176 | } | ||
| 147 | 177 | ||||
M objc/objc_runtime_darwin_test.go +36 -0
| @@ -117,3 +117,39 @@ func TestSend(t *testing.T) { | |||
| 117 | 117 | t.Failed() | |
| 118 | 118 | } | |
| 119 | 119 | } | |
| 120 | + | ||
| 121 | + | func ExampleSend() { | |
| 122 | + | type NSRange struct { | |
| 123 | + | Location, Range uint | |
| 124 | + | } | |
| 125 | + | class_NSString := objc.GetClass("NSString") | |
| 126 | + | sel_stringWithUTF8String := objc.RegisterName("stringWithUTF8String:") | |
| 127 | + | ||
| 128 | + | fullString := objc.ID(class_NSString).Send(sel_stringWithUTF8String, "Hello, World!\x00") | |
| 129 | + | subString := objc.ID(class_NSString).Send(sel_stringWithUTF8String, "lo, Wor\x00") | |
| 130 | + | ||
| 131 | + | r := objc.Send[NSRange](fullString, objc.RegisterName("rangeOfString:"), subString) | |
| 132 | + | ||
| 133 | + | fmt.Println(r) | |
| 134 | + | ||
| 135 | + | // Output: {3 7} | |
| 136 | + | } | |
| 137 | + | ||
| 138 | + | func ExampleSendSuper() { | |
| 139 | + | super := objc.AllocateClassPair(objc.GetClass("NSObject"), "SuperObject2", 0) | |
| 140 | + | super.AddMethod(objc.RegisterName("doSomething"), objc.NewIMP(func(self objc.ID, _cmd objc.SEL) int { | |
| 141 | + | return 16 | |
| 142 | + | }), "i@:") | |
| 143 | + | super.Register() | |
| 144 | + | ||
| 145 | + | child := objc.AllocateClassPair(super, "ChildObject2", 0) | |
| 146 | + | child.AddMethod(objc.RegisterName("doSomething"), objc.NewIMP(func(self objc.ID, _cmd objc.SEL) int { | |
| 147 | + | return 24 | |
| 148 | + | }), "i@:") | |
| 149 | + | child.Register() | |
| 150 | + | ||
| 151 | + | res := objc.SendSuper[int](objc.ID(child).Send(objc.RegisterName("new")), objc.RegisterName("doSomething")) | |
| 152 | + | ||
| 153 | + | fmt.Println(res) | |
| 154 | + | // Output: 16 | |
| 155 | + | } | |
| @@ -117,3 +117,39 @@ func TestSend(t *testing.T) { | |||||
| 117 | t.Failed() | 117 | t.Failed() | ||
| 118 | } | 118 | } | ||
| 119 | } | 119 | } | ||
| 120 | + | ||||
| 121 | + | func ExampleSend() { | |||
| 122 | + | type NSRange struct { | |||
| 123 | + | Location, Range uint | |||
| 124 | + | } | |||
| 125 | + | class_NSString := objc.GetClass("NSString") | |||
| 126 | + | sel_stringWithUTF8String := objc.RegisterName("stringWithUTF8String:") | |||
| 127 | + | ||||
| 128 | + | fullString := objc.ID(class_NSString).Send(sel_stringWithUTF8String, "Hello, World!\x00") | |||
| 129 | + | subString := objc.ID(class_NSString).Send(sel_stringWithUTF8String, "lo, Wor\x00") | |||
| 130 | + | ||||
| 131 | + | r := objc.Send[NSRange](fullString, objc.RegisterName("rangeOfString:"), subString) | |||
| 132 | + | ||||
| 133 | + | fmt.Println(r) | |||
| 134 | + | ||||
| 135 | + | // Output: {3 7} | |||
| 136 | + | } | |||
| 137 | + | ||||
| 138 | + | func ExampleSendSuper() { | |||
| 139 | + | super := objc.AllocateClassPair(objc.GetClass("NSObject"), "SuperObject2", 0) | |||
| 140 | + | super.AddMethod(objc.RegisterName("doSomething"), objc.NewIMP(func(self objc.ID, _cmd objc.SEL) int { | |||
| 141 | + | return 16 | |||
| 142 | + | }), "i@:") | |||
| 143 | + | super.Register() | |||
| 144 | + | ||||
| 145 | + | child := objc.AllocateClassPair(super, "ChildObject2", 0) | |||
| 146 | + | child.AddMethod(objc.RegisterName("doSomething"), objc.NewIMP(func(self objc.ID, _cmd objc.SEL) int { | |||
| 147 | + | return 24 | |||
| 148 | + | }), "i@:") | |||
| 149 | + | child.Register() | |||
| 150 | + | ||||
| 151 | + | res := objc.SendSuper[int](objc.ID(child).Send(objc.RegisterName("new")), objc.RegisterName("doSomething")) | |||
| 152 | + | ||||
| 153 | + | fmt.Println(res) | |||
| 154 | + | // Output: 16 | |||
| 155 | + | } | |||
M struct_arm64.go +6 -6
| @@ -16,17 +16,17 @@ func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) { | |||
| 16 | 16 | return reflect.New(outType).Elem() | |
| 17 | 17 | case outSize <= 8: | |
| 18 | 18 | r1 := syscall.a1 | |
| 19 | - | if isAllSameFloat(outType) { | |
| 19 | + | if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { | |
| 20 | 20 | r1 = syscall.f1 | |
| 21 | - | if outType.NumField() == 2 { | |
| 21 | + | if numFields == 2 { | |
| 22 | 22 | r1 = syscall.f2<<32 | syscall.f1 | |
| 23 | 23 | } | |
| 24 | 24 | } | |
| 25 | 25 | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem() | |
| 26 | 26 | case outSize <= 16: | |
| 27 | 27 | r1, r2 := syscall.a1, syscall.a2 | |
| 28 | - | if isAllSameFloat(outType) { | |
| 29 | - | switch outType.NumField() { | |
| 28 | + | if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { | |
| 29 | + | switch numFields { | |
| 30 | 30 | case 4: | |
| 31 | 31 | r1 = syscall.f2<<32 | syscall.f1 | |
| 32 | 32 | r2 = syscall.f4<<32 | syscall.f3 | |
| @@ -42,8 +42,8 @@ func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) { | |||
| 42 | 42 | } | |
| 43 | 43 | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem() | |
| 44 | 44 | default: | |
| 45 | - | if isAllSameFloat(outType) && outType.NumField() <= 4 { | |
| 46 | - | switch outType.NumField() { | |
| 45 | + | if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats && numFields <= 4 { | |
| 46 | + | switch numFields { | |
| 47 | 47 | case 4: | |
| 48 | 48 | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b, c, d uintptr }{syscall.f1, syscall.f2, syscall.f3, syscall.f4})).Elem() | |
| 49 | 49 | case 3: | |
| @@ -16,17 +16,17 @@ func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) { | |||||
| 16 | return reflect.New(outType).Elem() | 16 | return reflect.New(outType).Elem() | ||
| 17 | case outSize <= 8: | 17 | case outSize <= 8: | ||
| 18 | r1 := syscall.a1 | 18 | r1 := syscall.a1 | ||
| 19 | - | if isAllSameFloat(outType) { | 19 | + | if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { |
| 20 | r1 = syscall.f1 | 20 | r1 = syscall.f1 | ||
| 21 | - | if outType.NumField() == 2 { | 21 | + | if numFields == 2 { |
| 22 | r1 = syscall.f2<<32 | syscall.f1 | 22 | r1 = syscall.f2<<32 | syscall.f1 | ||
| 23 | } | 23 | } | ||
| 24 | } | 24 | } | ||
| 25 | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem() | 25 | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem() | ||
| 26 | case outSize <= 16: | 26 | case outSize <= 16: | ||
| 27 | r1, r2 := syscall.a1, syscall.a2 | 27 | r1, r2 := syscall.a1, syscall.a2 | ||
| 28 | - | if isAllSameFloat(outType) { | 28 | + | if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { |
| 29 | - | switch outType.NumField() { | 29 | + | switch numFields { |
| 30 | case 4: | 30 | case 4: | ||
| 31 | r1 = syscall.f2<<32 | syscall.f1 | 31 | r1 = syscall.f2<<32 | syscall.f1 | ||
| 32 | r2 = syscall.f4<<32 | syscall.f3 | 32 | r2 = syscall.f4<<32 | syscall.f3 | ||
| @@ -42,8 +42,8 @@ func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) { | |||||
| 42 | } | 42 | } | ||
| 43 | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem() | 43 | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem() | ||
| 44 | default: | 44 | default: | ||
| 45 | - | if isAllSameFloat(outType) && outType.NumField() <= 4 { | 45 | + | if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats && numFields <= 4 { |
| 46 | - | switch outType.NumField() { | 46 | + | switch numFields { |
| 47 | case 4: | 47 | case 4: | ||
| 48 | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b, c, d uintptr }{syscall.f1, syscall.f2, syscall.f3, syscall.f4})).Elem() | 48 | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b, c, d uintptr }{syscall.f1, syscall.f2, syscall.f3, syscall.f4})).Elem() | ||
| 49 | case 3: | 49 | case 3: | ||
M struct_test.go +12 -0
| @@ -585,6 +585,18 @@ func TestRegisterFunc_structReturns(t *testing.T) { | |||
| 585 | 585 | t.Fatalf("ReturnFourDoubles returned %+v wanted %+v", ret, expected) | |
| 586 | 586 | } | |
| 587 | 587 | } | |
| 588 | + | { | |
| 589 | + | type FourDoublesInternal struct { | |
| 590 | + | f struct{ a, b float64 } | |
| 591 | + | g struct{ c, d float64 } | |
| 592 | + | } | |
| 593 | + | var ReturnFourDoublesInternal func(a, b, c, d float64) FourDoublesInternal | |
| 594 | + | purego.RegisterLibFunc(&ReturnFourDoublesInternal, lib, "ReturnFourDoublesInternal") | |
| 595 | + | expected := FourDoublesInternal{f: struct{ a, b float64 }{a: 1, b: 2}, g: struct{ c, d float64 }{c: 3, d: 4}} | |
| 596 | + | if ret := ReturnFourDoublesInternal(1, 2, 3, 4); ret != expected { | |
| 597 | + | t.Fatalf("ReturnFourDoublesInternal returned %+v wanted %+v", ret, expected) | |
| 598 | + | } | |
| 599 | + | } | |
| 588 | 600 | { | |
| 589 | 601 | type FiveDoubles struct{ a, b, c, d, e float64 } | |
| 590 | 602 | var ReturnFiveDoubles func(a, b, c, d, e float64) FiveDoubles | |
| @@ -585,6 +585,18 @@ func TestRegisterFunc_structReturns(t *testing.T) { | |||||
| 585 | t.Fatalf("ReturnFourDoubles returned %+v wanted %+v", ret, expected) | 585 | t.Fatalf("ReturnFourDoubles returned %+v wanted %+v", ret, expected) | ||
| 586 | } | 586 | } | ||
| 587 | } | 587 | } | ||
| 588 | + | { | |||
| 589 | + | type FourDoublesInternal struct { | |||
| 590 | + | f struct{ a, b float64 } | |||
| 591 | + | g struct{ c, d float64 } | |||
| 592 | + | } | |||
| 593 | + | var ReturnFourDoublesInternal func(a, b, c, d float64) FourDoublesInternal | |||
| 594 | + | purego.RegisterLibFunc(&ReturnFourDoublesInternal, lib, "ReturnFourDoublesInternal") | |||
| 595 | + | expected := FourDoublesInternal{f: struct{ a, b float64 }{a: 1, b: 2}, g: struct{ c, d float64 }{c: 3, d: 4}} | |||
| 596 | + | if ret := ReturnFourDoublesInternal(1, 2, 3, 4); ret != expected { | |||
| 597 | + | t.Fatalf("ReturnFourDoublesInternal returned %+v wanted %+v", ret, expected) | |||
| 598 | + | } | |||
| 599 | + | } | |||
| 588 | { | 600 | { | ||
| 589 | type FiveDoubles struct{ a, b, c, d, e float64 } | 601 | type FiveDoubles struct{ a, b, c, d, e float64 } | ||
| 590 | var ReturnFiveDoubles func(a, b, c, d, e float64) FiveDoubles | 602 | var ReturnFiveDoubles func(a, b, c, d, e float64) FiveDoubles | ||
M testdata/structtest/structreturn_test.c +14 -0
| @@ -129,6 +129,20 @@ struct FourDoubles ReturnFourDoubles(double a, double b, double c, double d) { | |||
| 129 | 129 | return e; | |
| 130 | 130 | } | |
| 131 | 131 | ||
| 132 | + | struct FourDoublesInternal{ | |
| 133 | + | struct { | |
| 134 | + | double a, b; | |
| 135 | + | } f; | |
| 136 | + | struct { | |
| 137 | + | double c, d; | |
| 138 | + | } g; | |
| 139 | + | }; | |
| 140 | + | ||
| 141 | + | struct FourDoublesInternal ReturnFourDoublesInternal(double a, double b, double c, double d) { | |
| 142 | + | struct FourDoublesInternal e = { {a, b}, {c, d} }; | |
| 143 | + | return e; | |
| 144 | + | } | |
| 145 | + | ||
| 132 | 146 | struct FiveDoubles{ | |
| 133 | 147 | double a, b, c, d, e; | |
| 134 | 148 | }; | |
| @@ -129,6 +129,20 @@ struct FourDoubles ReturnFourDoubles(double a, double b, double c, double d) { | |||||
| 129 | return e; | 129 | return e; | ||
| 130 | } | 130 | } | ||
| 131 | 131 | ||||
| 132 | + | struct FourDoublesInternal{ | |||
| 133 | + | struct { | |||
| 134 | + | double a, b; | |||
| 135 | + | } f; | |||
| 136 | + | struct { | |||
| 137 | + | double c, d; | |||
| 138 | + | } g; | |||
| 139 | + | }; | |||
| 140 | + | ||||
| 141 | + | struct FourDoublesInternal ReturnFourDoublesInternal(double a, double b, double c, double d) { | |||
| 142 | + | struct FourDoublesInternal e = { {a, b}, {c, d} }; | |||
| 143 | + | return e; | |||
| 144 | + | } | |||
| 145 | + | ||||
| 132 | struct FiveDoubles{ | 146 | struct FiveDoubles{ | ||
| 133 | double a, b, c, d, e; | 147 | double a, b, c, d, e; | ||
| 134 | }; | 148 | }; | ||