Search code examples
cswiftmacosdylib

Swift: How to call a C function loaded from a dylib


Is there a way to call a C function loaded from a dylib from Swift?

This is my dylib file:

cppdemofile.cpp

#include "cppdemofile.h"

int add(int a, int b) {
    return a + b;
}

cppdemofile.h

#ifndef __CppDemoLibrary__cppdemofile__
#define __CppDemoLibrary__cppdemofile__

#pragma GCC visibility push(default)

extern "C" int add(int a, int b);

#pragma GCC visibility pop

#endif

compile to dylib and check:

nm -gU libCppDemoLibrary.dylib
0000000000000f80 T _add

... copy libCppDemoLibrary.dylib to ~/lib...

Swift program:

@IBAction func buttonClick(sender: NSButton) {
    let handle = dlopen("libCppDemoLibrary.dylib", RTLD_NOW)
    if (handle != nil) {
        var sym = dlsym(handle, "add")
        if (sym != nil) {
            let pointer = UnsafeMutablePointer<(CInt, CInt) -> CInt>(sym)

            // When debugging, I'm reaching up to this point...
            // but now, how do I call the 'add' function here???
            // var result = ???

            // label.stringValue = "Total: " + String(result)
        }
    }
}

How do I call the add function? Is it ok to use a dylib? Should I instead add these sources to my swift project?


Solution

  • Calling the add function from Swift is possible because you defined it to have C linkage with extern "C".

    Making the library a Swift module (as suggested by jtbandes in above comments) might be the better solution, but here is how you can use the function pointer return by dlsym() from Swift:

    First add

    typedef int(*addFunc)(int, int);
    

    to the bridging header file, or alternatively define

    typealias addFunc = @convention(c) (CInt, CInt) -> CInt
    

    in Swift. Then the following works:

    let handle = dlopen(path, RTLD_NOW)
    if (handle != nil) {
        var sym = dlsym(handle, "add")
        if (sym != nil) {
            let f = unsafeBitCast(sym, addFunc.self)
            let result = f(12, 45)
            print(result)
        }
        dlclose(handle)
    }
    

    Of course this will crash if addFunc does not match the actual signature of the loaded function.


    Update for Swift 3:

    if let handle = dlopen(path, RTLD_NOW) {
        if let sym = dlsym(handle, "add") {
            let f = unsafeBitCast(sym, to: addFunc.self)
            let result = f(12, 45)
            print(result)
        }
        dlclose(handle)
    }