Search code examples
ioscswiftmacosdylib

How can I pass complex swift datatypes to C datatypes when calling native functions in iOS/MacOS?


I managed to get a basic dylib that I have included in a framework that allows me to pass in an Int and returns and Int working but how would I pass and return more complex datatypes like pointer, byte arrays or actual data structures from swift to the C dylib?

Are there any tutorials or resources to map/pass/convert from swift datatype to to C datatype and vice versa like in JNI for java?


Solution

  • An example of interpreting a C struct and dynamically extract functions from the c-dylib on Swift side could look like this:

    .c File

    Person *get_person() {
        Person *p = malloc(sizeof(Person));
        p->first_name = strdup("Branford");
        p->last_name = strdup("Marsalis");
        p->age = 60;
        return p;
    }
    
    void free_person(Person *person) {
        free(person->first_name);
        free(person->last_name);
        free(person);
    }
    

    .h File

    typedef struct {
        char *first_name;
        char *last_name;
        int age;
    } Person;
    

    Swift

    typealias getPersonFunc = @convention(c) () -> UnsafeMutablePointer<Person>
    typealias freePersonFunc = @convention(c) (UnsafeMutablePointer<Person>) -> Void
    
    ...
    
    let handle = dlopen("libsimple.dylib", RTLD_LOCAL|RTLD_NOW)
    let get_person_sym = dlsym(handle, "get_person")
    let getPerson = unsafeBitCast(get_person_sym, to: getPersonFunc.self)
    let cPerson = getPerson()
    let person = cPerson.withMemoryRebound(to: Person.self, capacity: 1) { $0.pointee }
    let firstName = String(cString: UnsafeRawPointer(person.first_name).assumingMemoryBound(to: CChar.self))
    let lastName = String(cString: UnsafeRawPointer(person.last_name).assumingMemoryBound(to: CChar.self))
    
    print(firstName)
    print(lastName)
    print(person.age)
    
    let free_person_sym = dlsym(handle, "free_person")
    let freePerson = unsafeBitCast(free_person_sym, to: freePersonFunc.self)
    freePerson(cPerson)
    
    dlclose(handle)
    

    Test

    Output on the debug console for this example would look like:

    Branford
    Marsalis
    60
    

    From Swift to C

    Assume this in .c:

    void print_person(Person *person) {
        printf("%s %s is %d years old\n",
               person->first_name,
               person->last_name,
               person->age);
    }
    

    Then on Swift side one could write:

    typealias printPersonFunc = @convention(c) (UnsafeMutablePointer<Person>) -> Void
    ...
    let newPerson = UnsafeMutablePointer<Person>.allocate(capacity: 1)
    newPerson.pointee.first_name = UnsafeMutablePointer<Int8>(mutating: ("Norah" as NSString).utf8String)
    newPerson.pointee.last_name = UnsafeMutablePointer<Int8>(mutating: ("Jones" as NSString).utf8String)
    newPerson.pointee.age = 41
    let print_person_sym = dlsym(handle, "print_person")
    let printPerson = unsafeBitCast(print_person_sym, to: printPersonFunc.self)
    printPerson(newPerson)
    newPerson.deallocate()
    

    This would give the following output on the console:

    Norah Jones is 41 years old