Objective-C: Load "Library" using reflection - objective-c

I'm tasked with creating an application using XCode for OSX. This application needs to be able to load and run separate "modules" which will be determined dynamically (i.e., one user may have purchased modules 1 & 2, while user 2 would have purchased modules 3 and 6 -- only purchased modules should "run").
In C#, I would create a "library" project (that compiles to just a DLL). When the user purchases a module, I'd supply the appropriate DLL files and then my app would look for and load/run the DLL using reflection.
What would be the equivalent to this in XCode? Can I create a "library" and then load it using reflection? Keeping in mind that the app can't have prior knowledge of the module since in some cases, the user wouldn't even own the module files. I see various options such as "Cocoa Framework" and "Cocoa Library" as well as "C/C++ Library." What does each do and would any work to do what I need?

You can create a bundle. Xcode has templates for this (it is called "Loadable Bundle" and the icon is a Lego brick). You typically load a bundle using NSBundle's load method.
An example of loading it would be:
- (BOOL)loadPluginAtURL:(NSURL *)URL {
NSBundle *pluginBundle = [NSBundle bundleWithURL:URL];
if (![pluginBundle load]) { // is false if pluginBundle == nil automatically.
return NO;
}
id plugin = nil;
#try { // Use #try-#catch in case the principle class doesn't respond to +alloc or -init. Otherwise the host application would crash and that kinda sucks.
plugin = [[pluginBundle principalClass] alloc] init]; // Set the principle class in the bundle's info plist.
} #catch (id e) {
[bundle unload];
return NO;
}
if (plugin) {
[self.loadedPlugins addObject:plugin];// Define this as an NSMutableSet object.
return YES;
}
[pluginBundle unload];
return NO;
}
You can eventually provide a framework that the plugins can use, which can include protocols and classes. You may, for example check if the principle class of the bundle is a subclass of a specific class in your framework, so you don't send any messages the plugin doesn't respond to.

Related

Cocoa get current running path of SIMBL Plugin

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.
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.

Objective-C library category methods not working in Swift app environment

