24

Apple によると、Lion で導入された新しい XPC Services API は、Grand Central Dispatch (GCD) および launchd と統合された基本的なプロセス間通信のための軽量メカニズムを提供します。

この API を POSIX IPC のような一種の IPC として使用することは可能のようですが、その方法がわかりません。

XPC API を使用して 2 つのプロセスを通信しようとしていますが、それらの間でメッセージを渡すことができますが、サーバー側で常に「XPC 接続が無効です」というエラーが発生します。

XPC サービスは必要ありません。クライアント サーバー アーキテクチャを使用してメッセージを交換したいだけです。

私は2つのBSDのようなプロセスを使用しているため、Info.plistなどはありません...

私はこの議論をフォローしてきましたhttp://lists.macosforge.org/pipermail/launchd-dev/2011-November/000982.htmlですが、このトピックは少しあいまいで文書化されていないようです。

ありがとう!

4

3 に答える 3

20

はい、それは可能ですが、期待する方法ではありません。

(launchd 以外の) プロセスにサービスを提供させることはできません。これはセキュリティ上の理由によるもので、中間者攻撃が容易になるためです。

ただし、目的を達成することはできます。XPC / mach サービスを提供する launchd サービスをセットアップする必要があります。次に、プロセス A と B の両方が launchd サービスに接続します。その後、プロセス A はいわゆる匿名接続を作成し、それを launchd サービスに送信してプロセス B に転送できます。プロセス A と B は、その接続を介して直接互いに通信できます (つまり、launchd サービスを終了できます)。接続を切断することなく)。

これは回りくどいように思えるかもしれませんが、セキュリティ上の理由から必要です。

匿名接続の詳細については、xpc_object(3)man ページを参照してください。

プロセス 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()これは、クライアントの接続時に のイベント ハンドラーが接続オブジェクトを受け取る方法と同様に機能します。

于 2012-02-07T20:54:35.250 に答える
12

これが、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) 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];
}

クライアントを起動するための実装は次のとおりです。

- (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];
    };
}

今ここに物事の順序があります。

  1. メインアプリがヘルパーを開始しますヘルパーはbundleIDを使用してリッスンを開始します<---重要!
  2. メインアプリはグローバル通知をリッスンしてからメッセージを送信します
  3. クライアントがメッセージを送信すると、接続が確立されます

これで、サーバーはクライアントにメッセージを送信でき、クライアントはサーバーにメッセージを送信できます(応答の有無にかかわらず)。

非常に高速で、うまく機能し、OSX10.7.3以降用に設計されています。

いくつかの注意:

  • ヘルパーの名前は、バンドルIDと同じ名前である必要があります
  • 名前はチームIDで始まる必要があります
  • サンドボックス化の場合、メインアプリとヘルパーアプリの両方のアプリケーショングループ設定は、ヘルパーバンドルIDのプレフィックスで開始する必要があります

例:ヘルパーバンドルIDは次のとおりです。ABC123XYZ.CompanyName.GroupName.HelperアプリグループIDは次のようになります:ABC123XYZ.CompanyName.GroupName

誰も退屈させないように、私が省略した追加の詳細があります。しかし、それでも不明な場合は、質問してください。回答します。

わかりました。これがお役に立てば幸いです。アーヴィン

于 2012-08-30T19:07:46.720 に答える