drummingdemon drummingdemon - 1 month ago 21
Swift Question

Using a C API function in Swift 3 that has a callback function pointer as an argument

I'm building a wrapper/interface for a C library with Swift 3. One of the functions that I need to call require a function pointer for callback as an argument.

In detail: after said function has successfully done the file operation it has to, it then calls the function the given argument pointer refers to - essentially letting me do other operations with said data

The function looks like this:

HSYNC MXDEF (Syncer)(DWORD h, DWORD t, QWORD p, SYNCPROC *proc, void *user);


The callback function type
SYNCPROC
is defined as follows:

typedef void (CALLBACK SYNCPROC)(HSYNC h, DWORD c, DWORD d, void *user);


I have only been able to use the
Syncer
function with setting the callback argument to nil so far.

I tried creating a function outside the Class as someone suggested:

func callbackCalled(handle: HSYNC, channel: DWORD, data: DWORD, user: UnsafeMutableRawPointer) -> Void {
print("callback called")
}


And I even tried this method within the Class:

var callbackTest : @convention(c) (_ handle: HSYNC, _ channel: DWORD, _ data: DWORD, _ user: UnsafeMutableRawPointer) -> Void = {
print("\($0) \($1) \($2) \($3)")
}


But however I tried / read upon this topic I always end up with this error message:

Cannot convert value of type 'Void' (aka '()') to expected argument type '(@convention(c) (HSYNC, DWORD, DWORD, UnsafeMutableRawPointer?) -> Void)!'


My question is: How am I supposed to satisfy the criteria regarding this type of a callback function?

I haven't been able to find any info regarding these types of callback functions, presumably due to my lack of knowledge and thorough understanding of the problem. Thank you in advance!

Answer

Passing a global function as callback should work, but the last parameter must be an optional UnsafeMutableRawPointer?:

func callbackCalled(handle: HSYNC, channel: DWORD, data: DWORD, user: UnsafeMutableRawPointer?) -> Void {
    print("callback called")
}

Syncer(h, c, d, callbackCalled, u)

Alternatively, pass a closure expression (and let compiler infer the types of the parameters):

Syncer(h, d, c, {
    (handle, channel, data, user) in
    // ...
}, u)

You can not pass an instance method as a callback, compare Swift - Proper use of CFNotificationCenterAddObserver w/callback.

If NULL is not a valid value for the user parameter then you can add "Nullability annotations" to the C declaration, e.g.

typedef void (CALLBACK SYNCPROC)(HSYNC h, DWORD c, DWORD d, void * _Nonnull user);

and then it will be represented as a non-optional UnsafeMutableRawPointer in Swift.