简体   繁体   English

-[NSInvocation getReturnValue:] 使用 double 值意外产生 0

[英]-[NSInvocation getReturnValue:] with double value produces 0 unexpectedly

I am trying to call a method that returns a double using NSInvocation .我正在尝试调用一个使用NSInvocation返回double NSInvocation But I found that it does not working in 64 bit iOS apps.但我发现它在 64 位 iOS 应用程序中不起作用。 It works on on OS X, in the simulator -- both 32-bit and 64 bit -- iPad 2, and iPad Air with a 32-bit build.它适用于 OS X 模拟器——32 位和 64 位——iPad 2 和 32 位版本的 iPad Air。 Only the 64-bit build on an iPad Air device has this problem.只有 iPad Air 设备上的 64 位版本有这个问题。

This is the code to demo the problem:这是演示问题的代码:

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);
}

expected output预期产出

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

Output for the 64-bit build on iPad Air 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

This also happen for float value.这也发生在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

Agreed with @David H that NSInvocation is broken in this case, or possibly the NSString doubleValue method.同意@David H 在这种情况下NSInvocation被破坏,或者可能是NSString doubleValue方法。 I was able to force it to work, however.然而,我能够强迫它工作。

It appears to me that NSInvocation is broken due to a calling-convention issue / mismatch.在我看来,由于调用约定问题/不匹配, NSInvocation被破坏了。 Typically, parameters and return values for objective-c methods are passed in registers.通常,objective-c 方法的参数和返回值在寄存器中传递。 ( 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知道如何执行这种类型的调用。)但是如果参数或返回值是一个不适合寄存器的结构或类型,那么它们将被传递到堆栈上。 ( 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 . objc_msgSend_stret执行这种类型的调用。) NSInvocation通常使用方法签名来决定是否需要调用objc_msgSendobjc_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.我玩了一下你的代码,似乎在 arm64 上,双返回值是作为结构传递的,但NSInvocation将其视为在寄存器中传递。 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.也就是说,在我看来,在 arm(32 位)和 arm64 中传递参数和结果的方式发生了重大变化。 See the Result Return sections in both the ARM Procedure Call Standard for arm64 and ARM Procedure Call Standard (non-64 bit).请参阅arm64 的ARM 过程调用标准ARM 过程调用标准(非 64 位)中的结果返回部分。

I was able to force NSInvocation to treat the call as returning a struct containing a double, and this made it work as expected.我能够强制NSInvocation将调用视为返回包含双NSInvocation的结构,这使其按预期工作。 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.我把它放在一个NSString类别中,但它可以存在于任何地方。

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);
    }
}

You have no hope of getting this to work at the current time.您目前没有希望让它发挥作用。 I tried many variations of your code, and tested on an iPhone 5S and the latest Simulator in both 32 and 64 bit mode, but this must be a bug with arm64 since the 64bit simulator works just fine.我尝试了您代码的许多变体,并在 iPhone 5S 和最新的模拟器上以 32 位和 64 位模式进行了测试,但这一定是 arm64 的错误,因为 64 位模拟器工作得很好。

So first I modified your code to try all kinds of variations, and it turns out using floatValue fails as well.所以首先我修改了你的代码以尝试各种变化,结果使用floatValue失败了。 Since the size of a float is common, this reduces the number of variables between the various trial platforms.由于浮点数的大小很常见,这减少了各种试验平台之间的变量数量。

Also, I tried to use a NSNumber target created using an integral and float method: the float method actually results in a crash!此外,我尝试使用使用积分和浮点方法创建的 NSNumber 目标:浮点方法实际上会导致崩溃! I tried other options like retaining the string and setting the invocation retain setting, no change.我尝试了其他选项,如保留字符串和设置调用保留设置,没有改变。

I've entered a bug on this: 15441447: NSInvocation fails only on arm64 devices - anyone concerned about it can dup it.我已经输入了一个错误: 15441447: NSInvocation fails only on arm64 devices - 任何关心它的人都可以15441447: NSInvocation fails only on arm64 devices它。 I uploaded a demo project as well.我也上传了一个演示项目。

The code I uploaded is:我上传的代码是:

- (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);
    }
}

Fix based on @TomSwift answer.根据@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;
    }

Wow this is still apparently broken today and now in the simulator too (where I'm testing it).哇,这在今天和现在在模拟器中仍然明显损坏(我正在测试它)。 I got bit by this bug today, found my double arguments were always coming back zero in the selector of the invocation.今天我被这个错误所困扰,发现我的双参数总是在调用的选择器中返回零。

In my case it was easy to just send it as an NSNumber with @(myDouble) as the argument and then on the other side unwrap it with myNumber.doubleValue在我的情况下,很容易将它作为 NSNumber 发送,@(myDouble) 作为参数,然后在另一边用 myNumber.doubleValue 解开它

Nasty.讨厌。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM