5

On macOS, I use an external framework (written in C) that must be installed by the user. In Swift, I need to check at runtime if it exists, and I can't use #available() since it is for OS-related features, and I am trying to track down an external framework. Also, NSClassFromString() is not useful since it is not an Objective-C framework.

I have been trying to understand how to replicate the Objective-C equivalent for checking for a weakly-linked symbol such as:

if ( anExternalFunction == NULL ) {
    // fail graciously
} else {
    // do my thing
}

but in Swift, this does not seem to work: the compiler states that since anExternalFunction is not optional, I will always get != nil, which make "Swift sense" but does not help me one bit.

I have found two solutions, but they stink up my code like you wouldn't believe:

Option 1, create an Objective-C file with a function called isFrameworkAvailable() doing the work, and calling from Swift

Option 2, actually checking for the library with the following Swift code:

let libHandle = dlopen("/Library/Frameworks/TheLib.framework/TheLib", RTLD_NOW)

if (libHandle != nil) {
    if dlsym(libHandle, "anExternalFunction") != nil {
        return true
    }
}
return false

I have been unable to get Option 2 to work nicely with RTLD_DEFAULT since for some reason, it is defined in dlfcn.h (-2) but does not seem to be imported in Swift (like all negative pointers in that header: RTLD_NEXT, RTLD_DEFAULT, RTLD_SELF, RTLD_MAIN_ONLY). I have found this ugliest hack to make it work:

if dlsym(unsafeBitCast(-2, to: UnsafeMutableRawPointer.self), "anExternalFunction") != nil {
    // library installed
}

So for option 2, I require the path or a hack, which I find particularly ugly (but it works) and Option 1 is not very "Swifty" and makes little sense to me: what is the proper way of doing this in Swift?

Edits: clarified question, explained Option 2 better.

Daniel
  • 578
  • 4
  • 17
  • Does this work: https://stackoverflow.com/questions/24591952/how-do-i-do-weak-linking-in-swift – rmaddy Nov 14 '17 at 04:47
  • No it does not because the third-party framework I am using is not an Objective-C framework, it is a straight C library. (Updated the question) – Daniel Nov 14 '17 at 05:09
  • This looks related (duplicate?) [Check existence of global function in Swift](https://stackoverflow.com/questions/38353450/check-existence-of-global-function-in-swift) – Martin R Nov 14 '17 at 05:11
  • It is useful, but I would not call it a duplicate: the answer is from 2016 (which means not Swift 4) and it is asked with slightly different context and I did not find it with my hour-long survey before asking. Also, the solution with RTLD_SELF does not compile (it is undefined). It is possible a new solution in Swift 4 exists. – Daniel Nov 14 '17 at 05:20
  • 1
    A C macro like `#define RTLD_DEFAULT ((void *) -2` is not imported into Swift, but `if dlsym(UnsafeMutableRawPointer(bitPattern: -2), "anExternalFunction") ...` would be less ugly. – Compare https://forums.developer.apple.com/thread/18816. – Martin R Nov 14 '17 at 06:40
  • Granted, will change that but it remains hardcoded. This all feel like soy butter when all I want is real peanut butter: nothing above is very Swifty. – Daniel Nov 14 '17 at 06:43

1 Answers1

1

I've found two ways of doing this:

    1.
if let _ = dlopen("/Library/Frameworks/MyLibrary.framework/MyLibrary", RTLD_NOW) {
  print("Can open my library")
}
    2.
#if canImport(MyLibrary)
  print("Can import my library")
#endif

The second seems clearly better from just a readability and type safety standpoint but I'm not sure what the tradeoffs are between the two.

Joe Susnick
  • 6,544
  • 5
  • 44
  • 50