// // Message.m // HigherOrderMessaging // // Created by Ofri Wolfus on 26/08/06. // Copyright 2006 Ofri Wolfus. All rights reserved. // #import "Message.h" #include #include #import "HOMUtilities.h" #include "DPObjC-Compatibility.h" #import #import "HOMInvocationBuilder.h" /* * This file is the base of @HOM. * It contains the machanism that turns a Objective-C message into a Message instance, * and the entire framework relies on this. * NOTE: This is not possible without the ID() macro that tricks GCC to think the MSG() macro * returns an object. */ id _sharedMessageBuilder = nil; // No longer used @interface Message (MessageBuilderSupport) - (void)dealloc; @end #define _returnesStruct(list) (marg_getValue(list, 0, void *) != _sharedMessageBuilder) @implementation Message static Class autoreleasePoolCls = Nil; + (void)initialize { autoreleasePoolCls = objc_getClass("NSAutoreleasePool"); } + (id)_messageWithArgs:(marg_list)list method:(MethodDescription)desc { Message *m = class_createInstance(self, 0); if (m) { size_t len = strlen(description_getTypes(desc)); char *types = calloc(len+1, sizeof(char)); memcpy(types, description_getTypes(desc), len * sizeof(char)); m->refCount = 1U; m->args = list; //marg_setValue(m->args, (_returnesStruct(list) ? sizeof(void *) : 0), id, nil); m->argsSize = description_getSizeOfArguments(desc); // We cache the size of the method in order to speed up some HOM implementations m->description = description_createDescription(description_getName(desc), types); } return [m autorelease]; } - (void)dealloc { free(args); free((void *)description_getTypes(description)); // We copied the types at initialization time free(description); free(self); } - (SEL)selector { return marg_getValue(args, (_returnesStruct(args) ? sizeof(void *) : 0) + sizeof(id), SEL); } - (marg_list)arguments { return args; } - (unsigned)argumentsSize { return argsSize; } - (BOOL)returnsStruct { return _returnesStruct(args); } - (unsigned)numberOfArguments { return hom_getNumberOfArguments([self selector]); } - (const char *)types { return description_getTypes(description); } - (MethodDescription)methodDescription { return description; } - (id)forward:(SEL)sel :(marg_list)arglist { return nil; } - (id)retain { ++refCount; return self; } - (id)autorelease { if (autoreleasePoolCls) [autoreleasePoolCls addObject:self]; return self; } - (void)release { --refCount; if (refCount == 0U) [self dealloc]; } - (unsigned)retainCount { return refCount; } - (NSString *)description { return [NSString stringWithFormat:@"<0x%x> Message: \"%s\"", self, sel_getName([self selector])]; } - (id)sendTo:(id)receiver { return objc_msgSendv(receiver, [self selector], argsSize, args); } @end @implementation Message (CocoaConventions) - (id)invocationWithTarget:(id)target { NSInvocation *r; id builder = [HOMInvocationBuilder builderForTarget:target]; r = [self sendTo:builder]; [builder dealloc]; return r; } @end //=================================================================================// //=================================================================================// //=================================================================================// // Holds all our cached method descriptions static CFMutableDictionaryRef _methodsCache; // Finds a method description for the given selector, which has the largest arguments size. // The returned description must be freed properly. MethodDescription _hom_findMethodForSelector(SEL sel) { static int classCount = 0; // The number of classes that was in our previous search static Class *classes = NULL; // A block with pointers to all classes int count; MethodDescription result; Method meth = NULL; NSString *key = NSStringFromSelector(sel); // Like in the ObjC runtime, thread synchronization is always active @synchronized ((NSDictionary *)_methodsCache) { // Set up our cache dictionary if it wasn't already active if (!_methodsCache) _methodsCache = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, NULL); // Look for a cached version of our method. // If no new classes were registered since the last time we did a search, this method will be used result = (MethodDescription)CFDictionaryGetValue(_methodsCache, key); // Get the current number of classes registered with the runtime count = objc_getClassList(NULL, 0); /* * We work only if new classes were registered since our last search. * NOTE: When unloading of classes is implemened this check may not be enough * if some classes were added, and some others were removed but the total number of classes remained the same. * Even in this situation though, the chances are very small the new classes added new method for existing selector(s) * with a different size of total arguments. */ if (count != classCount) { // Allocate enough memory for pointers to all classes available if (!classes) classes = malloc(sizeof(Class) * count); else classes = reallocf(classes, sizeof(Class) * count); if (classes) { // Remember the current number of classes classCount = count; // Get all available classes from the runtime objc_getClassList(classes, classCount); // Clean up our cache CFDictionaryRemoveAllValues(_methodsCache); } // Free our previous cached method if (result) { //free(result->method_types); free(result); result = NULL; } } if (!result) { Class cls; int i; Method m = NULL; unsigned argsSize = 0U; /* * Loop through all classes and search for a method with a matching selector * This is a very long loop and that's why we use our cache if possible * NOTE: Since method lookups scans through all super classes, we can cut down the number of lookups * by remembering which super classes were already searched and skip those. */ for (i = 0; i < classCount; i++) { cls = classes[i]; // First, attempt to get an instance method m = class_getInstanceMethod(cls, sel); // If it's not available, try a class method if (!m) m = class_getClassMethod(cls, sel); /* * If we found a method, we need to get the size of its arguments. * Since we can't know which method will be used in practice, we'll pick the one with the largest * size so that we don't accidentally cut some arguments. */ if (m) { unsigned s = method_getSizeOfArguments(m); if (s > argsSize) { meth = m; argsSize = s; } } } if (meth) { //unsigned len = strlen(meth->method_types); // Allocate a new method, and copy the method we just found. // NOTE: The IMP of our method always points to NULL. /*result = calloc(1, sizeof(struct objc_method)); result->method_name = sel; result->method_types = malloc(sizeof(char) * (len + 1)); strcpy(result->method_types, meth->method_types);*/ result = dp_copyMethodDescription(meth); // Cache our method description CFDictionarySetValue(_methodsCache, key, result); } } } return result; } //=================================================================================// //=================================================================================// //=================================================================================// /* * The MessageBuilder class is a special class that responds to has no methods. * Every message sent to it will be packed and returned as a Message instance. * Unlike normal singleton objects, there is no instance of this class and messages will be sent * to the class directly. */ @interface MessageBuilder { id isa; } @end @implementation MessageBuilder // Set up the public pointer to ourself + (void)load { _sharedMessageBuilder = self; } // The runtime automatically calls this method, so we make sure it won't get accidentally forwarded. + (void)initialize { } // Catche all messages and turn them into Message instances - (id)forward:(SEL)sel :(marg_list)args { marg_list list; MethodDescription d = _hom_findMethodForSelector(sel); if (d) { //marg_malloc(list, m); list = dp_marg_malloc(description_getSizeOfArguments(d)); memcpy(list, args, malloc_size(list)); return [Message _messageWithArgs:list method:d]; } return nil; } @end