Foreign Callbacks via Raw Pointers

I am embedding a dynamic programming language inside my Swift application. Said programming language's runtime runs in a background thread and my app normally communicates with it asynchronously via pipes. Now, I have a need for the programs written in that language to be able to occasionally execute Swift callbacks. So, I have the following mechanism for installing these callbacks:

public func installCallback(id: CallbackID, proc: @escaping (InputPort) -> Void) {
  callbacks[id] = proc
  let addr = Int(bitPattern: unsafeBitCast(callbackHandler, to: Optional<UnsafeRawPointer>.self)!)
  registerWithOtherLang(id, Int64(addr))
}

fileprivate typealias CallbackID = UInt64
fileprivate var callbacks = [CallbackID: (InputPort) -> Void]()
fileprivate let callbackHandler: @convention(c) (CallbackID, Int, UnsafePointer<CChar>) -> Void = ...

Where the callbackHandler procedure looks up a callback by its id when called from the other side and applies it to the data it receives (handler args 2 and 3 are a size and data, respectively). The registerWithOtherLang procedure instructs the other language's runtime to keep track of a foreign procedure (from its perspective) with the address of the callback handler.

This works reliably, but I'm new to Swift and I'm concerned I don't know what I don't know. Specifically, I wonder if callbackHandler's address could change at runtime or if there are any other problems with this approach.

If you're looking for more information, I have How do closures work (memory management) and maybe this post also lightly touches the topic Dynamically loading a Swift `shared object` / `dylib` .

At the first glance I don't see anything wrong (albeit the callback casting seems a little clumsy to me). The context of those Swift closures never "leaves" the Swift environment - worst case scenario: those closures are never released by the dictionary that retains them.

My only concern would be, that if you're in multi-threaded environment, the access to the callbacks dictionary should be done in a safe manner.

1 Like

Thanks! I'll check out those threads. Good point about access to callbacks. I'm not too worried about retaining the closures perpetually since installCallbacks will only be used at most once or twice for the life time of the app and the closures themselves won't capture much.

1 Like