cocos2dx中菜單響應回調的實現原理

來源:互聯網
上載者:User

前言:這幾天在做cocos2dx上的跨平台的socket庫,看來看去也只能用BSD去實現了。因為要接收伺服器的推送訊息,所以要做成非同步處理。也參考了很多別人的文章,本來是想能有個輪子直接給我用的,看來還是得自己重複造下。目前的想法是封裝一個業務層,內部包含一個socket封裝,使用pthread起一個線程去select接收資料,當有資料到達時,調用業務層的函數處理資料。業務層註冊有介面層的回呼函數,然後預先處理完畢後直接丟給對應的介面回呼函數處理。但是中途編譯上遇到了問題,當今天看了下cocos2dx內部對應的菜單響應事件才搞懂原來是這樣弄的。

其實這個問題可以簡單得看成是A類註冊了B類的回呼函數,當A類對象接收到對應的事件後,直接調用B類的回呼函數處理。本來回呼函數就是給C語言的規範,因為到了C++裡面有對象對象的概念後,每個成員對象調用的時候都隱含了一個this指標,當然,A類是不具有B類的回呼函數實現的,自然編譯的時候就會有問題。

看到網上也有很多解決方案,其中最牛氣的一個就是用一個thunk解決,這個有點高端,因為是直接通過彙編代碼修改EAX寄存器的值來實現這個this指標的問題,怕不通用,其實最麻煩的是對這個不熟悉,到時候出問題都不知道如何解決,想想還是作罷。其實開始的時候就有人提到可以把調用的this指標替換成回呼函數的真正擁有者,當時沒理解,現在翻看了cocos2dx的代碼才發現,這貨就是這麼搞的!當時我還覺得很神奇,原來也是這種土辦法,呵呵。就拿CCMenuItemImage做例子好了。

看下CCMenuItem的類定義:

