macos 是否可以像 IPC 一样使用 Mac OS X XPC 在进程之间交换消息?如何?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/8637206/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-21 08:43:25  来源:igfitidea点击:

Is possible to use Mac OS X XPC like IPC to exchange messages between processes? How?

macosipcosx-lionsandboxxpc

提问by poorDeveloper

According to Apple, the new XPC Services API, introduced in Lion, provides a lightweight mechanism for basic interprocess communication integrated with Grand Central Dispatch (GCD) and launchd.

据 Apple 称,Lion 中引入的新 XPC 服务 API 为与 Grand Central Dispatch (GCD) 集成并启动的基本进程间通信提供了一种轻量级机制。

It seems possible to use this API as a kind of IPC, like the POSIX IPC, however, I cannot find how to do it.

似乎可以将此 API 用作一种 IPC,如 POSIX IPC,但是,我找不到如何去做。

I am trying to communicate two processes using the XPC API so I can pass messages between them but I always get a "XPC connection invalid" error in the server side.

我正在尝试使用 XPC API 通信两个进程,以便我可以在它们之间传递消息,但我总是在服务器端收到“XPC 连接无效”错误。

I don't want an XPC Service, I just want to exchange messages using a client-server architecture.

我不想要 XPC 服务,我只想使用客户端-服务器架构交换消息。

I am using two BSD-like processes, so there is no Info.plist or whatever...

我正在使用两个类似 BSD 的进程,所以没有 Info.plist 或其他什么......

I have been following this discussion http://lists.macosforge.org/pipermail/launchd-dev/2011-November/000982.htmlbut this topic seems a bit obscure and undocumented.

我一直在关注这个讨论http://lists.macosforge.org/pipermail/launchd-dev/2011-November/000982.html但这个话题似乎有点晦涩和无证。

Thanks!

谢谢!

回答by Daniel Eggert

Yes, that is possible, but not the way you'd expect.

是的,这是可能的,但不是您期望的方式。

You can nothave a (non launchd) process vend a service. That is for security reasons, since it would make it easy to do man-in-the-middle attacks.

不能让(非启动)进程提供服务。这是出于安全原因,因为这样可以轻松进行中间人攻击。

You can still achieve what you want, though: You have to set up a launchd service that vends an XPC / mach service. Both process A and B then connect to your launchd service. Process A can then create a so called anonymous connectionand send that to the launchd service which will forward it to process B. Once that has happened, processes A and B can talk to each other directly through that connection (i.e. the launchd service can exit without the connection breaking).

不过,您仍然可以实现您想要的:您必须设置一个可以提供 XPC/mach 服务的 launchd 服务。然后进程 A 和 B 都连接到您的 launchd 服务。然后,进程 A 可以创建一个所谓的匿名连接并将其发送到 launchd 服务,后者将其转发给进程 B。一旦发生这种情况,进程 A 和 B 可以通过该连接直接相互交谈(即,launchd 服务可以退出没有连接断开)。

This may seem round-about, but it's necessary for security reasons.

这可能看起来有点绕,但出于安全原因,这是必要的。

See the xpc_object(3)man page for details about anonymous connections.

xpc_object(3)有关匿名连接的详细信息,请参阅手册页。

It's a bit counter intuitive, because process A will create a listenerobject with xpc_connection_create(). A then creates an endpointobject from the listener with xpc_endpoint_create()and sends that endpoint across the wire (over XPC) to process B. B can then turn that object into a connection with xpc_connection_create_from_endpoint(). A's event handler for the listenerwill then receive a connection object matching the connection that B created with xpc_connection_create_from_endpoint(). This works similar to the way that the event handler of xpc_connection_create_mach_service()will receive connection objects when clients connect.

这是一个有点直觉,因为进程A将创建一个侦听器与对象xpc_connection_create()。A 然后从侦听器创建一个端点对象xpc_endpoint_create(),并将该端点通过线路(通过 XPC)发送到进程 B。然后 B 可以将该对象转换为与 的连接xpc_connection_create_from_endpoint()。然后,A 的侦听器事件处理程序将接收与 B 创建的连接匹配的连接对象xpc_connection_create_from_endpoint()。这类似于xpc_connection_create_mach_service()客户端连接时的事件处理程序将接收连接对象的方式。

回答by Arvin

Here is how I am doing Bi-Directional IPC using XPC.

