[email protected]
Your Profile
Dimas Ashidiqi
Sunday, 14 January 2024
#AppKit

How to pass References from Swift to C Functions with Unmanaged Type

Discover how to pass references from Swift to C functions in your Swift app using Unmanaged type

How to pass References from Swift to C Functions with Unmanaged Type

For one of my macOS app, I want my non-active app to listen to and modified keyboard inputs. In Cocoa, we usually use the keyDown(with:) method to listen for shortcuts, but this will only work if our app is focused. Since my app is a background app, and it will never be activated, I can’t use this method.

So I turn to addGlobalMonitorForEvents(matching:handler:), but this also will not work because the docs say:

Events are delivered asynchronously to your app and you can only observe the event; you cannot modify or otherwise prevent the event from being delivered to its original target application.

So I dug deeper and found this old method: tapCreate(tap:place:options:eventsOfInterest:callback:userInfo:). This API is from Carbon, which is older than AppKit. Here, the callback parameter is a C function, which makes it hard to work with because C functions cannot “capture” the values they depend on like Swift’s Closures.

In Swift, closures not only contains code, but also the values it depends on.

class Foo {
    var bar = 1
    
    func printBarAfterDelay() {
        let closureToBeExecuted = {
            print(self.bar)
        }
        DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: closureToBeExecuted)
    }
}

In this code, we print bar after 1 second using DispatchQueue.main.asyncAfter. This method take a closure, and we pass the closureToBeExecuted closure. As you can see, the closureToBeExecuted access the bar property by capturing the self explicitly. Note that this may not the best way for working with closures, since it will capture self strongly and may result in strong reference cycle.

In C, there is no such thing like Swift closure. If you capture a value in your C functions, you will get error:

A C function pointer cannot be formed from a closure that captures context

Then I came across this StackOverflow post on how we can pass reference to C functions. Usually, C functions will have a parameter of an opaque pointer (void*). The idea is, you can convert the self reference into an UnsafeMutableRawPointer and pass it into the this opaque pointer parameter. Then, inside the callback, you can convert the UnsafeMutableRawPointer back into self.

To create an UnsafeMutableRawPointer from self, you can do this:

let userInfo = Unmanaged.passUnretained(self).toOpaque()

passUnretained() will create an Unmanaged, which is a type that let us manage the memory of an instance manually without Automatic Reference Counting (ARC). Then, the toOpaque() method will create an UnsafeMutableRawPointer. The method is named toOpaque because it returns an opaque pointer, which is just a generic pointer without a spesific type.

Then to convert the pointer back to its type, you can do:

let instance = Unmanaged<YOUR_TYPE>.fromOpaque(userInfo!).takeUnretainedValue()