9

Consider the private-yet-sort-of-documented Cocoa C functions _NSLogCStringFunction() and _NSSetLogCStringFunction(). _NSLogCStringFunction() returns a function pointer to the C function used by the Objective-C runtime behind-the-scenes for NSLog(), and _NSSetLogCStringFunction() allows developers to specify their own C function for logging. More information on both of these functions can be found in this Stack Overflow question and this WebObjects support article.

In C, I can pass in a NULL function pointer to _NSSetLogCStringFunction():

extern void _NSSetLogCStringFunction(void(*)(const char*, unsigned, BOOL));

_NSSetLogCStringFunction(NULL); // valid

However, I'm running into some issues when I try to do this in pure Swift:

/// Represents the C function signature used under-the-hood by NSLog
typealias NSLogCStringFunc = (UnsafePointer<Int8>, UInt32, Bool) -> Void

/// Sets the C function used by NSLog
@_silgen_name("_NSSetLogCStringFunction")
func _NSSetLogCStringFunction(_: NSLogCStringFunc) -> Void

_NSSetLogCStringFunction(nil) // Error: nil is not compatible with expected argument type 'NSLogCStringFunc' (aka '(UnsafePointer<Int8>, UInt32, Bool) -> ()')

If I try bypass this compile-time warning with unsafeBitCast, my program just crashes with EXC_BAD_INSTRUCTION (as expected, since the signature is wrong):

let nullPtr: UnsafePointer<Void> = nil
let nullFuncPtr = unsafeBitCast(nullPtr, NSLogCStringFunc.self)
_NSSetLogCStringFunction(nullFuncPtr) // crash

How do I represent a NULL function pointer to (void *) or (void(*)(const char *, unsigned, BOOL))/(UnsafePointer<Int8>, UInt32, Bool) -> Void in Swift?

Community
  • 1
  • 1
JAL
  • 41,701
  • 23
  • 172
  • 300
  • 3
    lol, instant downvote for whatever reason - there was not even time to read the entire question. – luk2302 Apr 28 '16 at 13:53
  • 1
    @luk2302 Guess I have a fan :) That's a new record for me, -1 in 44 seconds. – JAL Apr 28 '16 at 13:54

1 Answers1

5

The Swift mapping of the (Objective-)C declaration

extern void _NSSetLogCStringFunction(void(*)(const char*, unsigned, BOOL));

is

public func _NSSetLogCStringFunction(_: (@convention(c) (UnsafePointer<Int8>, UInt32, ObjCBool) -> Void)!)

The easiest solution would be to put the Objective-C extern declaration into an Objective-C header file and include that from the bridging header.

Alternatively, in pure Swift it should be

typealias NSLogCStringFunc = @convention(c) (UnsafePointer<Int8>, UInt32, ObjCBool) -> Void

@_silgen_name("_NSSetLogCStringFunction")
func _NSSetLogCStringFunction(_: NSLogCStringFunc!) -> Void

In either case, the function parameter is an implicitly unwrapped optional, and you can call it with nil. Example:

func myLogger(message: UnsafePointer<Int8>, _ length: UInt32, _ withSysLogBanner: ObjCBool) -> Void {
    print(String(format:"myLogger: %s", message))
}

_NSSetLogCStringFunction(myLogger) // Set NSLog hook.
NSLog("foo")
_NSSetLogCStringFunction(nil) // Reset to default.
NSLog("bar")

Output:

myLogger: foo
2016-04-28 18:24:05.492 prog[29953:444704] bar
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • Ugh, really? All I needed to do was change `Bool` to `ObjCBool`? -_- There are too many Bool types in Swift. Thanks Martin. – JAL Apr 28 '16 at 14:18
  • @JAL: And the `@convention(c)` and the implicitly unwrapped parameter. – Martin R Apr 28 '16 at 14:18
  • Actually, I don't think `ObjCBool` is required. Looks like I was just missing the `@convention(C)` and the implicitly unwrapped parameter. Using `Bool` with the other two seems to work. Are you seeing the same thing? – JAL Apr 28 '16 at 14:20
  • @JAL: That may be, but `ObjCBool` is what the "Generated Interface" shows for the Objective-C declaration, so that's what *I* would use. – Martin R Apr 28 '16 at 14:22
  • Yes, I see the same thing. But can't `ObjCBool` be used interchangeably with Swift's `Bool` type like `CBool`? Or is this a special case since `CBool` is a typealias of `Bool`. – JAL Apr 28 '16 at 14:23
  • 2
    @JAL: I wrote a bit about the various bools here: http://stackoverflow.com/a/33667761/1187415. `BOOL` is often mapped to `Bool`, but not always. I am not sure if that applies here for the function pointer. – But in my experience the "Generated Interface" shows the correct mapping. – Martin R Apr 28 '16 at 14:30