问题
To simplify, let's say I have a function like that
void myFunc(id _self, SEL _cmd, id first, ...)
{
}
In that method I wanna call the implementation(imp) on the superclass of _self. I can reach that IMP with this code:
Class class = object_getClass(_self);
Class superclass = class_getSuperClass(class);
IMP superimp = class_getMethodImplementation(superclass, _cmd);
now, how can I do to call that imp ?
回答1:
Just call it using variable arguments:
superImp(self, _cmd, argument1, argument2, argument3, etc...)
IMP is already typedef
'd as
typedef id (*IMP)(id, SEL, ...);
So you can call it with variable arguments with no issue.
回答2:
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
usingva_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
, notobjc_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 valueva_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 size1
(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
andmethod_getImplementation
, and then we can install that implementation into a new temporary proxy method on the object's class usingclass_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 amsgSendSuper
. 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
. (SeePearlForwardIMP
)
回答3:
Actually, i can't find anyway to call IMP
with variable number of arguments, but i have another solution for this situation.
By using NSInvocation
, we can resolve this problem.
void myFunc(id _self, SEL _cmdS, id first, ...) {
Class clazz = object_getClass(_self);
Class superClass = class_getSuperclass(clazz);
NSMethodSignature *signature = [superClass methodSignatureForSelector:_cmdS];
if (!signature) {
return;
}
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:signature];
[inv setSelector:_cmdS];
[inv setTarget:superClass];
va_list args;
va_start(args, first);
id arg = first;
// Arguments 0 and 1 are self and _cmd respectively, automatically set
// by NSInvocation. So start setting arguments from index 2
for (int i = 2; i < signature.numberOfArguments; i++) {
[inv setArgument:&arg atIndex:i];
if (i < signature.numberOfArguments - 1) {
arg = va_arg(args, id);
}
}
va_end(args);
[inv invoke];
}
来源:https://stackoverflow.com/questions/12250525/how-to-call-an-implementation-with-variable-number-of-arguments