1

I am working on a SIMBL Plugin that is loaded by a 3rd-party host application on macOS. It is almost entirely written in C++ and has only minimal objective-c components. (The UI is largely provided by API calls into the host app.) One of the requirements is that the plugin bundle can be loaded multiple times from different sub-directories. It is a Lua interpreter, and the goal is for each instance to host a different configuration of lua scripts that appear in separate menus on the host application. Third parties could bundle this plugin with a custom configuration for their script(s) and they would appear as separate items in the app's plugin menu.

This issue I have is this: I need to find out what directory my plugin is executing in. I could create a special class called MY_BUNDLE_ID_CLASS and use:

[NSBundle bundleForClass:[MY_BUNDLE_ID_CLASS class]];

Once I have the correct NSBundle, getting the file path is trivial.

The problem is that if multiple instances of my bundle are loaded (from different folders), Cocoa complains that the class MY_BUNDLE_ID_CLASS is defined in multiple locations and won't guarantee me which one was used. For other similar classes this would be fine for my plugin, because my unique class names are macros that equate to a mangled name that includes the version number, but in this case it isn't okay. It would potentially be multiple instances of the same version. Is there any other way to find out the folder my plugin code is executing from? It seems like a simple request, but I am coming up empty. I welcome suggestions.

rpatters1
  • 382
  • 1
  • 11
  • It's not 100% clear from the question: are you in control of both the host app and the plugin, or just the plugin? (If you control the host app too, how are you actually loading the plugin itself?) From the plugin, do you have access to any symbol that you can guarantee is unique? If so, you may be able to use [`dladdr`](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/dladdr.3.html) to get the path (`dli_fname`) of the image containing that symbol. That _may_ be sufficient for your needs. – Itai Ferber Feb 09 '22 at 03:12
  • I do not control the host app. It knows the folder paths but I don't. However dladdr sounds very promising. I'll let you know. – rpatters1 Feb 09 '22 at 12:50
  • `dladdr` is exactly what I needed. Thank you. If you want to put it in an answer, I'll give it the checkmark. – rpatters1 Feb 09 '22 at 13:00
  • Excellent! Answered below. – Itai Ferber Feb 09 '22 at 14:32
  • Thanks again. I've been looking for this solution for years but somehow never stumbled on it before. (Maybe I should have asked sooner, lol.) – rpatters1 Feb 09 '22 at 17:20
  • Haha, happy to have helped! :) – Itai Ferber Feb 09 '22 at 17:58

1 Answers1

1

Given an address in an executable, the dladdr function can be used to query the dynamic linker about the dynamically-linked image containing that address; i.e., given a reference to a symbol in your plugin, dladdr can give you the dynamic linking information about your plugin.

The runtime lookup can look as follows:

// Sample: BundleClass.m, the principal class for the plugin

#import "BundleClass.h"
#import <dlfcn.h>

// We'll be using a reference to this variable compiled into the plugin,
// but we can just as easily use a function pointer or similar -- anything
// that will be statically compiled into the plugin.
int someVariable = 0;

@implementation BundleClass

+ (void)load {
    Dl_info info;
    if (dladdr(&someVariable, &info) != 0) {
        NSLog(@"Plugin loaded from %s", info.dli_fname);
    } else {
        // Handle lookup failure.
    }
}

@end

Instead of &someSymbol, you can also use a reference to a function (e.g. &someFunctionDefinedInThePlugin), but you should be careful not to pass in a pointer that could be dynamically allocated — since that will likely either fail, or point you to the memory space of the host process.

On my machine, with a trivial macOS host app setup, the following loading code:

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    NSURL *bundleURL = [[NSBundle mainBundle] URLForResource:@"DynamicBundle" withExtension:@"bundle"];
    if (!bundleURL) {
        NSLog(@"Failed to find bundle!");
        return;
    }

    NSLog(@"Bundle class before loading: %@", NSClassFromString(@"BundleClass"));

    NSBundle *bundle = [NSBundle bundleWithURL:bundleURL];
    NSError *error = nil;
    if (![bundle loadAndReturnError:&error]) {
        NSLog(@"Failed to load bundle: %@", error);
        return;
    }

    NSLog(@"Bundle class after loading: %@", NSClassFromString(@"BundleClass"));
}

successfully produces

Bundle class before loading: (null)
Loaded plugin from /Volumes/ExtSSD/Developer/Xcode/DerivedData/HostApp/Build/Products/Debug/HostApp.app/Contents/Resources/DynamicBundle.bundle/Contents/MacOS/DynamicBundle
Bundle class after loading: BundleClass

which is indeed the path to the plugin on disk.

Itai Ferber
  • 28,308
  • 5
  • 77
  • 83