1

I'm interacting to the Core MIDI API with Swift and having some trouble with the MIDIThruConnectionFind function.

The documentation states the following

func MIDIThruConnectionFind(_ inPersistentOwnerID: CFString, 
                      _ outConnectionList: UnsafeMutablePointer<Unmanaged<CFData>>) -> OSStatus

This is my function and what ever I try, I get build errors. For instance, the variable is used but not initialised, wrong type etc.

@IBAction func listConnections(_ sender: Any) {
    var connectionRef: Unmanaged<CFData>

    MIDIThruConnectionFind("" as CFString, &connectionRef)        
}

What I am expecting is that I should provide a adress to a pointer for outConnectionList and that the function is allocating the memory for the data. But how do I do this in Swift?

Update

At least this compiles, but how to de-reference and access the data?

@IBAction func listConnections(_ sender: Any) {
    let connectionRefs = UnsafeMutablePointer<Unmanaged<CFData>>.allocate(capacity: 1)

    MIDIThruConnectionFind("" as CFString, connectionRefs)
}
norq
  • 1,404
  • 2
  • 18
  • 35
  • related https://stackoverflow.com/questions/27169807/swift-unsafemutablepointerunmanagedcfstring-allocation-and-print – Sulthan Apr 27 '19 at 10:24
  • @ihatetoregister: Did you have a chance to check the answer? Some feedback would be appreciated :) – Martin R May 23 '19 at 11:48

2 Answers2

1

I am a little bit guessing and cannot actually test the code at the moment, but here are my thoughts:

The MIDIThruConnectionFind() function is declared in MIDIThruConnection.h as

extern OSStatus
MIDIThruConnectionFind(     CFStringRef                     inPersistentOwnerID,
                            CFDataRef __nonnull * __nonnull outConnectionList )

and therefore imported to Swift as

public func MIDIThruConnectionFind(_ inPersistentOwnerID: CFString,
            _ outConnectionList: UnsafeMutablePointer<Unmanaged<CFData>>) -> OSStatus

which means that the last parameter must be the address of an (initialized and) non-optional Unmanaged<CFData> value.

But that makes no sense: The data is allocated by the function, and we don't want to pass any data in. I strongly assume that this is a bug in the nullability annotation of that function in the C header. Other Core MIDI functions with an out parameter are correctly annotated, e.g.

extern OSStatus
MIDIObjectGetStringProperty(    MIDIObjectRef           obj,
                                CFStringRef             propertyID,
                                CFStringRef __nullable * __nonnull str )        

The following workaround might work: Declare connectionRef as an optional pointer (so that it is initialized as nil), and “cast” it to a non-optional pointer when calling the function:

var connectionRef: Unmanaged<CFData>?

let status = withUnsafeMutablePointer(to: &connectionRef) {
    $0.withMemoryRebound(to: Unmanaged<CFData>.self, capacity: 1) {
        MIDIThruConnectionFind("" as CFString, $0)
    }
}

If that succeeds, the optional pointer can be unwrapped, and the CFData reference obtained with takeRetainedValue(). CFData is toll-free bridged to NSData, and that can be cast to the Swift overlay type Data:

if status == noErr, let connectionRef = connectionRef {
    let data = connectionRef.takeRetainedValue() as Data

}

Another workaround is to define a wrapper function with the correct nullability annotations in the bridging header file:

#include <CoreMIDI/CoreMIDI.h>

static OSStatus myMIDIThruConnectionFind(CFStringRef inPersistentOwnerID,
                                          CFDataRef __nullable * __nonnull outConnectionList) {
    return MIDIThruConnectionFind(inPersistentOwnerID, outConnectionList);
}

which can then be called as

var connectionRef: Unmanaged<CFData>?
let status = myMIDIThruConnectionFind("" as CFString, &connectionRef)
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • That code compiles (and does not crash), but unfortunately I cannot test if it really works at the moment. Please let me know if it does not work, then I'll delete the answer. – Martin R Apr 27 '19 at 11:01
1
  • To quickly answer, you do like this
// Creates an unmanaged<CFData>>
var unmanagedData = Unmanaged.passUnretained(Data() as CFData)

knowing if you use retained or unretained depends of the context. For coreMidi Objects, I would assume CoreMidi does the memory management

  • And about the "how to access data", you need to rebound memory.

Here is an exemple of two functions that interact with CoreMidi. One for a single value, the other for an array of values ( as in your question ).

Note that in real life, it's probably better to declare function as throwing and throw some swift error when the CoreMidi functions return an error. In this exemple, we simply return nil if it fails.

Single value case

    func getMidiThruParams(connectionRef: MIDIThruConnectionRef) -> MIDIThruConnectionParams? {
        // 1 - allocate an unmanaged CFData
        var unmanagedData = Unmanaged.passUnretained(Data() as CFData)

        // 2 - Pass the data pointer to C API
        let err = MIDIThruConnectionGetParams(connectionRef, &unmanagedData)
        guard err == noErr else {
            return nil
        }

        // 3 - Extract the data from unmanaged CFData
        let data = unmanagedData.takeUnretainedValue() as Data

        // 4 - Remap to the swift type
        return data.withUnsafeBytes { bytes -> MIDIThruConnectionParams in
            UnsafeRawPointer(bytes).assumingMemoryBound(to: MIDIThruConnectionParams.self).pointee
        }
    }

Array case

    func getMidiThruConnections() -> [MIDIThruConnectionRef]? {
        
        // 1 - allocate an unmanaged data reference
        var unmanagedData = Unmanaged.passUnretained(Data() as CFData)
        
        // 2 - Pass the data pointer to C API
        let err = MIDIThruConnectionFind("com.moosefactory.midiCenter.midiThru" as CFString, &unmanagedData)
        guard err == noErr else {
            return nil
        }
        
        // 3 - Extract the CFData from unmanaged data
        // We prefer CFData to Data here, to access pointer and size
        let cfData = unmanagedData.takeUnretainedValue()
        guard let dataPtr = CFDataGetBytePtr(cfData) else {
            return nil
        }
        
        // 4  - Compute the number of elements
        let dataSize = CFDataGetLength(cfData)
        let numberOfConnections = dataSize / MemoryLayout<MIDIThruConnectionRef>.stride
        
        // 5 - Rebound pointer from <Int8> to <MIDIThruConnectionRef>
        return dataPtr.withMemoryRebound(to: MIDIThruConnectionRef.self,
                                         capacity: numberOfConnections) { typedPtr in
            // Convert pointer to buffer pointer
            let bufferPointer = UnsafeBufferPointer(start: typedPtr, count: numberOfConnections)
            // Construct array
            return [MIDIThruConnectionRef].init(unsafeUninitializedCapacity: numberOfConnections) { refPtr, count in
                count = numberOfConnections
                for i in 0..<count {
                    refPtr[i] = bufferPointer[i]
                }
            }
        }
    }

Moose
  • 2,607
  • 24
  • 23