这是我如何使用 XPC 进行双向 IPC。

The Helper (login item) is the server or listener. The main app or any other app are considered clients.

Helper(登录项)是服务器或侦听器。主应用程序或任何其他应用程序都被视为客户端。

I created the following manager:

我创建了以下管理器:

Header:

标题:

@class CommXPCManager;

typedef NS_ENUM(NSUInteger, CommXPCErrorType) {

    CommXPCErrorInvalid     = 1,
    CommXPCErrorInterrupted = 2,
    CommXPCErrorTermination = 3
};

typedef void (^XPCErrorHandler)(CommXPCManager *mgrXPC, CommXPCErrorType errorType, NSError *error);
typedef void (^XPCMessageHandler)(CommXPCManager *mgrXPC, xpc_object_t event, NSDictionary *message);
typedef void (^XPCConnectionHandler)(CommXPCManager *peerConnection);

@interface CommXPCManager : NSObject

@property (readwrite, copy, nonatomic) XPCErrorHandler errorHandler;
@property (readwrite, copy, nonatomic) XPCMessageHandler messageHandler;
@property (readwrite, copy, nonatomic) XPCConnectionHandler connectionHandler;

@property (readonly, nonatomic) BOOL clientConnection;
@property (readonly, nonatomic) BOOL serverConnection;
@property (readonly, nonatomic) BOOL peerConnection;

@property (readonly, nonatomic) __attribute__((NSObject)) xpc_connection_t connection;

@property (readonly, strong, nonatomic) NSString *connectionName;
@property (readonly, strong, nonatomic) NSNumber *connectionEUID;
@property (readonly, strong, nonatomic) NSNumber *connectionEGID;
@property (readonly, strong, nonatomic) NSNumber *connectionProcessID;
@property (readonly, strong, nonatomic) NSString *connectionAuditSessionID;

- (id) initWithConnection:(xpc_connection_t)aConnection;
- (id) initAsClientWithBundleID:(NSString *)bundleID;
- (id) initAsServer;

- (void) suspendConnection;
- (void) resumeConnection;
- (void) cancelConnection;

- (void) sendMessage:(NSDictionary *)dict;
- (void) sendMessage:(NSDictionary *)dict reply:(void (^)(NSDictionary *replyDict, NSError *error))reply;
+ (void) sendReply:(NSDictionary *)dict forEvent:(xpc_object_t)event;

@end

Implementation:

执行:

@interface CommXPCManager ()
@property (readwrite, nonatomic) BOOL clientConnection;
@property (readwrite, nonatomic) BOOL serverConnection;
@property (readwrite, nonatomic) BOOL peerConnection;
@property (readwrite, strong, nonatomic) __attribute__((NSObject)) dispatch_queue_t dispatchQueue;
@end

@implementation CommXPCManager

@synthesize clientConnection, serverConnection, peerConnection;
@synthesize errorHandler, messageHandler, connectionHandler;
@synthesize connection    = _connection;
@synthesize dispatchQueue = _dispatchQueue;

#pragma mark - Message Methods:

- (void) sendMessage:(NSDictionary *)dict {

    dispatch_async( self.dispatchQueue, ^{

        xpc_object_t message = dict.xObject;
        xpc_connection_send_message( _connection, message );
        xpc_release( message );
    });
}

- (void) sendMessage:(NSDictionary *)dict reply:(void (^)(NSDictionary *replyDict, NSError *error))reply {

    dispatch_async( self.dispatchQueue, ^{

        xpc_object_t message = dict.xObject;
        xpc_connection_send_message_with_reply( _connection, message, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(xpc_object_t object) {

            xpc_type_t type = xpc_get_type( object );

            if ( type == XPC_TYPE_ERROR ) {

                /*! @discussion Reply: XPC Error */
                reply( [NSDictionary dictionary], [NSError errorFromXObject:object] );

            } else if ( type == XPC_TYPE_DICTIONARY ) {

                /*! @discussion Reply: XPC Dictionary */
                reply( [NSDictionary dictionaryFromXObject:object], nil );
            }
        }); xpc_release( message );
    });
}

+ (void) sendReply:(NSDictionary *)dict forEvent:(xpc_object_t)event {

    xpc_object_t message = [dict xObjectReply:event];
    xpc_connection_t replyConnection = xpc_dictionary_get_remote_connection( message );
    xpc_connection_send_message( replyConnection, message );
    xpc_release( message );
}

