c++objective-csignals-slotsboost-signalsboost-signals2

Can a C++ signals2 slot callback contain Objective-C/C++ Class/Selector (Method) information?


This must be so obvious to some of you, but I cannot find an example of this:

I need for a boost::signals2 signal to connect a slot callback that is a C++ class member function or functor, so I can make model callbacks into Objective-C/C++ Controller code.

That callback needs to store the Class and Selector of an instance of an Objective-C/C++ Method that can be called inside the C++ callback function. (I'm assuming there's no possible way to actually provide a direct callback function address of an Objective-C/C++ method). I've assumed I need to create an instance of the C++ class / functor to CONTAIN the information for calling the Objective-C/C++ method.

I'm also not sure if I can separate out Class and SEL (selector) and store them inside the instance of the C++ class for callback without passing them as void*. Once the C++ callback is called by signal(), I expect I can convert them to a usable (callable) form with class_getInstanceMethod and method_getImplementation.

Additionally, I'll probably want to send at least one parameter with an arbitrary struct ("EventInfo") to the slot from the signal, that can provide information about the nature of the signal.

Can anyone please shine some light on the darkness?


Solution

  • It took me a long time, but I finally figured this out. THere may be easier ways to do this, but I found I needed to create a C++ class in a .mm file that acts as a bridge between boost::signals2 signal and an Objective-C callback function:

    In CPPToCocoaModelMessageCallbacks.h:

    /* ------------------------------------------------------------------------
        class CPPToCocoaModelMessageCallback - 
    --------------------------------------------------------------------------- */
    class CPPToCocoaModelMessageCallback
    {
    public:
        CPPToCocoaModelMessageCallback( PMD_Signal_Messenger<PrefEvent> *theSignalClass, 
                                        int         whichPrefIdxToObserve, 
                                        id          pObjCClass, 
                                        SEL         pObjCMethod);
    
        ~CPPToCocoaModelMessageCallback();
    
        void    CallBackMessage(PrefEvent* pPrefEvent);
    
    
    private:
    
        id          fpObjCClass;
        SEL         fpObjCMethod;
    
        ls_index    fWhichPrefIdxToObserve;
    
        boost::signals2::connection fTheConnection;
    
    };  // CPPToCocoaModelMessageCallback
    

    In CPPToCocoaModelMessageCallbacks.mm

    /* ------------------------------------------------------------------------
        CPPToCocoaModelMessageCallback - CONSTRUCTOR
    
        whichPrefIdxToObserve - the preference idx to observe
    
        Pass the id and selector of the Objective-C/C++ object & method to be
        called.
    --------------------------------------------------------------------------- */
    CPPToCocoaModelMessageCallback::CPPToCocoaModelMessageCallback(PMD_Signal_Messenger<PrefEvent> *theSignalClass, int whichPrefIdxToObserve, id pObjCClass, SEL pObjCMethod) 
            :   fpObjCClass (pObjCClass),
                fpObjCMethod (pObjCMethod),
                fWhichPrefIdxToObserve (whichPrefIdxToObserve)
    {
        fTheConnection = theSignalClass->ObserveSignal(&CPPToCocoaModelMessageCallback::CallBackMessage, this);
    }
    
    /* ------------------------------------------------------------------------
        ~CPPToCocoaModelMessageCallback - DESTRUCTOR
    
        Pass the id and selector of the Objective-C/C++ object & method to be
        called.
    --------------------------------------------------------------------------- */
    CPPToCocoaModelMessageCallback::~CPPToCocoaModelMessageCallback()
    {
        fTheConnection.disconnect();
    }
    
    
    /* ------------------------------------------------------------------------
        CPPToCocoaModelMessageCallback::CallBackMessage - 
    
        Handles single and range-type preference change events.
    --------------------------------------------------------------------------- */
    void CPPToCocoaModelMessageCallback::CallBackMessage(PrefEvent* pPrefEvent)
    {
        // Only make the callback if this event is the preference we're observing
    
        if (pPrefEvent->GetChangedPrefIdx() == fWhichPrefIdxToObserve) {
            [fpObjCClass performSelector:fpObjCMethod];
        }
    }
    

    ///////////////////////////////////////////////////////////////////////////////

    In your controller.mm:

    // set up messaging from Model.  The message callback functions must be destructed in dealloc.  
    // I've done this in awakeFromNib but it could be elsewhere
    
    - (void)awakeFromNib {
    
        PMD_Signal_Messenger<MyEventKind>* theModelClass = GetMyModelClassPointer();
    
        displayMenuPrefChangedCallBack = new CPPToCocoaModelMessageCallback(theModelClass, kAppPrefDictionaryDisplayShortDef, self, @selector(displayMenuChanged));
    }
    
    
    /* ------------------------------------------------------------------------
        displayMenuChanged - this gets called when the model fires a signal
            (via CPPToCocoaModelMessageCallback::CallBackMessage())
    
    --------------------------------------------------------------------------- */
    - (void) displayMenuChanged
    {
        NSLog(@"displayMenuChanged\n");
    
        // DO SOMETHING TO RESPOND TO THE SIGNAL (in this case I'm reloading an NSWebView):
    
        [self reloadWebViewText];
    }
    

    //////////////////////////////////////////////////////////////////////////////

    Class to combine with model class for signaling observers:

    PMD_Signal_Messenger.h:

    /* ------------------------------------------------------------------------
        class PMD_Signal_Messenger<MyEventKind> -
    
            This class is designed to be multiple inherited with various
            Model classes.
    --------------------------------------------------------------------------- */
    template <class MyEventKind>
    class PMD_Signal_Messenger {
    public:
    
        PMD_Signal_Messenger() { }
        ~PMD_Signal_Messenger() { }
    
            template<typename Fn, typename Obj>
                boost::signals2::connection ObserveSignal(Fn callback, Obj &object) {
                    return fSignalObservers.connect(boost::bind(callback, object, _1));
                }
    
    protected:
        boost::signals2::signal<void (MyEventKind*)> fSignalObservers;  // all observers of my preference changes
    
    private:
        PMD_Signal_Messenger(const PMD_Signal_Messenger& thePMD_Signal_Messenger)   { assert(false); }  // prevent copy constructor
    };
    

    In the .cpp MODEL file where you want to signal a model change:

    // construct theEvent (your own struct) and fire the signal with your event structure that gets passed to CPPToCocoaModelMessageCallback::CallBackMessage()
    
    MyEventKind theEvent(someUsefulParams);
    
    fSignalObservers(&theEvent);