- [NSInvocation getReturnValue:] с двойным значением производит 0 неожиданно
Я пытаюсь вызвать метод, который возвращает double
с помощью NSInvocation
. Но я обнаружил, что он не работает в 64-битных приложениях iOS. Он работает на OS X, в симуляторе - и 32-битном, и 64-битном - iPad 2, и iPad Air с 32-битной сборкой. Эта проблема связана только с 64-разрядной версией устройства iPad Air.
Это код для демонстрации проблемы:
NSMethodSignature *signature = [NSString instanceMethodSignatureForSelector:@selector(doubleValue)];
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];
union {
double d;
uint64_t u;
} d;
[invocation getReturnValue:&d.d];
NSLog(@"%lf, %llx", d.d, d.u);
}
ожидаемый выход
2013-11-09 22:34:18.645 test[49075:907] 0.000000, 0
2013-11-09 22:34:18.647 test[49075:907] 1.000000, 3ff0000000000000
2013-11-09 22:34:18.648 test[49075:907] 2.000000, 4000000000000000
2013-11-09 22:34:18.649 test[49075:907] 3.000000, 4008000000000000
2013-11-09 22:34:18.650 test[49075:907] 4.000000, 4010000000000000
2013-11-09 22:34:18.651 test[49075:907] 5.000000, 4014000000000000
2013-11-09 22:34:18.652 test[49075:907] 6.000000, 4018000000000000
2013-11-09 22:34:18.653 test[49075:907] 7.000000, 401c000000000000
2013-11-09 22:34:18.654 test[49075:907] 8.000000, 4020000000000000
2013-11-09 22:34:18.654 test[49075:907] 9.000000, 4022000000000000
Выход для 64-битной сборки на iPad Air
2013-11-09 22:33:55.846 test[998:60b] 0.000000, 18710a969
2013-11-09 22:33:55.847 test[998:60b] 0.000000, 18710a969
2013-11-09 22:33:55.848 test[998:60b] 0.000000, 18710a969
2013-11-09 22:33:55.848 test[998:60b] 0.000000, 18710a969
2013-11-09 22:33:55.849 test[998:60b] 0.000000, 18710a969
2013-11-09 22:33:55.849 test[998:60b] 0.000000, 18710a969
2013-11-09 22:33:55.850 test[998:60b] 0.000000, 18710a969
2013-11-09 22:33:55.850 test[998:60b] 0.000000, 18710a969
2013-11-09 22:33:55.851 test[998:60b] 0.000000, 18710a969
2013-11-09 22:33:55.851 test[998:60b] 0.000000, 18710a969
Это также происходит при значении float
.
2013-11-09 23:51:10.021 test[1074:60b] -0.000000, 80000000
2013-11-09 23:51:10.023 test[1074:60b] -0.000000, 80000000
2013-11-09 23:51:10.024 test[1074:60b] -0.000000, 80000000
2013-11-09 23:51:10.024 test[1074:60b] -0.000000, 80000000
2013-11-09 23:51:10.025 test[1074:60b] -0.000000, 80000000
2013-11-09 23:51:10.026 test[1074:60b] -0.000000, 80000000
2013-11-09 23:51:10.026 test[1074:60b] -0.000000, 80000000
2013-11-09 23:51:10.027 test[1074:60b] -0.000000, 80000000
2013-11-09 23:51:10.027 test[1074:60b] -0.000000, 80000000
2013-11-09 23:51:10.028 test[1074:60b] -0.000000, 80000000
Ответы
Ответ 1
Согласовано с @David H, которое в этом случае разбивается на NSInvocation
или, возможно, на метод NSString
doubleValue
. Однако я смог заставить его работать.
Мне кажется, что NSInvocation
нарушается из-за проблемы/несоответствия вызова. Как правило, параметры и возвращаемые значения для методов objective-c передаются в регистры. (objc_msgSend
знает, как выполнить этот тип вызова.) Но если параметр или возвращаемое значение является структурой или типом, который не вписывается в регистр, они передаются в стек. (objc_msgSend_stret
выполняет этот тип вызова.) NSInvocation
обычно использует подпись метода, чтобы иметь возможность решить, нужно ли ему звонить objc_msgSend
или objc_msgSendStret
. Я предполагаю, что теперь он также должен знать, на какой платформе он работает, и вот где ошибка.
Я немного поиграл с вашим кодом, и кажется, что на arm64 двойное возвращаемое значение передается как структура, но NSInvocation
рассматривает его как переданный в регистр. Я понятия не имею, какая из сторон правильная. (Я знаю, что достаточно только в этой области, чтобы быть опасным. Мои пальцы пересекаются с кем-то с более низкоуровневыми отбивными, чем я, и читаю это, и дает лучшее объяснение!)
Тем не менее, мне кажется, что есть значительные изменения в том, как параметры и результаты передаются в руке (32 бит) против arm64. См. Разделы Возврат результата в ARM Procedure Call Standard для arm64 и Стандарт обработки вызовов ARM (не 64 бит).
Мне удалось заставить NSInvocation
обрабатывать вызов как возвращающий структуру, содержащую double, и это заставило ее работать должным образом. Для этого я подделал сигнатуру метода поддельной подписи метода, возвращающего структуру. Я поместил это в категорию NSString
, но он мог жить где угодно.
Не зная, что конкретно нарушено, или что произойдет, когда оно будет исправлено, я вряд ли отправил бы код с этим "исправлением". Я найду другое решение.
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);
}
}
Ответ 2
У вас нет надежды заставить это работать в настоящее время. Я пробовал много вариантов вашего кода и тестировал на iPhone 5S и новейший симулятор в режиме 32 и 64 бит, но это должно быть ошибкой с arm64, поскольку 64-битный симулятор работает просто отлично.
Итак, сначала я изменил свой код, чтобы попробовать всевозможные варианты, и получается, что с помощью floatValue
также не работает. Поскольку размер поплавка является общим, это уменьшает количество переменных между различными пробными платформами.
Кроме того, я попытался использовать цель NSNumber, созданную с использованием интегрального метода и метода float: метод float фактически приводит к сбою! Я пробовал другие параметры, такие как сохранение строки и установка настройки сохранения вызова, без изменений.
Я ввел ошибку об этом: 15441447: NSInvocation fails only on arm64 devices
- любой, кто ее беспокоит, может его обмануть. Я также загрузил демонстрационный проект.
Загруженный мной код:
- (void)test
{
NSMethodSignature *signature = [NSString instanceMethodSignatureForSelector:@selector(floatValue)];
for (int i = 0; i < 10; i++) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
#if 0
NSString *str = [NSString stringWithFormat:@"%d", i];
[invocation setTarget:str]; // fails on iPhone 5s
#else
//[invocation setTarget:[NSNumber numberWithFloat:(float)i]]; // crashes in 'invoke' on iPhone 5s, works fine in simulator
[invocation setTarget:[NSNumber numberWithInteger:i]]; // fails on iphone 5S
#endif
[invocation setSelector:@selector(floatValue)];
[invocation invoke];
float f;
[invocation getReturnValue:&f];
NSLog(@"%f", f);
}
}
Ответ 3
Исправлено на основе ответа @TomSwift.
- (void)testInvocation
{
NSInvocation *invocation = [[self class] invocationWithObject:self selector:@selector(getAFloat)];
[invocation setTarget:self];
[invocation invoke];
double d;
[invocation getReturnValue: &d];
NSLog(@"d == %f", d);
return YES;
}
+ (NSInvocation *)invocationWithObject:(id)object selector:(SEL)selector
{
NSMethodSignature *sig = [object methodSignatureForSelector:selector];
if (!sig) {
return nil;
}
#ifdef __LP64__
BOOL isReturnDouble = (strcmp([sig methodReturnType], "d") == 0);
BOOL isReturnFloat = (strcmp([sig methodReturnType], "f") == 0);
if (isReturnDouble || isReturnFloat) {
typedef struct {double d;} doubleStruct;
typedef struct {float f;} floatStruct;
NSMutableString *types = [NSMutableString stringWithFormat:@"%[email protected]:", isReturnDouble ? @encode(doubleStruct) : @encode(floatStruct)];
for (int i = 2; i < sig.numberOfArguments; i++) {
const char *argType = [sig getArgumentTypeAtIndex:i];
[types appendFormat:@"%s", argType];
}
sig = [NSMethodSignature signatureWithObjCTypes:[types UTF8String]];
}
#endif
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:sig];
[inv setSelector:selector];
return inv;
}