簡體   English   中英

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

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

我正在嘗試調用一個使用NSInvocation返回double 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

同意@David H 在這種情況下NSInvocation被破壞,或者可能是NSString doubleValue方法。 然而,我能夠強迫它工作。

在我看來,由於調用約定問題/不匹配, NSInvocation被破壞了。 通常,objective-c 方法的參數和返回值在寄存器中傳遞。 objc_msgSend知道如何執行這種類型的調用。)但是如果參數或返回值是一個不適合寄存器的結構或類型,那么它們將被傳遞到堆棧上。 objc_msgSend_stret執行這種類型的調用。) NSInvocation通常使用方法簽名來決定是否需要調用objc_msgSendobjc_msgSendStret 我猜現在它還需要知道它在什么平台上運行,這就是錯誤所在。

我玩了一下你的代碼,似乎在 arm64 上,雙返回值是作為結構傳遞的,但NSInvocation將其視為在寄存器中傳遞。 我不知道哪一邊是正確的。 (我只知道在這方面很危險。我的手指交叉,比我低級的人走過來讀到這個,並提供了更好的解釋!)

也就是說,在我看來,在 arm(32 位)和 arm64 中傳遞參數和結果的方式發生了重大變化。 請參閱arm64 的ARM 過程調用標准ARM 過程調用標准(非 64 位)中的結果返回部分。

我能夠強制NSInvocation將調用視為返回包含雙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);
    }
}

您目前沒有希望讓它發揮作用。 我嘗試了您代碼的許多變體,並在 iPhone 5S 和最新的模擬器上以 32 位和 64 位模式進行了測試,但這一定是 arm64 的錯誤,因為 64 位模擬器工作得很好。

所以首先我修改了你的代碼以嘗試各種變化,結果使用floatValue失敗了。 由於浮點數的大小很常見,這減少了各種試驗平台之間的變量數量。

此外,我嘗試使用使用積分和浮點方法創建的 NSNumber 目標:浮點方法實際上會導致崩潰! 我嘗試了其他選項,如保留字符串和設置調用保留設置,沒有改變。

我已經輸入了一個錯誤: 15441447: NSInvocation fails only on arm64 devices - 任何關心它的人都可以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);
    }
}

根據@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;
    }

哇,這在今天和現在在模擬器中仍然明顯損壞(我正在測試它)。 今天我被這個錯誤所困擾,發現我的雙參數總是在調用的選擇器中返回零。

在我的情況下,很容易將它作為 NSNumber 發送,@(myDouble) 作為參數,然后在另一邊用 myNumber.doubleValue 解開它

討厭。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM