Interacting with C Pointers in Swift. Part 3: using CFunctionPointer
Hi there!
In one of my previous posts (see Interacting with C Pointers in Swift. Part 2), I told you how the work with pointers in Swift is organized, and I also mentioned CFunctionPointer type. Let's scrutinize this type today and see why we actually need it.
In “Using Swift with Cocoa and Objective-C. Interacting with C APIs” doc, CFunctionPointer type is described in few words only:
C function pointers are imported into Swift as CFunctionPointer, where Type is a Swift function type. For example, a function pointer that has the type int (*)(void) in C is imported into Swift asCFunctionPointer<() -> Int32>.
The same way as pointers to variables let us get values of variables they are pointing to, pointers to functions let calling the corresponding functions.
int getNextRandomValue() { srand(time(NULL)); int randomNumber = rand() % 100 + 1; printf("New random number: %i\n",randomNumber); return randomNumber; }
The pointer to this function in C would have int (*)(void) type, while in Swift it will have CFunctionPointer<() -> Int32> type. To create CFunctionPointer, COpaquePointer is needed, e.g.:
let pointer = UnsafeMutablePointer<() -> Int32>.alloc(1) pointer.initialize(getNextRandomValue) let cPointer = COpaquePointer(pointer) let functionPointer = CFunctionPointer<() -> Int32>(cPointer)
To call a function via a pointer to it, do the following:
let newCPointer = COpaquePointer(functionPointer) let newPointer = UnsafeMutablePointer<() -> Int32>(newCPointer) let rNumber = newPointer.memory()
Ok. Now let's turn our eyes to callback functions. Pointers to functions are valued as they can be passed as parameter to other functions. A function passed to another function for calling as pointer is called a callback function. Suppose, we need to track when new disk or partition is created.
Let's create Disk Arbitration session, register DADiskAppearedCallback and pass the object for handling:
func gotDisk(disk: DADisk!, context: UnsafeMutablePointer) { NSLog("Got new disk: \(DADiskGetBSDName(disk))") } … var session = DASessionCreate(kCFAllocatorDefault).takeRetainedValue() let p = UnsafeMutablePointer<((DADisk!, UnsafeMutablePointer) -> Void)>.alloc(1) p.initialize(gotDisk) let cp = COpaquePointer(p) let fp = DADiskAppearedCallback(cp) DARegisterDiskAppearedCallback( session, kDADiskDescriptionMatchVolumeMountable.takeRetainedValue(), fp, nil) /* Schedule a disk arbitration session. */ DASessionScheduleWithRunLoop(session, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode) ...
On handling stage we will get EXC_BAD_ACCESS exception.
Let's see what caused the exception. We'll create our own C-function, which takes pointer of void f(void) type:
void executeFunction(void(*f)(void)) { f(); }
Call this function from Swift, passing a pointer to greeting function as parameter:
func greeting() { NSLog("Hello from Swift") } ... let p = UnsafeMutablePointer<()->()>.alloc(1) p.initialize(greeting) let cp = COpaquePointer(p) let fp = CFunctionPointer<()->()>(cp) executeFunction(fp) ...
On handling stage we'll get EXC_BAD_ACCESS exception again.
In debugger we see that instead of greeting function address, CFunctionPointer address is returned to executeFunction method:
Thus, it seems to be impossible to call a function via CFunctionPointer.
Hope this problem will be resolved in the nearest future. I'll keep you updated.