"soft declare" leading to crash when framework is missing

I suspect a bug here, especially since it does not crash when using Real Studio 2012r2.1, but it does with Xojo 2016r4.1:

Consider this sample code:

[code]soft declare sub checkForUpdates lib “@executable_path/…/Frameworks/Sparkle.framework/Sparkle” selector “checkForUpdates:” (obj_id as Ptr, sender as Ptr)

static b as boolean
if b then
checkForUpdates (nil, nil)
end if[/code]

The “checkForUpdates” will never execute. Regardless, when you place this code into an app, the app crashes at launch with the report:

[quote]dyld: launch, loading dependent libraries

Dyld Error Message:
Library not loaded: @executable_path/…/Frameworks/Sparkle.framework/Sparkle
Referenced from: /private/var/folders/*/soft_declare_test.debug.app/Contents/MacOS/soft_declare_test.debug
Reason: image not found

This is not right, IMO. If I a “soft declare”, the lib should not get attempted to be loaded until I actually access it. And in RS this behaved as expected.


I believe that’s how it’s supposed to work, this is also from the doc:

[quote]Soft declare statements are resolved at run-time only when your application tries to use the Declare.

In most cases, you should use a Soft declare in order to prevent your app from launching due to missing libraries or library functions.

Soooo I’m as confused as you ¯\(?)

Oh, you know what, try putting the declare inside the if statement and see if that makes it less temperamental.

static b as boolean
if b then
  soft declare sub checkForUpdates lib "@executable_path/../Frameworks/Sparkle.framework/Sparkle" selector "checkForUpdates:" (obj_id as Ptr, sender as Ptr)
  checkForUpdates (nil, nil)
end if

Tim - good idea. But that makes no difference, unfortunately.

is the path right?


Did you try without /Sparkle on the end?
I am not sure if Xojo would expect path to framework here or path to executable inside framework.

Christian, that makes no difference. Plus, if I make the framework available inside Contents/Framework, that path works.

I agree.

This can be gotten around by changing the library to something like Foundation and loading the Sparkle framework yourself. The library name for Objective-C declares has no relation to how the function gets called. It just is used for recording the dependency and ensuring the library gets loaded.

Thanks for the work-around, Joe. I had been wondering why a framework needs to be specifed when I use ObjC declares with selectors. This answers it.

I’ve filed it here, and will also added your work-around to it: <https://xojo.com/issue/46618>

And here’s the code to load a framework manually. I already did that anyway:

Private Shared Function loadBundle(f as FolderItem) As Boolean #if TargetMacOS declare function CFURLCreateWithString lib "Cocoa" (allocator as Ptr, URLString as CFStringRef, baseURL as Ptr) as Ptr declare function bundleWithURL lib "Cocoa" selector "bundleWithURL:" (cls as Ptr, url as Ptr) as Ptr declare function load lib "Cocoa" selector "load" (obj as Ptr) as Boolean declare sub CFRelease lib "Carbon" (cf as Ptr) if f is nil then return false dim fileURL as String = f.URLPath dim bundleURL as Ptr = CFURLCreateWithString(nil, fileURL, nil) if bundleURL = nil then return false dim cls as Ptr = Cocoa.NSClassFromString ("NSBundle") dim bundle as Ptr = bundleWithURL (cls, bundleURL) CFRelease bundleURL return load (bundle) #endif End Function

The above code is missing a declare for Cocoa.

To fix that, add this line to the declares in the function:

declare function NSClassFromString lib "Cocoa" (aClassName as CFStringRef) as Ptr

And then change Cocoa.NSClassFromString into NSClassFromString

Then, instead of using “@executable_path/…/Frameworks/Sparkle.framework” as the lib path to the framework, use “Foundation” as the Lib name, and invoke

if not loadBundle App.ExecutableFile.Parent.Child("Frameworks").Child("Sparkle.framework") then // lib is missing return end

before calling into any of the framework ObjC methods.