2

I am trying to create a plugin for Unity using Objective-C for an app running on Mac. I need to get the URL when launching my app from a link using an url protocol. I haven't used Objective-C before, so I am having trouble trying to make it work.

I am using an example provided by Unity (download example) and changing the methods to the ones I need to get the URL, but my app crashes on the line nsApplication = [[NSApplication alloc] init]; on the _GetUrl method. I have no idea what I am missing/doing wrong. Also, _GetUrl is the method called from Unity when I want to ask for the url (which is called at the first frame), but I am afraid it might be called after applicationWillFinishLaunching. So where should I actually set the delegate so that applicationWillFinishLaunching happens after the delegate is set?

I use an .h and a .m script and then compile the bundle and import it into Unity as a plugin. This is my code:

PluginUrlHandler.h

#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>

@interface NSApplicationDelegate : NSObject
{
    NSString* urlString;
}

// NSApplication delegate methods
- (void)applicationWillFinishLaunching:(NSNotification *)aNotification;
- (void)handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent;

//Other methods
- (NSString *)getUrl;
@end

PluginUrlHandler.m

#import <Foundation/Foundation.h>
#import "PluginUrlHandler.h"

@implementation NSApplicationDelegate

- (id)init
{
    self = [super init];
    urlString = @"nourl";
    return self;
}

- (void)applicationWillFinishLaunching:(NSNotification *)aNotification
{
    NSAppleEventManager *appleEventManager = [NSAppleEventManager sharedAppleEventManager];
    [appleEventManager setEventHandler:self
                           andSelector:@selector(handleGetURLEvent:withReplyEvent:)
                         forEventClass:kInternetEventClass andEventID:kAEGetURL];

}

- (void)handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent
{
    [event paramDescriptorForKeyword:keyDirectObject] ;

    NSString *urlStr = [[event paramDescriptorForKeyword:keyDirectObject] stringValue];

    urlString = urlStr;
}

- (NSString *)getUrl
{
    return urlString;
}

@end

static NSApplicationDelegate* delegateObject = nil;
static NSApplication* nsApplication = nil;


// Helper method to create C string copy
char* MakeStringCopy (const char* string)
{
    if (string == NULL)
        return NULL;

    char* res = (char*)malloc(strlen(string) + 1);
    strcpy(res, string);
    return res;
}

#if c__plusplus
extern "C" {
#endif

    const char* _GetUrl ()
    {
        if (delegateObject == nil)
        delegateObject = [[NSApplicationDelegate alloc] init];

        if (nsApplication == nil)
            nsApplication = [[NSApplication alloc] init];

        [nsApplication setDelegate:delegateObject];

        return MakeStringCopy([[delegateObject getUrl] UTF8String]);
    }

#if c__plusplus
}
#endif
Rob Napier
  • 286,113
  • 34
  • 456
  • 610
Sandra
  • 21
  • 3
  • "I haven't used Objective-C before" !!!!!!!! you're going to find it extremely difficult, it takes at least 3 man-years to have basic knowledge of getting around in Xcode / OSX builds. :O – Fattie Mar 06 '16 at 14:04
  • 3 years?! C'mon! But I see that the OP does not have a clue regarding delegate and the app instance. I'd recommend writing an OS X app first and take a few tutorials, until you understand the basic architecture – Julian F. Weinert Mar 07 '16 at 09:23
  • Found this, which helped me, actually http://stackoverflow.com/a/2154666 – Julian F. Weinert Mar 07 '16 at 09:38

3 Answers3

0

If your application is crashing, you need to provide some information, such as a stack trace or error message so we can best help. I'm assuming the error you're receiving looks like this:

2016-03-06 10:07:14.388 test[5831:230418] *** Assertion failure in -[NSApplication init], /Library/Caches/com.apple.xbs/Sources/AppKit/AppKit-1404.34/AppKit.subproj/NSApplication.m:1980
2016-03-06 10:07:14.391 test[5831:230418] An uncaught exception was raised
2016-03-06 10:07:14.391 test[5831:230418] Creating more than one Application

You shouldn't create your own NSApplication object. Just use the system one by referencing [NSApplication sharedApplication].

Generally speaking, you shouldn't need an NSApplication (or NSApplicationDelegate) for a plugin, though. The program that's loaded you should already have one, and you don't want to mess with that. Just create a custom NSObject subclass to be your AppleEvent handler. You don't need NSApplication (or it's delegate) at all for this. Any object can be the target of an AppleEvent.

You can't use things like applicationDidFinishLaunching:withOptions: from a plugin in any case. It's too late. The application has long since launched. You'll need to add your AppleEvent handler in some function called from Unity. I'm not particularly familiar with Unity's plugin engine, so I don't know if there's a particular "load" function that gets called automatically (I don't see one in the sample code). You may have to call something yourself. It would have to occur after the plugin is loaded, but before the Get URL Apple Event happens (it's unclear what you expect to generate that).

Just curious what you're trying to pull off with this. I've never seen a protocol handler used this way.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • Hey Rob, thanks for your answer. What I'm trying to do is launching my app when the user clicks a link, so I declared an url protocol on info.plist. Now I need to get some values from that link, so get the url that launched the app. I can't do it from Unity, so need a native plugin that handles that data. I found that the way to do it was using handleGetURLEvent. So how do I use that event in my plugin? Should I just create the appleEventManager in a method called from Unity instead of in applicationWillFinishLaunching? – Sandra Mar 06 '16 at 17:23
  • I don't know enough about Unity to answer this. It depends on how Unity works on Mac. If it doesn't give you a launch-time hook, then it may not be able to respond to the event. If that were the case, a common solution would be to create a separate Cocoa launcher app that handles this protocol and launches your Unity app when required (but even then, you'd need some way to communicate your URL to the Unity app, which is possible, but I don't know if Unity has anything built-in to support it). I just don't know enough about Unity to say how their OS-integration is implemented at startup. – Rob Napier Mar 06 '16 at 18:00
0

Creation of NSApplication instance looks very suspicious. Normally you don't create it as it is a singleton by definition.

So instead of this:

if (nsApplication == nil)
   nsApplication = [[NSApplication alloc] init];

you should have rather this (getting current NSApplication instance):

if (nsApplication == nil)
   nsApplication = [NSApplication sharedApplication];
c-smile
  • 26,734
  • 7
  • 59
  • 86
0

I made a tutorial on this, see Override app delegate in Unity for iOS and OSX (4/4) Inject code to Mach-O binary.

It uses code injection to set an Objective-C class to respond the corresponding Apple Event.

Geri Borbás
  • 15,810
  • 18
  • 109
  • 172