#pragma mark - Connection Methods:

- (void) suspendConnection {

    dispatch_async(self.dispatchQueue, ^{ xpc_connection_suspend( _connection ); });
}

- (void) resumeConnection {

    dispatch_async(self.dispatchQueue, ^{ xpc_connection_resume(_connection); });
}

- (void) cancelConnection {

    dispatch_async(self.dispatchQueue, ^{ xpc_connection_cancel(_connection); });
}

#pragma mark - Accessor Overrides:

- (void) setDispatchQueue:(dispatch_queue_t)queue {

    if ( queue ) dispatch_retain( queue );
    if ( _dispatchQueue ) dispatch_release( _dispatchQueue );
    _dispatchQueue = queue;

    xpc_connection_set_target_queue( self.connection, self.dispatchQueue );
}

#pragma mark - Getter Overrides:

- (NSString *) connectionName {

    __block char* name = NULL;
    dispatch_sync(self.dispatchQueue, ^{ name = (char*)xpc_connection_get_name( _connection ); });

    if(!name) return nil;
    return [NSString stringWithCString:name encoding:[NSString defaultCStringEncoding]];
}

- (NSNumber *) connectionEUID {

    __block uid_t uid = 0;
    dispatch_sync(self.dispatchQueue, ^{ uid = xpc_connection_get_euid( _connection ); });
    return [NSNumber numberWithUnsignedInt:uid];
}

- (NSNumber *) connectionEGID {

    __block gid_t egid = 0;
    dispatch_sync(self.dispatchQueue, ^{ egid = xpc_connection_get_egid( _connection ); });
    return [NSNumber numberWithUnsignedInt:egid];
}

- (NSNumber *) connectionProcessID {

    __block pid_t pid = 0;
    dispatch_sync(self.dispatchQueue, ^{ pid = xpc_connection_get_pid( _connection ); });
    return [NSNumber numberWithUnsignedInt:pid];
}

- (NSNumber *) connectionAuditSessionID{ 

    __block au_asid_t auasid = 0;
    dispatch_sync(self.dispatchQueue, ^{ auasid = xpc_connection_get_asid( _connection ); });
    return [NSNumber numberWithUnsignedInt:auasid];
}

#pragma mark - Setup Methods:

- (void) setupConnectionHandler:(xpc_connection_t)conn {

    __block CommXPCManager *this = self;

    xpc_connection_set_event_handler( conn, ^(xpc_object_t object) {

        xpc_type_t type = xpc_get_type( object );

        if ( type == XPC_TYPE_ERROR ) {

            /*! @discussion Client | Peer: XPC Error */

            NSError *xpcError = [NSError errorFromXObject:object];

            if ( object == XPC_ERROR_CONNECTION_INVALID ) {

                if ( this.errorHandler )
                    this.errorHandler( this, CommXPCErrorInvalid, xpcError );

            } else if ( object == XPC_ERROR_CONNECTION_INTERRUPTED ) {

                if ( this.errorHandler )
                    this.errorHandler( this, CommXPCErrorInterrupted, xpcError );

            } else if ( object == XPC_ERROR_TERMINATION_IMMINENT ) {

                if ( this.errorHandler )
                    this.errorHandler( this, CommXPCErrorTermination, xpcError );
            }

            xpcError = nil; return;

        } else if ( type == XPC_TYPE_CONNECTION ) {

            /*! @discussion XPC Server: XPC Connection */

            CommXPCManager *xpcPeer = [[CommXPCManager alloc] initWithConnection:object];

            if ( this.connectionHandler )
                this.connectionHandler( xpcPeer );

            xpcPeer = nil; return;

        } else if ( type == XPC_TYPE_DICTIONARY ) {

            /*! @discussion Client | Peer: XPC Dictionary */

            if ( this.messageHandler )
                this.messageHandler( this, object, [NSDictionary dictionaryFromXObject:object] );
        }

    });
}

- (void) setupDispatchQueue {

    dispatch_queue_t queue = dispatch_queue_create( xpc_connection_get_name(_connection), 0 );
    self.dispatchQueue = queue;
    dispatch_release( queue );
}

- (void) setupConnection:(xpc_connection_t)aConnection {

    _connection = xpc_retain( aConnection );

    [self setupConnectionHandler:aConnection];
    [self setupDispatchQueue];
    [self resumeConnection];
}

