Communicate with another app using XPC
Asked Answered
M

3

28

I have a windowed app, and to add some functionality I need another app which launches at login and sync data to server if available.

I have tried with NSDistributionNotification but its practically useless in a sandboxed app. I looked up XPC and hoped it will work but I just dont know how to get it to work with the helper. So far I have done this using XPC.

Main App

    NSXPCInterface *remoteInterface = [NSXPCInterface interfaceWithProtocol:@protocol(AddProtocol)];
    NSXPCConnection *xpcConnection = [[NSXPCConnection alloc] initWithServiceName:@"com.example.SampleService"];

    xpcConnection.remoteObjectInterface = remoteInterface;

    xpcConnection.interruptionHandler = ^{
        NSLog(@"Connection Terminated");
    };

    xpcConnection.invalidationHandler = ^{
        NSLog(@"Connection Invalidated");
    };

    [xpcConnection resume];

    NSInteger num1 = [_number1Input.stringValue integerValue];
    NSInteger num2 = [_number2Input.stringValue integerValue];

    [xpcConnection.remoteObjectProxy add:num1 to:num2 reply:^(NSInteger result) {
        NSLog(@"Result of %d + %d = %d", (int) num1, (int) num2, (int) result);
    }];

XPC Service

In main () ...
SampleListener *delegate = [[SampleListener alloc] init];
NSXPCListener *listener = [NSXPCListener serviceListener];
listener.delegate = delegate;
[listener resume];

// In delegate
-(BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection {
    NSXPCInterface *interface = [NSXPCInterface interfaceWithProtocol:@protocol(AddProtocol)];
    newConnection.exportedInterface = interface;
    newConnection.exportedObject = [[SampleObject alloc] init];
    [newConnection resume];
    return YES;
}

// In Exported Object class

-(void)add:(NSInteger)num1 to:(NSInteger)num2 reply:(void (^)(NSInteger))respondBack {
    resultOfAddition = num1 + num2;
    respondBack(resultOfAddition);
}

This works fine, now I need to pass this result to Helper app. How Can I do this ? If XPC is not the answer here to communicate, then which one should I be using ? Any pointers please ?

Munich answered 4/6, 2014 at 14:52 Comment(0)
C
24

Alright for anyone that has been struggling with this, I was finally able to 100% get communication working between two application processes, using NSXPCConnection

The key to note is that you can only create an NSXPCConnection to three things.

  1. An XPCService. You can connect to an XPCService strictly through a name
  2. A Mach Service. You can also connect to a Mach Service strictly through a name
  3. An NSXPCEndpoint. This is what we're looking for, to communicate between two application processes.

The problem being that we can't directly transfer an NSXPCEndpoint from one application to another.

It involved creating a machservice Launch Agent (See this example for how to do that) that held an NSXPCEndpoint property. One application can connect to the machservice, and set that property to it's own [NSXPCListener anonymousListener].endpoint

Then the other application can connect to the machservice, and ask for that endpoint.

Then using that endpoint, an NSXPCConnection can be created, which successfully established a bridge between the two applications. I have tested sending objects back and forth, and it all works as expected.

Note that if your application is sandboxed, you will have to create an XPCService, as a middle man between your Application and the Machservice

I'm pretty pumped that I got this working-- I'm fairly active in SO, so if anybody is interested in source code, just add a comment and I can go through the effort to post more details

Some hurdles I came across:

You have to launch your machservice, these are the lines:

   OSStatus                    err;
   AuthorizationExternalForm   extForm;

   err = AuthorizationCreate(NULL, NULL, 0, &self->_authRef);
   if (err == errAuthorizationSuccess) {
      NSLog(@"SUCCESS AUTHORIZING DAEMON");
   }
   assert(err == errAuthorizationSuccess);

   Boolean             success;
   CFErrorRef          error;

   success = SMJobBless(
                        kSMDomainSystemLaunchd,
                        CFSTR("DAEMON IDENTIFIER HERE"),
                        self->_authRef,
                        &error
                        );

Also, every time you rebuild your daemon, you have to unload the previous launch agent, with these bash commands:

sudo launchctl unload /Library/LaunchDaemons/com.example.apple-samplecode.EBAS.HelperTool.plist
sudo rm /Library/LaunchDaemons/com.example.apple-samplecode.EBAS.HelperTool.plist
sudo rm /Library/PrivilegedHelperTools/com.example.apple-samplecode.EBAS.HelperTool

(With your corresponding identifiers, of course)

Cense answered 7/2, 2017 at 16:0 Comment(5)
Please post more code if any. Need to communicate between my own app and other apps that use the SDK that I have built.Deflective
what issues are you having? it's been a while and there's code in a bunch of places, if you can help explain the issue further i can try to post more relevant code for ye ^Cense
is this possible in production apps?Haydenhaydn
I have left the company a couple years ago, so I'm not sure if they ended up moving this to production-- I know that there was a problem with sandboxed apps on the appstore somebody else would have to answer that :\ or you'd have to try it outCense
Yeah, for anyone wants to know how to do it in sandboxed app, Apple had this demo to do it: developer.apple.com/library/archive/samplecode/…Merchantman
A
14

If you are searching on how to accomplish this in Swift. I wrote a tutorial on how to do this:

https://rderik.com/blog/creating-a-launch-agent-that-provides-an-xpc-service-on-macos/

You have to first create a Launch Agent (or a daemon, if you need more privileges) that exposes an XPC service. The XPC service will be registered as a mach service that your agent provides. So your Agent will have to create a listener like the following:

let listener = NSXPCListener(machServiceName: "com.rderik.exampleXPC" )

And to use that service from other client, you'll need to create aNSXPCConnection to that mach service. Like this:

let connection = NSXPCConnection(machServiceName: "com.rderik.exampleXPC")

Behind the scenes, a simplification of what happens is that your Agent will register your mach service to launchd. When your "client" wants to connect to to a mach service launchd will already have it register, so it will build the connection between the two.

I hope that helps.

Apical answered 30/11, 2019 at 12:28 Comment(1)
I am new in swift, Could you make in Objectvie cVidavidal
U
2

I think I figured out how to do this. All you have to do is create a command line helper tool in Xcode, install it as a Launchd job (Either a daemon or an Agent depending on the privilege requirement). You can use the defined protocol to communicate with the helper tool. Refer to the below sample code from Apple to understand how it is done.

Sample Code from Apple: https://developer.apple.com/library/mac/samplecode/EvenBetterAuthorizationSample/Listings/Read_Me_About_EvenBetterAuthorizationSample_txt.html#//apple_ref/doc/uid/DTS40013768-Read_Me_About_EvenBetterAuthorizationSample_txt-DontLinkElementID_17

Read the below link to understand what you really want, a Daemon or an Agent: https://developer.apple.com/library/mac/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/DesigningDaemons.html#//apple_ref/doc/uid/10000172i-SW4-BBCBHBFB

Uraemia answered 30/7, 2015 at 1:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.