Passing a Swift function as an OpaquePointer to another C function

Given (this is the libxml2 C library):

void xmlSetGenericErrorFunc(void * ctx, xmlGenericErrorFunc handler)
//Function type: xmlGenericErrorFunc
//void xmlGenericErrorFunc(void * ctx, const char * msg, ...)

I'm trying to do:

private func handleValidationError(ctx: OpaquePointer, msg: UnsafePointer<CChar>, args: CVarArg...) {
    NSLog("xx validation error handler called")
}

then:

xmlSetGenericErrorFunc(nil, handleValidationError)

and I'm getting this:

cannot convert value of type '(OpaquePointer, UnsafePointer, CVarArg...) -> ()' (aka '(OpaquePointer, UnsafePointer, CVarArg...) -> ()') to expected argument type 'xmlGenericErrorFunc?' (aka 'Optional')

I suppose I need to do something like this instead:

xmlSetGenericErrorFunc(nil, OpaquePointer(handleValidationError)) //I can't figure out how to create an OpaquePointer instance here

How should I write and pass in the callback?

You aren't going to be able to interface with this API from Swift, unfortunately, since Swift cannot use C variadic functions. You'll need to write a C wrapper function to interface with the API and then call into that from Swift.

Ah. Just for the sake of education. If the callback isn't a variadic function, how would we get it to work? (I couldn't find any docs or anything online that showed how to do it).

Normally, what you can do is create a C function pointer by specifying a function variable with @convention(c) in Swift:

func add(x: Int, y: Int) -> Int { return x + y }

let addp: @convention(c) (Int, Int) -> Int = add

If you need to pass this into C as a non-function pointer, you can unsafeBitCast it:

let addRawPointer = unsafeBitCast(add, to: UnsafeRawPointer)
2 Likes

Is this why we don’t have printf in Glibc? The %.2f formatter is still one of the few things that C includes, but Swift does not.

For printf, there is at least also vprintf, an alternative entry point that takes a va_list. You should be able to use the withVaList standard library function to pass down a va_list.

3 Likes

did not even know that existed,, thanks!

Here's an implementation of printf based on vprintf:

import func Foundation.vprintf

@discardableResult
public func printf(_ format: String, _ args: CVarArg...) -> Int {
    return Int(withVaList(args) { vprintf(format, $0) })
}
1 Like