Distributed Objects are easier than you think

For my current project, I had to implement interprocess communication between a background process and an application. In the past, I would probably use AppleEvents or even CFMessage, but this tech note advises against using those techniques.

After some investigation, I found that Distributed Objects is the easiest & cleanest way to do it.

Distributed Objects works its magic with a set of proxy classes that pose as the actual object which exists in the other application. Here’s how to use it.

The object you want to share can be any ordinary object, like this one:

@interface MyObject: NSObject
- (int)myMethod;
@end

@implementation MyObject
- (int)myMethod {
	return 42;
}
@end

On the server side, you create a connection and vend that object using NSConnection’s setRootObject: method.

#define SOCK_NAME "/var/tmp/com.example.socketname"

   // make sure socket file doesn't exist, or we'll fail
    unlink(SOCK_NAME);
    
    // create an AF_UNIX socket address
    struct sockaddr_un socketAddress;
    bzero(&socketAddress,sizeof(socketAddress));
    socketAddress.sun_family = AF_UNIX;
    strcpy(socketAddress.sun_path,SOCK_NAME);
    socketAddress.sun_len = SUN_LEN(&socketAddress);
    NSData* socketAddressData = [NSData dataWithBytes:&socketAddress length: sizeof(socketAddress)];

    NSPort * theMessagePort = [[NSSocketPort alloc] initWithProtocolFamily: AF_UNIX
                                                     socketType: SOCK_STREAM
                                                       protocol: 0
                                                        address: socketAddressData];

    NSConnection * theConnection = [[NSConnection alloc] initWithReceivePort: theMessagePort  sendPort: nil];
    
    // create our object to be shared
    theObject = [[MyObject alloc] init];
    [theConnection setRootObject: theObject];

In the client application, you create a remote connection and ask for a proxy object. You then use that proxy object as if it was the actual object.

    // create an AF_UNIX socket address
    struct sockaddr_un socketAddress;
    bzero(&socketAddress,sizeof(socketAddress));
    socketAddress.sun_family = AF_UNIX;
    strcpy(socketAddress.sun_path,SOCK_NAME);
    socketAddress.sun_len = SUN_LEN(&socketAddress);
    NSData* socketAddressData = [NSData dataWithBytes:&socketAddress length: sizeof(socketAddress)];
    
    // Create a message handler socket and add it to our runloop
    NSPort * theMessagePort = [[NSSocketPort alloc] initRemoteWithProtocolFamily: AF_UNIX
                                                           socketType: SOCK_STREAM
                                                             protocol: 0
                                                              address: socketAddressData];
        
    NSConnection * theConnection = [[NSConnection alloc] initWithReceivePort: nil sendPort: theMessagePort];
    
    // this is actually a NSDistantObject which acts as a proxy to our actual object
    MyObject *anObject = (MyObject*)[theConnection rootProxy];

    // call a method on the remote object
    int result = [anObject myMethod];

What really happens when you call the method in the client is that it gets packaged as a NSInvocation, which gets sent over the connection and is executed by the server process.

Leave a Reply