Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A C function pointer cannot be formed from a local function that captures context on Swift Package

Tags:

swift

I am trying to use a C library for a robotic project which should run on both macOS and Linux. I am trying to call a Swift callback function inside the C function passed as a parameter to the library call.

I tried the solutions proposed here and here, but they do not work.

As suggested in those answers, I pass in the userData (or similar) object passed to the C function, an object which can call the Swift callback function.

But when I access the passed userData object I get a Thread 2: EXC_BAD_ACCESS (code=1, address=0x20) error on the second line of cHandler function. And I am not able to figure out why.

Here the code:

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

Here is the definition of SubscribeUserData, the object that I pass in the C function:

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
    }
}
like image 379
Cla Avatar asked Oct 16 '25 13:10

Cla


1 Answers

Thanks to Andy for giving me different advice which made me solve this issue.

One problem was that I was passing the UnsafeMutableRawPointer to the cHandler function prefixed with the & operator.

The second problem is that the object I pass inside the cHandler function was getting deallocated. So keep a reference to it is essential.

Here the working code:

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

Thanks to everyone for the help!

like image 155
Cla Avatar answered Oct 18 '25 09:10

Cla



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!