AudioToolbox、C 函数指针和 Swift

AudioToolbox, C-function pointers, and Swift

我暂时使用 AudioToolbox API 使用 Swift 2.0 和 Xcode 7b6。 API 使用了大量的 C 语言结构,包括函数指针。这是我第一次使用 withUnsafeMutablePointerunsafeBitCast 这样的命令。我正在寻找现实检查,以确保我在做的事情上没有偏离基础。

例如,要打开文件流,您可以使用以下函数:

func AudioFileStreamOpen(
      _ inClientData: UnsafeMutablePointer<Void>
    , _ inPropertyListenerProc: AudioFileStream_PropertyListenerProc
    , _ inPacketsProc: AudioFileStream_PacketsProc
    , _ inFileTypeHint: AudioFileTypeID
    , _ outAudioFileStream: UnsafeMutablePointer<AudioFileStreamID>) -> OSStatus

光是函数的类型签名就让我汗流浃背。

无论如何,inClientData 参数需要是一个 UnsafeMutablePointer<Void>,指针将指向我正在使用的 class 的实例。换句话说,它需要是指向 self 的指针。我的方法是使用 withUnsafeMutablePointer 调用函数,如下所示:

var proxy = self

let status = withUnsafeMutablePointer(&proxy) {
      AudioFileStreamOpen([=12=], AudioFileStreamPropertyListener
    , AudioFileStreamPacketsListener, 0, &audioFileStreamID)
    }

我的第一个问题是我在这里使用的 withUnsafeMutablePointer 是否正确。我不确定如何获得指向 self 的指针 - 只写 &self 是行不通的,因为 self 是不可变的。所以我将 proxy 声明为一个变量并传递了一个对它的引用。我不知道这是否可行,但这是我想出的最好的主意。

接下来,AudioFileStreamPropertyListenerAudioFileStreamPacketsListenerC回调函数。他们每个人都获得了指向 self 的指针,这是我在 AudioFileStreamOpen 中使用 withUnsafeMutablePointer 创建的。指针作为 UnsafeMutablePointer<Void> 传入,我需要将其转换回我的 class (AudioFileStream) 的类型。为此,我相信我需要使用 unsafeBitCast。例如,这里是 AudioFileStreamPropertyListener:

let AudioFileStreamPropertyListener: AudioFileStream_PropertyListenerProc 
    = { inClientData, inAudioFileStreamID, inPropertyID, ioFlags in

        let audioFileStream = unsafeBitCast(inClientData, AudioFileStream.self)

        audioFileStream.didChangeProperty(inPropertyID, flags: ioFlags)
}

编译很好,但我再次不确定我是否正确使用了 unsafeBitCast,或者在这种情况下是否使用了正确的函数。那么,unsafeBitCast 是获取 UnsafeMutablePointer<Void> 并将其转换为您可以在 C 函数指针内部实际使用的类型的正确方法吗?

有趣的是,inClientData "context" 参数桥接为 UnsafeMutablePointer,因为我怀疑 AudioToolbox API 会修改您的数据。好像用COpaquePointer比较合适。可能想要 file a bug.

我认为您对 withUnsafeMutablePointer 的使用是错误的。指针 ([=18=]) 将是 变量 proxy 的地址,而不是您的实例的地址。 (例如,您可以说 [=20=].memory = [a new instance] 来将其更改为不同的实例。这有点令人困惑,因为它的类型是 UnsafeMutablePointer<MyClass> — 而在 Swift 中, class type 本身就是一个 pointer/reference 类型。)

我本来打算推荐你使用 Unmanaged / COpaquePointer,但我测试了它,并意识到这与 unsafeAddressOf(self)!

完全一样

这些是等价的:

let data = UnsafeMutablePointer<Void>(Unmanaged.passUnretained(self).toOpaque())
let data = unsafeAddressOf(self)

这些是等价的:

let obj = Unmanaged<MyClass>.fromOpaque(COpaquePointer(data)).takeUnretainedValue()
let obj = unsafeBitCast(data, MyClass.self)

虽然 Unmanaged 方法在逻辑上是合理的,但我认为您可以理解为什么使用 unsafeAddressOf/unsafeBitCast 可能更可取:-)

或者,为了您自己的方便,您可能会考虑在 Unmanaged 上进行扩展:

extension Unmanaged
{
    func toVoidPointer() -> UnsafeMutablePointer<Void> {
        return UnsafeMutablePointer<Void>(toOpaque())
    }
    static func fromVoidPointer(value: UnsafeMutablePointer<Void>) -> Unmanaged<Instance> {
        return fromOpaque(COpaquePointer(value))
    }
}

那么你可以使用:

let data = Unmanaged.passUnretained(self).toVoidPointer()
let obj = Unmanaged<MyClass>.fromVoidPointer(data).takeUnretainedValue()

当然,您需要确保您的对象在您期望它在回调中有效的持续时间内被保留。您可以使用 passRetained,但我建议让您的顶级控制器抓住它。

https://forums.developer.apple.com/thread/5134#15725 查看一些相关讨论。