我正在尝试将C库用于应该在macOS和Linux上运行的机器人项目。我试图在作为库调用参数传递的C函数内调用Swift回调函数。
正如在那些答案中所建议的那样,我传入传递给C函数的userData
(或类似)对象,该函数可以调用Swift回调函数。
但是当我访问传递的userData
对象时,我在Thread 2: EXC_BAD_ACCESS (code=1, address=0x20)
函数的第二行得到了cHandler
错误。我无法弄清楚原因。
这里的代码:
public func subscribe(newMessageHandler: @escaping () -> Void) -> Result<Subscription> {
func cHandler(buffer: UnsafePointer<lcm_recv_buf_t>?, channel: UnsafePointer<Int8>?, userData: UnsafeMutableRawPointer?) {
guard let userData = userData else { return }
let subscribeUserData = Unmanaged<SubscribeUserData>.fromOpaque(userData).takeUnretainedValue()
subscribeUserData.handler()
}
let userData = SubscribeUserData(handler: newMessageHandler)
var userDataPointer = UnsafeRawPointer(Unmanaged.passUnretained(userData).toOpaque())
self.subscribeUserData = userData
self.subscribeUserDataPointer = userDataPointer
if let subscription = lcm_subscribe(context, "ExampleMessage", cHandler, &userDataPointer) {
return .success(subscription)
} else {
return .failure(nil)
}
}
这是SubscribeUserData
的定义,我在C函数中传递的对象:
typealias NewMessageHandler = () -> Void
/// User data object passed in the subscribe C handler function. Needed to pass in a Swift handler function.
class SubscribeUserData {
let handler: NewMessageHandler
init(handler: @escaping NewMessageHandler) {
self.handler = handler
}
}
感谢Andy给了我不同的建议让我解决了这个问题。
一个问题是我将UnsafeMutableRawPointer传递给前缀为&
运算符的cHandler函数。
第二个问题是我在cHandler函数内传递的对象被取消分配。所以保持对它的引用是至关重要的。
这里的工作代码:
public func subscribe(newMessageHandler: @escaping NewMessageHandler) -> Result<Subscription> {
func cHandler(buffer: UnsafePointer<lcm_recv_buf_t>?, channel: UnsafePointer<Int8>?, userData: UnsafeMutableRawPointer?) {
guard let userData = userData else { return }
let subscribeUserData = Unmanaged<SubscribeUserData>.fromOpaque(userData).takeUnretainedValue()
subscribeUserData.handler()
}
self.subscribeUserData = SubscribeUserData(handler: newMessageHandler)
let subscribeUserDataPointer = UnsafeMutableRawPointer(Unmanaged.passUnretained(subscribeUserData).toOpaque())
if let subscription = lcm_subscribe(context, "ExampleMessage", cHandler, subscribeUserDataPointer) {
return .success(subscription)
} else {
return .failure(nil)
}
}
感谢大家的帮助!
更新
仔细观察one of your examples,你似乎在做同样的事情,这让我想知道你的处理程序签名。
func cHandler(buffer: UnsafePointer<lcm_recv_buf_t>?, channel: UnsafePointer<Int8>?, userData: UnsafeMutableRawPointer?)
我发现了一个lcm_subscribe
typedef void(* lcm_msg_handler_t) (const lcm_recv_buf_t *rbuf, const char *channel, void *user_data)
仔细观察这个签名让我注意到两个问题:
&
。这是不正确的。要么对变量使用该运算符,要么按照您的方式创建指针(所以只需删除&
)。cHandler
的所有参数都被声明为选项。这也是错误的 - 选项是Swift的概念。您的回调签名是发布C可以理解的函数。func cHandler(buffer: UnsafePointer<lcm_recv_buf_t>, channel: UnsafePointer<Int8>, userData: UnsafeMutableRawPointer)
糟糕的第一个想法
我非常警惕这里的结构和生命。
代替
let userData = SubscribeUserData(handler: newMessageHandler)
var userDataPointer = UnsafeRawPointer(Unmanaged.passUnretained(userData).toOpaque())
self.subscribeUserData = userData
self.subscribeUserDataPointer = userDataPointer
获取指向您的成员的指针并立即使用返回的结构。
self.subscribeUserData = SubscribeUserData(handler: newMessageHandler)
self.subscribeUserDataPointer = UnsafeRawPointer(Unmanaged.passUnretained(self.subscribeUserData).toOpaque())
特别是,我认为危险是在passUnretained
中使用局部变量。
如果你认为内部模型是一个指向包含引用的位置的指针,那么它就更有意义了 - 你的原始文件正在获取指向堆栈上局部变量的指针。