5

A have a structure like this (defined in bson.h of mongodb c driver):

typedef struct
{
  uint32_t domain;
  uint32_t code;
  char message[504];
} bson_error_t;

In Swift I have a pointer to this structure like this:

err: UnsafePointer<bson_error_t> = ...

Now whatever I do I cannot convert message[504] (which Swift sees as a tuple of (Int8, Int8, Int8, ...504 times)) to char* to use it in String.fromCString(). Is it even possible to do that in Swift? As a temporary solution I created a helper C function in a separate .c file which takes err *bson_error_t and returns char*, but this is weird if Swift cannot do it by itself.

Uniqus
  • 564
  • 1
  • 5
  • 21
  • I get the feeling they bridge arrays to giant-friggin-tuples because access matters significantly more than copying this stuff out as one contiguous array. Any way you cut it you'll need to iterate over this and marshal it into the proper mallocated memory, so the C function, like it or not, is your best bet. – CodaFi Nov 03 '14 at 06:42
  • Well, I don't see any point in converting a C char array (or any C array) to a tuple for accessibility's sake. Arrays are arrays, tuples are tuples. I can't remember a case when I used an array for accessing elements with hard-coded indexes. For that purposes tuples and structs do exist, not arrays. – Uniqus Nov 03 '14 at 11:20

3 Answers3

3

It's not pretty, not intuitive, but it's doable. Purely in Swift, no C glue code needed. A minimal demo:

b.h

typedef struct {
    int n;
    char s[8];
} Bridged;

Bridged *make_b(void);

b.c

#include <stdlib.h>
#include <string.h>
#include "b.h"

Bridged *make_b(void)
{
    Bridged *p = calloc(sizeof(*p), 1);
    memcpy(p->s, "foobarz", 8);
    return p;
}

b.swift:

// half compile-time, half run-time black magic
func toCharArray<T>(t: T) -> [CChar] {
     var a: [CChar] = []
     let mirror = reflect(t)
     for i in 0 ..< mirror.count {
         a.append(mirror[i].1.value as CChar)
     }
     return a
}

let b = make_b().memory.s     // bridged tuple of 8 chars
let a = toCharArray(b)        // Swift array of (8) CChars
let s = String.fromCString(a) // proper Swift string

println(s)

Compile:

$ xcrun swiftc -O -c b.swift -import-objc-header b.h
$ clang -O2 -c b.c -o b.c.o
$ xcrun swiftc b.o b.c.o -o b

Run:

$ ./b
Optional("foobarz")
1

Here my suggestion (similar to rintaro's approach, perhaps slightly simpler):

var err: UnsafeMutablePointer<bson_error_t> = ...

var msg = err.memory.message
let msgString = withUnsafePointer(&msg) { String.fromCString(UnsafePointer($0)) }
println(msgString)
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • Thank you. I chose your answer as it's the shortest one. I tried to do the same thing, but without the intermediate `var msg`, and it didn't compile with just `&err.memory.message`. – Uniqus Nov 03 '14 at 11:05
0

Quick hack to retrieve message String from bson_error_t:

extension bson_error_t {
    mutating func messageString() -> String? {
        return String.fromCString(
            { (p:UnsafePointer<Void>) in UnsafePointer<CChar>(p) }(&self.message.0)
        )
    }
}

// Usage:
var err: UnsafeMutablePointer<bson_error_t> = ...
...
let errMessage = err.memory.messageString()
rintaro
  • 51,423
  • 14
  • 131
  • 139