0
votes

I just started with with Swift this week, specifically Swift 4, and I'm using a C library through a bridging header, liblo, which handles sending/receiving OSC (Open Sound Control) formatted messages over a network socket.

I'm able to start a server thread, receive OSC messages via C callback->Swift closure, and read the numeric argument values with Swift just fine. I'm running into trouble, however, with reading string values.

The liblo message argument type lo_arg is a C typedef for a union and the string argument types are declared as simple chars which are mapped to Swift as Int8.

In C, you can grab the string via &argv[i]->s from your callback's lo_arg **argv array. In an Obj-C project with liblo, I use:

// get string value of first argument
lo_arg arg = argv[0];
NSString *s = [NSString stringWithUTF8String:&arg->s];
// do something with s

In Swift, I've tried getting the address of the Int8 and feeding it to String which works, but only grabs the first character:

// get string value of first argument
if var arg : lo_arg = argv?.pointee![0] {
    withUnsafePointer(to: &arg.s) {
        let s = String(cString: $0)
        // so something with s
    }
}

Am I doing something wrong? I would think these would be equivalent but passing $0 to strlen() ala print("strlen: \(strlen($0)") only prints a length of "1". I've verified a multi-character string is indeed being sent with a non-Swift test program. I'm wondering now if Swift is somehow assuming the string is a single character instead of C string head address and/or I need some further pointer conversion.

1

1 Answers

0
votes

After some digging, I can confirm Swift truncates the lo_arg->s & lo_arg->S string values to 8 bytes on my 64 bit system aka sizeof(char). This happens when trying to read the string from an lo_arg coming from Swift. Reading the same value in C works fine, so Swift seems to reserve/allow reading from only the space for a single char. Forwarding the lo_arg from Swift to C and printing the string via printf() also shows truncated strings up to 8 characters.

The quick fix is to avoid reading the strings from the lo_arg generated by Swift, grab an lo_arg from the raw lo_message in C, and cast the char "pointer" to a const char* Swift will understand as a variable length string. Here are some working utility functions I added to my bridging header:

/// return an lo_message argv[i]->s in a format Swift can understand as a String
const char* lo_message_get_string(lo_message message, int at) {
    return (const char *)&lo_message_get_argv(message)[at]->s;
}

/// return an lo_message argv[i]->S in a format Swift can understand as a String
const char* lo_message_get_symbol(lo_message message, int at) {
    return (const char *)&lo_message_get_argv(message)[at]->S;
}

In Swift, I can then convert to a String:

let s = String(cString: lo_message_get_string(msg, 0))
// do something with s