class CC_DLL CCMenuItem : public CCNodeRGBA{protected:    /** whether or not the item is selected     @since v0.8.2     */    bool m_bSelected;    bool m_bEnabled;public:    CCMenuItem()    : m_bSelected(false)    , m_bEnabled(false)                , m_pListener(NULL)                , m_pfnSelector(NULL)    , m_nScriptTapHandler(0)    {}    virtual ~CCMenuItem();    /** Creates a CCMenuItem with no target/selector */    static CCMenuItem* create();    /** Creates a CCMenuItem with a target/selector */    static CCMenuItem* create(CCObject *rec, SEL_MenuHandler selector);    /** Initializes a CCMenuItem with a target/selector */    bool initWithTarget(CCObject *rec, SEL_MenuHandler selector);    /** Returns the outside box */    CCRect rect();    /** Activate the item */    virtual void activate();    /** The item was selected (not activated), similar to "mouse-over" */    virtual void selected();    /** The item was unselected */    virtual void unselected();        /** Register menu handler script function */    virtual void registerScriptTapHandler(int nHandler);    virtual void unregisterScriptTapHandler(void);    int getScriptTapHandler() { return m_nScriptTapHandler; };    virtual bool isEnabled();    //@note: It's 'setIsEnable' in cocos2d-iphone.     virtual void setEnabled(bool value);    virtual bool isSelected();        virtual void setOpacityModifyRGB(bool bValue) {CC_UNUSED_PARAM(bValue);}    virtual bool isOpacityModifyRGB(void) { return false;}        /** set the target/selector of the menu item*/    void setTarget(CCObject *rec, SEL_MenuHandler selector);protected:    CCObject*       m_pListener;      // 回呼函數擁有者    SEL_MenuHandler    m_pfnSelector; // 回呼函數    int             m_nScriptTapHandler;};

其中有兩個很關鍵的成員變數,CCObject* m_pListener 和 SEL_MenuHandler m_pfnSelector。來看下SEL_MenuHandler的定義:

typedef void (CCObject::*SEL_MenuHandler)(CCObject*);#define menu_selector(_SELECTOR) (SEL_MenuHandler)(&_SELECTOR)

是的,這個就是回呼函數的類型,然後下面的宏定義是為了方便壘代碼用的。

再來看看註冊的代碼:

CCMenuItemImage * CCMenuItemImage::create(const char *normalImage, const char *selectedImage){    return CCMenuItemImage::create(normalImage, selectedImage, NULL, NULL, NULL);}CCMenuItemImage * CCMenuItemImage::create(const char *normalImage, const char *selectedImage, CCObject* target, SEL_MenuHandler selector){    return CCMenuItemImage::create(normalImage, selectedImage, NULL, target, selector);}CCMenuItemImage * CCMenuItemImage::create(const char *normalImage, const char *selectedImage, const char *disabledImage, CCObject* target, SEL_MenuHandler selector){    CCMenuItemImage *pRet = new CCMenuItemImage();    if (pRet && pRet->initWithNormalImage(normalImage, selectedImage, disabledImage, target, selector))    {        pRet->autorelease();        return pRet;    }    CC_SAFE_DELETE(pRet);    return NULL;}bool CCMenuItemImage::initWithNormalImage(const char *normalImage, const char *selectedImage, const char *disabledImage, CCObject* target, SEL_MenuHandler selector){    CCNode *normalSprite = NULL;    CCNode *selectedSprite = NULL;    CCNode *disabledSprite = NULL;    if (normalImage)    {        normalSprite = CCSprite::create(normalImage);    }    if (selectedImage)    {        selectedSprite = CCSprite::create(selectedImage);    }        if(disabledImage)    {        disabledSprite = CCSprite::create(disabledImage);    }    return initWithNormalSprite(normalSprite, selectedSprite, disabledSprite, target, selector);}

最後反正就是調用下面的一個實現回呼函數註冊。

// 後期註冊void CCMenuItem::setTarget(CCObject *rec, SEL_MenuHandler selector){    m_pListener = rec;    m_pfnSelector = selector;}// 初期綁定CCMenuItem* CCMenuItem::create(CCObject *rec, SEL_MenuHandler selector){    CCMenuItem *pRet = new CCMenuItem();    pRet->initWithTarget(rec, selector);    pRet->autorelease();    return pRet;}bool CCMenuItem::initWithTarget(CCObject *rec, SEL_MenuHandler selector){    setAnchorPoint(ccp(0.5f, 0.5f));    m_pListener = rec;    m_pfnSelector = selector;    m_bEnabled = true;    m_bSelected = false;    return true;}

好了,就這麼綁定起來了,調用的時候也特簡單,看如下代碼:

void CCMenuItem::activate(){    if (m_bEnabled)    {        if (m_pListener && m_pfnSelector)        {            (m_pListener->*m_pfnSelector)(this);    // 直接調用了便是        }                if (kScriptTypeNone != m_eScriptType)        {            CCScriptEngineManager::sharedManager()->getScriptEngine()->executeMenuItemEvent(this);        }    }}

(m_pListener->*m_pfnSelector)(this); 這句可能要稍微解釋下,其實也挺好懂的。首先前面這個括弧就是對應的函數,後面的this就是參數,這個滿足對函數指標的定義,即void (CCObject::*)(CCObject*); 因為CCMenuItem基類是CCObject,那其實調用的情境(CScene)基類也一樣。然後就是m_pListener->*m_pfnSelector這個了,前面的Listener應該沒啥疑議,關鍵是*m_pfnSelector可能有些人會懵,其實也很簡單,m_pfnSelector是函數指標,首先他是個指標,存的是個地址,然後*自然是取對應地址內的內容(函數地址),對於函數調用來講,其實就是調用一個地址,而m_pListener->m_pfnSelector明顯是錯誤的,因為調用者沒有m_pfnSelector這個成員變數,他只有對應這個指標內部儲存的函數地址對其是有效。

順便把調用active的代碼也放一下更清楚:

void CCMenu::ccTouchEnded(CCTouch *touch, CCEvent* event){    CC_UNUSED_PARAM(touch);    CC_UNUSED_PARAM(event);    CCAssert(m_eState == kCCMenuStateTrackingTouch, "[Menu ccTouchEnded] -- invalid state");    if (m_pSelectedItem)    {        m_pSelectedItem->unselected();        m_pSelectedItem->activate();    // 就是這裡    }    m_eState = kCCMenuStateWaiting;}

ccTouchEnded這種反正就是響應螢幕的操作了,再網上我就不貼了,自己找找也能找到的。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.