Unable to load object bundle executable

charles spencer's icon

Hello Max community,

I'm developing a set of Max externals that use a shared C++ library and I'm running into persistent loading issues. I've tried numerous approaches but still can't get the original external to load properly in Max. I'm hoping someone with experience linking externals to shared libraries can help.

## The Setup

- Three Max externals: selector, player, and stepper

- All depend on a shared library: Library.dylib

- macOS environment with universal binary support (x86_64 and arm64)

- Max for Live 12 (also a universal binary)

- The library is a C++ dylib with extern "C" functions for the C API

- Externals are written in C with jbox/drawing functionality

## The Error

When trying to load the externals in Max, I get:

```

selector: unable to load object bundle executable

```

## What I've Ruled Out

1. Basic External Loading: I created a minimal test external with no dependencies that loads fine in Max, confirming that my Max installation can load externals correctly.

2. Self-contained Implementation: I also created a standalone version of my external with all functionality embedded directly (no dylib dependency) that works perfectly, proving that the issue is specifically with the dynamic library loading mechanism.

## What I've Tried

1. Library Path Fixes:

   - Set -install_name "@loader_path/Support/Library.dylib" when building

   - Placed copies in multiple locations (external's Support folder, central support dir, Extensions dir)

   - Tried multiple search paths with dlopen()

2. Function Name Compatibility:

   - Added alias functions to support different naming conventions:

   ```cpp

   // Original function

   void selector_free_instance(void* instance) { ... }

   

   // Added alias

   extern "C" void selector_free(void* instance) {

       selector_free_instance(instance);

   }

   ```

3. Code Signing and Security:

   - Signed all components with my Developer ID

   - Notarized and stapled all parts

   - Set proper permissions and removed quarantine attributes

4. Simplified Versions:

   - Created versions without jbox/drawing functionality

   - Tried various object structure simplifications

## My Questions

1. Is there something specific about the combination of shared libraries and jbox/drawing functionality that requires special handling in Max externals?

2. Are there known issues with dynamic library loading in Max for Live 12 on macOS?

3. Does the error "unable to load object bundle executable" indicate a specific issue with the bundle structure or permissions?

4. Are there any environment variables, entitlements, or special build settings needed for externals that use shared libraries?

5. Has anyone successfully implemented a Max external that uses a separate dylib and includes UI/drawing functionality?

## Relevant Code Snippets

My library loading code:

```c

const char* lib_paths[] = {

    "@loader_path/Support/Library.dylib",

    "./Support/Library.dylib",

    "@executable_path/../Support/Library.dylib",

    "~/Library/Application Support/Cycling '74/Max 8/Externals/support/Library.dylib",

    "~/Library/Application Support/Cycling '74/Max 8/Extensions/Library.dylib",

    NULL

};

void* lib_handle = NULL;

for (int i = 0; lib_paths[i] != NULL; i++) {

    lib_handle = dlopen(lib_paths[i], RTLD_NOW | RTLD_LOCAL);

    if (lib_handle) break;

}

```

Any help or insights would be greatly appreciated. I'm at my wits' end after days of troubleshooting!

Thank you,

Charles

JBG's icon

Hi!

Is there a particular reason why you need to use dlopen (explicit linking) instead of just linking it normally in the cmake (implicit linking)?

If explicit linking is necessary, I see at least two potential problems in your scenario:

  • None of your paths are absolute. The first thing I would try is to just keep the dylib where you build it and provide the full path without shell expansions (e.g. "/Users/<yourname>/Documents/Max/Packages/MyPackage/support/mylib.dylib"). Obviously you can't use this if you need to distribute it to other machines, but it'll help to get you started

  • There's no need to notarize code that's run locally on your machine. Using dlopen might require a different set of entitlements, and could be a reason why your external won't load. Again, you'll have to do this later, but for now it'd be better to skip this in order to isolate the error

In general, I would try to experiment with this in a way more simple scenario outside Max. Just create a simple main.c and try to use dlopen there, which will provide you with way more descriptive errors than the generic "unable to load object bundle executable". Then once everything works there, port your working solution to Max and see if it still works

Rob Ramirez's icon

For bundling external object dylib dependencies we recommend distributing the external in a package and using the package support folder for the dylibs. In this way you can use the @loader_path to navigate to the dependency. This is the path to the external binary (not the bundle), so you have to dive down several layers to retrieve the support folder.

For example your external path with be something like: /path/to/packages/Mypackage/externals/myexternal.mxo/Contents/MacOS/myexternal

And we need to get to the support folder here:

/path/to/packages/Mypackage/support

So we set the path to @loader_path/../../../../support/mylib.dylib (and of course copy the lib to that location).

We typically change this on the build product from cmake using a post build step, something like the following:

add_custom_command(TARGET myexternal POST_BUILD COMMAND install_name_tool -change "@rpath/mylib.dylib" "@loader_path/../../../../support/mylib.dylib" $<TARGET_FILE:myexternal>

Note, if you lib needs to find other libs, you have to do the same process on that lib!

In any case otool -l and otool -L are your friends here.