The original platform for Substrate was iOS, where it was used to provide a replacement for SummerBoard, a theming engine popular back before Apple had even conceived of an App Store. This replacement, WinterBoard, needed to change the implementations of various Objective-C messages.
The way that SummerBoard worked was using "categories", an Objective-C primitive that allows classes to be overlaid on top of one another, replacing methods with conflicting names. However, this feature has a few important downsides that keep it from being generally useful.
For one, the original implementation is outright replaced: unlike when subclassing a class, developers cannot call through to the original code after changing arguments; instead, they must reimplement the entire functionality of the message, which may change between code revisions.
Further, usage of categories with the Objective-C 2.0 ABI requires being able to link against the original class: if the original class is inside of an executable, as it would be when changing behaviors inside of SpringBoard (the iOS launcher), it is not possible to use the linker.
Luckily, Objective-C provides a fairly high-level runtime API, allowing developers to use class_getInstanceMethod, method_setImplementation, and some other even more powerful APIs such as method_exchangeImplementations, to "swizzle" methods and change behaviors.
However, while these APIs function quite well when there are only a small number of people making modifications, they fail to satisfy more complex use cases; in particular, there are ordering problems if multiple people attempt to hook the same message at different points in an inheritance hierarchy.
Finally, it is important that classes that are being instrumented are not "initialized" as they are being modified (which would both change the ordering of the target program, as well as make it impossible to hook the initialization sequence); over time, the way Objective-C runtime APIs implement this has changed.
Substrate solves all of these problems by providing a replacement API that takes all of these issues into account, always making certain that the classes are not initialized and that the right "next implementation" is used while walking back up an inheritance hierarchy.
Note: This API replaces an older API, MSHookMessage (which failed to take into account these issues). Under no circumstance should new code use the older API, which is deprecated and will not be available on future platforms supported by Substrate.
void MSHookMessageEx(Class _class, SEL message, IMP hook, IMP *old);
Parameter | Description |
---|---|
_class |
Objective-C class on which a message will be instrumented. This class can be a meta-class (obtained directly using objc_getMetaClass or by calling object_getClass on a class), so as to allow hooking non-instance or "class" messages. |
message |
Objective-C selector of message that will be instrumented. This might be a literal using @selector or generated at runtime with sel_registerName. |
hook |
The address of an ABI-compatible replacement for the implementation of the message being instrumented. |
old |
A pointer to a function pointer that will be filled in with a stub which may be used to call the original implementation. This can be NULL if you do not proceed to the original. |
NSString *(*oldDescription)(id self, SEL _cmd); // implicit self and _cmd are explicit with IMP ABI NSString *newDescription(id self, SEL _cmd) { NSString *description = (*oldDescription)(self, _cmd); description = [description stringByAppendingString:@"!"]; return description; } MSHookMessageEx( [NSObject class], @selector(description), &newDescription, &oldDescription );