From 34b634755316e76af6d671647550277974b9398d Mon Sep 17 00:00:00 2001 From: Ryan Krug Date: Tue, 1 Jul 2025 06:39:40 -0500 Subject: [PATCH 01/12] Improve handling of structs We have one spot where we need to build the FFI type for a struct and we don't need to know the name of it, just its arity and its individual types. This adds a utility function in order to check on that and uses it instead of checking knownStructs. --- ext/obj_ext/RIGSBridgeSupportParser.m | 74 ++++++----- ext/obj_ext/RIGSCore.h | 5 +- ext/obj_ext/RIGSCore.m | 182 +++++++++++++++----------- ext/obj_ext/RIGSUtilities.h | 3 + ext/obj_ext/RIGSUtilities.m | 103 +++++++-------- 5 files changed, 201 insertions(+), 166 deletions(-) diff --git a/ext/obj_ext/RIGSBridgeSupportParser.m b/ext/obj_ext/RIGSBridgeSupportParser.m index dc4655f..073d8d6 100644 --- a/ext/obj_ext/RIGSBridgeSupportParser.m +++ b/ext/obj_ext/RIGSBridgeSupportParser.m @@ -54,24 +54,16 @@ - (void)parser:(NSXMLParser *)parser [self parseFunctionWithName:[attributeDict objectForKey:@"name"]]; } else if ([elementName isEqualToString:@"retval"]) { - [self parseArgWithIndex:-1 - type:[attributeDict objectForKey:@"type64"] - printf:nil - block:nil]; + [self parseArgWithType:[attributeDict objectForKey:@"type64"] + index:@"-1" + printf:nil + block:nil]; } else if ([elementName isEqualToString:@"arg"]) { - if (_methodName) { - [self parseArgWithIndex:[attributeDict objectForKey:@"index"] ? [[attributeDict objectForKey:@"index"] intValue] : _argIndex++ - type:[attributeDict objectForKey:@"type64"] - printf:[attributeDict objectForKey:@"printf_format"] - block:[attributeDict objectForKey:@"function_pointer"]]; - } - else if (_functionName) { - [self parseArgWithIndex:_argIndex++ - type:[attributeDict objectForKey:@"type64"] - printf:[attributeDict objectForKey:@"printf_format"] - block:nil]; - } + [self parseArgWithType:[attributeDict objectForKey:@"type64"] + index:[attributeDict objectForKey:@"index"] + printf:[attributeDict objectForKey:@"printf_format"] + block:[attributeDict objectForKey:@"function_pointer"]]; } } @@ -139,36 +131,46 @@ - (void)parseProtocolWithName:(NSString*)name _argDepth = 0; } -- (void)parseArgWithIndex:(NSInteger)index type:(NSString*)type printf:(NSString*)printf block:(NSString*)block +- (void)parseArgWithType:(NSString*)type index:(NSString*)index printf:(NSString*)printf block:(NSString*)block { _argDepth++; if (_methodName) { - if ([printf isEqualToString:@"true"]) { - _formatStringIndex = index; + if (_argDepth == 1) { + if ([printf isEqualToString:@"true"]) { + _formatStringIndex = [index integerValue]; + } + if ([block isEqualToString:@"true"] && [type isEqualToString:@"@?"]) { + _blockIndex = [index integerValue]; + _objcTypes = [[type mutableCopy] retain]; + } + if (type) { + rb_objc_register_type_arg_from_objc([_methodName UTF8String], [index intValue], [type UTF8String]); + } } - if ([block isEqualToString:@"true"] && [type isEqualToString:@"@?"]) { - _blockIndex = index; - _objcTypes = [[NSMutableString string] retain]; + else { + if (_objcTypes) { + if ([index isEqualToString:@"-1"]) { + [_objcTypes insertString:type atIndex:0]; + } + else { + [_objcTypes appendString:type]; + } + } } - if (_objcTypes) { - if (index == -1) { + } + else if (_functionName) { + if (_argDepth == 1) { + if ([printf isEqualToString:@"true"]) { + _formatStringIndex = _argIndex; + } + if ([index isEqualToString:@"-1"]) { [_objcTypes insertString:type atIndex:0]; } else { [_objcTypes appendString:type]; } - } - } - else if (_functionName) { - if ([printf isEqualToString:@"true"]) { - _formatStringIndex = index; - } - if (index == -1) { - [_objcTypes insertString:type atIndex:0]; - } - else { - [_objcTypes appendString:type]; + _argIndex++; } } } @@ -306,7 +308,7 @@ - (void)finalizeArg if (_blockIndex != -1) { if (_methodName) { - rb_objc_register_block_from_objc([_methodName UTF8String], _blockIndex, [_objcTypes UTF8String]); + rb_objc_register_block_arg_from_objc([_methodName UTF8String], _blockIndex, [_objcTypes UTF8String]); } _blockIndex = -1; diff --git a/ext/obj_ext/RIGSCore.h b/ext/obj_ext/RIGSCore.h index 2a7e927..8e91f2c 100644 --- a/ext/obj_ext/RIGSCore.h +++ b/ext/obj_ext/RIGSCore.h @@ -40,11 +40,12 @@ VALUE rb_objc_register_instance_method_from_rb(VALUE rb_class, VALUE rb_method); void rb_objc_register_float_from_objc(const char *name, double value); void rb_objc_register_integer_from_objc(const char *name, long long value); void rb_objc_register_struct_from_objc(const char *key, const char *name, const char *args[], size_t argCount); -void rb_objc_register_format_string_from_objc(const char *selector, size_t index); -void rb_objc_register_block_from_objc(const char *selector, size_t index, const char *objcTypes); void rb_objc_register_constant_from_objc(const char *name, const char *type); void rb_objc_register_function_from_objc(const char *name, const char *objcTypes); void rb_objc_register_protocol_from_objc(const char *selector, const char *objcTypes); +void rb_objc_register_format_string_from_objc(const char *selector, size_t index); +void rb_objc_register_type_arg_from_objc(const char *selector, int index, const char *objcTypes); +void rb_objc_register_block_arg_from_objc(const char *selector, size_t index, const char *objcTypes); VALUE rb_objc_import(VALUE rb_self, VALUE rb_name); VALUE rb_objc_new(int rigs_argc, VALUE *rigs_argv, VALUE rb_class); diff --git a/ext/obj_ext/RIGSCore.m b/ext/obj_ext/RIGSCore.m index 045aab0..adb2f56 100644 --- a/ext/obj_ext/RIGSCore.m +++ b/ext/obj_ext/RIGSCore.m @@ -48,8 +48,11 @@ // Hash table that maps known ObjC functions to objcTypes encoding static NSMapTable *knownFunctions = 0; -// Hash table that maps known ObjC selectors to block objcTypes encoding -static NSMapTable *knownBlocks = 0; +// Hash table that maps known ObjC selectors with arg position to block objcTypes encoding +static NSMapTable *knownBlockArgs = 0; + +// Hash table that maps known ObjC selectors with arg position to objcTypes encoding +static NSMapTable *knownTypeArgs = 0; // Hash table that maps known ObjC selectors to objcTypes encoding static NSMapTable *knownProtocols = 0; @@ -154,9 +157,8 @@ rb_objc_ffi_type_for_type(const char *type) { ffi_type *inStruct = NULL; - unsigned long inStructHash; int inStructIndex = 0; - long inStructCount = 0; + unsigned long inStructCount = 0; type = rb_objc_skip_type_qualifiers(type); @@ -165,16 +167,15 @@ } if (*type == _C_STRUCT_B) { - inStructHash = rb_objc_hash_struct(type); - type = rb_objc_skip_type_sname(type); - inStructCount = rb_array_len(rb_struct_s_members((VALUE)NSMapGet(knownStructs, (void*)inStructHash))); - + inStructCount = rb_objc_struct_type_arity(type); + inStruct = (ffi_type *)malloc(sizeof(ffi_type)); inStruct->size = 0; inStruct->alignment = 0; inStruct->type = FFI_TYPE_STRUCT; inStruct->elements = malloc((inStructCount + 1) * sizeof(ffi_type *)); - + + type = rb_objc_skip_type_sname(type); while (*type != _C_STRUCT_E) { inStruct->elements[inStructIndex++] = rb_objc_ffi_type_for_type(type); type = rb_objc_skip_typespec(type); @@ -192,10 +193,10 @@ case _C_PTR: return &ffi_type_pointer; case _C_BOOL: - case _C_UCHR: - return &ffi_type_uchar; case _C_CHR: return &ffi_type_schar; + case _C_UCHR: + return &ffi_type_uchar; case _C_SHT: return &ffi_type_sshort; case _C_USHT: @@ -575,12 +576,7 @@ object by calling rb_objc_release() */ break; } - if (inStruct) { - // skip the component we have just processed - type = rb_objc_skip_typespec(type); - } - - } while (inStruct && *type != _C_STRUCT_E); + } while (inStruct && (type = rb_objc_skip_typespec(type)) && *type != _C_STRUCT_E); if (ret == NO) { /* raise exception - Don't know how to handle this type of argument */ @@ -659,7 +655,6 @@ object by calling rb_objc_release() */ break; case _C_CHR: - // Assume that if YES or NO then it's a BOOLean if (__OBJC_BOOL_IS_BOOL != 1 && *(char *)where == YES) rb_val = Qtrue; else if (__OBJC_BOOL_IS_BOOL != 1 && *(char *)where == NO) @@ -760,15 +755,13 @@ object by calling rb_objc_release() */ // the end of the running Ruby array rb_ary_push(end, rb_val); } - // skip the type of the component we have just processed - type = (char*)rb_objc_skip_typespec(type); } else { // We are not in a C structure so simply return the // Ruby value *rb_val_ptr = rb_val; } - } while (inStruct && *type != _C_STRUCT_E); + } while (inStruct && (type = rb_objc_skip_typespec(type)) && *type != _C_STRUCT_E); if (end != Qnil && NSMapGet(knownStructs, (void*)inStructHash) != NULL) { *rb_val_ptr = rb_struct_alloc((VALUE)NSMapGet(knownStructs, (void*)inStructHash), end); @@ -793,7 +786,7 @@ object by calling rb_objc_release() */ nbArgs = [signature numberOfArguments]; objcTypesIndex = 0; - type = [signature methodReturnType]; + type = [signature methodReturnType]; while (*type) { objcTypes[objcTypesIndex++] = *type++; } @@ -961,10 +954,9 @@ object by calling rb_objc_release() */ } static VALUE -rb_objc_dispatch(id rcv, const char *method, NSMethodSignature *signature, int rigs_argc, VALUE *rigs_argv) +rb_objc_dispatch(id rcv, const char *method, unsigned long hash, const char *types, int rigs_argc, VALUE *rigs_argv) { void *sym; - unsigned long hash; int nbArgs; int nbArgsExtra; int nbArgsAdjust; @@ -982,9 +974,14 @@ object by calling rb_objc_release() */ void *closurePtr; struct rb_objc_block *block; ffi_cif closureCif; + NSMethodSignature *signature; + + signature = [NSMethodSignature signatureWithObjCTypes:types]; + if (!signature) { + rb_raise(rb_eTypeError, "selector %s is missing an Objective-C signature", method); + } if (rcv != nil) { - // TODO: perhaps check [rcv methodForSelector:sel] for IMP nbArgsAdjust = 2; switch(*(signature.methodReturnType)) { #ifndef __aarch64__ @@ -1006,8 +1003,6 @@ object by calling rb_objc_release() */ return Qnil; } - hash = rb_objc_hash(method); - nbArgs = (int)[signature numberOfArguments]; nbArgsExtra = rigs_argc - (nbArgs - nbArgsAdjust); @@ -1058,7 +1053,7 @@ object by calling rb_objc_release() */ for (i=nbArgsAdjust;i Date: Thu, 10 Jul 2025 06:49:43 -0500 Subject: [PATCH 02/12] All the types --- ext/obj_ext/RIGSBridgeSupportParser.h | 1 + ext/obj_ext/RIGSBridgeSupportParser.m | 25 +++++-- ext/obj_ext/RIGSCore.m | 93 ++++++++++++++++++--------- ext/obj_ext/RIGSUtilities.h | 1 + ext/obj_ext/RIGSUtilities.m | 34 +++++----- 5 files changed, 101 insertions(+), 53 deletions(-) diff --git a/ext/obj_ext/RIGSBridgeSupportParser.h b/ext/obj_ext/RIGSBridgeSupportParser.h index 6569708..fe50ac9 100644 --- a/ext/obj_ext/RIGSBridgeSupportParser.h +++ b/ext/obj_ext/RIGSBridgeSupportParser.h @@ -25,6 +25,7 @@ @interface RIGSBridgeSupportParser : NSObject { + NSString *_className; NSString *_methodName; NSString *_functionName; NSString *_protocolName; diff --git a/ext/obj_ext/RIGSBridgeSupportParser.m b/ext/obj_ext/RIGSBridgeSupportParser.m index 073d8d6..1ebcf41 100644 --- a/ext/obj_ext/RIGSBridgeSupportParser.m +++ b/ext/obj_ext/RIGSBridgeSupportParser.m @@ -71,7 +71,10 @@ - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { - if ([elementName isEqualToString:@"informal_protocol"]) { + if ([elementName isEqualToString:@"class"]) { + [self finalizeClass]; + } + else if ([elementName isEqualToString:@"informal_protocol"]) { [self finalizeProtocol]; } else if ([elementName isEqualToString:@"method"]) { @@ -80,7 +83,7 @@ - (void)parser:(NSXMLParser *)parser else if ([elementName isEqualToString:@"function"]) { [self finalizeFunction]; } - else if ([elementName isEqualToString:@"arg"] || [elementName isEqualToString:@"retval"]) { + else if ([elementName isEqualToString:@"retval"] || [elementName isEqualToString:@"arg"]) { [self finalizeArg]; } } @@ -254,11 +257,7 @@ - (void)parseEnumWithName:(NSString*)name value:(NSString*)value - (void)parseClassWithName:(NSString*)name { - Class objc_class = NSClassFromString(name); - - if (objc_class) { - rb_objc_register_class_from_objc(objc_class); - } + _className = [name retain]; } - (NSUInteger)parseStructArgCountWithType:(NSString*)type @@ -272,6 +271,18 @@ - (NSUInteger)parseStructArgCountWithType:(NSString*)type return quoteCount / 2; } +- (void)finalizeClass +{ + Class objc_class; + + if ((objc_class = NSClassFromString(_className))) { + rb_objc_register_class_from_objc(objc_class); + } + + [_className release]; + _className = nil; +} + - (void)finalizeProtocol { [_protocolName release]; diff --git a/ext/obj_ext/RIGSCore.m b/ext/obj_ext/RIGSCore.m index adb2f56..d598886 100644 --- a/ext/obj_ext/RIGSCore.m +++ b/ext/obj_ext/RIGSCore.m @@ -51,6 +51,8 @@ // Hash table that maps known ObjC selectors with arg position to block objcTypes encoding static NSMapTable *knownBlockArgs = 0; +static NSMapTable *knownMethods = 0; + // Hash table that maps known ObjC selectors with arg position to objcTypes encoding static NSMapTable *knownTypeArgs = 0; @@ -1135,16 +1137,12 @@ object by calling rb_objc_release() */ const char *our_method; int our_argc; VALUE *our_argv; + Class class; id rcv; SEL sel; const char *method; - const char *pos; - const char *lpos; - const char *type; - unsigned long typeIndex; unsigned long hash; - Method mth; - char types[128] = { '\0' }; + const char *objcTypes; our_method = rb_id2name(rb_frame_this_func()); our_argc = rigs_argc; @@ -1161,7 +1159,7 @@ object by calling rb_objc_release() */ sel = rb_objc_method_to_sel(our_method, our_argc); if (sel == NULL) { - rb_raise(rb_eTypeError, "method %s is not a valid Objective-C selector", method); + rb_raise(rb_eTypeError, "method %s is not a valid Objective-C selector", our_method); } method = sel_getName(sel); @@ -1171,35 +1169,27 @@ object by calling rb_objc_release() */ switch (TYPE(rb_self)) { case T_DATA: Data_Get_Struct(rb_self, void, rcv); - mth = class_getInstanceMethod(object_getClass(rcv), sel); + class = object_getClass(rcv); break; case T_CLASS: rcv = (Class)NUM2LL(rb_iv_get(rb_self, "@objc_class")); - mth = class_getClassMethod(rcv, sel); + class = rcv; break; default: - rb_raise(rb_eTypeError, "can't convert %"PRIsVALUE" into compatible objc_msgSend value", rb_self); + rb_raise(rb_eTypeError, "can't convert %"PRIsVALUE" into a compatible objc_msgSend value", rb_self); break; } - pos = rb_objc_skip_type_qualifiers(method_getTypeEncoding(mth)); - typeIndex = 0; - while((lpos = pos) && (pos = rb_objc_skip_typespec(lpos))) { - if (typeIndex != 1 && - typeIndex != 2 && - (type = NSMapGet(knownTypeArgs, (void*)(hash + (typeIndex == 0 ? -1 : typeIndex - 2))))) { - strlcat(types, type, strlen(type) + strlen(types) + 1); - } - else { - strlcat(types, lpos, pos - lpos + strlen(types) + 1); - } - pos = rb_objc_skip_type_size(pos); - pos = rb_objc_skip_type_qualifiers(pos); - typeIndex++; + do { + objcTypes = NSMapGet(knownMethods, (void*)rb_objc_hash_s(class_getName(class), hash)); + } while (objcTypes == NULL && (class = class_getSuperclass(class))); + + if (objcTypes == NULL) { + rb_raise(rb_eTypeError, "unable to find Objective-C type encodings for method %s", our_method); } @try { - return rb_objc_dispatch(rcv, method, hash, types, our_argc, our_argv); + return rb_objc_dispatch(rcv, method, hash, objcTypes, our_argc, our_argv); } @catch (NSException *exception) { rb_objc_raise_exception(exception); @@ -1232,6 +1222,49 @@ object by calling rb_objc_release() */ } } +static BOOL +rb_objc_register_method(Class class, Method method) +{ + unsigned long hash; + unsigned long chash; + void *data; + const char *pos; + const char *lpos; + const char *type; + unsigned long typeIndex; + char objcTypes[256] = { '\0' }; + + pos = method_getTypeEncoding(method); + hash = rb_objc_hash(sel_getName(method_getName(method))); + chash = rb_objc_hash_s(class_getName(class), hash); + data = NSMapGet(knownMethods, (void*)chash); + + if (data) return YES; + if (strlen(pos) > 255) return NO; + + pos = rb_objc_skip_type_qualifiers(pos); + typeIndex = 0; + while((lpos = pos) && (pos = rb_objc_skip_typespec(lpos))) { + if (typeIndex != 1 && + typeIndex != 2 && + (type = NSMapGet(knownTypeArgs, (void*)(hash + (typeIndex == 0 ? -1 : typeIndex - 2))))) { + strlcat(objcTypes, type, strlen(type) + strlen(objcTypes) + 1); + } + else { + strlcat(objcTypes, lpos, pos - lpos + strlen(objcTypes) + 1); + } + pos = rb_objc_skip_type_size(pos); + pos = rb_objc_skip_type_qualifiers(pos); + typeIndex++; + } + + data = malloc(sizeof(char) * (strlen(objcTypes) + 1)); + strcpy(data, objcTypes); + NSMapInsertKnownAbsent(knownMethods, (void*)chash, (void*)data); + + return YES; +} + static unsigned int rb_objc_register_instance_methods(Class objc_class, VALUE rb_class) { @@ -1250,6 +1283,7 @@ object by calling rb_objc_release() */ mthRubyName = rb_objc_sel_to_method(mthSel); if (mthRubyName == NULL) continue; + if (!rb_objc_register_method(objc_class, methods[i])) continue; rb_define_method(rb_class, mthRubyName, rb_objc_send, -1); @@ -1273,16 +1307,13 @@ object by calling rb_objc_release() */ SEL mthSel; char *mthRubyName; char *mthRubyAlias; - Class objc_meta_class; unsigned int cmth_cnt; unsigned int i; Method *methods; VALUE rb_singleton; - objc_meta_class = objc_getMetaClass(class_getName(objc_class)); - /* Define all Ruby Class (singleton) methods for this Class */ - methods = class_copyMethodList(objc_meta_class, &cmth_cnt); + methods = class_copyMethodList(object_getClass(objc_class), &cmth_cnt); rb_singleton = rb_singleton_class(rb_class); for (i=0;i Date: Fri, 11 Jul 2025 06:44:51 -0500 Subject: [PATCH 03/12] Trickery --- ext/obj_ext/RIGSCore.m | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/ext/obj_ext/RIGSCore.m b/ext/obj_ext/RIGSCore.m index d598886..039a684 100644 --- a/ext/obj_ext/RIGSCore.m +++ b/ext/obj_ext/RIGSCore.m @@ -1142,6 +1142,7 @@ object by calling rb_objc_release() */ SEL sel; const char *method; unsigned long hash; + unsigned long mhash; const char *objcTypes; our_method = rb_id2name(rb_frame_this_func()); @@ -1170,18 +1171,20 @@ object by calling rb_objc_release() */ case T_DATA: Data_Get_Struct(rb_self, void, rcv); class = object_getClass(rcv); + mhash = hash; break; case T_CLASS: rcv = (Class)NUM2LL(rb_iv_get(rb_self, "@objc_class")); class = rcv; + mhash = hash << 1; break; default: rb_raise(rb_eTypeError, "can't convert %"PRIsVALUE" into a compatible objc_msgSend value", rb_self); break; } - do { - objcTypes = NSMapGet(knownMethods, (void*)rb_objc_hash_s(class_getName(class), hash)); + do { + objcTypes = NSMapGet(knownMethods, (void*)rb_objc_hash_s(class_getName(class), mhash)); } while (objcTypes == NULL && (class = class_getSuperclass(class))); if (objcTypes == NULL) { @@ -1236,10 +1239,10 @@ object by calling rb_objc_release() */ pos = method_getTypeEncoding(method); hash = rb_objc_hash(sel_getName(method_getName(method))); - chash = rb_objc_hash_s(class_getName(class), hash); + chash = rb_objc_hash_s(class_getName(class), hash << (class_isMetaClass(class) ? 1 : 0)); data = NSMapGet(knownMethods, (void*)chash); - if (data) return YES; + if (data) return NO; if (strlen(pos) > 255) return NO; pos = rb_objc_skip_type_qualifiers(pos); @@ -1313,7 +1316,8 @@ object by calling rb_objc_release() */ VALUE rb_singleton; /* Define all Ruby Class (singleton) methods for this Class */ - methods = class_copyMethodList(object_getClass(objc_class), &cmth_cnt); + objc_class = object_getClass(objc_class); + methods = class_copyMethodList(objc_class, &cmth_cnt); rb_singleton = rb_singleton_class(rb_class); for (i=0;i Date: Fri, 11 Jul 2025 06:54:29 -0500 Subject: [PATCH 04/12] Clean up --- ext/obj_ext/RIGSCore.m | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/ext/obj_ext/RIGSCore.m b/ext/obj_ext/RIGSCore.m index 039a684..d3f7310 100644 --- a/ext/obj_ext/RIGSCore.m +++ b/ext/obj_ext/RIGSCore.m @@ -979,9 +979,6 @@ object by calling rb_objc_release() */ NSMethodSignature *signature; signature = [NSMethodSignature signatureWithObjCTypes:types]; - if (!signature) { - rb_raise(rb_eTypeError, "selector %s is missing an Objective-C signature", method); - } if (rcv != nil) { nbArgsAdjust = 2; @@ -1212,8 +1209,8 @@ object by calling rb_objc_release() */ hash = rb_objc_hash(method); objcTypes = NSMapGet(knownFunctions, (void*)hash); - if (!objcTypes) { - return Qnil; + if (objcTypes == NULL) { + rb_raise(rb_eTypeError, "unable to find Objective-C type encodings for function %s", method); } @try { @@ -1238,13 +1235,15 @@ object by calling rb_objc_release() */ char objcTypes[256] = { '\0' }; pos = method_getTypeEncoding(method); + + if (strlen(pos) > 255) return NO; + hash = rb_objc_hash(sel_getName(method_getName(method))); chash = rb_objc_hash_s(class_getName(class), hash << (class_isMetaClass(class) ? 1 : 0)); data = NSMapGet(knownMethods, (void*)chash); if (data) return NO; - if (strlen(pos) > 255) return NO; - + pos = rb_objc_skip_type_qualifiers(pos); typeIndex = 0; while((lpos = pos) && (pos = rb_objc_skip_typespec(lpos))) { From 87c83d870c4f882c84c696bb5a81bbbeba6a7df5 Mon Sep 17 00:00:00 2001 From: Ryan Krug Date: Fri, 11 Jul 2025 07:04:47 -0500 Subject: [PATCH 05/12] Docs --- ext/obj_ext/RIGSCore.m | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/ext/obj_ext/RIGSCore.m b/ext/obj_ext/RIGSCore.m index d3f7310..25fbca4 100644 --- a/ext/obj_ext/RIGSCore.m +++ b/ext/obj_ext/RIGSCore.m @@ -48,23 +48,24 @@ // Hash table that maps known ObjC functions to objcTypes encoding static NSMapTable *knownFunctions = 0; -// Hash table that maps known ObjC selectors with arg position to block objcTypes encoding -static NSMapTable *knownBlockArgs = 0; - +// Hash table that maps known ObjC selectors with class to objcTypes encoding static NSMapTable *knownMethods = 0; // Hash table that maps known ObjC selectors with arg position to objcTypes encoding static NSMapTable *knownTypeArgs = 0; +// Hash table that maps known ObjC selectors with arg position to block objcTypes encoding +static NSMapTable *knownBlockArgs = 0; + +// Hash table that maps known ObjC selectors to printf arg positions (index+1) +static NSMapTable *knownFormatStrings = 0; + // Hash table that maps known ObjC selectors to objcTypes encoding static NSMapTable *knownProtocols = 0; // Hash table that maps known objcTypes encoding to Ruby proxy method implementations static NSMapTable *knownImplementations = 0; -// Hash table that maps known ObjC selectors to printf arg positions (index+1) -static NSMapTable *knownFormatStrings = 0; - // Hash table that contains loaded Framework bundleIdentifiers static NSHashTable *knownFrameworks = 0; @@ -1830,11 +1831,11 @@ void __attribute__((noreturn)) knownStructs = NSCreateMapTable(NSIntegerMapKeyCallBacks, NSNonOwnedPointerMapValueCallBacks, 0); knownFunctions = NSCreateMapTable(NSIntegerMapKeyCallBacks, NSNonOwnedPointerMapValueCallBacks, 0); knownMethods = NSCreateMapTable(NSIntegerMapKeyCallBacks, NSNonOwnedPointerMapValueCallBacks, 0); - knownBlockArgs = NSCreateMapTable(NSIntegerMapKeyCallBacks, NSNonOwnedPointerMapValueCallBacks, 0); knownTypeArgs = NSCreateMapTable(NSIntegerMapKeyCallBacks, NSNonOwnedPointerMapValueCallBacks, 0); + knownBlockArgs = NSCreateMapTable(NSIntegerMapKeyCallBacks, NSNonOwnedPointerMapValueCallBacks, 0); + knownFormatStrings = NSCreateMapTable(NSIntegerMapKeyCallBacks, NSIntegerMapValueCallBacks, 0); knownProtocols = NSCreateMapTable(NSIntegerMapKeyCallBacks, NSNonOwnedPointerMapValueCallBacks, 0); knownImplementations = NSCreateMapTable(NSIntegerMapKeyCallBacks, NSNonOwnedPointerMapValueCallBacks, 0); - knownFormatStrings = NSCreateMapTable(NSIntegerMapKeyCallBacks, NSIntegerMapValueCallBacks, 0); knownFrameworks = NSCreateHashTable(NSIntegerHashCallBacks, 0); // Ruby class methods under the ObjC Ruby module From ab7823ff825b97f67d54138aff668ccdce0f835a Mon Sep 17 00:00:00 2001 From: Ryan Krug Date: Fri, 11 Jul 2025 07:16:24 -0500 Subject: [PATCH 06/12] Refactor --- ext/obj_ext/RIGSCore.m | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/ext/obj_ext/RIGSCore.m b/ext/obj_ext/RIGSCore.m index 25fbca4..9006a00 100644 --- a/ext/obj_ext/RIGSCore.m +++ b/ext/obj_ext/RIGSCore.m @@ -48,8 +48,11 @@ // Hash table that maps known ObjC functions to objcTypes encoding static NSMapTable *knownFunctions = 0; -// Hash table that maps known ObjC selectors with class to objcTypes encoding -static NSMapTable *knownMethods = 0; +// Hash table that maps known ObjC class selectors with class to objcTypes encoding +static NSMapTable *knownClassMethods = 0; + +// Hash table that maps known ObjC instance selectors with class to objcTypes encoding +static NSMapTable *knownInstanceMethods = 0; // Hash table that maps known ObjC selectors with arg position to objcTypes encoding static NSMapTable *knownTypeArgs = 0; @@ -1140,7 +1143,7 @@ object by calling rb_objc_release() */ SEL sel; const char *method; unsigned long hash; - unsigned long mhash; + NSMapTable *knownMethods; const char *objcTypes; our_method = rb_id2name(rb_frame_this_func()); @@ -1169,12 +1172,12 @@ object by calling rb_objc_release() */ case T_DATA: Data_Get_Struct(rb_self, void, rcv); class = object_getClass(rcv); - mhash = hash; + knownMethods = knownInstanceMethods; break; case T_CLASS: rcv = (Class)NUM2LL(rb_iv_get(rb_self, "@objc_class")); class = rcv; - mhash = hash << 1; + knownMethods = knownClassMethods; break; default: rb_raise(rb_eTypeError, "can't convert %"PRIsVALUE" into a compatible objc_msgSend value", rb_self); @@ -1182,7 +1185,7 @@ object by calling rb_objc_release() */ } do { - objcTypes = NSMapGet(knownMethods, (void*)rb_objc_hash_s(class_getName(class), mhash)); + objcTypes = NSMapGet(knownMethods, (void*)rb_objc_hash_s(class_getName(class), hash)); } while (objcTypes == NULL && (class = class_getSuperclass(class))); if (objcTypes == NULL) { @@ -1226,6 +1229,7 @@ object by calling rb_objc_release() */ static BOOL rb_objc_register_method(Class class, Method method) { + NSMapTable *knownMethods; unsigned long hash; unsigned long chash; void *data; @@ -1238,9 +1242,10 @@ object by calling rb_objc_release() */ pos = method_getTypeEncoding(method); if (strlen(pos) > 255) return NO; - + + knownMethods = class_isMetaClass(class) ? knownClassMethods : knownInstanceMethods; hash = rb_objc_hash(sel_getName(method_getName(method))); - chash = rb_objc_hash_s(class_getName(class), hash << (class_isMetaClass(class) ? 1 : 0)); + chash = rb_objc_hash_s(class_getName(class), hash); data = NSMapGet(knownMethods, (void*)chash); if (data) return NO; @@ -1830,7 +1835,8 @@ void __attribute__((noreturn)) knownObjects = NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks, NSNonOwnedPointerMapValueCallBacks, 0); knownStructs = NSCreateMapTable(NSIntegerMapKeyCallBacks, NSNonOwnedPointerMapValueCallBacks, 0); knownFunctions = NSCreateMapTable(NSIntegerMapKeyCallBacks, NSNonOwnedPointerMapValueCallBacks, 0); - knownMethods = NSCreateMapTable(NSIntegerMapKeyCallBacks, NSNonOwnedPointerMapValueCallBacks, 0); + knownClassMethods = NSCreateMapTable(NSIntegerMapKeyCallBacks, NSNonOwnedPointerMapValueCallBacks, 0); + knownInstanceMethods = NSCreateMapTable(NSIntegerMapKeyCallBacks, NSNonOwnedPointerMapValueCallBacks, 0); knownTypeArgs = NSCreateMapTable(NSIntegerMapKeyCallBacks, NSNonOwnedPointerMapValueCallBacks, 0); knownBlockArgs = NSCreateMapTable(NSIntegerMapKeyCallBacks, NSNonOwnedPointerMapValueCallBacks, 0); knownFormatStrings = NSCreateMapTable(NSIntegerMapKeyCallBacks, NSIntegerMapValueCallBacks, 0); From 6d1bd4e937d42142223ea73ecbf555688b07115a Mon Sep 17 00:00:00 2001 From: Ryan Krug Date: Sat, 12 Jul 2025 07:13:53 -0500 Subject: [PATCH 07/12] Add a benchmark --- bin/benchmark | 111 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100755 bin/benchmark diff --git a/bin/benchmark b/bin/benchmark new file mode 100755 index 0000000..a8fe19f --- /dev/null +++ b/bin/benchmark @@ -0,0 +1,111 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "bundler/setup" +require "benchmark" + +SIZE = 1_000_000 +COLS = 12 + +Benchmark.bm(COLS) do |bm| + bm.report("Warmup:") do + require "obj_ruby" + require "obj_ruby/cocoa" + end + + bm.report("NSDictionary:") do + dict = ObjRuby::NSMutableDictionary.new + (0...SIZE).each do |i| + if i % 2 == 0 + dict.setObject_forKey("foo", i) + end + end + raise unless dict.count == SIZE / 2 + (0...SIZE).each do |i| + unless dict.objectForKey(i) + dict.setObject_forKey("bar", i) + end + end + raise unless dict.count == SIZE + end + + bm.report("Hash:") do + hash = Hash.new + (0...SIZE).each do |i| + if i % 2 == 0 + hash[i] = "foo" + end + end + raise unless hash.size == SIZE / 2 + (0...SIZE).each do |i| + unless hash[i] + hash[i] = "bar" + end + end + raise unless hash.size == SIZE + end + + bm.report("NSSet:") do + set = ObjRuby::NSMutableSet.new + (0...SIZE).each do |i| + if i % 2 == 0 + set.addObject(i) + end + end + raise unless set.count == SIZE / 2 + (0...SIZE).each do |i| + unless set.containsObject(i) + set.addObject(i) + end + end + raise unless set.count == SIZE + end + + bm.report("Set:") do + set = Set.new + (0...SIZE).each do |i| + if i % 2 == 0 + set << i + end + end + raise unless set.size == SIZE / 2 + (0...SIZE).each do |i| + unless set.include?(i) + set << i + end + end + raise unless set.size == SIZE + end + + bm.report("NSArray:") do + array = ObjRuby::NSMutableArray.new + (0...SIZE).each do |i| + if i % 2 == 0 + array.addObject("foo") + end + end + raise unless array.count == SIZE / 2 + (0...SIZE).each do |i| + unless i % 2 == 0 + array.addObject("bar") + end + end + raise unless array.count == SIZE + end + + bm.report("Array:") do + array = Array.new + (0...SIZE).each do |i| + if i % 2 == 0 + array << "foo" + end + end + raise unless array.size == SIZE / 2 + (0...SIZE).each do |i| + unless i % 2 == 0 + array << "bar" + end + end + raise unless array.size == SIZE + end +end From 9c72cdf6e814f77ca552b58348f4e7d312748242 Mon Sep 17 00:00:00 2001 From: Ryan Krug Date: Sat, 12 Jul 2025 07:21:03 -0500 Subject: [PATCH 08/12] Add Process info spec --- spec/obj_ruby/ns_process_info_spec.rb | 30 +++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 spec/obj_ruby/ns_process_info_spec.rb diff --git a/spec/obj_ruby/ns_process_info_spec.rb b/spec/obj_ruby/ns_process_info_spec.rb new file mode 100644 index 0000000..ecf34ba --- /dev/null +++ b/spec/obj_ruby/ns_process_info_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe ObjRuby::NSProcessInfo do + it "can create an instance" do + info = described_class.new + + expect(info).not_to be_nil + expect(info).to be_a described_class + end + + it "can retreive operating system versions" do + info = described_class.processInfo + + version = info.operatingSystemVersion + + pp version + + expect(version).not_to be_nil + expect(version).to be_a ObjRuby::NSOperatingSystemVersion + end + + it "can compare operating system versions" do + info = described_class.processInfo + version = ObjRuby::NSOperatingSystemVersion.new(10, 0, 0) + + expect(info.isOperatingSystemAtLeastVersion(version)).to be true + end +end From 6d8bbb8f606bdefd0b405bdfe2dee65e6dd7fe09 Mon Sep 17 00:00:00 2001 From: Ryan Krug Date: Sat, 12 Jul 2025 08:56:47 -0500 Subject: [PATCH 09/12] Rubocop --- .rubocop.yml | 2 + bin/benchmark | 203 +++++++++++++++++++++++++++++++------------------- 2 files changed, 127 insertions(+), 78 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 6a2e616..c507f9d 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -3,6 +3,8 @@ AllCops: NewCops: enable Naming/MethodName: Enabled: false +Metrics/MethodLength: + Enabled: false Style/IfUnlessModifier: Enabled: false Style/StringLiterals: diff --git a/bin/benchmark b/bin/benchmark index a8fe19f..a74f234 100755 --- a/bin/benchmark +++ b/bin/benchmark @@ -7,105 +7,152 @@ require "benchmark" SIZE = 1_000_000 COLS = 12 -Benchmark.bm(COLS) do |bm| - bm.report("Warmup:") do - require "obj_ruby" - require "obj_ruby/cocoa" - end - - bm.report("NSDictionary:") do - dict = ObjRuby::NSMutableDictionary.new - (0...SIZE).each do |i| - if i % 2 == 0 - dict.setObject_forKey("foo", i) - end +def run_objc_hash + dict = ObjRuby::NSMutableDictionary.new + + (0...SIZE).each do |i| + if (i % 2).zero? + dict.setObject_forKey("foo", i) end - raise unless dict.count == SIZE / 2 - (0...SIZE).each do |i| - unless dict.objectForKey(i) - dict.setObject_forKey("bar", i) - end + end + + raise unless dict.count == SIZE / 2 + + (0...SIZE).each do |i| + unless dict.objectForKey(i) + dict.setObject_forKey("bar", i) end - raise unless dict.count == SIZE end - bm.report("Hash:") do - hash = Hash.new - (0...SIZE).each do |i| - if i % 2 == 0 - hash[i] = "foo" - end + raise unless dict.count == SIZE +end + +def run_ruby_hash + hash = {} + + (0...SIZE).each do |i| + if (i % 2).zero? + hash[i] = "foo" end - raise unless hash.size == SIZE / 2 - (0...SIZE).each do |i| - unless hash[i] - hash[i] = "bar" - end + end + + raise unless hash.size == SIZE / 2 + + (0...SIZE).each do |i| + unless hash[i] + hash[i] = "bar" end - raise unless hash.size == SIZE end - bm.report("NSSet:") do - set = ObjRuby::NSMutableSet.new - (0...SIZE).each do |i| - if i % 2 == 0 - set.addObject(i) - end + raise unless hash.size == SIZE +end + +def run_objc_set + set = ObjRuby::NSMutableSet.new + + (0...SIZE).each do |i| + if (i % 2).zero? + set.addObject(i) end - raise unless set.count == SIZE / 2 - (0...SIZE).each do |i| - unless set.containsObject(i) - set.addObject(i) - end + end + + raise unless set.count == SIZE / 2 + + (0...SIZE).each do |i| + unless set.containsObject(i) + set.addObject(i) end - raise unless set.count == SIZE end - bm.report("Set:") do - set = Set.new - (0...SIZE).each do |i| - if i % 2 == 0 - set << i - end + raise unless set.count == SIZE +end + +def run_ruby_set + set = Set.new + + (0...SIZE).each do |i| + if (i % 2).zero? + set << i end - raise unless set.size == SIZE / 2 - (0...SIZE).each do |i| - unless set.include?(i) - set << i - end + end + + raise unless set.size == SIZE / 2 + + (0...SIZE).each do |i| + unless set.include?(i) + set << i end - raise unless set.size == SIZE end - bm.report("NSArray:") do - array = ObjRuby::NSMutableArray.new - (0...SIZE).each do |i| - if i % 2 == 0 - array.addObject("foo") - end + raise unless set.size == SIZE +end + +def run_objc_array + array = ObjRuby::NSMutableArray.new + + (0...SIZE).each do |i| + if (i % 2).zero? + array.addObject("foo") end - raise unless array.count == SIZE / 2 - (0...SIZE).each do |i| - unless i % 2 == 0 - array.addObject("bar") - end + end + + raise unless array.count == SIZE / 2 + + (0...SIZE).each do |i| + unless (i % 2).zero? + array.addObject("bar") end - raise unless array.count == SIZE end + raise unless array.count == SIZE +end - bm.report("Array:") do - array = Array.new - (0...SIZE).each do |i| - if i % 2 == 0 - array << "foo" - end +def run_ruby_array + array = [] + + (0...SIZE).each do |i| + if (i % 2).zero? + array << "foo" end - raise unless array.size == SIZE / 2 - (0...SIZE).each do |i| - unless i % 2 == 0 - array << "bar" - end + end + + raise unless array.size == SIZE / 2 + + (0...SIZE).each do |i| + unless (i % 2).zero? + array << "bar" end - raise unless array.size == SIZE + end + + raise unless array.size == SIZE +end + +Benchmark.bm(COLS) do |bm| + bm.report("Warmup:") do + require "obj_ruby" + require "obj_ruby/cocoa" + end + + bm.report("NSDictionary:") do + run_objc_hash + end + + bm.report("Hash:") do + run_ruby_hash + end + + bm.report("NSSet:") do + run_objc_set + end + + bm.report("Set:") do + run_ruby_set + end + + bm.report("NSArray:") do + run_objc_array + end + + bm.report("Array:") do + run_ruby_array end end From 791b5a469edb924c65354c95757d455603ed97d7 Mon Sep 17 00:00:00 2001 From: Ryan Krug Date: Sat, 12 Jul 2025 09:32:13 -0500 Subject: [PATCH 10/12] NSAffineTransform spec --- spec/obj_ruby/ns_affine_transform_spec.rb | 29 +++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 spec/obj_ruby/ns_affine_transform_spec.rb diff --git a/spec/obj_ruby/ns_affine_transform_spec.rb b/spec/obj_ruby/ns_affine_transform_spec.rb new file mode 100644 index 0000000..8fd99d7 --- /dev/null +++ b/spec/obj_ruby/ns_affine_transform_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe ObjRuby::NSAffineTransform do + it "can create an instance" do + transform = described_class.new + + expect(transform).not_to be_nil + expect(transform).to be_a described_class + end + + it "can get a struct transform" do + transform = described_class.new + + expect(transform.transformStruct).not_to be_nil + expect(transform.transformStruct).to be_a ObjRuby::NSAffineTransformStruct + end + + it "can set a struct transform" do + transform = described_class.new + struct = ObjRuby::NSAffineTransformStruct.new(1.0, 1.0, 1.0, 1.0, 1.0, 1.0) + + transform.transformStruct = struct + + expect(transform.transformStruct).not_to be_nil + expect(transform.transformStruct).to eq(struct) + end +end From 3ad445b65b283599021ae829594cacab18468e8e Mon Sep 17 00:00:00 2001 From: Ryan Krug Date: Sat, 12 Jul 2025 10:40:21 -0500 Subject: [PATCH 11/12] Use runtime methods --- ext/obj_ext/RIGSCore.m | 6 ++++-- spec/obj_ruby/ns_process_info_spec.rb | 2 -- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/obj_ext/RIGSCore.m b/ext/obj_ext/RIGSCore.m index 9006a00..182615a 100644 --- a/ext/obj_ext/RIGSCore.m +++ b/ext/obj_ext/RIGSCore.m @@ -292,6 +292,7 @@ { id val; Class retClass; + Class class; VALUE rb_val; VALUE rb_class; BOOL ret; @@ -312,10 +313,11 @@ object by calling rb_objc_release() */ } retClass = [val classForCoder]; - if (retClass != [val class] && strncmp(object_getClassName(val), "NSConcrete", 10) == 0) { + class = object_getClass(val); + if (retClass != class && strncmp(class_getName(class), "NSConcrete", 10) == 0) { // [NSAttributedString alloc] returns NSConcreteAttributedString // which is where initWithString is defined so we need it defined in Ruby - retClass = [val class]; + retClass = class; } rb_class = (VALUE) NSMapGet(knownClasses, (void *)retClass); diff --git a/spec/obj_ruby/ns_process_info_spec.rb b/spec/obj_ruby/ns_process_info_spec.rb index ecf34ba..f4087d2 100644 --- a/spec/obj_ruby/ns_process_info_spec.rb +++ b/spec/obj_ruby/ns_process_info_spec.rb @@ -15,8 +15,6 @@ version = info.operatingSystemVersion - pp version - expect(version).not_to be_nil expect(version).to be_a ObjRuby::NSOperatingSystemVersion end From 40724080a5cdcbb001103206962ae9ae4b5d30f4 Mon Sep 17 00:00:00 2001 From: Ryan Krug Date: Sun, 13 Jul 2025 06:25:40 -0500 Subject: [PATCH 12/12] Swap --- ext/obj_ext/RIGSCore.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/obj_ext/RIGSCore.m b/ext/obj_ext/RIGSCore.m index 182615a..033c043 100644 --- a/ext/obj_ext/RIGSCore.m +++ b/ext/obj_ext/RIGSCore.m @@ -395,12 +395,12 @@ object by calling rb_objc_release() */ switch (TYPE(rb_val)) { case T_DATA: - if (rb_obj_is_kind_of(rb_val, rb_cTime) == Qtrue) { - *(id*)where = rb_objc_date_from_rb(rb_val); - } - else if (rb_iv_get(CLASS_OF(rb_val), "@objc_class") != Qnil) { + if (rb_iv_get(CLASS_OF(rb_val), "@objc_class") != Qnil) { Data_Get_Struct(rb_val, void, *(id*)where); } + else if (rb_obj_is_kind_of(rb_val, rb_cTime) == Qtrue) { + *(id*)where = rb_objc_date_from_rb(rb_val); + } else { ret = NO; }