15

我正在尝试调用一个返回doubleusing的方法NSInvocation。但我发现它不适用于 64 位 iOS 应用程序。它适用于 OS X、模拟器——32 位和 64 位——iPad 2 和 32 位版本的 iPad Air。只有 iPad Air 设备上的 64 位版本存在此问题。

这是演示问题的代码:

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

iPad Air 上 64 位版本的输出

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
4

4 回答 4

17

同意NSInvocation在这种情况下被破坏的@David H,或者可能是NSString doubleValue方法。但是,我能够强制它工作。

在我看来,NSInvocation由于呼叫约定问题/不匹配而被破坏。通常,objective-c 方法的参数和返回值在寄存器中传递。(objc_msgSend知道如何执行这种类型的调用。)但是如果参数或返回值是不适合寄存器的结构或类型,那么它们将在堆栈上传递。(objc_msgSend_stret执行这种类型的调用。) NSInvocation通常使用方法签名来决定是否需要调用objc_msgSendobjc_msgSendStret. 我猜现在它还需要知道它在哪个平台上运行,这就是错误所在。

我对您的代码进行了一些尝试,似乎在 arm64 上,双返回值被作为结构传递,但NSInvocation将其视为在寄存器中传递。我不知道哪一方是正确的。(我只知道在这个领域是危险的。我的手指交叉,比我低级排骨的人来读这个,并提供更好的解释!)

也就是说,在我看来,参数和结果在 arm(32 位)与 arm64 中的传递方式发生了重大变化。请参阅arm64 的 ARM 过程调用标准ARM 过程调用标准(非 64 位)中的结果返回部分。

我能够强制NSInvocation将调用视为返回包含双精度的结构,这使其按预期工作。为此,我将方法签名伪造为返回结构的方法的伪造签名。我把它放在一个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);
    }
}
于 2013-11-11T23:57:58.273 回答
2

您目前没有希望让它发挥作用。我尝试了您的代码的许多变体,并在 iPhone 5S 和 32 位和 64 位模式下的最新模拟器上进行了测试,但这一定是 arm64 的错误,因为 64 位模拟器工作得很好。

因此,首先我修改了您的代码以尝试各种变体,结果发现使用floatValue也失败了。由于浮点数的大小很常见,这减少了各种试验平台之间的变量数量。

另外,我尝试使用使用积分和浮点方法创建的 NSNumber 目标:浮点方法实际上会导致崩溃!我尝试了其他选项,例如保留字符串和设置调用保留设置,没有变化。

我已经输入了一个错误: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);
    }
}
于 2013-11-11T22:31:28.420 回答
0

基于@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:@"%s@:", 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;
    }
于 2014-04-11T09:19:15.563 回答
0

哇,今天这显然仍然被打破,现在也在模拟器中(我正在测试它)。我今天被这个错误所困扰,发现我的双重参数在调用的选择器中总是归零。

在我的情况下,很容易将其作为 NSNumber 以 @(myDouble) 作为参数发送,然后在另一侧用 myNumber.doubleValue 解开它

讨厌。

于 2020-07-02T21:22:40.310 回答