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

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. I am currently testing on macOS.

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

class SubscribeUserData {
    let handler: NewMessageHandler
    init(handler: @escaping NewMessageHandler) {
        self.handler = handler
    }
}

@ccarnino: check these files out:
(1) https://github.com/Zewo/Zewo/blob/0.16.1/Sources/HTTP/Parser/Parser.swift
and
(2) https://github.com/Zewo/Zewo/blob/0.16.1/Sources/CHTTPParser/include/http_parser.h

The code from (1) instantiates a C struct from Swift, and the C struct from (2) will then call Swift functions as callbacks.

In (1), at line 111, a C struct (http_parser_settings) is instantiated in a Swift class, and the swift callback functions are assigned from lines 114 to 121. That structure (http_parser_settings) is defined in (2) at line 248. It’s a collection of callback function pointers (defined in (2) at lines 84 and 85).

In (1), from lines 249, the swift function used as callbacks are defined.

it works both on MacOS and Linux.

HTH.

Thanks Alex for the answer!
I tried to replicate the same setup, but without success.

I have defined the following types in a C header file:

typedef void (*subscribe_new_message_handler) (void);

struct subscribe_user_data {
    subscribe_new_message_handler    handler;
};

typedef struct subscribe_user_data subscribe_user_data;

The updated my code as follows:

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<subscribe_user_data>.fromOpaque(userData).takeUnretainedValue()
        subscribeUserData.handler()
    }
    
    var userData = subscribe_user_data()
    userData.handler = newMessageHandler
    
    if let subscription = lcm_subscribe(context, "ExampleMessage", cHandler, &userData) {
        return .success(subscription)
    } else {
        return .failure(nil)
    }
}

The new type is recognized, but only in the subscribe function scope. Not inside the cHandler function.
And also the new function type singature does not match the newMessageHandler one.

Here you can see details regarding the error: https://imgur.com/a/e1cl4

Annotate both cHandler and newMessageHandler with @convention(c) to let the Swift compiler know it shouldn’t capture any outer-scope variables.

Also, with regards to the error with Unmanaged – since you’re declaring subscribe_user_data as a C struct, you no longer need to use the Unmanaged APIs. Call userData.assumingMemoryBound(to: subscribe_user_data.self) and you’ll get an UnsafeMutablePointer<subscribe_user_data> that you can dereference.

The goal is to call a Swift function (newMessageHandler which captures the context) from a C function (cHandler which does not allow capturing the context).

Annotating newMessageHandler with @convention(c) just makes it into a C function, which prevents me from achieving my goal.

Thanks for the suggestion, but this is not solving my problem.

PS: the suggested way to load the subscribe_user_data struct inside the C function works. Thank you!

Ah! Well, if your goal is for newMessageHandler to capture context, then you will have to put it in a Swift type, rather than a C type. Go back to your old version with the UserData class and Unmanaged, and make sure to annotate cHandler with @convention(c).

Thank you Harlan and Alex for the help.
I have figure out why it was not working and how to make it work.

Here you find the details:

Thank you all!

More of a general question. Does stack overflow provide the ability to say the swift version you are working with?

Well not really. When I created the initial post I can add tags. I just added the tag Swift. I could have added the Swift 4 tag, but I felt it was not necessary.

Thanks!