Why does C-callback function crashes my app?

I have Swift based project that uses a (third party) C-based library

I can use all the code from the library without any problems

Only the event handling I can't get to work:


The library defines a type for any callback functions:


  typedef void (*callBackFunctionType)(SubEventType subEventType,
                                             UInt32 param2,
                                             UInt32 param3);


And a function to register an event listener and the callback to be used with it:


  SHARED_FUNCTION void addEventListener(void * eventCallback, TYPOFEvent eventType);



On the Swift side I try to implement this as follows:


  // Global scope
  public typealias swiftCallbackFunctionType = @convention(c) (SubEventType , UInt32, UInt32) -> Void

  func callBackFunction(subEvent: SubEventType, param2: UInt32, param3: UInt32){
    print("Callback function gets called")
  }

  var callBackFuctionPointer:swiftCallbackFunctionType = callBackFunction



  // Class scope
  class func registerForEvents(){
        addEventListener(&callBackFuctionPointer, DEVICE_POWER_UP)
  }



however my app crashes when an DEVICE_POWER_UP-event gets fired

Answered by OOPer in 282891022

Unrecommended Swift-only solution :

    class func registerForEvents(){
        let callBackFunction: callBackFunctionType = {subEventType, param1, param2 in
            print("Callback function gets called")
        }
        addEventListener(unsafeBitCast(callBackFunction, to: UnsafeMutableRawPointer.self) , DEVICE_POWER_UP)
    }

(Assuming the `callBackFunctionType` is imported into Swift.)


But the C-wrapper would not be so difficult...

EventListenerWrapper.h:

#ifndef EventListenerWrapper_h
#define EventListenerWrapper_h
#include <stdio.h>
#include "EventListener.h"
SHARED_FUNCTION void addEventListenerWrapped(callBackFunctionType eventCallback, TYPOFEvent eventType);
#endif /* EventListenerWrapper_h */

EventListenerWrapper.c:

#include "EventListenerWrapper.h"
void addEventListenerWrapped(callBackFunctionType eventCallback, TYPOFEvent eventType) {
    addEventListener(eventCallback, eventType);
}

(You may need to modify some parts according to the actual headers of the library.)


From Swift:

    class func registerWrappedForEvents(){
        let callBackFunction: callBackFunctionType = {subEventType, param1, param2 in
            print("Callback function gets called")
        }
        addEventListenerWrapped(callBackFunction, DEVICE_POWER_UP)
    }

You can avoid using horrible `unsafeBitCast`.

You are, alas, definitely off in the weeds here. The correct approach for this sort of thing is to pass your closure directly to the C function that takes a callback. For example:

var years = [2048, 1984, 1941]
qsort(&years, years.count, MemoryLayout.size(ofValue: years[0]), { (lPtrRaw, rPtrRaw) -> Int32 in
    let l = lPtrRaw!.assumingMemoryBound(to: Int.self).pointee
    let r = rPtrRaw!.assumingMemoryBound(to: Int.self).pointee
    if l < r {
        return -1
    } else if l > r {
        return 1
    } else {
        return 0
    }
})
print(years)    // prints: [1941, 1984, 2048]

IMPORTANT This code is obviously madness given that Swift has built-in sorting facilities. It’s intended as an example of how to deal with this specific problem, providing a C callback in Swift, not as an example of good Swift.

Here Swift recognises that the callback is using C calling conventions and sets up the closure correctly. For example, the following code refuses to compile with the error a C function pointer cannot be formed from a closure that captures context.

var years = [2048, 1984, 1941]
var callCount = 0
qsort(&years, years.count, MemoryLayout.size(ofValue: years[0]), { (lPtrRaw, rPtrRaw) -> Int32 in
    callCount += 1
    return -1
})

The problem you have is that the C code defines the

addEventListener
function to take its callback as
void *
. That prevents you from calling it directly from Swift. If you can edit the C header then the easiest way to fix this problem is to fix the C prototype of
addEventListener
.

If that’s not an option then my recommendation is that you define your own C wrapper around

addEventListener
that takes the right type. I think it’s possible to implement a Swift-only solution to this, but a C wrapper is by far the most straightforward solution. And folks maintaining this project years from now (which might be Future You™) will think you keeping it simple (-:

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Eskimo thnx for the reply.


But what would such a C-wrapper look like?

I really struggle with C and all those pointers that go with it.

It is the final part of my app just need to get this one going :-/

Accepted Answer

Unrecommended Swift-only solution :

    class func registerForEvents(){
        let callBackFunction: callBackFunctionType = {subEventType, param1, param2 in
            print("Callback function gets called")
        }
        addEventListener(unsafeBitCast(callBackFunction, to: UnsafeMutableRawPointer.self) , DEVICE_POWER_UP)
    }

(Assuming the `callBackFunctionType` is imported into Swift.)


But the C-wrapper would not be so difficult...

EventListenerWrapper.h:

#ifndef EventListenerWrapper_h
#define EventListenerWrapper_h
#include <stdio.h>
#include "EventListener.h"
SHARED_FUNCTION void addEventListenerWrapped(callBackFunctionType eventCallback, TYPOFEvent eventType);
#endif /* EventListenerWrapper_h */

EventListenerWrapper.c:

#include "EventListenerWrapper.h"
void addEventListenerWrapped(callBackFunctionType eventCallback, TYPOFEvent eventType) {
    addEventListener(eventCallback, eventType);
}

(You may need to modify some parts according to the actual headers of the library.)


From Swift:

    class func registerWrappedForEvents(){
        let callBackFunction: callBackFunctionType = {subEventType, param1, param2 in
            print("Callback function gets called")
        }
        addEventListenerWrapped(callBackFunction, DEVICE_POWER_UP)
    }

You can avoid using horrible `unsafeBitCast`.

Works like a charm. App now up and running 🙂

Why does C-callback function crashes my app?
 
 
Q