ひtoりgoと

callback を登録解除できない IOKit のバグ

macOS 開発

IOKit で HID デバイスからデータを受け取るには IOHIDDeviceRegisterInputReportCallback() で callback を登録する。逆に callback の登録を解除したいときは専用の関数があるわけではなく Apple の資料によると

Note: To unregister pass NULL for the callback.

登録するときと同じ関数を、(callback: NULL) で呼べばいいらしい。

...と知ってからずっと試しているけど、どんな書き方をしても解除したはずの古い context で callback が呼ばれてしまい、アプリケーションがクラッシュ。

どうしようもないので IOKit のソースコードを読んでみることに。

void __IOHIDDeviceRegisterInputReportCallback(IOHIDDeviceRef                    device,
                                              uint8_t *                         report,
                                              CFIndex                           reportLength,
                                              IOHIDReportCallback               callback,
                                              IOHIDReportWithTimeStampCallback  callbackWithTimeStamp,
                                              void *                            context)
{
    IOHIDDeviceReportCallbackInfo   info    = {context, callback, callbackWithTimeStamp, device};
    CFDataRef                       infoRef = NULL;

    CFRetain(device);

    if (!context)
        os_log(_IOHIDLog(), "called with a null context");

    if (!device->inputReportCallbackSet) {
        device->inputReportCallbackSet = CFSetCreateMutable(NULL, 0, &__callbackBaseSetCallbacks);
    }
    require(device->inputReportCallbackSet, cleanup);

    // adding or modifying a callback
    infoRef = CFDataCreate(CFGetAllocator(device), (const UInt8 *) &info, sizeof(info));
    require(infoRef, cleanup);

    if (callback || callbackWithTimeStamp) {
        CFSetAddValue(device->inputReportCallbackSet, infoRef);

        if (device->deviceTimeStampedInterface) {
            (*device->deviceTimeStampedInterface)->
                    setInputReportWithTimeStampCallback(device->deviceInterface,
                                                        report,
                                                        reportLength,
                                                        __IOHIDDeviceInputReportWithTimeStampCallback,
                                                        device,
                                                        0);
        }
        else {
            (*device->deviceInterface)->setInputReportCallback(device->deviceInterface,
                                                               report,
                                                               reportLength,
                                                               __IOHIDDeviceInputReportCallback,
                                                               device,
                                                               0);
        }
    }
    else {
        CFSetRemoveValue(device->inputReportCallbackSet, infoRef);
    }

cleanup:
    CFRELEASE_IF_NOT_NULL(infoRef);
    CFRelease(device);
}

確かに CFSetRemoveValue() で取り除こうとしているようだけど callback 込みで比較しているからどうやっても取り除かれないという...

Apple に報告する元気は残っていない。

Share

リンクも共有もお気軽に。記事を書くモチベーションの向上に役立てます。

© 2005-2021 zumuya