Macos 可以使用Mac OS X XPC(如IPC)在进程之间交换消息吗?怎么用?

Macos 可以使用Mac OS X XPC(如IPC)在进程之间交换消息吗?怎么用?,macos,ipc,osx-lion,sandbox,xpc,Macos,Ipc,Osx Lion,Sandbox,Xpc,据苹果称,Lion中引入的新XPC服务API为与Grand Central Dispatch(GCD)和launchd集成的基本进程间通信提供了一种轻量级机制 似乎可以将此API用作一种IPC,如POSIX IPC,但是,我找不到如何做到这一点 我尝试使用XPCAPI与两个进程通信,以便在它们之间传递消息,但在服务器端总是出现“XPCConnection invalid”错误 我不想要XPC服务,我只想使用客户机-服务器体系结构交换消息 我使用了两个类似BSD的进程,所以没有Info.plist

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

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

我尝试使用XPCAPI与两个进程通信,以便在它们之间传递消息,但在服务器端总是出现“XPCConnection invalid”错误

我不想要XPC服务,我只想使用客户机-服务器体系结构交换消息

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

我一直在关注这个讨论,但这个话题似乎有点模糊,没有记录在案


谢谢

是的,这是可能的,但不是你期望的那样

您可以拥有一个(非启动)进程来提供服务。这是出于安全原因,因为这将使中间人攻击变得容易

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

这可能看起来很简单,但出于安全考虑,这是必要的

有关匿名连接的详细信息,请参阅
xpc\u对象(3)
手册页


这有点违反直觉,因为进程a将使用
xpc\u connection\u create()
创建一个侦听器对象。然后,A使用
xpc\u endpoint\u create()
从侦听器创建端点对象,并将该端点通过线路(通过xpc)发送到进程B。B然后可以将该对象转换为具有
xpc\u connection\u create\u from\u endpoint()
的连接。然后,A的侦听器事件处理程序将接收一个连接对象,该对象与B使用
xpc\u connection\u create\u from\u endpoint()
创建的连接相匹配。这与
xpc\u connection\u create\u mach\u service()
的事件处理程序在客户端连接时接收连接对象的方式类似。

以下是我如何使用xpc实现双向IPC

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

我创建了以下管理器:

标题:

@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
实施:

@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
显然,我使用的是一些自解释的分类方法。 例如:

@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
好的,使用这个,我为客户端和服务器端设置了一个接口。标题如下所示:

@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
以下是启动客户端的实现:

- (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];
    };
}
现在事情的顺序是这样的

  • 您的主应用程序启动其助手,助手开始使用
    它的bundleID对任何一直在努力解决这个问题的人来说,我终于能够使用
    NSXPCConnection在两个应用程序进程之间100%地实现通信

    需要注意的关键是,您只能为三件事创建
    NSXPCConnection

  • XPC服务。您可以严格通过以下方式连接到XPCService 名字
  • 马赫数服务。您还可以连接到马赫服务 严格地通过一个名字
  • 一个
    NSXPCEndpoint
    。这就是我们要做的 寻找,以便在两个应用程序进程之间进行通信
  • 问题是我们不能直接将
    nsxpclisterendpoint
    从一个应用程序传输到另一个应用程序

    它涉及到创建一个包含
    nsxpclisterendpoint
    属性的machservice启动代理()。一个应用程序可以连接到machservice,并将该属性设置为它自己的
    [NSXPCListener anonymousListener].endpoint

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

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

    请注意,如果您的应用程序是沙盒的,必须创建一个
    XPCService
    ,作为应用程序和服务之间的中间人

    我很高兴我能做到这一点——我在这方面相当活跃,所以如果有人对源代码感兴趣,只需添加一条评论,我就可以发布更多细节

    我遇到的一些障碍:

    您必须启动您的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
                            );
    
    此外,每次重建守护程序时,都必须使用以下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
    

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

    似乎有人已经做到了这一点……但我自己仍然不知道如何做到这一点……如果你真的有父子关系,那么XPC是为你准备的,但如果你有两个独立的进程,XPC就不是出路。macOS基于Mach微内核,因此它有一个非常强大的IPC机制,比其他任何东西都快:Mach消息。它的工作原理有点像通过套接字发送数据,但您也可以让它通过共享内存为您传输数据(即写时拷贝)。这是一个有点糟糕的文件和概念是复杂的开始,但它值得学习。macOS中的所有其他IPC实际上都是在Mach消息之上实现的。如果我不想让进程B连接到该服务怎么办?例如,假设B是bash脚本或已经编译的C程序,那么是否可以通过XPC实现“经典”IPC?或者,我可以在某些情况下绕过XPC吗
    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