This is Connection.m in view mode; [Download] [Up]
/* Implementation of connection object for remote object messaging Copyright (C) 1994 Free Software Foundation, Inc. Written by: R. Andrew McCallum <mccallum@gnu.ai.mit.edu> Date: July 1994 This file is part of the GNU Objective C Class Library. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* RMC == Remote Method Coder, or Remote Method Call. It's an instance of ConnectedCoder. */ #include <objects/stdobjects.h> #include <objects/Connection.h> #include <objects/Proxy.h> #include <objects/ConnectedCoder.h> #include <objects/SocketPort.h> #include <objects/Array.h> #include <objects/Dictionary.h> #include <objects/Queue.h> #include <objects/mframe.h> #include <assert.h> @interface Connection (GettingCoderInterface) - doReceivedRequestsWithTimeout: (int)to; - newReceivedReplyRmcWithSequenceNumber: (int)n; - newSendingRequestRmc; - newSendingReplyRmcWithSequenceNumber: (int)n; - (int) _newMsgNumber; @end #define proxiesHashGate refGate #define sequenceNumberGate refGate static inline BOOL class_is_kind_of(CLASS self, CLASS aClassObject) { CLASS class; for (class = self; class!=Nil; class = class_get_super_class(class)) if (class==aClassObject) return YES; return NO; } static inline unsigned int hash_int (cache_ptr cache, const void *key) { return (unsigned)key & cache->mask; } static inline int compare_ints (const void *k1, const void *k2) { return !(k1 - k2); } static int type_get_number_of_arguments (const char *type) { int i = 0; while (*type) { type = objc_skip_argspec (type); i += 1; } return i - 1; } static elt exc_func_return_nil (arglist_t af) { return nil; } /* class defaults */ static id default_port_class; static id defaultProxyClass; static id defaultCoderClass; static int default_in_timeout; static int default_out_timeout; static BOOL debug_connection = NO; /* Perhaps this should be a hashtable, keyed by remote port. But we may also need to include the local port---even though when receiving the local port is fixed, there may be more than one registered connection (with different in ports) in the application. */ /* We could write -hash and -isEqual implementations for Connection */ static Array *connectionArray; static Lock *connectionArrayGate; static Dictionary *rootObjectDictionary; static Lock *rootObjectDictionaryGate; /* rmc handling */ static Queue *receivedRequestRmcQueue; static Lock *receivedRequestRmcQueueGate; static Queue *receivedReplyRmcQueue; static Lock *receivedReplyRmcQueueGate; static int messagesReceivedCount; @implementation Connection + initialize { connectionArray = [[Array alloc] init]; connectionArrayGate = [Lock new]; receivedRequestRmcQueue = [[Queue alloc] init]; receivedRequestRmcQueueGate = [Lock new]; receivedReplyRmcQueue = [[Queue alloc] init]; receivedReplyRmcQueueGate = [Lock new]; rootObjectDictionary = [[Dictionary alloc] init]; rootObjectDictionaryGate = [Lock new]; messagesReceivedCount = 0; default_port_class = [SocketPort class]; defaultProxyClass = [Proxy class]; defaultCoderClass = [ConnectedCoder class]; default_in_timeout = CONNECTION_DEFAULT_TIMEOUT; default_out_timeout = CONNECTION_DEFAULT_TIMEOUT; return self; } + setDefaultPortClass: aClass { default_port_class = aClass; return self; } + setDefaultProxyClass: aClass { defaultProxyClass = aClass; return self; } + defaultProxyClass { return defaultProxyClass; } + setDefaultCoderClass: aClass { defaultCoderClass = aClass; return self; } + defaultCoderClass { return defaultCoderClass; } + (int) defaultOutTimeout { return default_out_timeout; } + setDefaultOutTimeout: (int)to { default_out_timeout = to; return self; } + (int) defaultInTimeout { return default_in_timeout; } + setDefaultInTimeout: (int)to { default_in_timeout = to; return self; } + (int) messagesReceived { return messagesReceivedCount; } /* For encoding and decoding the method arguments, we have to know where to find things in the "argframe" as returned by __builtin_apply_args. For some situations this is obvious just from the selector type encoding, but structures passed by value cause a problem because some architectures actually pass these by reference, i.e. use the structure-value-address mentioned in the gcc/config/_/_.h files. These differences are not encoded in the selector types. Below is my current guess for which architectures do this. I've also been told that some architectures may pass structures with sizef(structure) > sizeof(void*) by reference, but pass smaller ones by value. The code doesn't currently handle that case. */ /* Do we need separate _PASSED_BY_REFERENCE and _RETURNED_BY_REFERENCE? */ #if (sparc) || (hppa) || (AM29K) #define CONNECTION_STRUCTURES_PASSED_BY_REFERENCE 1 #else #define CONNECTION_STRUCTURES_PASSED_BY_REFERENCE 0 #endif /* Float and double return values are stored at retframe + 8 bytes by __builtin_return() The retframe consists of 16 bytes. The first 4 are used for ints, longs, chars, etc. The last 8 are used for floats and doubles. xxx This is disgusting. I should get this info from the gcc config machine description files. xxx */ #define FLT_AND_DBL_RETFRAME_OFFSET 8 #if 1 - (retval_t) connectionForward: (Proxy*)object : (SEL)sel : (arglist_t)argframe { ConnectedCoder *op; void encoder(int argnum, void *datum, const char *type, int flags) { #define ENCODED_ARGNAME "argument value" switch (*type) { case _C_ID: if (flags & _F_BYCOPY) [op encodeObjectBycopy:*(id*)datum withName:ENCODED_ARGNAME]; else [op encodeObject:*(id*)datum withName:ENCODED_ARGNAME]; break; default: [op encodeValueOfType:type at:datum withName:ENCODED_ARGNAME]; } } { BOOL out_parameters; const char *type; retval_t retframe; int seq_num; op = [self newSendingRequestRmc]; seq_num = [op sequenceNumber]; /* get the method types from the selector */ #if NeXT_runtime [self error: "sorry, distributed objects does not work with the NeXT runtime"]; /* type = [object selectorTypeForProxy:sel]; */ #else type = sel_get_type(sel); #endif assert(type); assert(*type); /* Send the types that we're using, so that the performer knows exactly what qualifiers we're using. If all selectors included qualifiers and I could make sel_types_match() work the way I wanted, we wouldn't need to do this. */ [op encodeValueOfSimpleType:@encode(char*) at:&type withName:"selector type"]; /* xxx This doesn't work with proxies and the NeXT runtime because type may be a method_type from a remote machine with a different architecture, and its argframe layout specifiers won't be right for this machine! */ out_parameters = dissect_method_call(argframe, type, encoder); [op dismiss]; { ConnectedCoder *ip = nil; int last_argnum; void decoder(int argnum, void *datum, const char *type, int flags) { assert(ip != (id)-1); if (!ip) ip = [self newReceivedReplyRmcWithSequenceNumber:seq_num]; [ip decodeValueOfType:type at:datum withName:NULL]; if (argnum == last_argnum) { /* this must be here to avoid trashing alloca'ed retframe */ [ip dismiss]; ip = (id)-1; } } last_argnum = type_get_number_of_arguments(type) - 1; retframe = dissect_method_return(argframe, type, out_parameters, decoder); return retframe; } } } #else - (retval_t) connectionForward: (SEL)sel : (arglist_t)argframe { id op; id ip; retval_t* retframe = NULL; /* NULL just to avoid uninitialized warning */ const char *type; const char *tmptype; unsigned flags; int retsize; char *datum; BOOL out_parameters = NO; int argnum; /* for debugging */ char argname[16]; /* for debugging */ op = [self newSendingRequestRmc]; /* get the method types from the selector */ type = sel_get_type(sel); assert(type); assert(*type); /* Send the types that we're using, so that the performer knows exactly what qualifiers we're using. If all selectors included qualifiers and I could make sel_types_match() work the way I wanted, we wouldn't need to do this. */ [op encodeValueOfSimpleType:@encode(char*) at:&type withName:"selector type"]; /* encode arguments */ tmptype = type; datum = my_method_get_next_argument(argframe, &tmptype); assert(datum); assert(*tmptype == _C_ID); if (![*(id*)datum isProxy]) [self error:"Asking connection to forward to a non-Proxy object"]; #if 0 if (![*(id*)datum proxyIsRemote]) [self error:"Asking connection to forward to non-remote Proxy"]; #endif if ([*(id*)datum connectionForProxy] != self) [self error:"Asking connection to forward to a Proxy it doesn't own"]; [op encodeValueOfType:tmptype at:datum withName:"arg1 receiver"]; datum = my_method_get_next_argument(argframe, &tmptype); assert(datum); assert(*tmptype == _C_SEL); [op encodeValueOfType:tmptype at:datum withName:"arg2 selector"]; for (datum = my_method_get_next_argument(argframe, &tmptype), argnum=3; datum; datum = my_method_get_next_argument(argframe, &tmptype), argnum++) { flags = objc_get_type_qualifiers(tmptype); tmptype = objc_skip_type_qualifiers(tmptype); sprintf(argname, "arg%d", argnum); /* too expensive? */ if (*tmptype == _C_ID) { if (flags & _F_BYCOPY) [op encodeObjectBycopy:*(id*)datum withName:argname]; else [op encodeObject:*(id*)datum withName:argname]; } else if (*tmptype == _C_CHARPTR) { if ((flags & _F_OUT) || !(flags & _F_IN)) out_parameters = YES; if ((flags & _F_IN) || !(flags & _F_OUT)) [op encodeValueOfType:tmptype at:datum withName:argname]; } else if (*tmptype == _C_PTR) { tmptype++; if ((flags & _F_OUT) || !(flags & _F_IN)) out_parameters = YES; /* xxx These two cases currently the same */ if (*tmptype == _C_STRUCT_B || *tmptype == _C_ARY_B) { if ((flags & _F_IN) || !(flags & _F_OUT)) [op encodeValueOfType:tmptype at:*(void**)datum withName:argname]; } else { if ((flags & _F_IN) || !(flags & _F_OUT)) [op encodeValueOfType:tmptype at:*(void**)datum withName:argname]; } } else if (*tmptype == _C_STRUCT_B || *tmptype == _C_ARY_B) { #if CONNECTION_STRUCTURES_PASSED_BY_REFERENCE [op encodeValueOfType:tmptype at:*(void**)datum withName:argname]; #else [op encodeValueOfType:tmptype at:datum withName:argname]; #endif } else { [op encodeValueOfType:tmptype at:datum withName:argname]; } } [op dismiss]; /* get return values, if necessary */ flags = objc_get_type_qualifiers(type); tmptype = objc_skip_type_qualifiers(type); /* xxx What happens with method declared "- (oneway) foo: (out int*)ip;" */ /* xxx What happens with method declared "- (in char *) bar;" */ /* Is this right? Do we also have to check _F_ONEWAY? */ if (out_parameters || *tmptype != _C_VOID) { ip = [self newReceivedReplyRmcWithSequenceNumber: [op sequenceNumber]]; if (*tmptype != _C_VOID) { /* decode return value */ retsize = objc_sizeof_type(tmptype); /* xxx We need to test retsize's less than 4 Also note that if we return structures using a structure-value-address, we are potentially alloca'ing much more than we need here. */ /* xxx Find out about returning structures by reference on non--structure-value-address machines, and potentially just always alloca(RETFRAME_SIZE == sizeof(void*)*4) */ retframe = alloca(MAX(retsize, sizeof(void*)*4)); /* xxx change this to a switch (*tmptype) */ if (*tmptype == _C_PTR) { tmptype++; /* xxx these two cases are the same */ if (*tmptype == _C_STRUCT_B || *tmptype == _C_ARY_B) { *(void**)retframe = (*objc_malloc)(objc_sizeof_type(tmptype)); [ip decodeValueOfType:tmptype at:*(void**)retframe withName:NULL]; } else { *(void**)retframe = (*objc_malloc)(objc_sizeof_type(tmptype)); [ip decodeValueOfType:tmptype at:*(void**)retframe withName:NULL]; } } else if (*tmptype == _C_STRUCT_B || *tmptype == _C_ARY_B) { /* xxx These two cases currently the same */ #if CONNECTION_STRUCTURES_PASSED_BY_REFERENCE *(void**)retframe = alloca(objc_sizeof_type(tmptype)); [ip decodeValueOfType:tmptype at:*(void**)retframe withName:NULL]; #else *(void**)retframe = alloca(objc_sizeof_type(tmptype)); [ip decodeValueOfType:tmptype at:*(void**)retframe withName:NULL]; #endif } else if (*tmptype == _C_FLT || *tmptype == _C_DBL) { [ip decodeValueOfType:tmptype at:((char*)retframe) + FLT_AND_DBL_RETFRAME_OFFSET withName:NULL]; } else /* Among other things, _C_CHARPTR is handled here */ { /* int typesize; xxx Use retsize instead! */ /* xxx was: (typesize = objc_sizeof_type(tmptype)) */ /* Special case BOOL (and other types smaller than int) because retframe doesn't actually point to the char */ /* xxx What about structures smaller than int's that are passed by reference on true structure reference- passing architectures? */ /* xxx Is this the right test? Use sizeof(int*) instead? */ if (retsize < sizeof(void*)) { *(void**)retframe = 0; [ip decodeValueOfType:tmptype at:((char*)retframe)+sizeof(void*)-retsize withName:NULL]; } else { [ip decodeValueOfType:tmptype at:retframe withName:NULL]; } } } /* decode values returned by reference */ if (out_parameters) { for (datum = my_method_get_next_argument(argframe, &tmptype); datum; (datum = my_method_get_next_argument(argframe, &tmptype))) { flags = objc_get_type_qualifiers(tmptype); tmptype = objc_skip_type_qualifiers(tmptype); if (*tmptype == _C_PTR && ((flags & _F_OUT) || !(flags & _F_IN))) { tmptype++; /* xxx Note that a (char**) is malloc'ed anew here. Yucky, or worse than yucky. If the returned string is smaller than the original, we should just put it there; if the returned string is bigger, I don't know what to do. */ /* xxx These two cases are the same */ if (*tmptype == _C_STRUCT_B || *tmptype == _C_ARY_B) { [ip decodeValueOfType:tmptype at:*(void**)datum withName:NULL]; } else { [ip decodeValueOfType:tmptype at:*(void**)datum withName:NULL]; } } /* __builtin_return can't return structures by value */ else if (*tmptype == _C_CHARPTR && ((flags & _F_OUT) || !(flags & _F_IN))) { [ip decodeValueOfType:tmptype at:datum withName:NULL]; } } } [ip dismiss]; } else /* void return value */ { retframe = alloca(sizeof(void*)); } return retframe; } #endif /* 1 */ #if 1 - connectionPerformAndDismissCoder: aRmc { char *forward_type; id op = nil; int reply_sequence_number; int numargs; void decoder (int argnum, void *datum, const char *type) { [aRmc decodeValueOfType:type at:datum withName:NULL]; /* We need this "dismiss" to happen here and not later so that Coder "-awake..." methods will get sent before the __builtin_apply! */ if (argnum == numargs-1) [aRmc dismiss]; } void encoder (int argnum, void *datum, const char *type, int flags) { #define ENCODED_RETNAME "return value" if (op == nil) op = [self newSendingReplyRmcWithSequenceNumber: reply_sequence_number]; switch (*type) { case _C_ID: if (flags & _F_BYCOPY) [op encodeObjectBycopy:*(id*)datum withName:ENCODED_RETNAME]; else [op encodeObject:*(id*)datum withName:ENCODED_RETNAME]; break; default: [op encodeValueOfType:type at:datum withName:ENCODED_RETNAME]; } } /* Save this for later */ reply_sequence_number = [aRmc sequenceNumber]; /* Get the types that we're using, so that we know exactly what qualifiers the forwarder used. If all selectors included qualifiers and I could make sel_types_match() work the way I wanted, we wouldn't need to do this. */ [aRmc decodeValueOfSimpleType:@encode(char*) at:&forward_type withName:NULL]; numargs = type_get_number_of_arguments(forward_type); make_method_call(forward_type, decoder, encoder); [op dismiss]; (*objc_free)(forward_type); return self; } #else - connectionPerformAndDismissCoder: aRmc { const char *type, *tmptype; char *forward_type; const char *ftmptype; id object; SEL selector; IMP imp; void *retframe; arglist_t argframe; int stack_argsize; int reg_argsize; char *datum; id op; unsigned flags; BOOL out_parameters = NO; int reply_sequence_number; char argname[16]; /* for debugging with TextCoder */ int argnum; /* for debugging with TextCoder */ /* Save this for later */ reply_sequence_number = [aRmc sequenceNumber]; /* Get the types that we're using, so that we know exactly what qualifiers the forwarder used. If all selectors included qualifiers and I could make sel_types_match() work the way I wanted, we wouldn't need to do this. */ [aRmc decodeValueOfSimpleType:@encode(char*) at:&forward_type withName:NULL]; /* get object and selector */ [aRmc decodeValueOfType:@encode(id) at:&object withName:NULL]; /* @encode(SEL) produces "^v" in gcc 2.5.8. It should be ":" */ [aRmc decodeValueOfType:":" at:&selector withName:NULL]; assert(selector); type = sel_get_type(selector); assert(type); /* Set up argframe */ stack_argsize = types_get_size_of_stack_arguments(type); reg_argsize = types_get_size_of_register_arguments(type); argframe = (arglist_t) alloca(sizeof(char*) + reg_argsize); if (stack_argsize) argframe->arg_ptr = alloca(stack_argsize); else argframe->arg_ptr = 0; /* decode rest of arguments */ tmptype = type; ftmptype = objc_skip_argspec(forward_type); datum = my_method_get_next_argument(argframe, &tmptype); assert(datum); assert(*tmptype == _C_ID); *(id*)datum = object; assert(object); ftmptype = objc_skip_argspec(ftmptype); datum = my_method_get_next_argument(argframe, &tmptype); assert(datum); assert(*tmptype == _C_SEL); *(SEL*)datum = selector; assert(selector); for (datum = my_method_get_next_argument(argframe, &tmptype), ftmptype = objc_skip_argspec(ftmptype); datum; datum = my_method_get_next_argument(argframe, &tmptype), ftmptype = objc_skip_argspec(ftmptype)) { flags = objc_get_type_qualifiers(ftmptype); tmptype = objc_skip_type_qualifiers(tmptype); if (*tmptype == _C_CHARPTR) { if ((flags & _F_OUT) || !(flags & _F_IN)) out_parameters = YES; if ((flags & _F_IN) || !(flags & _F_OUT)) [aRmc decodeValueOfType:tmptype at:datum withName:NULL]; } else if (*tmptype == _C_PTR) { tmptype++; if ((flags & _F_OUT) || !(flags & _F_IN)) out_parameters = YES; /* xxx These two cases currently the same */ if (*tmptype == _C_STRUCT_B || *tmptype == _C_ARY_B) { /* *(void**)datum = alloca(sizeof(void*)); */ /* xxx or should this be alloca?! What about inout params? Where do they get freed? */ *(void**)datum = (*objc_malloc)(objc_sizeof_type(tmptype)); if ((flags & _F_IN) || !(flags & _F_OUT)) [aRmc decodeValueOfType:tmptype at:*(void**)datum withName:NULL]; } else { /* xxx or should this be alloca?! What about inout params? Where dothey get freed? */ *(char**)datum = (*objc_malloc)(objc_sizeof_type(tmptype)); if ((flags & _F_IN) || !(flags & _F_OUT)) [aRmc decodeValueOfType:tmptype at:*(void**)datum withName:NULL]; } } else if (*tmptype == _C_STRUCT_B || *tmptype == _C_ARY_B) { #if CONNECTION_STRUCTURES_PASSED_BY_REFERENCE *(void**)datum = alloca(objc_sizeof_type(tmptype)); [aRmc decodeValueOfType:tmptype at:*(void**)datum withName:NULL]; #else [aRmc decodeValueOfType:tmptype at:datum withName:NULL]; #endif } else { [aRmc decodeValueOfType:tmptype at:datum withName:NULL]; } } /* We need this "dismiss" to happen here and not later so that Coder "-awake..." methods will get sent before the __builtin_apply! */ [aRmc dismiss]; /* Call the method */ imp = objc_msg_lookup(object, selector); assert(imp); retframe = __builtin_apply((apply_t)imp, argframe, stack_argsize); /* Return results, if necessary */ flags = objc_get_type_qualifiers(forward_type); ftmptype = objc_skip_type_qualifiers(forward_type); tmptype = objc_skip_type_qualifiers(type); /* Is this right? Do we also have to check _F_ONEWAY? */ if (out_parameters || *tmptype != _C_VOID) { op = [self newSendingReplyRmcWithSequenceNumber: reply_sequence_number]; #define ENCODE_RETNAME "return value" if (*tmptype != _C_VOID) { /* encode return value */ /* xxx Change this to switch(*tmptype) */ if (*tmptype == _C_ID) { if (flags & _F_BYCOPY) [op encodeObjectBycopy:*(id*)retframe withName:ENCODE_RETNAME]; else [op encodeObject:*(id*)retframe withName:ENCODE_RETNAME]; } else if (*tmptype == _C_PTR) { tmptype++; /* xxx These two cases currently the same */ if (*tmptype == _C_STRUCT_B || *tmptype == _C_ARY_B) [op encodeValueOfType:tmptype at:*(void**)retframe withName:ENCODE_RETNAME]; else [op encodeValueOfType:tmptype at:*(void**)retframe withName:ENCODE_RETNAME]; } else if (*tmptype == _C_STRUCT_B || *tmptype == _C_ARY_B) { /* xxx these two cases currently the same? */ #if CONNECTION_STRUCTURES_PASSED_BY_REFERENCE [op encodeValueOfType:tmptype at:*(void**)retframe withName:ENCODE_RETNAME]; #else [op encodeValueOfType:tmptype at:*(void**)retframe withName:ENCODE_RETNAME]; #endif } else if (*tmptype == _C_FLT || *tmptype == _C_DBL) { [op encodeValueOfType:tmptype at:((char*)retframe) + FLT_AND_DBL_RETFRAME_OFFSET withName:ENCODE_RETNAME]; } else /* Among other types, _C_CHARPTR is handled here */ { int retsize = objc_sizeof_type(tmptype); /* Special case BOOL (and other types smaller than int) because retframe doesn't actually point to the char */ /* xxx What about structures smaller than int's that are passed by reference on true structure reference- passing architectures? */ /* xxx Is this the right test? Use sizeof(int*) instead? */ if (retsize < sizeof(void*)) { [op encodeValueOfType:tmptype at:((char*)retframe)+sizeof(void*)-retsize withName:ENCODE_RETNAME]; } else { [op encodeValueOfType:tmptype at:retframe withName:ENCODE_RETNAME]; } } } /* encode values returned by reference */ if (out_parameters) { for (datum = my_method_get_next_argument(argframe,&tmptype), argnum = 1, ftmptype = objc_skip_argspec(ftmptype); datum; datum = my_method_get_next_argument(argframe,&tmptype), argnum++, ftmptype = objc_skip_argspec(ftmptype)) { flags = objc_get_type_qualifiers(ftmptype); tmptype = objc_skip_type_qualifiers(tmptype); sprintf(argname, "arg%d", argnum); /* too expensive? */ if ((*tmptype == _C_PTR) && ((flags & _F_OUT) || !(flags & _F_IN))) { tmptype++; /* xxx These two cases currently the same */ if (*tmptype == _C_STRUCT_B || *tmptype == _C_ARY_B) { [op encodeValueOfType:tmptype at:*(void**)datum withName:argname]; } else { [op encodeValueOfType:tmptype at:*(void**)datum withName:argname]; } } else if (*tmptype == _C_CHARPTR && ((flags & _F_OUT) || !(flags & _F_IN))) { [op encodeValueOfType:tmptype at:datum withName:argname]; } } } [op dismiss]; } (*objc_free)(forward_type); return self; } #endif + (id <Collecting>) allConnections { return [connectionArray copy]; } + (unsigned) connectionsCount { return [connectionArray count]; } + (unsigned) connectionsCountWithInPort: (Port*)aPort { unsigned count = 0; elt e; [connectionArrayGate lock]; FOR_ARRAY(connectionArray, e) { if ([aPort isEqual:[e.id_u inPort]]) count++; } FOR_ARRAY_END; [connectionArrayGate unlock]; return count; } /* This should get called whenever an object free's itself */ + removeObject: anObj { id c; int i, count = [connectionArray count]; for (i = 0; i < count; i++) { c = [connectionArray objectAtIndex:i]; [c removeLocalObject:anObj]; [c removeProxy:anObj]; } return self; } + unregisterForInvalidationNotification: anObj { int i, count = [connectionArray count]; for (i = 0; i < count; i++) { [[connectionArray objectAtIndex:i] unregisterForInvalidationNotification:anObj]; } return self; } - init { id newPort = [default_port_class newPort]; id newConn = [Connection newForInPort:newPort outPort:nil ancestorConnection:nil]; [self free]; return newConn; } + new { id newPort = [default_port_class newPort]; id newConn = [Connection newForInPort:newPort outPort:nil ancestorConnection:nil]; return newConn; } + (Connection*) newWithRootObject: anObj; { id newPort; id newConn; newPort = [default_port_class newPort]; newConn = [self newForInPort:newPort outPort:nil ancestorConnection:nil]; [self setRootObject:anObj forInPort:newPort]; return newConn; } /* I want this method name to clearly indicate that we're not connecting to a pre-existing registration name, we're registering a new name, and this method will fail if that name has already been registered. This is why I don't like "newWithRegisteredName:" --- it's unclear if we're connecting to another Connection that already registered with that name. */ + (Connection*) newRegisteringAtName: (const char*)n withRootObject: anObj { id newPort; id newConn; newPort = [default_port_class newRegisteredPortWithName:n]; newConn = [self newForInPort:newPort outPort:nil ancestorConnection:nil]; [self setRootObject:anObj forInPort:newPort]; return newConn; } + (Proxy*) rootProxyAtName: (const char*)n; { return [self rootProxyAtName:n onHost:""]; } + (Proxy*) rootProxyAtName: (const char*)n onHost: (const char*)h; { id p = [default_port_class newPortFromRegisterWithName:n onHost:h]; return [self rootProxyAtPort:p]; } + (Proxy*) rootProxyAtPort: (Port*)anOutPort; { id newInPort = [default_port_class newPort]; return [self rootProxyAtPort: anOutPort withInPort:newInPort]; } + (Proxy*) rootProxyAtPort: (Port*)anOutPort withInPort: (Port*)anInPort; { Connection *newConn = [self newForInPort:anInPort outPort:anOutPort ancestorConnection:nil]; Proxy *newRemote; newRemote = [newConn rootProxy]; return newRemote; } - _superInit { [super init]; return self; } /* This is the designated initializer for Connection */ + (Connection*) newForInPort: (Port*)ip outPort: (Port*)op ancestorConnection: (Connection*)ancestor; { Connection *newConn; int i, count; id newConnInPort, newConnOutPort; [connectionArrayGate lock]; /* Find previously existing connection if there */ /* xxx Clean this up */ count = [connectionArray count]; for (i = 0; i < count; i++) { newConn = [connectionArray objectAtIndex:i]; newConnInPort = [newConn inPort]; newConnOutPort = [newConn outPort]; if ([newConnInPort isEqual:ip] && [newConnOutPort isEqual:op]) { [connectionArrayGate unlock]; return newConn; } } newConn = [[Connection alloc] _superInit]; if (debug_connection) printf("new connection 0x%x, inPort 0x%x outPort 0x%x\n", (unsigned)newConn, (unsigned)ip, (unsigned)op); newConn->in_port = ip; [ip retain]; newConn->out_port = op; [op retain]; newConn->message_count = 0; /* Careful: We might want to use (void*) encoding because we don't want Dictionary to send -isEqual messages to the proxy's that are in the Dictionary. YES. */ newConn->local_targets = [[Dictionary alloc] initWithType:@encode(id) keyType:@encode(unsigned)]; newConn->remote_proxies = [[Dictionary alloc] initWithType:@encode(void*) keyType:@encode(unsigned)]; newConn->incoming_const_ptrs = [[Dictionary alloc] initWithType:@encode(void*) keyType:@encode(unsigned)]; newConn->outgoing_const_ptrs = [[Dictionary alloc] initWithType:@encode(void*) keyType:@encode(unsigned)]; newConn->in_timeout = [self defaultInTimeout]; newConn->out_timeout = [self defaultOutTimeout]; newConn->port_class = [ancestor portClass]; newConn->queue_dialog_interruptions = YES; newConn->delegate = nil; /* Here ask the delegate for permission. */ /* delegate is responsible for freeing newConn if it returns something different. */ if ([[ancestor delegate] respondsTo:@selector(connection:didConnect:)]) newConn = [[ancestor delegate] connection:ancestor didConnect:newConn]; [ip registerForInvalidationNotification:newConn]; [op registerForInvalidationNotification:newConn]; [connectionArray addObject:newConn]; [connectionArrayGate unlock]; return newConn; } /* This needs locks */ - (void) dealloc { if (debug_connection) printf("deallocating 0x%x\n", (unsigned)self); [self invalidate]; [connectionArray removeObject:self]; /* Remove rootObject from rootObjectDictionary if this is last connection */ if (![Connection connectionsCountWithInPort:in_port]) [Connection setRootObject:nil forInPort:in_port]; [in_port unregisterForInvalidationNotification:self]; [out_port unregisterForInvalidationNotification:self]; [in_port release]; [out_port release]; { void deallocObj (elt o) { [o.id_u dealloc]; } [proxiesHashGate lock]; [remote_proxies withElementsCall:deallocObj]; [remote_proxies free]; [local_targets free]; [incoming_const_ptrs free]; [outgoing_const_ptrs free]; [proxiesHashGate unlock]; } [super dealloc]; return; } /* to < 0 will never time out */ - (void) runConnectionWithTimeout: (int)to { [self doReceivedRequestsWithTimeout:to]; } - (void) runConnection { [self runConnectionWithTimeout:-1]; } - (Proxy*) rootProxy { id op, ip; Proxy *newProxy; int seq_num = [self _newMsgNumber]; assert(in_port); op = [[self coderClass] newEncodingWithConnection:self sequenceNumber:seq_num identifier:ROOTPROXY_REQUEST]; [op dismiss]; ip = [self newReceivedReplyRmcWithSequenceNumber:seq_num]; [ip decodeObjectAt:&newProxy withName:NULL]; assert(class_is_kind_of(newProxy->isa, objc_get_class("Proxy"))); [ip dismiss]; return newProxy; } - _sendShutdown { id op; assert(in_port); op = [[self coderClass] newEncodingWithConnection:self sequenceNumber:[self _newMsgNumber] identifier:CONNECTION_SHUTDOWN]; [op dismiss]; return self; } - (const char *) _typeForSelector: (SEL)sel remoteTarget: (unsigned)target { id op, ip; char *type; int seq_num; assert(in_port); seq_num = [self _newMsgNumber]; op = [[self coderClass] newEncodingWithConnection:self sequenceNumber:seq_num identifier:METHODTYPE_REQUEST]; [op encodeValueOfType:":" at:&sel withName:NULL]; [op encodeValueOfSimpleType:@encode(unsigned) at:&target withName:NULL]; [op dismiss]; ip = [self newReceivedReplyRmcWithSequenceNumber:seq_num]; [ip decodeValueOfSimpleType:@encode(char*) at:&type withName:NULL]; [ip dismiss]; return type; } - _handleMethodTypeRequest: rmc { ConnectedCoder* op; unsigned target; SEL sel; const char *type; struct objc_method* m; assert(in_port); assert([rmc connection] == self); op = [[self coderClass] newEncodingWithConnection:[rmc connection] sequenceNumber:[rmc sequenceNumber] identifier:METHODTYPE_REPLY]; [rmc decodeValueOfType:":" at:&sel withName:NULL]; [rmc decodeValueOfSimpleType:@encode(unsigned) at:&target withName:NULL]; /* xxx We should make sure that TARGET is a valid object. */ /* Not actually a Proxy, but we avoid the warnings "id" would have made. */ m = class_get_instance_method(((Proxy*)target)->isa, sel); /* Perhaps I need to be more careful in the line above to get the version of the method types that has the type qualifiers in it. Search the protocols list. */ if (m) type = m->method_types; else type = ""; [op encodeValueOfSimpleType:@encode(char*) at:&type withName:"Requested Method Type for Target"]; [op dismiss]; return self; } - _handleRemoteRootObject: rmc { id rootObject = [Connection rootObjectForInPort:in_port]; ConnectedCoder* op = [[self coderClass] newEncodingWithConnection:[rmc connection] sequenceNumber:[rmc sequenceNumber] identifier:ROOTPROXY_REPLY]; assert(in_port); /* Perhaps we should turn this into a class method. */ assert([rmc connection] == self); [op encodeObject:rootObject withName:"root object"]; [op dismiss]; return self; } - _newReceivedRmcWithTimeout: (int)to { id rmc; rmc = [[self coderClass] newDecodingWithConnection:self timeout:to]; /* if (!rmc) [self error:"received timed out"]; */ assert((!rmc) || [rmc isDecoding]); return rmc; } - (int) _newMsgNumber { int n; [sequenceNumberGate lock]; n = message_count++; [sequenceNumberGate unlock]; return n; } - doReceivedRequestsWithTimeout: (int)to { id rmc; unsigned count; for (;;) { if (debug_connection) printf("%s\n", sel_get_name(_cmd)); /* Get a rmc */ [receivedRequestRmcQueueGate lock]; count = [receivedRequestRmcQueue count]; if (count) { if (debug_connection) printf("Getting received request from queue\n"); rmc = [receivedRequestRmcQueue dequeueObject]; [receivedRequestRmcQueueGate unlock]; } else { [receivedRequestRmcQueueGate unlock]; rmc = [self _newReceivedRmcWithTimeout:to]; } if (!rmc) return self; /* timed out */ assert([rmc isDecoding]); /* Process the rmc */ switch ([rmc identifier]) { case ROOTPROXY_REQUEST: [[rmc connection] _handleRemoteRootObject:rmc]; [rmc dismiss]; break; case ROOTPROXY_REPLY: [self error:"Got ROOTPROXY reply when looking for request"]; break; case METHOD_REQUEST: { assert([rmc isDecoding]); [[rmc connection] connectionPerformAndDismissCoder:rmc]; break; } case METHOD_REPLY: /* Will this ever happen? Yes, with multi-threaded callbacks */ [receivedReplyRmcQueueGate lock]; [receivedReplyRmcQueue enqueueObject:rmc]; [receivedReplyRmcQueueGate unlock]; break; case METHODTYPE_REQUEST: [[rmc connection] _handleMethodTypeRequest:rmc]; [rmc dismiss]; break; case METHODTYPE_REPLY: /* Will this ever happen? Yes, with multi-threaded callbacks */ [receivedReplyRmcQueueGate lock]; [receivedReplyRmcQueue enqueueObject:rmc]; [receivedReplyRmcQueueGate unlock]; break; case CONNECTION_SHUTDOWN: { id c = [rmc connection]; [c invalidate]; if (c == self) [self error:"connection waiting for request was shut down"]; [c dealloc]; break; } default: [self error:"unrecognized ConnectedCoder identifier"]; } } return self; /* we never get here */ } - newReceivedReplyRmcWithSequenceNumber: (int)n { id rmc, aRmc; unsigned count, i; again: /* Get a rmc */ rmc = nil; [receivedReplyRmcQueueGate lock]; count = [receivedReplyRmcQueue count]; /* There should be a per-thread queue of rmcs so we can do callbacks when multi-threaded. */ for (i = 0; i < count; i++) { aRmc = [receivedReplyRmcQueue objectAtIndex:i]; if ([aRmc connection] == self && [aRmc sequenceNumber] == n) { if (debug_connection) printf("Getting received reply from queue\n"); [receivedReplyRmcQueue removeObjectAtIndex:i]; rmc = aRmc; break; } } [receivedReplyRmcQueueGate unlock]; if (rmc == nil) rmc = [self _newReceivedRmcWithTimeout:in_timeout]; if (rmc == nil) { /* We timed out */ [self error:"connection timed out after waiting %d milliseconds " "for a reply", in_timeout]; /* Eventually we need to change this from crashing to connection invalidating? I want to use gcc exceptions for this. */ } /* Process the rmc we got */ switch ([rmc identifier]) { case ROOTPROXY_REQUEST: [self _handleRemoteRootObject: rmc]; [rmc dismiss]; break; case METHODTYPE_REQUEST: [self _handleMethodTypeRequest:rmc]; [rmc dismiss]; break; case ROOTPROXY_REPLY: case METHOD_REPLY: case METHODTYPE_REPLY: if ([rmc connection] != self) { [receivedReplyRmcQueueGate lock]; [receivedReplyRmcQueue enqueueObject:rmc]; [receivedReplyRmcQueueGate unlock]; } else { if ([rmc sequenceNumber] != n) [self error:"sequence number mismatch %d != %d\n", [rmc sequenceNumber], n]; if (debug_connection) printf("received reply number %d\n", n); return rmc; } break; case METHOD_REQUEST: /* While waiting for a reply, we can either honor new requests from other connections immediately, or just queue them. */ if (queue_dialog_interruptions && [rmc connection] != self) { /* Here we queue them */ [receivedRequestRmcQueueGate lock]; [receivedRequestRmcQueue enqueueObject:rmc]; [receivedRequestRmcQueueGate unlock]; } else { /* Here we honor them right away */ [self connectionPerformAndDismissCoder:rmc]; } break; case CONNECTION_SHUTDOWN: { id c = [rmc connection]; [c invalidate]; if (c == self) [self error:"connection waiting for reply was shut down"]; [c dealloc]; [rmc dismiss]; break; } default: [self error:"unrecognized ConnectedCoder identifier"]; } goto again; return rmc; } - newSendingRequestRmc { id rmc; assert(in_port); rmc = [[self coderClass] newEncodingWithConnection:self sequenceNumber:[self _newMsgNumber] identifier:METHOD_REQUEST]; return rmc; } - newSendingReplyRmcWithSequenceNumber: (int)n { id rmc = [[self coderClass] newEncodingWithConnection:self sequenceNumber:n identifier:METHOD_REPLY]; return rmc; } - removeLocalObject: anObj { unsigned target = PTR2LONG(anObj); [proxiesHashGate lock]; if ([local_targets includesKey:target]) { [local_targets removeElementAtKey:target]; [anObj release]; } [proxiesHashGate unlock]; return self; } - removeProxy: (Proxy*)aProxy { unsigned target = [aProxy targetForProxy]; [proxiesHashGate lock]; if ([remote_proxies includesKey:target]) [remote_proxies removeElementAtKey:target]; [proxiesHashGate unlock]; return self; } - (id <Collecting>) localObjects { id l = [Array alloc]; [proxiesHashGate lock]; [l initWithContentsOf:local_targets]; [proxiesHashGate unlock]; return l; } - (id <Collecting>) proxies { id a = [[Array alloc] initWithCapacity:[remote_proxies count]]; void doit (elt e) { [a appendElement:e]; } [proxiesHashGate lock]; [remote_proxies withElementsCall:doit]; [proxiesHashGate unlock]; return a; } - (Proxy*) proxyForTarget: (unsigned)target { Proxy *p; [proxiesHashGate lock]; if ([remote_proxies includesKey:target]) p = [remote_proxies elementAtKey:target].id_u; else p = nil; [proxiesHashGate unlock]; assert(!p || [p connectionForProxy] == self); return p; } - addProxy: (Proxy*) aProxy { unsigned target = [aProxy targetForProxy]; assert(aProxy->isa == [Proxy class]); assert([aProxy connectionForProxy] == self); [proxiesHashGate lock]; if ([remote_proxies includesKey:target]) [self error:"Trying to add the same proxy twice"]; [remote_proxies putElement:aProxy atKey:target]; [proxiesHashGate unlock]; return self; } - (BOOL) includesProxyForTarget: (unsigned)target { BOOL ret; [proxiesHashGate lock]; ret = [remote_proxies includesKey:target]; [proxiesHashGate unlock]; return ret; } - (BOOL) includesLocalObject: anObj { unsigned target = PTR2LONG(anObj); BOOL ret; [proxiesHashGate lock]; ret = [local_targets includesKey:target]; [proxiesHashGate unlock]; return ret; } - addLocalObject: anObj { unsigned target = PTR2LONG(anObj); [proxiesHashGate lock]; if (![local_targets includesKey:target]) { [anObj retain]; [local_targets putElement:anObj atKey:target]; } [proxiesHashGate unlock]; return self; } /* Pass nil to remove any reference keyed by aPort. */ + setRootObject: anObj forInPort: (Port*)aPort { id oldRootObject = [self rootObjectForInPort:aPort]; if (oldRootObject != anObj) { if (anObj) { [anObj retain]; [rootObjectDictionaryGate lock]; [rootObjectDictionary putElement:anObj atKey:aPort]; [rootObjectDictionaryGate unlock]; } else /* anObj == nil && oldRootObject != nil */ { [rootObjectDictionaryGate lock]; [rootObjectDictionary removeElementAtKey:aPort]; [rootObjectDictionaryGate unlock]; } [oldRootObject release]; } return self; } + rootObjectForInPort: (Port*)aPort { id ro; [rootObjectDictionaryGate lock]; ro = [rootObjectDictionary elementAtKey:aPort ifAbsentCall:exc_func_return_nil].id_u; [rootObjectDictionaryGate unlock]; return ro; } - setRootObject: anObj { [Connection setRootObject:anObj forInPort:in_port]; return self; } - rootObject { return [Connection rootObjectForInPort:in_port]; } - (int) outTimeout { return out_timeout; } - (int) inTimeout { return in_timeout; } - setOutTimeout: (int)to { out_timeout = to; return self; } - setInTimeout: (int)to { in_timeout = to; return self; } - portClass { return port_class; } - setPortClass: aPortClass { port_class = aPortClass; return self; } - proxyClass { /* we might replace this with a per-Connection proxy class. */ return defaultProxyClass; } - coderClass { /* we might replace this with a per-Connection proxy class. */ return defaultCoderClass; } - (Port *) outPort { return out_port; } - (Port *) inPort { return in_port; } - delegate { return delegate; } - setDelegate: anObj { delegate = anObj; return self; } - _incomingConstPtrs { return incoming_const_ptrs; } - _outgoingConstPtrs { return outgoing_const_ptrs; } - senderIsInvalid: anObj { if (anObj == in_port || anObj == out_port) [self invalidate]; /* xxx What else? */ return self; } /* xxx This needs locks */ - invalidate { if (!isValid) return nil; /* xxx Note: this is causing us to send a shutdown message to the connection that shut *us* down. Don't do that. Well, perhaps it's a good idea just in case other side didn't really send us the shutdown; this way we let them know we're going away */ [self _sendShutdown]; [super invalidate]; return self; } - (void) encodeWithCoder: (Coder*)anEncoder { [self shouldNotImplement:_cmd]; } + newWithCoder: (Coder*)aDecoder; { [self shouldNotImplement:_cmd]; return self; } @end #if 0 /* temporarily moved to Coder.m */ @implementation Object (ConnectionRequests) /* By default, Object's encode themselves as proxies across Connection's */ - classForConnectedCoder:aRmc { return [[aRmc connection] proxyClass]; } /* But if any object overrides the above method to return [Object class] instead, the Object implementation of the coding method will actually encode the object itself, not a proxy */ + (void) encodeObject: anObject withConnectedCoder: aRmc { [anObject encodeWithCoder:aRmc]; } @end @implementation Object (Retaining) - (id) retain { return self; } - (oneway void) release { /* Do nothing */ } - (void) dealloc { /* Do nothing */ } - (unsigned) retainCount { return 0; } @end #endif /* 0 temporarily moved to Coder.m */
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.