I am trying to call a method that returns a double
using NSInvocation
. But I found that it does not working in 64 bit iOS apps. It works on on OS X, in
Agreed with @David H that NSInvocation
is broken in this case, or possibly the NSString
doubleValue
method. I was able to force it to work, however.
It appears to me that NSInvocation
is broken due to a calling-convention issue / mismatch. Typically, parameters and return values for objective-c methods are passed in registers. (objc_msgSend
knows how to perform this type of call.) But if a parameter or return value is a struct or type that doesn't fit in a register then they're passed on the stack. (objc_msgSend_stret
performs this type of call.) NSInvocation
typically uses the method signature to be able to decide whether it needs to call objc_msgSend
or objc_msgSendStret
. I'm guessing that now it also needs to know what platform it's operating on, and this is where the bug lies.
I played around with your code a bit and it appears that on arm64 the double return value is being passed as a structure would be, yet NSInvocation
is treating it as being passed in a register. I have no idea which side is correct. (I know only enough in this area to be dangerous. My fingers are crossed that someone with more low-level chops than I comes along and reads this, and provides a better explanation!)
That said, it appears to me that there are significant changes in how parameters and results are passed in arm (32bit) vs. arm64. See the Result Return sections in both the ARM Procedure Call Standard for arm64 and ARM Procedure Call Standard (non-64 bit).
I was able to force NSInvocation
to treat the call as returning a struct containing a double, and this made it work as expected. To do this I faked out the method signature to a fake signature of a method returning a struct. I placed this in a NSString
category but it could live anywhere.
Not knowing what specifically is broken, or what will happen when it's fixed, I wouldn't likely ship code with this 'fix'. I'd find some other workaround.
typedef struct
{
double d;
} doubleStruct;
@interface NSString (TS)
- (doubleStruct) ts_doubleStructValue;
@end
@implementation NSString (TS)
- (doubleStruct) ts_doubleStructValue
{
doubleStruct ds;
return ds;
}
@end
- (void) test
{
NSMethodSignature *signature = [NSString instanceMethodSignatureForSelector: @selector( ts_doubleStructValue )];
for (int i = 0; i < 10; i++) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
NSString *str = [NSString stringWithFormat:@"%d", i];
[invocation setTarget:str];
[invocation setSelector:@selector(doubleValue)];
[invocation invoke];
double d;
[invocation getReturnValue: &d];
NSLog(@"%lf", d);
}
}