前言:這幾天在做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這種反正就是響應螢幕的操作了,再網上我就不貼了,自己找找也能找到的。