So, after a lot of work, actually figured this out myself.
The problem
The reality is that it's impossible to pass varargs to a C vararg function. In order to do this, you really need the API to provide a v
-style function, such as vprintf
. Now, there actually IS a objc_msgSendv
, but it's deprecated. Also, there is no objc_msgSendSuperv
. That means the low-level API is completely off-limits to us. This API is only useful if you know the exact message signature at compile time.
The only other option is to use the NSInvocation
machinery and build a message rather than invoking implementations directly. A naive solution is fairly trivial but there are many caveats in doing this properly.
There are a few things we need to solve:
- Passing arguments of different types (read sizes). We'll copy our arguments to our
NSInvocation
using va_arg
, but this macro depends on us passing it the type of the argument in order for it to determine the size of the bytes to copy and advance.
- Invoking an implementation that is overridden (read hidden) by another. Also, since we're now just sending messages, there is no way to select exact implementation of the class. In essence, we can only do
objc_msgSend
, not objc_msgSendSuper
. There is no way to directly invoke a method that was overridden by another.
- Retrieving the return value. Our new IMP function needs to have a return type that matches the return type of the function we're replacing. The OP used a
void
return value, but that's certainly not always the case. Further difficulty is that this return value must be specified at compile time since it's a C function, not runtime.
The Solution
I've actually solved the above problems in code available here:
https://github.com/Lyndir/Pearl/blob/master/Pearl/NSInvocation%2BPearl.h
https://github.com/Lyndir/Pearl/blob/master/Pearl/NSInvocation%2BPearl.m
To address these issues, I've done the following:
- For each argument, we look at the method's signature, which encodes the argument type. We determine the byte size of the argument type, and do a series of
if
's to check if the argument size is equal to that of a known size, which allows us to perform a compile-time fixed value va_arg
for that size. This resolves the issue for arguments of types with a size equal to a series of "supported" sizes. In my implementation, I support any type of byte size 1
(eg. char), 2
(eg. short), 4
(eg. int), 8
(eg. id), 16
(eg. CGPoint), 32
(eg. CGRect), 48
(eg. CGAffineTransform) (See - (void)setArguments:(va_list)args
).
- To invoke the implementation of a method that was overridden by another method, we can look up the hidden implementation using
class_getInstanceMethod
and method_getImplementation
, and then we can install that implementation into a new temporary proxy method on the object's class using class_addMethod
. If we then invoke the proxy method instead of the target method, the runtime will invoke the hidden implementation we were looking for, effectively doing the same as a msgSendSuper
. However, this would lead to us invoking the implementation with a bad _cmd
, so what we do instead is assign the hidden implementation to the top level at the correct method name. Doing so would overwrite our method's override code, though, so we set that aside for a moment and after the invocation, we restore it (See - (void)invokeWithTarget:(id)target superclass:(Class)type
).
- In order to create a C function with a return type that has the right size to fit our arbitrary IMP's return value, we again look at the method signature to, this time, figure out the size of our return value. We do another series of conditionals to check the size against known sizes and for each known size, we create a C function with that size hardcoded as the function's return value. I've created support for return values of sizes To facilitate this, we use
imp_implementationWithBlock
. (See PearlForwardIMP
)
superimp
to also have type signaturevoid (id, SEL, id, ...)
? Also, how do you callmyFunc
? – Karillaid
,SEL
,id
orIMP
.In that method
what method ? – Sexistid
,SEL
, andIMP
are standard parts of the Objective-C runtime. They don't need to be explained in the question. – Karilla