How to call c function from .dylib file in SPM package swift 5

There’s two parts to this:

  • Calling a C dynamic library from Swift (A)

  • Doing that in SPM (B)

As I’ve mentioned on an earlier threads, I can’t help with B. However, that thread does have some suggestions from others about how to proceed.

With regards A, I tested this here in my office today using the following steps:

  1. Using Xcode 11.1 GM seed on macOS 10.14.6, I created a new project macOS > App template.

  2. Within that project I created a new target from the macOS > Library template, selecting None (Plain C/C++) in the Framework popup and Dynamic in the Type popup.

  3. In the library target, I created a file from the macOS > C File template, making sure that “Also create a header file” was checked.

  4. In the header I added this code:

     struct WaffleVarnish {
         int glossiness;
         int durability;
     };
     typedef struct WaffleVarnish WaffleVarnish;
    
     extern WaffleVarnish waffleVarnishDoubleDurability(WaffleVarnish existing);
    

    .

  5. In the C file I added this code:

     extern WaffleVarnish waffleVarnishDoubleDurability(WaffleVarnish existing) {
         existing.durability *= 2;
         existing.glossiness /= 2;
         return existing;
     }
    

    .

  6. I built the library target, just to make sure it was working.

  7. I switched to the app target and added a target dependency on the library target.

  8. I then created a Copy Files build phase to copy the library to the Frameworks directory of the app.

  9. Still in the app target, I use the macOS > Cocoa Class template to create an Objective-C class called Hack. This is an easy way to trigger the creation of a bridging header.

  10. I then deleted Hack.h and Hack.m because I don’t need them.

  11. In the bridging header I added this:

    #include "QTestLib.h"
    
    typedef WaffleVarnish (*WaffleVarnishDoubleDurabilityFunctionPointer)(WaffleVarnish existing);
    

    QTestLib.h is the name of the C header from step 3.

  12. I then added the following code to the app:

    let libURL = Bundle.main.bundleURL
        .appendingPathComponent("Contents")
        .appendingPathComponent("Frameworks")
        .appendingPathComponent("libQTestLib.dylib")
    guard let lib = dlopen(libURL.path, RTLD_LAZY) else {
        // … handle error …
        return
    }
    guard let sym = dlsym(lib, "waffleVarnishDoubleDurability") else {
        // … handle error …
        return
    }
    let waffleVarnishDoubleDurability = unsafeBitCast(sym, to: WaffleVarnishDoubleDurabilityFunctionPointer.self)
    let before = WaffleVarnish(glossiness: 32, durability: 10)
    let after = waffleVarnishDoubleDurability(before)
    print(after)
    

    .

  13. When I run this, it prints:

    WaffleVarnish(glossiness: 16, durability: 20)
    

    .

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

3 Likes