#pragma mark - Initialization:

- (id) initWithConnection:(xpc_connection_t)aConnection {

    if ( !aConnection ) return nil;

    if ( (self = [super init]) ) {

        self.peerConnection = YES;
        [self setupConnection:aConnection];

    } return self;
}

- (id) initAsClientWithBundleID:(NSString *)bundleID {

    xpc_connection_t xpcConnection = xpc_connection_create_mach_service( [bundleID UTF8String], nil, 0 );

    if ( (self = [super init]) ) {

        self.clientConnection = YES;
        [self setupConnection:xpcConnection];
    }

    xpc_release( xpcConnection );
    return self;
}

- (id) initAsServer {

    xpc_connection_t xpcConnection = xpc_connection_create_mach_service( [[[NSBundle mainBundle] bundleIdentifier] UTF8String],
                                                                         dispatch_get_main_queue(),
                                                                         XPC_CONNECTION_MACH_SERVICE_LISTENER );
    if ( (self = [super init]) ) {

        self.serverConnection = YES;
        [self setupConnection:xpcConnection];
    }

    xpc_release( xpcConnection );
    return self;
}

@end

Obviously, I am using some Category methods which are self explanatory. For example:

显然,我正在使用一些不言自明的 Category 方法。例如:

@implementation NSError (CategoryXPCMessage)
+ (NSError *) errorFromXObject:(xpc_object_t)xObject {

    char *description = xpc_copy_description( xObject );
    NSError *xpcError = [NSError errorWithDomain:NSPOSIXErrorDomain code:EINVAL userInfo:@{
                         NSLocalizedDescriptionKey:
                        [NSString stringWithCString:description encoding:[NSString defaultCStringEncoding]] }];
    free( description );
    return xpcError;
}
@end

Okay, using this I set myself up an interface for both the client-side and server-side. The header looks like this:

好的,使用它我为客户端和服务器端设置了一个接口。标题如下所示:

@class CommXPCManager;

@protocol AppXPCErrorHandler <NSObject>
@required
- (void) handleXPCError:(NSError *)error forType:(CommXPCErrorType)errorType;
@end

static NSString* const kAppXPCKeyReturn = @"AppXPCInterfaceReturn";    // id returnObject
static NSString* const kAppXPCKeyReply  = @"AppXPCInterfaceReply";     // NSNumber: BOOL
static NSString* const kAppXPCKeySEL    = @"AppXPCInterfaceSelector";  // NSString
static NSString* const kAppXPCKeyArgs   = @"AppXPCInterfaceArguments"; // NSArray (Must be xObject compliant)

@interface AppXPCInterface : NSObject

@property (readonly, strong, nonatomic) CommXPCManager *managerXPC;
@property (readonly, strong, nonatomic) NSArray *peerConnections;

- (void) sendMessage:(SEL)aSelector withArgs:(NSArray *)args reply:(void (^)(NSDictionary *replyDict, NSError *error))reply;
- (void) sendMessageToPeers:(SEL)aSelector withArgs:(NSArray *)args reply:(void (^)(NSDictionary *replyDict, NSError *error))reply;

- (id) initWithBundleID:(NSString *)bundleID andDelegate:(id<AppXPCErrorHandler>)object forProtocol:(Protocol *)proto;
- (id) initListenerWithDelegate:(id<AppXPCErrorHandler>)object forProtocol:(Protocol *)proto;

- (void) observeListenerHello:(CommReceptionistNoteBlock)helloBlock;
- (void) removeListenerObserver;

- (void) startClientConnection;
- (void) startListenerConnection;
- (void) stopConnection;

@end

Here is the implementation to start the listener:

这是启动侦听器的实现:

- (void) startListenerConnection {

    [self stopConnection];
    self.managerXPC = [[CommXPCManager alloc] initAsServer];

    __block AppXPCInterface *this = self;

    self.managerXPC.connectionHandler = ^(CommXPCManager *peerConnection) {

        [(NSMutableArray *)this.peerConnections addObject:peerConnection];

        peerConnection.messageHandler = ^(CommXPCManager *mgrXPC, xpc_object_t event, NSDictionary *message) {

            [this processMessage:message forEvent:event];
        };

        peerConnection.errorHandler = ^(CommXPCManager *peer, CommXPCErrorType errorType, NSError *error) {

            [this processError:error forErrorType:errorType];
            [(NSMutableArray *)this.peerConnections removeObject:peer];
        };
    };

    [CommReceptionist postGlobalNote:kAppXPCListenerNoteHello];
}

