On Thu, Jun 11, 2009 at 04:04:47PM +0100, David Chisnall wrote: > Patches welcome for other higher-order messaging functions (e.g. the > same thing on NSSet, fold and similar operations)
I'd really like to have them as well and in a surge of major ignorance I tried to implement -filter on NSArray yesterday, which turns out to be less than trivial. I originally thought one get away with minor modifications to the mapping code, but due to the nature of NSInvocation that is not the case. An example: Suppose you are trying to send an -isEqualToString: message to all string objects in an array, the second-chance dispatch mechanism contructs an invocation based on the method signature of that method. This way, it is set up for a BOOL return type, but what is returned after forwarding the invocation to the members of the array is really an object. I'm now doing ugly stuff to the runtime by manually modifying the method signatures and invocations, but it's still not working. I stepped through the whole process with gdb and the NSInvocation/GSFFIInvocation code seems to handle the result flawlessly. (I have no idea what is going on inside libffi, though.) I'm now highly suspecting that the compiler is to "blame" for using the type information from -isEqualToString:. It's barking at me that I'm trying to cast an integer type (BOOL?) to a differenty sized pointer, so perhaps I should assume this to fail. FWIW it the pointer I'm trying to return seems to be mangled somewhere: The return value is always nil. Unfortunately, I know next to nothing about compilers and am thoroughly at a loss now. I have attached my (admittedly, rough) implementation and sample code to demonstrate the problem. I understand that you probably wont have the time to look at this, but I highly appreciate any pointers on where to look next. kind regards, Niels
#import <Foundation/Foundation.h> #import <EtoileFoundation/EtoileFoundation.h> #import <EtoileFoundation/NSArray+filter.h> int main (void) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSArray *input = A(@"one",@"two",@"three"); NSArray *filtered; filtered = (NSArray*)[[input filter] isEqualToString: @"one"]; NSLog(@"%@", filtered); [pool release]; return 0; }
#import "NSArray+filter.h" #import "Macros.h" #include <objc/objc-api.h> @interface NSArrayFilterProxy : NSObject { NSArray * array; BOOL invert; NSMethodSignature *objSig; } - (id) initWithArray:(NSArray*)anArray forInverse: (BOOL)shallInvert; @end /* * Returns the address of an instance variable. It is needed for everything that * is declared protected so that the compiler won't let me access it directly. * This code borrows heavily form EtoileSerialize. * (BTW "NSAFP" stands for "NSArrayFilterProxy") */ void* NSAFPAddrForIvarOfObject(char* ivarName, id object) { Class theClass = [object class]; void *address = NULL; do { struct objc_ivar_list* ivarList; ivarList = theClass->ivars; if (ivarList != NULL) { int i, ivarCount; ivarCount = ivarList->ivar_count; for(i=0; ((i < ivarCount) && (address == NULL)); i++) { char* name = (char*)ivarList->ivar_list[i].ivar_name; if(strcmp(ivarName,name) == 0 ) { address =(char*)object + ivarList->ivar_list[i].ivar_offset; } } } theClass = theClass->super_class; } while((NULL == address) && (theClass != NULL)); return address; } /* * FIXME: This is in part redundant and also not as general as the originally * conceived because it is dealing specifically with a char pointer now. */ BOOL NSAFPCopyIvarForClassFromToWithLength(char* ivarName, Class theClass, char* source, char* target, int len) { void *sourceAddress = NULL; void *targetAddress = NULL; do { struct objc_ivar_list* ivarList; ivarList = theClass->ivars; if (ivarList != NULL) { int i, ivarCount; ivarCount = ivarList->ivar_count; for(i=0; ((i < ivarCount)&& (targetAddress == NULL)); i++) { char * name = (char*)ivarList->ivar_list[i].ivar_name; if(strcmp(ivarName,name) == 0 ) { /* * this are the addresses. */ char * targetAddress =target + ivarList->ivar_list[i].ivar_offset; sourceAddress =source + ivarList->ivar_list[i].ivar_offset; //Copy it memcpy(*(char**)targetAddress, *(char**)sourceAddress, len); return YES; }//if the ivar exists }//loop on ivars }//if any ivars exist theClass = theClass->super_class; } while((NULL == targetAddress) && (theClass != NULL)); return NO; } @implementation NSArrayFilterProxy - (id) initWithArray:(NSArray*)anArray forInverse: (BOOL)shallInvert { SELFINIT; array = [anArray retain]; invert = shallInvert; objSig = nil; return self; } - (id) methodSignatureForSelector:(SEL)aSelector { FOREACHI(array, object) { if([object respondsToSelector:aSelector]) { /* We need to cheat and trick the runtime into * believing that we will return an object. */ NSMethodSignature *fakeSig, *realSig; /* This signature is derived from NSArray and describes * an method that will return an object. */ fakeSig = [array methodSignatureForSelector: @selector(arrayByAddingObject:)]; /* * This is the signature that will actually be invoked. * It has all the right arguments. */ realSig = [object methodSignatureForSelector: aSelector]; //We keep another one for future reference. objSig = [object methodSignatureForSelector: aSelector]; /* * Now we copy the necessary data from one instance to * the other. */ Class sigClass = [realSig class]; BOOL go; /* * We copy the first byte of the _methodTypes ivar * because it contains the type of the return value. * ("@" is needed for objects, I believe). */ go = NSAFPCopyIvarForClassFromToWithLength("_methodTypes", sigClass, (char*)fakeSig, (char*)realSig, 1); if(go == YES) { return realSig; } } } return [super methodSignatureForSelector:aSelector]; } - (void) forwardInvocation:(NSInvocation*)anInvocation { SEL selector = [anInvocation selector]; //A proper invocation is needed in addition to the modified one. NSInvocation *objInvocation; objInvocation = [NSInvocation invocationWithMethodSignature: objSig]; [objInvocation retain]; [objInvocation setSelector: selector]; //Copy the arguments from one invocation to the other. int i, argNum; argNum = [objSig numberOfArguments]; for (i=2;i<argNum;i++) { NSArgumentInfo **argInfoBase = NULL; NSArgumentInfo *argInfoPtr; /* * Base address of the _info ivar. Points to an array of * NSArgumentInfo. */ argInfoBase = NSAFPAddrForIvarOfObject("_info",anInvocation); /* * The index is offset by one, because the first element * contains the type information for the return value. */ argInfoPtr = *argInfoBase+i+1; //objects need special treatment if(strcmp("@",argInfoPtr->type)==0) { id obj; [anInvocation getArgument:&obj atIndex:i]; [objInvocation setArgument:&obj atIndex:i]; } else {//all other arguments need proper memory allocation int len = argInfoPtr->size; void *buffer = malloc(len); [anInvocation getArgument:buffer atIndex:i]; [objInvocation setArgument:buffer atIndex:i]; free(buffer); buffer=NULL; } } NSMutableArray * filteredArray = [NSMutableArray array]; FOREACHI(array, object) { if([object respondsToSelector:selector]) { [objInvocation invokeWithTarget:object]; /* * TODO: Not sure why if the pointer is needed. It * didn't seem to work otherwise. */ BOOL *filterResponse; filterResponse = malloc(sizeof(BOOL)); memset(filterResponse,0,sizeof(BOOL)); /* If the filter is inverted, we add the objects which * respond with NO. */ BOOL comparator = !invert; [objInvocation getReturnValue:filterResponse]; if (*filterResponse == comparator) { [filteredArray addObject: object]; } free(filterResponse); filterResponse = 0; } } [objInvocation release]; /* * We can only fire this invocation be cause it is modified to carry the * correct return value. */ [anInvocation setReturnValue:&filteredArray]; } DEALLOC( [array release]; ) @end @implementation NSArray (FilterAllElements) - (id) filter { return [[[NSArrayFilterProxy alloc] initWithArray:self forInverse: NO] autorelease]; } - (id) filterInverse { return [[[NSArrayFilterProxy alloc] initWithArray:self forInverse: YES] autorelease]; } @end
_______________________________________________ Etoile-dev mailing list Etoile-dev@gna.org https://mail.gna.org/listinfo/etoile-dev