Saturday, January 29, 2011

Distributed Objects on OS X (2)

I struggled for a while to get Distributed Objects working between different machines (first post here for same-machine example). According to the docs and this, you should just use


NSSocketPortNameServer *name_server;
name_server = [NSSocketPortNameServer sharedInstance];
NSLog(@"%@", [name_server description]);
BOOL result;
result = [conn registerName:@"my_server"
withNameServer:name_server];


But it didn't work for me. So I asked a question on SO (here), and asked the Google. The latter approach finally worked. I got a working example with code from here. The discussion in the docs for the NSConnection class helped too (see initWithReceivePort:sendPort:. If you set it up with nil for the sendPort, it uses the same port for both sending and receiving, which is what you want.)

[UPDATE: On reflection, it does seem a bit weird, because a Mach port is supposed to be a one-way channel, yet these are clearly two way. I guess they are probably complex objects built from multiple NSMacPorts. ]

Server:


// gcc talk3.m -o test -framework Foundation
// http://web.archiveorange.com/archive/v/SEb6a6s543xreqfXtOCQ
#import <Foundation/Foundation.h>

@interface VendedObject:NSObject {}
-(NSString *) speak;
@end

@implementation VendedObject

-(NSString *) speak {
return @"woof";
}
@end

@interface NSConnection (NetworkServiceAdditions)

+ (id) networkServiceConnectionWithName:(NSString *) inName
rootObject:(id) inRootObject;

@end

@implementation NSConnection (NetworkServiceAdditions)

+ (id) networkServiceConnectionWithName:(NSString *) inName
rootObject:(id) inRootObject;
{
NSSocketPort *port = [[[NSSocketPort alloc] init] autorelease];
NSConnection *connection = [NSConnection connectionWithReceivePort: port
sendPort: nil];

[[NSSocketPortNameServer sharedInstance] registerPort:port
name:inName];
[connection setRootObject: inRootObject];
return connection;
}

@end

int main(){
NSAutoreleasePool *pool;
pool = [[NSAutoreleasePool alloc] init];
VendedObject *obj;
obj = [[[VendedObject alloc ] init] autorelease];
NSLog(@"%@", [obj description]);
NSConnection *conn;
conn = [NSConnection networkServiceConnectionWithName:@"my_server"
rootObject:obj ];
NSSocketPortNameServer *name_server;
name_server = [NSSocketPortNameServer sharedInstance];
NSLog(@"%@", [name_server description]);
NSLog(@"%@", [name_server portForName:@"my_server"]);
[[NSRunLoop mainRunLoop] run];
[pool drain];
return 0;
}


Note: if you're not just killing the Server (as I did) you should do invalidate on the connection (and I guess the ports too?).

Client listen.py is on my laptop:


from Foundation import *

proxy_obj = NSConnection.rootProxyForConnectionWithRegisteredName_host_usingNameServer_(
"my_server", "osxserver.local", NSSocketPortNameServer.sharedInstance())

if not proxy_obj:
print 'Did not get an object from the server.'
else:
print proxy_obj.description()
print type(proxy_obj)
print proxy_obj.speak()


Output:


> python listen.py 
<VendedObject: 0x10010ced0>
<objective-c class NSDistantObject at 0x7fff70a64868>
woof


It works!