Here is the implementation to start the client:

这是启动客户端的实现:

- (void) startClientConnection {

    [self stopConnection];
    self.managerXPC = [[CommXPCManager alloc] initAsClientWithBundleID:self.identifierXPC];

    __block AppXPCInterface *this = self;

    self.managerXPC.messageHandler = ^(CommXPCManager *mgrXPC, xpc_object_t event, NSDictionary *message) {

        [this processMessage:message forEvent:event];
    };

    self.managerXPC.errorHandler = ^(CommXPCManager *mgrXPC, CommXPCErrorType errorType, NSError *error) {

        [this processError:error forErrorType:errorType];
    };
}

Now here is the order of things.

现在是事情的顺序。

  1. Your main app starts its helper The helper starts listening using its bundleID <--- Important!
  2. The main app listens for a global notification and then sends a message
  3. When the client sends a message the connection is established
  1. 你的主应用程序启动它的助手助手开始使用它的 bundleID <--- 重要!
  2. 主应用程序侦听全局通知,然后发送消息
  3. 当客户端发送消息时,连接建立

Now the server can send messages to the client and the client can send messages to the server (with or without a reply).

现在服务器可以向客户端发送消息,客户端可以向服务器发送消息(有或没有回复)。

It's very fast, it works well, and is designed for OS X 10.7.3 or greater.

它非常快,运行良好,专为 OS X 10.7.3 或更高版本而设计。

A few notes:

一些注意事项:

  • The name of the helper must be the same name as the bundle ID
  • The name must begin with your team ID
  • For sandboxing, both the Main app and Helper app application group setting must be start with prefix of the helper Bundle ID
  • helper 的名字必须和 bundle ID 同名
  • 名称必须以您的团队 ID 开头
  • 对于沙箱,主应用程序和助手应用程序组设置都必须以助手包 ID 的前缀开头

e.g. Helper bundle id is: ABC123XYZ.CompanyName.GroupName.Helper App Group ID will be: ABC123XYZ.CompanyName.GroupName

例如,Helper bundle id 为:ABC123XYZ.CompanyName.GroupName.Helper App Group ID 将为:ABC123XYZ.CompanyName.GroupName

There are additional details I left out so as not to bore anyone. But if it's still unclear just ask and I will answer.

为了不让任何人感到厌烦,我省略了其他细节。但如果还不清楚,就问,我会回答。

Ok, hope this helps. Arvin

好的,希望这有帮助。阿尔文

回答by A O

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

好吧,对于任何一直在为此苦苦挣扎的人,我终于能够 100% 地在两个应用程序进程之间进行通信,使用 NSXPCConnection

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

需要注意的关键是您只能创建一NSXPCConnection到三件事。

  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.
  1. 一个 XPC 服务。您可以严格通过名称连接到 XPCService
  2. 马赫服务。您还可以严格通过名称连接到 Mach 服务
  3. 一个NSXPCEndpoint。这就是我们正在寻找的,在两个应用程序进程之间进行通信。

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

问题是我们不能直接将NSXPCListenerEndpoint一个应用程序从一个应用程序转移到另一个应用程序。

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

它涉及创建一个拥有属性的 machservice 启动代理(请参阅此示例了解如何执行此操作NSXPCListenerEndpoint。一个应用程序可以连接到 machservice,并将该属性设置为它自己的[NSXPCListener anonymousListener].endpoint

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

然后另一个应用程序可以连接到 machservice,并请求该端点。

Then using that endpoint, an NSXPCConnectioncan 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.

然后使用该端点,NSXPCConnection可以创建一个,它成功地在两个应用程序之间建立了一座桥梁。我已经测试过来回发送对象,一切都按预期工作。

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

请注意,如果您的应用程序是沙盒的,您必须创建一个XPCService, 作为您的应用程序和 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

我很高兴我得到了这个工作 - 我在 SO 中相当活跃,所以如果有人对源代码感兴趣,只需添加评论,我可以努力发布更多细节

Some hurdles I came across:

我遇到的一些障碍:

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

你必须启动你的 machservice,这些是几行:

   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:

此外,每次重建守护进程时,都必须使用以下 bash 命令卸载之前的启动代理:

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)

(当然,使用您相应的标识符)