I have an Objective-C iOS app that is composed of a workspace (AuthPort.xcworkspace) with a project (AuthPort.xcodeproj) and an Objective-C SDK project ("ANet SDK.xcodeproj") which builds a library called “libAnet SDK.a”).
“libAnet SDK.a” is linked in with the AuthPort target Build Phases.
In the SDK, there are some categories on NSString which get built into “libAnet SDK.a”. They are named:
NSString+stringWithXMLTag.h and NSString+stringWithXMLTag.m
.
.
.
etc.
Some code in the AuthPort.xcodeproj calls code in the library which in turn calls these category methods.
It all works smoothly in the above Objective-C environment!
Now…I am re-writing the AuthPort code in Swift in a new workspace with a new project and also using the Objective-C SDK which builds “libAnet SDK.a”
So, I created a new Swift Xcode project called AuthPort.xcodeproj and added it to a new workspace called AuthPort.xcworkspace. I also added the authorize.net SDK ("ANet SDK.xcodeproj", written in Objective-C) to the project.
So far, so good. I created an AuthPort-Bridging-Header.h file and imported all the headers from the SDK (without the category headers - more on this later). I also linked in “libAnet SDK.a” in the Build Phases of the AuthPort target.
AuthPort builds and runs with no errors or warnings.
Now, in viewDidLoad of the primary view controller I added code to access library methods to register a new device with the company, authorize.net:
var r: MobileDeviceRegistrationRequest = MobileDeviceRegistrationRequest()
an.mobileDeviceRegistrationRequest(r)
These two calls are the same library methods in both Objective-C and Swift versions. The last method calls other library methods which call methods from the NSString category above.
When I put breakpoints in a category method in the Objective-C environment (all Objective-C code in app and library), the program breaks as expected and I can step through the code.
When I put breakpoints in a category method in the Swift environment (Swift app project with Objective-C library), the debugger never enters a category method. At one point in the execution, the app SIGABRT’s with the following:
2015-04-21 12:25:47.388 AuthPort[4743:2404231] +[NSString stringWithXMLTag:andValue:]: unrecognized selector sent to class 0x38450e18
2015-04-21 12:25:47.390 AuthPort[4743:2404231] * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[NSString stringWithXMLTag:andValue:]: unrecognized selector sent to class 0x38450e18'
* First throw call stack:
(0x29a0a5a7 0x3792fc77 0x29a0f989 0x29a0d949 0x2993eb68 0xd9e4b 0xc301f 0xdd507 0xc4725 0xc44b1 0xb8f28 0xb9e60 0x2cf0eb65 0x2cf0e8d5 0x2d031c43 0x2d031ac7 0x2d03196b 0x2cf1b5f1 0x2d0357b3 0x2d03491f 0x2cf2433b 0x2cf159e1 0x2cf15003 0x2cf14e6b 0x2cf148ef 0x2cf121ef 0x2cf7c031 0x2d16e34f 0x2d170781 0x2d17b1a9 0x2d16f063 0x302880a1 0x299d125d 0x299d0521 0x299cecbf 0x2991bb51 0x2991b963 0x2cf72b8f 0x2cf6dc91 0xb5290 0x37ed8aaf)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb)
The [NSString stringWithXMLTag:andValue:] is from the category added by the library onto NSString.
The library method stringOfXMLRequest calls the category method stringWithXMLTag:andValue:
- (NSString *) stringOfXMLRequest {
NSString *s = [NSString stringWithFormat:#""
#"<merchantAuthentication>"
#"%#" //name (optional)
#"%#" //fingerPrint type Object #"%#" //transactionKey or
#"%#" //sessionToken or
#"%#" //password
#"%#" //mobileDeviceId (optional)
#"</merchantAuthentication>",
(self.name ? [NSString stringWithXMLTag:#"name" andValue:self.name] : #""),
(self.fingerPrint ? [self.fingerPrint stringOfXMLRequest] : #""),
(self.transactionKey ? [NSString stringWithXMLTag:#"transactionKey" andValue:self.transactionKey] : #""),
(self.sessionToken ? [NSString stringWithXMLTag:#"sessionToken" andValue:self.sessionToken] : #""),
(self.password ? [NSString stringWithXMLTag:#"password" andValue:self.password] : #""),
(self.mobileDeviceId ? [NSString stringWithXMLTag:#"mobileDeviceId" andValue:self.mobileDeviceId] : #"")];
return s;
}
The SIGABRT doesn't happen until the second call is made to stringWithXMLTag:andValue: for the transitionKey.
The exact same library code is run in both environments.
Here’s the code from stringWithXMLTag:andValue: which is never seen in the debugger:
+ (NSString *)stringWithXMLTag:(NSString *)t andValue:(NSString *)v {
if (v == nil) {
return #"";
}
else {
v = [NSString stringWithEscapedXMLValue:v];
return [NSString stringWithFormat:#"<%#>%#</%#>", t, v, t];
}
}
The problem is solely within the library code where a library method calls another library method which calls a library category method which is never entered in the debugger in the Swift version.
I also tried adding the category header files in the bridging header and adding -ObjC and -all_load to the Other Linker Flags for AuthPort. No luck.
I’ve searched the net for 2 days with no luck. Hopefully one of you will have seen this behavior.
I'm building using Xcode 6.3 and iOS 8.3 (deploying on iOS 8.2).
Thanks.
Check that:
you've added you category to the Umbrella Framework of your Framework project. Use #import "your.h"
I've marked the .h file from the category as public. Select the .h > Identity and Type > Target Membership > Public
In your project, set correct Framework Search Paths

Privileged Helper Tool within a Swift project

I've created an Xcode project using Swift and a privileged Helper tool using Objective-C. The helper tool works fine within a project which has also been created in Objective-C but it doesn't seem to work within a project created with Swift.
The service itself is being installed. I can see the helper binary within the /Library/PrivilegedHelperTools directory and it's permissions seem to be okay (as well as the user: root). Removing the helper by using launchctl results in re-installing the tool when my project runs (that works as expected) but I can't call any method of the helper tool.
There is neither any exception being thrown nor does any other error occur (at least there seem to be no error as the Console shows nothing as well).
Does anybody know whether this might be an issue with Swift? Because running the same helper tool within another project (written in Objective-C) works well.
I could figure out what the problem was. The helper tool has a main.m wich contains a main() method. I just forgot to fill it with code that creates an instance of my helper class and trigger its listener:
#import <Foundation/Foundation.h>
#import "Helper.h"
int main(int argc, const char * argv[])
{
#autoreleasepool
{
Helper *helper = [[Helper alloc] init];
[helper run];
}
return EXIT_FAILURE;
}
This code causes the Helper instance to run in an infinite loop waiting for incoming connections (from Helper.h):
- (void)run
{
[_listener resume];
[[NSRunLoop currentRunLoop] run];
}
_listener is an instance of NSXPCListener.

Communication between Main Application and loadable bundle

OK, it's rather self-explanatory.
Let's say we've got our cocoa application.
Let's all assume that you've already got some "plugins", packaged as independent loadable bundles.
This is how bundles are currently being loaded (given that the plugin's "principal class" is actually an NSWindowController subclass :
// Load the bundle
NSString* bundlePath = [[NSBundle mainBundle] pathForResource:#"BundlePlugin"
ofType:#"bundle"]
NSBundle* b = [NSBundle bundleWithPath:bundlePath];
NSError* err = nil;
[b loadAndReturnError:&err];
if (!err)
{
// if everything goes fine, initialise the main controller
Class mainWindowControllerClass = [b principalClass];
id mainWindowController = [[mainWindowControllerClass alloc] initWithWindowNibName:#"PluginWindow"];
}
Now, here's the catch :
How do I "publish" some of my main app's objects to the plugin?
How do I make my plugin "know" about my main app's classes?
Is it even possible to actually establish some sort of communication between them, so that e.g. the plugin can interact with my main app? And if so, how?
SIDENOTES :
Could using Protocols eliminate the "unknown selector" errors and the need for extensive use of performSelector:withObject:?
Obviously I can set and get value to-and-from my newly created mainWindowController as long as they're defined as properties. But is this the most Cocoa-friendly way?

Airstash with iOS

Does anyone know of tutorials or existing projects for Airstash SDK iOS integration? I have the frameworks in my project, but the existing comments inside the header files aren't incredibly helpful for initial setup. I've been googling for one, but I get a deluge of tech announcement news instead of developer resources.
-Background-
The Airstash is going to be used with an already-developed iPad application that sets equipment preferences. The targeted equipment has already been developed and has no wireless connectivity, but does have USB capability. The proposed solution is to wirelessly upload files from the iPad to an Airstash connected to the equipment.
In the SDK release there are two directories: AirStashSDK and sdk-demo. The sdk-demo directory contains an XCode project that demonstrates usage of the SDK.
The AirStashSDK folder contains the AirStash.framework to include in your project, and a Resources folder that contains a couple xib files that you should include in your project and may customize. If you plan to customize these files you may want to copy them to a different directory so your changes are not lost if you update to a newer release of the SDK. The xib files are used to display progress while getting a file from the AirStash, or activity when saving a file to the AirStash.
To save a file to an AirStash, look at the saveFileAction: method in sdk-demo/AirStashSDK Demo/RootViewController.m.
- (void)saveFileAction:(NSString*)filename
{
NSLog(#"Save a file to AirStash. filename: %#", filename);
NSURL *docDir = [self getDocumentsDirectory];
NSString *filepath = [[docDir URLByAppendingPathComponent:filename] path];
airstash = [[AirStash alloc] init];
// Save is very simple.
[airstash saveFileToAirStash:filepath
presentingFrom:self
successBlock:^(void){
NSString *msg = [NSString stringWithFormat:#"Success saving file to AirStash: original filename: %#", filename];
NSLog(#"%#", msg);
[self presentAlertWithMessage:msg];
self.airstash = nil;
}
errorBlock:^(AirStashStatus errorCode, NSString *reason) {
NSString *msg = [NSString stringWithFormat:#"Problem saving file to AirStash: (%d) %#", errorCode, reason];
NSLog(#"%#", msg);
[self presentAlertWithMessage:msg];
self.airstash = nil;
}];
}
The demo app presents a list of the files in the app's documents directory. If the user taps on a file, it calls the saveFileAction: method to save the selected file to an AirStash. An app can allocate an AirStash object and make multiple calls to its methods, or as in this case, it just makes the one call and then releases it. (The demo app's presentAlertWithMessage: method just pops up a UIAlertView with the given message and an OK button so you can see that the action is complete.)
The demo app has a couple other buttons, one to get a file from an AirStash (and save it to the app's documents directory), and the other to get the URL of a file on an AirStash. The method used by the second button would be useful for apps that want to stream a file rather than download the whole thing at once.