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
Is possible to use Mac OS X XPC like IPC to exchange messages between processes? How?
提问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.
现在是事情的顺序。
- Your main app starts its helper The helper starts listening using its bundleID <--- Important!
- The main app listens for a global notification and then sends a message
- When the client sends a message the connection is established
- 你的主应用程序启动它的助手助手开始使用它的 bundleID <--- 重要!
- 主应用程序侦听全局通知,然后发送消息
- 当客户端发送消息时,连接建立
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 NSXPCConnection
to three things.
需要注意的关键是您只能创建一NSXPCConnection
到三件事。
- An XPCService. You can connect to an XPCService strictly through a name
- A Mach Service. You can also connect to a Mach Service strictly through a name
- An
NSXPCEndpoint
. This is what we're looking for, to communicate between two application processes.
- 一个 XPC 服务。您可以严格通过名称连接到 XPCService
- 马赫服务。您还可以严格通过名称连接到 Mach 服务
- 一个
NSXPCEndpoint
。这就是我们正在寻找的,在两个应用程序进程之间进行通信。
The problem being that we can't directly transfer an NSXPCListenerEndpoint
from one application to another.
问题是我们不能直接将NSXPCListenerEndpoint
一个应用程序从一个应用程序转移到另一个应用程序。
It involved creating a machservice Launch Agent (See this example for how to do that) that held an NSXPCListenerEndpoint
property. 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 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.
然后使用该端点,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)
(当然,使用您相应的标识符)