UPDATE: This article was initially about some difficulties that I had calling some Objective-C++ code from C++ in a dynamic way using NSInvocation, which failed in the first place. I switched to directly calling the IMP pointer instead while passing C++ object pointers as arguments. Thanks to the comments that I received from bob, an obviously very experienced Objective-C++ developer, I could change the code and make it work also with NSInvocation.
Here is a C++ class that represents the details of a notifier/observer association called Observation:
class Observation: public Object { public: void setObjcObserver(NSObject *theObserver) { _nsObserver = theObserver; } void setObjcSlot(SEL theSlot) { _nsSlot = theSlot; } void notify(bool complete); Observation() : _nsObserver(nil), _nsSlot(nil) {} private: Object *_notifier; NSObject *_nsObserver; SEL _nsSlot; };
First Attempt Using NSInvocation Failed
void Observation::notify(bool complete) { NSMethodSignature *aSignature = [[_nsObserver class] instanceMethodSignatureForSelector:_nsSlot]; assert(aSignature); assert([_nsObserver respondsToSelector:_nsSlot]); NSInvocation *anInvocation = [NSInvocation invocationWithMethodSignature:aSignature]; [anInvocation setSelector:_nsSlot]; [anInvocation setArgument:this atIndex:2]; [anInvocation setArgument:&complete atIndex:3]; [anInvocation invokeWithTarget:_nsObserver]; }
this code compiled fine but it crashed as soon as I tried to access members of the received Observation object in Objective-C.
IMP Based Solution
My approach to solve the issue is that I am directly calling the IMP of the method in the observer object. For this, I slightly change the C++ Observation model to cache the IMP pointer:
class Observation: public Object { public: void setObjcObserver(NSObject *theObserver) { _nsObserver = theObserver; } void setObjcSlot(SEL theSlot) { _nsSlot = theSlot; } void notify(bool complete); Observation() : _nsObserver(nil), _nsSlot(nil), _nsSlotImpl(nil) {} private: Object *_notifier; NSObject *_nsObserver; SEL _nsSlot; typedef void (*slotImpl_t)(__strong id, SEL, lang::Observation *, BOOL); // used to cache the IMP of the objcSlot slotImpl_t _nsSlotImpl; };
Here, I simply add a typedef of the desired observer method signature called “slotImpl_t” and a member to cache the IMP function pointer. The cache would not be necessary but in my case the observer IMP will not change, so I cache it in order to save the look-up on frequent execution of the code. The notify() method now looks different:
void Observation::notify(bool complete) { if(!_nsSlotImpl) { _nsSlotImpl = (slotImpl_t)[_nsObserver methodForSelector:_nsSlot]; assert(_nsSlotImpl); } _nsSlotImpl(_nsObserver, _nsSlot, this, complete); }
This is working like a charm now. The address of the Observation object at the receiving end is correct as it should be, even in case of multiple-inheritance combined with virtual inheritance.
Correct Solution with NSInvocation
It turned out that in my original solution using NSInvocation I did it wrong. NSInvocation needs the address of the this pointer and not the this pointer itself. To make it worse, when simply doing
[anInvocation setArgument:&this atIndex:2];
the compiler complains with the error:
Address expression must be an lvalue or a function designator
So, instead of &this it has to be an lvalue which essentially requires to copy the this pointer to a temporary variable, ‘temp’ in this case. Here is the complete and working code:
void Observation::notify(bool complete) { NSMethodSignature *aSignature = [[_nsObserver class] instanceMethodSignatureForSelector:_nsSlot]; assert(aSignature); assert([_nsObserver respondsToSelector:_nsSlot]); NSInvocation *anInvocation = [NSInvocation invocationWithMethodSignature:aSignature]; [anInvocation setSelector:_nsSlot]; Observation *temp = this; [anInvocation setArgument:&temp atIndex:2]; [anInvocation setArgument:&complete atIndex:3]; [anInvocation invokeWithTarget:_nsObserver]; }
Conclusion
There is expert knowlegde about Objective-C++ available on the planet. But it’s not easy to get access to it. Sometimes, it helps to blog about a problem and the solution will come and find you;) Again, bob, thank you very much
You are doing it wrong.
It needs to be
[anInvocation setArgument:&this atIndex:2];
This one gives me an error: Address expression must be an lvalue or a function designator
Or if it is not possible to write “&this”, you can do
Observation *temp = this;
[anInvocation setArgument:&temp atIndex:2];
I’m pretty sure that I tried “&this” before but, because of the compiler error above, I was mislead to think that taking the address of the this pointer was the wrong idea. Turns out it wasn’t. Using your modification the code is working as expected. Thank you very much for your fix, bob, I will adjust the blog post accordingly in order to not blame the language nor NSInvocation but just my stupidity;)