MIDIPacketNext 崩溃

crash with MIDIPacketNext

我十年前在 Objective-C 中用 PGMidi 写了一个应用程序,它是 Core MIDI 的助手 class。 PGMidi 的输入函数如下所示:

static void PGMIDIReadProc(const MIDIPacketList *pktlist, void *readProcRefCon, void *srcConnRefCon) {
    PGMidiSource *source = arc_cast<PGMidiSource>(srcConnRefCon);
    [source midiRead:pktlist fromPort:source.name];
}

- (void) midiRead:(const MIDIPacketList *)pktlist fromPort:(NSString *)port {
    NSArray *delegates = self.delegates;
    for (NSValue *delegatePtr in delegates) {
        id<PGMidiSourceDelegate> delegate = (id<PGMidiSourceDelegate>)[delegatePtr pointerValue];
        [delegate midiSource:self midiReceived:pktlist fromPort:port];
    }
}

然后将数据传递到我的应用函数:

static NSMutableArray *packetToArray(const MIDIPacket *packet) {
    NSMutableArray *values = [[NSMutableArray alloc] initWithCapacity:3];
    NSNumber *value;
    for (int j=0; j<packet->length; j++) {
        value = [[NSNumber alloc] initWithUnsignedInt:packet->data[j]];
        [values addObject:value];
    }
    return values;
}

- (void)midiSource:(PGMidiSource *)midi midiReceived:(const MIDIPacketList *)packetList fromPort:(NSString *)port {
    const MIDIPacket *packet = &packetList->packet[0];
    for (int i=0; i<packetList->numPackets; ++i) {
        NSDictionary *data = [[NSDictionary alloc] initWithObjectsAndKeys:
            packetToArray(packet), @"values",
            port, @"port",
        nil];
        [self performSelectorOnMainThread:@selector(receiveMidiData:) withObject:data waitUntilDone:FALSE];
    
        packet = MIDIPacketNext(packet);
    }
}

这一直很好用。但今年早些时候,我将我的应用程序转换为 Swift。我单独留下了 PGMidi class,但将我的应用程序功能重写为:

func packetToArray(_ packet: MIDIPacket) -> [Int] {
    var values = [Int]()
    let packetMirror = Mirror(reflecting: packet.data)
    let packetValues = packetMirror.children.map({ [=12=].value as? UInt8 })
    for i in 0..<Int(packet.length) {
        if (packetValues.count > i), let value = packetValues[i] {
            values.append(Int(value))
        }
    }
    return values
}

@objc func midiSource(_ midi: PGMidiSource, midiReceived packetList: UnsafePointer<MIDIPacketList>, fromPort port: String) {
    var packet = packetList.pointee.packet // this gets the first packet
    for _ in 0..<packetList.pointee.numPackets {
        let packetArray = App.packetToArray(packet)
        let data: [AnyHashable : Any] = [
            "values" : packetArray,
            "port" : port
        ]
        self.performSelector(onMainThread: #selector(receiveMidiData(_:)), with: data, waitUntilDone: false)

        packet = MIDIPacketNext(&packet).pointee
    }
}

这大部分都有效,但我在 MIDIPacketNext 行遇到了一些崩溃。这是不一致的,并且似乎在大量数据输入时发生,例如来自键盘上的表情轮。一份崩溃日志包括:

Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x000000016b78914c
VM Region Info: 0x16b78914c is not in any region. Bytes after previous region: 37197 Bytes before following region: 360050356
REGION TYPE START - END [ VSIZE] PRT/MAX SHRMOD REGION DETAIL
Stack 16b6f8000-16b780000 [ 544K] rw-/rwx SM=PRV thread 6
---> GAP OF 0x15768000 BYTES
unused shlib __TEXT 180ee8000-180f2c000 [ 272K] r-x/r-x SM=COW ... this process

另一个略有不同,但指向同一行代码:

Exception Type:  EXC_BAD_ACCESS (SIGBUS)
Exception Subtype: KERN_PROTECTION_FAILURE at 0x000000016beb46cc
VM Region Info: 0x16beb46cc is in 0x16beb4000-0x16beb8000;  bytes after start: 1740  bytes before end: 14643
      REGION TYPE                 START - END      [ VSIZE] PRT/MAX SHRMOD  REGION DETAIL
      Stack                    16be2c000-16beb4000 [  544K] rw-/rwx SM=PRV  thread 5
--->  STACK GUARD              16beb4000-16beb8000 [   16K] ---/rwx SM=NUL  ... for thread 9
      Stack                    16beb8000-16bf40000 [  544K] rw-/rwx SM=PRV  thread 9

我是否应该以某种方式以不同方式访问数据包以避免这种情况,或者我可以做些什么来检测错误情况并避免崩溃?

我的第一个想法是您应该使用 MIDIReadBlock 而不是 MIDIReadProc。 MIDIReadProc 已弃用并且在处理多线程时存在问题,这正是高速数据传入时会发生的情况。MIDIReadBlock 被标记为已弃用,但它实际上是当前方法,直到您切换到 MIDI 2.0 的新功能。参见 https://bradleyross.github.io/ObjectiveC-Examples/Documentation/BRossTools/FunctionalArea.html and https://bradleyross.github.io/ObjectiveC-Examples/Documentation/BRossTools/CoreMidi.html. There are also a number of CoreMIDI examples on GitHub using Swift. My examples use Objective-C instead of Swift. There are a lot of methods in CoreMIDI that have the same first part of the function name. Many of these methods are not only deprecated, but have problems. (The names of these functions do not have the words Block or Protocol.)The second set must be used for everything before the latest version of iOS and MacOS. They will have the word Block at the end. The third set will have the word Protocol in it and can only be used with the latest version of the operating systems. These are designed to handle both MIDI 1.0 and MIDI 2.0. This means that there are functions MIDIDestinationCreate, MIDIDestinationCreateWithBlock, and MIDIDestinationCreateWith Protocol. The root of the documentation mentioned above is https://bradleyross.github.io/ObjectiveC-Examples/Documentation/BRossTools/。我们真的需要更好的文档,

让我找到了解决方案。我按如下方式重写了我的 midiSource 委托函数,并添加了 UnsafePointer 扩展:

@objc func midiSource(_ midi: PGMidiSource, midiReceived packetList: UnsafePointer<MIDIPacketList>, fromPort port: String) {
    let packetList = packetList.loadUnaligned(as: MIDIPacket.self, count: Int(packetList.pointee.numPackets))
    for packet in packetList {
        let packetArray = App.packetToArray(packet)
        let data: [AnyHashable : Any] = [
            "values" : packetArray,
            "port" : port
        ]
        self.performSelector(onMainThread: #selector(receiveMidiData(_:)), with: data, waitUntilDone: false)
    }
}

extension UnsafePointer {
    func loadUnaligned<T>(as: T.Type, count: Int) -> [T] {
        assert(_isPOD(T.self)) // relies on the type being POD (no refcounting or other management)
        let buffer = UnsafeMutablePointer<T>.allocate(capacity: count)
        defer { buffer.deallocate() }
        memcpy(buffer, self, MemoryLayout<T>.size * count)
        return (0..<count).map({ index in buffer.advanced(by: index).pointee })
    }
}

不再崩溃!