从 Cocoa 中的 C api 调用 Swift 闭包时出现错误访问错误

Bad Access error when calling Swift closure from C api in Cocoa

我正在尝试在 macOS 上的 Swift 中实现 "file copy with progress"。 经过大量搜索,我刚刚找到 rustle's implement in Objective-C。 它工作得很好。 但我愿意"swifty"。 我用一些简化的代码试了一下:

import Cocoa

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    var copyfileCallback: copyfile_callback_t = {(what, stage, state, sourcePath, destPath, context) -> Int32 in
        return COPYFILE_CONTINUE
    }

    @IBOutlet weak var window: NSWindow!

    func applicationDidFinishLaunching(_ aNotification: Notification) {

        let src = NSURL(fileURLWithPath: "Source_File_Path").fileSystemRepresentation
        let dst = NSURL(fileURLWithPath: "Destination_File_Path").fileSystemRepresentation
        let flag: copyfile_flags_t = UInt32(COPYFILE_ALL)

        let state = copyfile_state_alloc()

        // If I implement this, the copyfile() method will complain "EXC_BAD_ACCESS(code=2..." error
        copyfile_state_set(state, UInt32(COPYFILE_STATE_STATUS_CB), &copyfileCallback)

        copyfile(src, dst, state, flag)
    }
}

基本功能copyfile()工作正常。但是,如果我通过向 copyfile_state_set() 提供 copyfileCallback 闭包指针来实现回调函数,那么 copyfile() 只会抱怨 "Bad_Access..."。 我想也许闭包是在 C api 尝试访问它之前发布的。 但我不知道如何解决这个问题...... 任何线索将不胜感激。

错误在这里:

copyfile_state_set(state, UInt32(COPYFILE_STATE_STATUS_CB), &copyfileCallback)

因为是将copyfileCallback变量的地址传递给函数,而不是函数指针本身。在 C 中,您可以将任意函数作为 void * 参数传递。在 Swift 中,您必须将函数显式转换为指针:

let state = copyfile_state_alloc()
copyfile_state_set(state, UInt32(COPYFILE_STATE_STATUS_CB),
                   unsafeBitCast(copyfileCallback, to: UnsafeRawPointer.self))

并且不要忘记在复制操作之后最终释放内存:

copyfile_state_free(state)

备注:在Swift中建议使用(值叠加类型)URL而不是NSURL

let srcURL = URL(fileURLWithPath: "Source_File_Path")
let destURL = URL(fileURLWithPath: "Destination_File_Path")

let result = srcURL.withUnsafeFileSystemRepresentation { srcFile in
    destURL.withUnsafeFileSystemRepresentation { destFile in
        copyfile(srcFile, destFile, state, flag)
    }
}

除了 Martin 的有效答案之外,您还可以将 class 的实例(自身)作为传递。 'context' 和 COPYFILE_STATE_STATUS_CTX.

所以假设你的 class 被称为 'Copier' 并且它的回调函数被称为 'asyncCallback',你会像这样设置它 - 并释放 class' s 状态变量在其 deinit 中具有 copyfile_state_free'

    private func createState () throws {
        guard let state = copyfile_state_alloc() else {
            throw CopyFileError.cantCreateState
        }

        let callback: copyfile_callback_t = { what, stage, state, src, dst, context in
          let obj = Unmanaged<Copier>.fromOpaque(context!).takeUnretainedValue()
          return obj.asyncCallback(what: what, stage: stage, state: state!, src: src, dst: dst)
        }

        copyfile_state_set(state, UInt32(COPYFILE_STATE_STATUS_CB), unsafeBitCast(callback, to: UnsafeRawPointer.self))

        let context = Unmanaged.passUnretained(self)
        copyfile_state_set(state, UInt32(COPYFILE_STATE_STATUS_CTX), context.toOpaque())

        self.state = state
    }