如何在Swift中构造UnsafeMutablePointer <unmanaged <cfdata >>类型?

时间:2019-04-27 09:23:36

标签: swift coremidi

我正在使用Swift与Core MIDI API交互,并且在MIDIThruConnectionFind函数上遇到了一些麻烦。

documentation声明以下内容

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

这是我的功能,无论尝试什么,都会遇到构建错误。例如,使用了变量但未初始化,类型错误等。

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

    MIDIThruConnectionFind("" as CFString, &connectionRef)        
}

我期望的是我应该为outConnectionList提供一个指针地址,并且该函数正在为数据分配内存。但是我该如何在Swift中做到这一点?

更新

至少可以编译,但是如何取消引用和访问数据?

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

    MIDIThruConnectionFind("" as CFString, connectionRefs)
}

2 个答案:

答案 0 :(得分:1)

我有点猜测,目前无法实际测试代码,但这是我的想法:

MIDIThruConnectionFind()函数在MIDIThruConnection.h中声明为

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

,因此导入为Swift

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

这意味着最后一个参数必须是(初始化的且)非可选 Unmanaged<CFData>值的地址。

但是那没有意义:数据是由函数分配的,我们不想传递任何数据。我强烈认为这是C头中该函数的可空性注释中的错误。其他带有out参数的Core MIDI功能已正确注释,例如

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

以下解决方法可能会起作用:将connectionRef声明为可选指针(以便将其初始化为nil),然后将其“投射”为非可选调用函数时的指针:

var connectionRef: Unmanaged<CFData>?

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

如果成功,则可以解压缩可选指针,并使用CFData获得takeRetainedValue()引用。 CFData是免费电话桥接到NSData,并且可以强制转换为Swift覆盖类型Data

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

}

另一种解决方法是在桥接头文件中使用正确的可空性注释定义包装函数:

#include <CoreMIDI/CoreMIDI.h>

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

然后可以称为

var connectionRef: Unmanaged<CFData>?
let status = myMIDIThruConnectionFind("" as CFString, &connectionRef)

答案 1 :(得分:1)

  • 为了快速回答,你这样做
// Creates an unmanaged<CFData>>
var unmanagedData = Unmanaged.passUnretained(Data() as CFData)

是否使用保留或未保留取决于上下文。对于 coreMidi 对象,我假设 CoreMidi 进行内存管理

  • 关于“如何访问数据”,您需要重新分配内存。

以下是与 CoreMidi 交互的两个函数的示例。一个用于单个值,另一个用于一组值(如您的问题)。

请注意,在现实生活中,最好将函数声明为 throwing 并在 CoreMidi 函数返回错误时抛出一些快速错误。 在这个例子中,如果失败,我们简单地返回 nil。

单值案例

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

数组大小写

    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]
                }
            }
        }
    }