• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            cexer

            cexer
            posts - 12, comments - 334, trackbacks - 0, articles - 0
              C++博客 :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理

            GUI框架:談談框架,寫寫代碼

            Posted on 2009-11-15 18:09 cexer 閱讀(12312) 評論(176)  編輯 收藏 引用 所屬分類: GUIpattern

            1 開篇廢話

              我喜歡用C++寫 GUI 框架,因為那種成就感是實實在在地能看到的。從畢業到現在寫了好多個了,都是實驗性質的。什么拳腳飛刀毒暗器,激光核能反物質,不論是旁門左道的陰暗伎倆,還是名門正派的高明手段,只要是 C++ 里有的技術都試過了。這當中接觸過很多底層或是高級的技術,像編譯時類型檢測,運行時代碼修改等等,按實現的不同 GUI 涉及的東西是沒有邊際的。從最開始模仿 MFC,ATL 那樣的實現學到很多東西,然后開始看一些開源的著名的 GUI 框架,像 MFC,WTL,SmartWin++,win32gui,jlib2 ,VCF 獲得很多啟發,到現在似乎有一種已看盡天下 GUI 的感覺。在學習別人的框架和自己的實現過程中,真真實實地感覺自己成長了不少,也有很多感悟。

              寫到這,我作為輪子制造愛好者,在這里向那些喊著"不要重復制造輪子的"批評家們承認錯誤。在有那么多好的輪子的情況下,我不值得浪費地球資源,浪費時間精力來自己手工重復打造。但是不值得歸不值得,在值得和喜歡之間我還是選擇后者。并且人生在世,什么才是值得?我覺得不是拯救人類,為世界和平做貢獻,也不是努力奮斗,為地球人民謀福利,而是簡單地做自己喜歡的事。

              寫過的那些代碼很多都消失在硬盤的海洋里了,但那些挑燈苦想來的感悟還在。在它們也消失之前,我想利用空閑時間把這些覺得有點用處的經驗寫出來,正好這個博客也已經快一年沒更新了。另外也算是對那些發我郵件的朋友的回應。

              我的想法是用一系列日志,按照實現一個 GUI 框架的具體思維遞進過程來闡述實現一個 GUI 框架的具體思維遞進過程。這樣說好像有點遞歸,簡單地解釋就是這一系列日志不是想用《記憶碎片》那樣錯亂的敘述方式來說明一個多有意思的故事,而是盡量簡單自然地記錄一下寫 GUI 框架過程中我的思考。這個遞進過程也就是實現一個 GUI 框架的過程,一系列日志之后,我們將會看到一個長得漂亮眼,極富彈性,能干又節約的 GUI 框架。

              雖然寫的內容都是在 Windows 的 GUI 系統之上,但其原理是觸類旁通的,其它基于消息的 GUI 系統也都大同小異。所用的代碼也都是闡述原理的,自知絕對達不到商業巨作的水準,所以請不要一上來就批判,要知道我只是想分享而已。之所以先這樣說一下,是很害怕那種一上來就"怎么不跨平臺啊?","怎么都還看得到HWND啊?","怎么不能用成員函數處理消息啊?"的同志。不喜歡站在高處指著別人的天靈蓋說話的人。要知道車輪也是一步步造出來的,不要一開始就想載著MM在高速路上飆豪車像少年啦飛馳。

              我認為寫技術博客有三種境界,一種是一直在那繪聲繪色地描述自己的魚有多可口多美味,讓讀者只能垂涎興嘆,一種是授人以魚的人,悶頭就擺出來各種生猛海鮮,讓讀者難以消化,還有一種境界是授人以漁,怎么釣魚怎么煮魚都細細地教給讀者。讀博客的人有兩個境界,一種是只吃魚的,一上來就只要代碼,一種是學打魚的,想知其然更想知其所以然。讀博客時我努力做學打魚的類型,自己寫博客時我會努力做到授人以漁的境界。

              另外要說明的是,同樣作為塵世中的一個渺小個體,我大多數時候也是在為生存而奔波勞累著的。除此之外剩余的大多時候,更是要玩游戲,K歌,看電影,陪MM,吃喝玩樂。再剩余用來寫這個的時候不是很多,有可能這一系列日志一夜寫就,也有可能增刪五年披閱十載,孩子都叫爸了還沒完成。所以請大家不要對這個博客抱很大的期待,就當我是路邊街頭的表演,你打醬油經過時偶爾瞟過來一眼就好了。

              要說的廢話終于說完了,下面開始正題。

            2 基本概念

              基于消息的 GUI 框架的封裝,一切都圍繞消息展開。復雜的框架設計,明確了需求之后,第一步首先是劃分模塊。所以,要闡述一個設計過程,第一步也應該是先說清最基本的概念和模塊劃分,而不是一上來就用廣義相對論把讀者全部放倒。GUI 框架是干什么的當然是地球人都知道的,但 GUI 框架沒有什么已經劃分的標準概念,我是按照設計的需要來劃分的。如果把 GUI 框架看作一個單位,那么這個單位里最重要的角色有這幾個:

            • 消息發送者(message sender)
            • 消息監聽者(message listener)
            • 消息檢查者(message checker)
            • 消息處理者(message handler)
            • 消息分解者(message cracker)
            • 消息映射者(message mapper)

              下面分別說明。

            2.1 消息發送者和消息(message sender,message) 

              消息發送者其實只是在這里友情客串一下,它不在框架設計之內,由操作系統扮演這個勞苦功高的角色,它的工作是將消息發送到消息監聽者。在這里面隱含了一下最重要的角色,消息。其實剩余的所有角色說到底也只是死跑龍套的,真正領銜的是消息本身,比如窗口大小改變了的消息,按鈕被點擊了的消息等等,所有人都高舉旗幟緊密團結在它周圍進行工作。但消息本身只是一個很簡單的數據結構,因為再復雜的 GUI 系統,它的消息也不過是幾個參數,所以框架的實現重點在其它的角色。在此之前簡單地封裝一下消息,一個最簡單的封裝可能是這樣:

               1: // 消息封裝類
            
               2: class Message
            
               3: {
            
               4: public:
            
               5:     Message( UINT id_=0,WPARAM wparam_=0,LPARAM lparam_=0 )
            
               6:         :id( id_ )
            
               7:         ,wparam ( wparam_ )
            
               8:         ,lparam ( lparam_ )
            
               9:         ,result ( 0 )
            
              10:     {}
            
              11:
            
              12:     UINT      id;
            
              13:     WPARAM    wparam;
            
              14:     LPARAM    lparam;
            
              15:     LRESULT   result;
            
              16: };
            

              就這樣的我們的公司已經有了核心角色了。從概念上講,我們的這個基于消息的 GUI 框架已經完成了 99% 。然后我們可以以它為中心,按功能劃分進行詳細討論,一步步完成那剩余的 1% 的極富創意和挑戰的工作。在此之前,先得簡單解釋一下這幾個角色都各是什么概念。消息傳送者如上所述,將不在討論范圍內。

            2.2 消息監聽者(message listener) 

              消息監聽者完成的工作是從操作系統接收到消息,消息是從這里真正到達了框架之內。最簡單的消息監聽者是一個提供給操作系統的回調函數,比如在 Windows 平臺上這個函數的樣子是這樣:

               1: //我是最質樸的消息接收者
            
               2: LRESULT CALLBACK windowProc( HWND window,UINT id,WPARAM wparam,LPARAM lparam );
            

              一個好 GUI 框架當然不能赤祼祼地使用這個東西,我們要在此之上進行面向對象的封裝。消息監聽者能想到的最自然的封裝模式是觀察者模式(Observer),這樣的模式下的監聽者實現看起來像這個樣子:

               1: //我是一個漂亮的觀察者模式的消息監聽者
            
               2: class MessageListener
            
               3: {
            
               4: public:
            
               5:     virtual LRESULT onMessage( Message* message ) = 0;
            
               6: };
            
               7:
            
               8: //監聽者這樣工作
            
               9: MessageListener* listener;
            
              10: window->addListener( listener );
            
              11:
            

              jlib2 和 VCF 的實現就是這種模式。但現實當中大多數框架沒有使用這種模式,比如 SmartWin++ 和 win32gui ,甚至沒有使用任何模式比如 MFC 和 WTL 。我想它們所以不采用觀察者模式,有些是因為框架整體實現的牽制,有的則可能是因為沒能解決某些技術問題。我們的 GUI 框架將實現觀察者模式的消息監聽者,所以這些問題我們后面也會遇到,到時候再詳述。

            2.3 消息檢查者(message checker)

              消息檢查者完成的工作很簡單。當收到消息的時候,框架調用消息檢查者檢查這個消息是否符合某種條件,如果符合,則框架再調用消息處理者來處理這個消息,所以有點類似一個轉換者,輸入(消息),輸出一個(是/否)的值。最簡單的檢查者可能就是一個消息值的比較,比如:

               1:
            
               2: /最簡單的消息檢查者
            
               3: essage.id == /*消息值*/
            
               4:
            
               5: /比如
            
               6: essage.id == WM_CREATE
            

              展開MFC 和 ATL 的消息映射宏,可以看到它們的消息檢查就是用堆積起來的消息值比較語句完成。這就是消息檢查者最原始最自然最簡單的實現方式,但這種方式缺陷太多。我們的框架將實現一個自動化,具有擴展性的消息檢查者,后文詳細討論。

            2.4 消息處理者(message handler)

              消息處理者是我們最終的目的。GUI 框架所做的一切努力都只是前期的準備,直到消息處理者運行起來那一刻,整個公司才算是真正地運轉起來了。消息處理者的具體實現可能是自由函數,成員函數或者其它可調用體,甚至可以是外部腳本,處理完畢可能需要給操作系統返回一個結果。最簡單的消息處理者可以就是條語句,比如:

               1: //消息處理
            
               2: alert( "窗口創建成功了!" );
            
               3:
            
               4: //返回結果
            
               5: message.result = TRUE;
            

              上面代碼中"顯示消息框"的動作就是一個消息處理,以上兩行代碼可視為消息處理者。最常見的消息處理者是函數,比如:

               1: //消息處理
            
               2: _handleCreated( message );
            

              代碼中的函數 _handleCreated 就是一個典型的消息處理者。消息處理者的實現難處在于,既要支持多樣性的調用接口,又要支持統一的處理方式。我們的框架將實現一個支持自由函數,成員函數,函數對象,或者其它可調用體的消息處理者,并且這些可調用體可以具有不同參數列表。后文將進行消息處理者的詳細討論。

              在這里有必要再說明一下。一個判斷語句的大括號之前(判斷部分)是消息檢查的動作,大括號之內(執行部分)是實際的消息處理。因此一個判斷語句雖簡單,卻包含消息檢查者和消息處理者,以及另外一個神秘的部分(見后文),一共三個部分。代碼像這樣:

               1: if ( //消息檢查者 )
            
               2: {
            
               3:     //消息處理者
            
               4: }
            

              比如下面的代碼:

               1: // message.id == WM_CREATE 是消息檢查者
            
               2: // _handleCreated( message )是消息處理者
            
               3:
            
               4: if ( message.id == WM_CREATE )
            
               5: {
            
               6:     _handleCreated( message );
            
               7: }
            
               8:
            

            2.5 消息分解者(message cracker)

              消息分解者是為消息處理者服務的。不同的消息處理者需要的信息肯定不一樣,比如一個繪制消息(WM_PAINT)的消息處理者可能需要的是一個圖形設備的上下文句柄(HDC),而一個按鈕點擊消息(BN_CLICK)的消息處理者則可能需要的是按鈕的ID,它們都不想看到一個赤祼祼的消息杵在那里。從消息中分解出消息攜帶的具體信息,這就是消息分解者的工作。最簡單的消息分解者可能是一個強制轉換,比如:

               1: // WM_CREATE 消息參數分解
            
               2: CREATESTRUCT* createStruct = (CREATESTRUCT*)message.lparam;
            
               3:
            
               4: // WM_SIZE 消息參數分解
            
               5: long width  = LOWORD( message.lparam );
            
               6: long height = HIWORD( message.lparam );
            

              上面的的代碼雖然簡單但 100% 完成了消息分解的任務,所以它也是合格的消息分解者。我的框架將實現一個自動化,可擴展的消息分解者。后文將以此為目標進行詳細討論。

            2.6 消息映射者(message mapper)

              消息映射者是最直接與框架外部打交道的部分,顧名思義,它的工作就是負責將消息檢查者與消息處理者映射起來。最簡單的映射者可以是一條判斷語句,這個判斷語句,如代碼所示:

               1: // if 語句的框架就是一個消息映射者
            
               2:
            
               3: // 消息映射者
            
               4: if ( /*消息檢查者*/ )
            
               5: {
            
               6:     /*消息處理者*/
            
               7: }
            
               1: // if 語句將消息檢查者 message.id==WM_CREATE 和消息處理者 _handleCreated(message) 聯系起來了
            
               2: if ( message.id == WM_CREATE )
            
               3: {
            
               4:     _handleCreated( message );
            
               5: }
            

              上面的代碼 的if 語句中,判斷的部分是消息檢查者,執行的部分是消息處理者。if 語句把這兩個部分組成了一個映射,這是最簡單的消息映射者。到這里可以發現,這個簡單的 if 語句有多不簡單。它低調謙遜但獨自地完成了很多工作,就像公司的小張既要寫程序,又要掃地倒茶,還義務地給女同事講笑話。MFC 和 WTL 的消息映射宏展開就是這樣的 if 語句。像 jlib2 那樣的框架,雖然處理者都虛函數,但在底層也是用 if 語句判斷消息然后來進行調用的。當然還有華麗一點的消息映射者,像這樣:

               1: // 華麗一點的消息映射者
            
               2: window.onCreated( &_handledCreated );
            

              這個 onCreated 也是一個消息映射者,在它的內部把 WM_CREAE 消息和 _handleCreated 函數映射到一起,這種方式最有彈性,但實現起來也比宏和虛函數都要困難得多。SmarWin++ 就是使用的這種方式,它的消息映射者版本看起來一樣的陽光帥氣,但內部實現有些細節稍嫌猥瑣。我們的 GUI 框架將實現一個看起來更美,用起來很爽的消息映射者像這個樣子:

               1: // 將消息處理者列表清空,設置為某個處理者
            
               2: // 可以這樣
            
               3: window.onCreated  = &_handleCreated;
            
               4: // 或者這樣
            
               5: window.onCreated.add( &_handleCreated );
            
               6:
            
               7: // 在消息處理者列表中添加一個處理者
            
               8: // 可以這樣
            
               9: window.onCreated += &_handleCreated;
            
              10: // 或者這樣
            
              11: window.onCreated.add( &_handleCreated );
            
              12:
            
              13: // 清空消息處理者列表
            
              14: // 可以這樣
            
              15: window.onCreated --;
            
              16: // 或者這樣
            
              17: window.onCreated.clear();
            

              值得說一下,這種神奇的映射者是接近零成本的,它沒有數據成員沒有虛函數什么都沒有,就是一個簡單的空對象。就像傳說中的工作能力超強,但卻不拿工資,不泡公司MM,甚至午間盒飯也不要的理想職員。在后文當中會具體詳述這個消息映射者的實現。

            3 結尾廢話

              到目前為止我們的框架已經完成了 99% 。下篇準備開始寫最簡單的消息檢查者,但說實話我也不知道下一篇什么時候開始。看看上一篇日志,竟然是一年前寫的,這一年內發生的事情很多,但自己渾渾噩噩地的好像一眨眼就到了現在,看著 CPPBLOG 上的好多其它兄弟出的很多很有水準的東西,心里真是慚愧。昨天看了《2012》,現在心里還殘留有那種全世界在一間瞬間灰飛煙滅的震撼,2012年也不遠了,我也趕緊在地球毀滅之前加把油把這些日志寫完了吧。不管怎么樣,今天哥先走了,請不要迷戀哥。

            評論共2頁: 1 2 

            Feedback

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-16 18:12 by 陳梓瀚(vczh)
            @OwnWaterloo
            回避的是虛函數。舉個例子
            class Base<Derived>
            {
            public void NeedToInvokeOnDraw()
            {
            ((Derived*)this)->OnDraw();
            }
            public void OnDraw(){}//空實現
            }

            class Derived1 : public Base<Derived1>
            {
            public void OnDraw(){}//我有新OnDraw
            }

            class Derived2 : public Base<Derived2>
            {
            //我用舊OnDraw
            }

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-16 18:13 by 陳梓瀚(vczh)
            @OwnWaterloo
            如何在屬性實現狀態變化控制的技術嘛,沒有說過命名……看來我們是互相誤解了啊……

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-16 18:14 by 陳梓瀚(vczh)
            @cexer
            如果你的庫不用dll+lib+h提供的話,請盡情使用模板……

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-16 18:16 by OwnWaterloo
            @陳梓瀚(vczh)
            @cexer

            我上面也說了, 這和普通的繼承實現的功能是完全一樣的。
            依然沒有虛函數開銷,依然有一點點"多態"。

            用vczh的例子來說:
            class Base
            {
                    void OnDraw() { 空 }
            };

            class Derived1 : public Base
            {
                    void OnDraw() { 我有新OnDraw }
            };

            class Derived2 : public Base
            {
            // 我用默認Draw
            };

            不也一樣嗎? 除了Base現在是一個類,而不是類模板。
            所以,我現在唯一想出的ATL-style繼承的優勢就是:header-only。

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-16 18:20 by OwnWaterloo
            @陳梓瀚(vczh)
            【如果你的庫不用dll+lib+h提供的話,請盡情使用模板】……

            如果ATL-style繼承的好處就是為了header-only。

            class Base
            {
                    void OnDraw() { /* inline */ }
            };

            或者:
            base.h
            class Base
            {
                    void OnDraw();
            };


            base.inl
            #include "base.h"

            inline void Base::OnDraw() {}


            也可以是header-only ……


            那么,ATL-style繼承,還有什么好處么……
            一開始,我也覺得很神奇…… 后來越想越不是那么回事…… 然后一直沒想通……

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-16 18:39 by OwnWaterloo
            @cexer
            管飯的人呢……

            這問題反復困擾我很久了…… 一直沒得到解答…… 于是塵封了……
            用ATL/WTL的人很少,問都找不到人問……
            今天終于找到管飯的了, 一定得追問到底~_~

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-16 18:51 by OwnWaterloo
            @陳梓瀚(vczh)
            如何在屬性實現狀態變化控制的技術嘛,沒有說過命名……看來我們是互相誤解了啊…… 

            那你也說說你的想法嘛……
            比如那個“效率問題”應該怎么理解?

            有人說可以拿C/C++程序員作巴浦洛夫實驗,看提到"效率"2字時他們是否會流口水……

            我沒有流口水…… 但是我很好奇……

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-16 19:48 by 陳梓瀚(vczh)
            @OwnWaterloo
            撇開論點不講,其實我對語法最感興趣……因為我做編譯器……

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-16 19:49 by 陳梓瀚(vczh)
            @OwnWaterloo
            header-only已經不錯了,我現在最煩一個類要將界面重復在兩個文件里面。

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-16 19:51 by 陳梓瀚(vczh)
            @OwnWaterloo
            話說我想嘗試自己實現BOOST_ITERATE(只用宏復制代碼)的方法,最后研究了好久,發現用C的宏是不對的,應該自己制造代碼生成器……C的宏缺乏語法上的可維護性

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-16 19:55 by LOGOS
            我是來看神仙打架的
            cexer別刪 哈哈

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-16 20:09 by OwnWaterloo
            @陳梓瀚(vczh)
            【其實我對語法最感興趣……因為我做編譯器……】
            能否做一個,呃,怎么說呢……

            visual assistant你肯定用的吧?類似的還有:
            msvc,gcc :
            synedit : http://synedit.sourceforge.net/
            scintilla : http://www.scintilla.org/
            還有一些C/C++源代碼轉換到html、pdf等格式的工具。

            它們肯定都能分析C/C++源代碼。
            但是源代碼分析后的結果,它們自產自銷了,沒有暴露出來。

            你能否做做這方面的工作呢? 一個C/C++(或者其他語言)的源代碼分析器,將分析的結果暴露出來。
            方便其他用途使用,比如代碼染色。

            這樣,很多編輯器、網頁、甚至是pdf、doc都可以得益于你的工作。
            而不是每個工具都自己實現一套(有的功能薄弱得……比如網頁上的染色,基本就只能認識語言的關鍵字而已),然后自產自銷。


            你覺得這個提議怎樣?

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-16 20:13 by OwnWaterloo
            @陳梓瀚(vczh)
            【header-only已經不錯了,我現在最煩一個類要將界面重復在兩個文件里面。】

            頭文件不行嗎?為什么會這樣?


            話說我想嘗試自己實現BOOST_ITERATE(只用宏復制代碼)的方法,最后研究了好久,發現用C的宏是不對的,應該自己制造代碼生成器……C的宏缺乏語法上的可維護性

            我也覺得宏很不可靠……
            有時候完全可以利用其他一些很方便的語言,來產生C/C++源代碼。
            憑借C/C++自身機制來進行元編程,有時候會很…… 很麻煩……

            比如boost.pp,它用來產生模板類型參數列表的這個功能,其實也可以寫一些其他語言的代碼,用來生成C++源代碼……

            可讀性應該會好很多……

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-16 20:16 by cexer
            @OwnWaterloo
            模板有一大優勢就是,不必在意類型是否已經存在,就能夠任意調用它的任意成員。這是用虛函數也達不到的,因為虛函數也至少需要提供接口聲明。
            ATL這種方式,可以在不知道子類為何物的情況下調用它重寫或者覆蓋的成員。有時候可以完成用虛函數也無法達到的效果。
            你試試不用虛函數,不用ATL的這種編譯時的多態手法,在不知道子類為何物的情況下,在基類當中調用子類的方法試試?

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-16 20:29 by OwnWaterloo
            @cexer
            你說的是這種情況么?

            non-template

            class B
            {
                    virtual void f_do() = 0;
                    void f()
                    {
                            ... // 有一些其他工作,不僅僅是轉發
                            f_do();
                            ...
                    }
            };

            B可以自己不實現f_do,并且具有這么一個限制:如果派生類不實現f_do,派生類將繼續無法實例化。


            ATL-style繼承,提供"類似"的含義。
            template<class D>
            class B
            {
                    void f()
                    {
                            ... // 有一些其他工作,不僅僅是轉發
                            static_cast<D*>(this)->f_do();
                            ...
                    }
            };

            class D : public B<D>
            {
                    void f_do() {  }
            }

            如果不實現f_do,D就會產生編譯錯誤。
            類似于如果上面那種D如果不定義f_do就無法實例化。



            普通繼承無法提供這種限制語意:
            class B
            {
                    void f()
                    {
                            ... // 有一些其他工作,不僅僅是轉發
                            f_do();
                            ...
                    }
                    // void f_do();
                    // 如果提供,派生類不不覆蓋也能實例化,也不會產生編譯錯誤。
                    // 無法達到限制"派生類必須提供"的目的

                    // 如果不提供,B就會產生鏈接錯誤。
            };

            是這樣嗎?

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-16 20:37 by OwnWaterloo
            @cexer
            【你試試不用虛函數,不用ATL的這種編譯時的多態手法,在不知道子類為何物的情況下,在基類當中調用子類的方法試試? 】

            應該是不行的。

            只有template這種變態代碼生成器才可以:
            template<typename T>
            void f(T v)
            {
            v.f(); // 讓你先這么著了,等實例化的時候再做檢查……
            }

            如果不是模板,父類是不能調用子類函數的。

            那么,ATL-style繼承的作用就是:實現靜態的"template method"?

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-16 21:08 by OwnWaterloo
            @cexer
            有點感覺了。

            ATL-style繼承,一方面有點類類似concepts或者說ducking type的使用。
            template<class D>
            class B
            {
                    void f()
                    {
                            D* d = static_cast<B*>(this);
                            d->must_have_this_function(); // 必須有某個成員函數
                            d->must_have_this_variable;   // 必須有某個成員變量
                            typedef typename D::must_have_this_type type;
                            // 必須有某個嵌套類型
                    }
            };

            B可以對D提出一些要求,D必須滿足這些要求。
            對這些要求,B可以不必提供"默認實現"。
            這2點就是和普通繼承很不相同的地方了。


            同時,ATL并沒有通過"組合"來復用庫代碼,而是通過"共有實現繼承"來復用庫的代碼。
            使得編寫的代碼會少一些。 就像那個著名的例子:

            class person : public public nose, public mouth {};

            person p;
            p.eat(); mouth::eat
            p.smell(); nose::smell


            實際上呢,更"學術氣息"一些的方案是:
            class person
            {
                    nose n_;
                    mouth m_;
                    eye e_[2]; // left and right
            public:
                    // forwarding functions
                    void eat() { m_.eat(); }
                    void smell() { n_.smell(); }
            };


            但是在界面開發中……  這些轉發函數會寫死人的……
            所以,就產生了ATL-style繼承 —— concepts(ducking type) + 實現繼承的結合。


            只是為ATL-style繼承舉例的人的例子沒有選好,無法充分說明它的優點。
            我只粗看過《mfc程序員的wtl編程指南》還是什么的。
            里面就是一個"沒有體現出base對derived的需求",可以用普通繼承改寫的例子。


            這樣理解如何?


            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-16 21:13 by 陳梓瀚(vczh)
            @OwnWaterloo
            子類不需要非得實現什么,純粹是模擬非純虛函數。因為父類必須提供所有可以覆蓋的界面的空實現,才可以讓實現控件的人,只寫他們關心的東西。

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-16 21:15 by 陳梓瀚(vczh)
            @OwnWaterloo
            我重寫VL++的目的就是想提供一個用來分析字符串(速度不用爆快,但肯定要好用)的庫。雖然不一定會實現一個C++的語法分析器(實際上我可以告訴你我沒法產生精確的語法樹,如果不做語義分析我肯定不知道a<b,c>d;究竟是什么東西),但上下文無關著色什么的肯定是可以輕松實現的。

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-16 21:27 by OwnWaterloo
            @陳梓瀚(vczh)
            【子類不需要非得實現什么,純粹是模擬非純虛函數。因為父類必須提供所有可以覆蓋的界面的空實現,才可以讓實現控件的人,只寫他們關心的東西。】

            上面已經說明了:
            "父類提供所有可以覆蓋的界面的空實現",或者說默認實現,并讓實現控件的人,只寫他們關心的東西 —— 這不需要ATL-style式的繼承,普通繼承就可以了。

            只有在"向子類要求一些莫須有的東西"時,才需要依靠template。



            【我重寫VL++的目的就是想提供一個用來分析字符串(速度不用爆快,但肯定要好用)的庫。雖然不一定會實現一個C++的語法分析器(實際上我可以告訴你我沒法產生精確的語法樹,如果不做語義分析我肯定不知道a<b,c>d;究竟是什么東西),但上下文無關著色什么的肯定是可以輕松實現的。】
            編譯原理那些東西幾乎忘光……
            我覺得為編輯器著色有一個相對于編譯器來說很麻煩的地方……

            編譯器可以要求源代碼必須是合乎語法的,否則就報錯。
            編輯器的著色分析就不能這么暴力了,它必須能夠接受半完整的源代碼,并且盡可能從中榨取出信息……

            所以visual assistant還是很強大的……

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-16 21:44 by 空明流轉
            @OwnWaterloo
            GCC-XML,你可以搜搜這個工具,可以把C++分析成XML結構的東西。如果你需要在語言粒度上進行調整,可以在此基礎上做。

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-16 21:47 by OwnWaterloo
            @空明流轉
            我靠!還有這種工具!
            太感謝了~_~

            其實我也不做什么的啦……
            主要是寫文檔(設計文檔、博客)的時候染染色啦……

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-16 22:01 by OwnWaterloo
            @空明流轉
            好東西啊,好東西,看得我心花怒放~_~
            再次感謝~_~


            順便也感謝一下cexer。
            果然是【閱盡MFC,WTL,SmartWin++,win32gui,jlib2 ,VCF ;看盡天下 GUI 的感覺。】

            果然只看示例和書不寫代碼是不行的。就書上的示例代碼,我還領會不了其精髓……
            其實也不是我不想寫…… 我寫過一些……
            然后就沒機會寫了……

            # re: GUI框架:談談框架,寫寫代碼[未登錄]  回復  更多評論   

            2009-11-17 17:03 by Loaden
            強烈猛頂!
              寫到這,我作為輪子制造愛好者,在這里向那些喊著"不要重復制造輪子的"批評家們承認錯誤。在有那么多好的輪子的情況下,我不值得浪費地球資源,浪費時間精力來自己手工重復打造。但是不值得歸不值得,在值得和喜歡之間我還是選擇后者。并且人生在世,什么才是值得?我覺得不是拯救人類,為世界和平做貢獻,也不是努力奮斗,為地球人民謀福利,而是簡單地做自己喜歡的事。

            看來我和樓主是一樣類型。
            我也是從封裝類似ATL框架開發,目前正在模仿jlib2...
            還有很長的路要走!
            向樓主學習!

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-17 18:04 by visualfc
            c++源碼分析,可以利用ctags,源代碼可以看一下ctags,CodeBlocks的codecompletion,Qt Creator的cplusplus。

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-17 20:36 by 陳梓瀚(vczh)
            @OwnWaterloo
            只要你不是在為一個IDE提供著色分析,那都很好做……

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-17 20:54 by OwnWaterloo
            @visualfc
            Qt Creator我知道,Qt Creator的cplusplus是指的什么?
            Qt Creator的自動完成功能嗎?

            我看過你的一些作品,果然對這方面的東西了解很多~_~


            @陳梓瀚(vczh)
            其實我的需求上面也有說…… 就是將代碼染染色……
            不然直接在blog上貼代碼太丑陋了……

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-17 21:23 by visualfc
            CPlusPlus是Qt Creator中用于分析C++源文件的核心庫
            代碼位于 Qt Creator源碼的 src/libs/cplusplus/目錄下
            其中 C++ 語法分析/錯誤檢查部分使用標準C++庫完成,不使用QT庫。
            我也是剛開始看。

            對于CodeBlocks的C++源碼分析可以參看下面的wiki。
            http://wiki.codeblocks.org/index.php?title=Code_Completion_Design



            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-17 21:28 by OwnWaterloo
            @visualfc
            謝謝分享~_~

            英文…… sigh……

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-17 21:42 by visualfc
            @OwnWaterloo
            :-)
            QT的cplusplus寫的要比CB的好。

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-18 04:18 by OwnWaterloo
            差不多敲定方案了,貼下代碼吧:
             
            class date
            {
                    
            class impl
                    {
                            time_t time_;
                            friend 
            class date;
                    };

                    
            int get_(int tm::*field) const { return localtime(&impl_.time_)->*field; }
                    date
            & set_(int tm::*field,int v)
                    {
                            tm t 
            = *localtime(&impl_.time_);
                            t.
            *field = v;
                            impl_.time_ 
            = mktime(&t);
                            
            return *this;
                    }

            public:
                    date() { time(
            &impl_.time_); }

                    
            int get_hour() const { return get_(&tm::tm_hour); }
                    date
            & set_hour(int h) { return set_(&tm::tm_hour,h); }

                    
            int get_minute() const { return get_(&tm::tm_min); }
                    date
            & set_minute(int m) { return set_(&tm::tm_min,m); }

                    
            int get_second() const { return get_(&tm::tm_sec); }
                    date
            & set_second(int s) { return set_(&tm::tm_sec,s); }

                    union
                    {
                            impl impl_;

                            property::proxy
                                    
            <property::get<date,int,&date::get_hour>
                                    
            >               hour_read;

                            property::proxy
                                    
            <property::set<date,date&,int,&date::set_hour>
                                    
            >               hour_write;

                            property::proxy
                                    
            <property::get<date,int,&date::get_minute>
                                    ,property::
            set<date,date&,int,&date::set_minute>
                                    
            >               minute; // read write

                            property::proxy
                                    
            <property::set<date,date&,int,&date::set_second>
                                    ,property::
            get<date,int,&date::get_second>
                                    
            >               second; // read write
                    };
            };

            int main()
            {
                    typedef 
            int ASSERT_EQUAL_SIZE[sizeof(date)==sizeof(time_t)?1:-1];

                    date d;
                    
            volatile int v = 1212;
                    
                    v 
            = d.hour_read;
                    d.hour_write 
            = v;
                    
            // v = d.hour_write;
                    
            // d.hour_read = v;

                    v 
            = d.minute;
                    d.minute 
            = v;

                    v 
            = d.second;
                    d.second 
            = v;

                    
            return 0;
            }

             
            這次首先重點show出來:
            1. 0代價實現proxy
            typedef int ASSERT_EQUAL_SIZE[sizeof(date)==sizeof(time_t)?1:-1];
             
            2. 定義property的語法也還不算太壞吧?
            使用一種"named template parameter"技巧,將read、writer、read-write合體。
            hour_read, hour_write不用解釋了。
            second,minute需要解釋一下:set和get出現在proxy的參數中出現的次序是隨意的。
            所謂named parameter是也。
             
            不要拿hour_read用于寫或者拿hour_write用于讀,報出的錯誤可能很難讀。
            有空閑再處理報錯好看問題吧……
             
             
             
            proxy的其他技巧有請vczh同學介紹。
             
             
             
             
            關于union中不能放非pod的問題……
            1. 基于C-API來構造自己的類
            那自然沒什么問題。
            如果還想進行訪問控制,就像date::impl一樣,包到一個private nested class中,并設置外部class為friend。
             
            依然0代價。
             
             
            2. 基于現有的非pod來構造自己的類
            樓上已經提到了union base和union member兩種方案。
            不過property::proxy不支持這種用法,得手工實現……
            遇到的問題也是樓上提到的"先雞還是先蛋"……
            為了解決雞蛋問題,想了很久都沒想出可以很清晰定義property的方案……
             
            換種思路:使用boost.aligned_storage + placement new + 直接調用析構函數吧……
            也算一種解決辦法了…… 而且也是0代價的。
             
             
             
            玩具代碼,僅為展示上面的2個重點技術而已。實際應用還需自行編寫。
            property我以前也沒用過…… 沒什么經驗,不知道通常會怎么使用。
             
            測試編譯器:msvc8、9、10,gcc3.4.x。
            寫命名模板參數就沒打算支持vc6……  不測了……
            gcc4.x懶得切換系統了…… 應該沒什么問題……
             

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-18 04:43 by OwnWaterloo
            為了避免vczh同學誤會……
            我再申明一下……

            1. 這是徹底的玩具代碼
            所以對日期的合法性不作檢查……

            2. 代碼還需在頂部加入兩行:
            #include <time.h>
            #include "property.hpp"
            才算完整

            3. "property.hpp"
            的搜索路徑需要自己配置,或者就和date.cpp放一起。

            4. 需要在<time.h>前加入:
            #define _CRT_SECURE_NO_DEPRECATE
            才能避免msvc上常見的4996警告。

            5. 或者另一種方式,代碼行數較多:
            #ifdef _MSC_VER
            #pragma warning(disable: 4996)
            #endif

            6. main的return 0可以省略
            因為一定是C++代碼,也不用兼容vc6



            能想到的我都說了……
            你就繞了我吧……

            # re: GUI框架:談談框架,寫寫代碼[未登錄]  回復  更多評論   

            2009-11-18 09:46 by Loaden
            @ OwnWaterloo
            @ vczh
            (注:排名不分先后,按字母順序)

            你們兩個都是超級牛人!!我看你們的爭論時,更多的是自卑感!
            因為你們討論的很多東西我看不懂!!
            唉!!
            我把郁悶發在這里了,有空就過去給我簽個名吧。
            http://topic.csdn.net/u/20091117/19/54a04541-094f-4d7c-960e-c0ce34783821.html

            兩位不要傷了和氣!技術問題,有爭論總是好的!!
            我就在旁邊學習好了...

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-18 09:51 by 矩陣操作
            @OwnWaterloo
            好長的回帖,年度最強了吧?
            看了一下,似乎
            union
            {
            class
            {
            time_t time_;
            };
            ....
            };
            就可以了吧?impl感覺多余了。當然我沒有試,在上班,嘿。
            好像前面OwnWaterloo提到過隱藏union中成員問題,
            像上面那樣寫應該可以吧?


            如果非要依賴標準cpp來實現某些東西,例如屬性、委托,動則搞個幾千行,那我就寧愿承認自己無能,用VC的擴展了,哈哈

            # re: GUI框架:談談框架,寫寫代碼[未登錄]  回復  更多評論   

            2009-11-18 09:54 by Loaden
            @ OwnWaterloo
            能否QQ、gmail、Live聯系?有問題請教。
            我的QQ:1090833
            我的GMail、Live:loaden AT gmail or live dot com
            之前在gmail是向vczh請教時,受益匪淺。
            想結識牛人:不會啰嗦,只想在關鍵的疑惑上得到點撥。

            @ cexer
            非常期待你的GUI設計。
            我現在傾向于使用thunk封裝,但由于還碰到很多問題,沒辦法拿出來討論。
            非常期待能與你在QQ or GMail or Live上探討GUI框架設計。
            我目前實現的消息映射是這個樣子:
            [code]class MainFrm : public Frame
            {
            public:
            MainFrm() : Frame(_T("Test Demo!"), _T("MainFrame")), m_testBtn(_T("Test!"))
            {
            Add(&m_testBtn);
            m_testBtn.EvtLButtonDown.Bind(this, &MainFrm::OnLBtnClicked);
            this->EvtCreate.Bind(this, &MainFrm::OnCreate);
            }

            ~MainFrm()
            {
            this->EvtCreate.UnBind(this, &MainFrm::OnCreate);
            }

            qpEvt OnCreate(evt::Create& evt)
            {
            evt.handled = true;
            qpDbgInt(evt.handled);
            }

            qpEvt OnLBtnClicked(evt::LButtonDown& evt)
            {
            qpDbgInt(evt.owner);
            }

            protected:
            Button m_testBtn;
            };[/code]

            # re: GUI框架:談談框架,寫寫代碼[未登錄]  回復  更多評論   

            2009-11-18 09:56 by Loaden
            另,任何一個類如果要處理消息,需要這樣繼承:
            class Test : public EvtReceiver<Test>
            {
            public:
            Test();
            ~Test();

            void UnBind();

            private:
            qpEvt OnCreate(evt::Create& evt);
            };

            然后這樣注冊:
            Test::Test()
            {
            FrameEvt *sender = App::Module().GetEvtSender<FrameEvt>(_T("MainFrame"));
            if (sender != NULL)
            {
            sender->EvtCreate.Bind(this, &Test::OnCreate);
            }
            }

            Test::~Test()
            {
            }

            void Test::UnBind()
            {
            FrameEvt *sender = App::Module().GetEvtSender<FrameEvt>(_T("MainFrame"));
            if (sender != NULL)
            {
            sender->EvtCreate.UnBind(this, &Test::OnCreate);
            }
            }

            qpEvt Test::OnCreate(evt::Create& evt)
            {
            qpDbgInt(evt.handled);
            }

            但其中:
            FrameEvt *sender = App::Module().GetEvtSender<FrameEvt>(_T("MainFrame"));
            是極度齷齪的!可是我找不到更多的辦法了。

            # re: GUI框架:談談框架,寫寫代碼[未登錄]  回復  更多評論   

            2009-11-18 09:57 by Loaden
            其中qpEvt的定義:void __stdcall
            因為使用thunk,所以回調函數必須是__stdcall的調用約定。

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-18 12:19 by OwnWaterloo
            @矩陣操作
            union里面好像不能出現private的東西。具體得查標準……

            我那個,也是權宜之計……
            客戶代碼訪問不了這個類型:date::impl
            但可以被推導……
            template<typename T>
            void set_default(T& v) { v = T(); }

            date d;
            set_default(d.impl_); // 掛了


            不過,private也不是萬能的……
            指針算術,宏,都有可能讓它失效。
            取名為impl_, 應該能起到警示作用 —— 不要使用這個名字 —— 吧……

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-18 12:38 by OwnWaterloo
            @Loaden
            對框架設計,我不熟…… 因為一般來說,我討厭那個……

            為什么?
            框架和類庫有什么區別?
            類庫是幫助你的,框架是限制你的……

            作為一個框架的設計者,他必須對這個領域十分了解:哪些過程是常用的,哪些是有害的。
            才能準確的做出限制。
            我對gui編程經驗不多,所以就不做這方面的活了……



            說說win32下gui庫/框架必須解決的一個問題吧: user_data,或者叫context。
            因為那個萬惡的WndProc沒有傳給你……

            thunk是一種辦法,一種i386(其他平臺我不知道怎么實現)上通用的辦法 —— 只要是因為回調設計不正確,沒有帶context參數,就可以用thunk解決。
            實際上…… 我畢業設計就是做的這個……
            __stdcall也不是必須的…… __cdecl也可以……
            比如qsort, 它就是需要一個 __cdecl的。

            這里有一部分代碼:
            http://code.google.com/p/callback-currying/

            主要是做自由函數的thunk,而不是成員函數。
            一方面是可以給C程序員用。
            第2個原因是因為this和成員函數指針的可移植性很糟糕。
            第3個原因是因為可以通過自由函數,轉發到成員函數中,對成員函數的支持不是絕對必要的。

            直接thunk成員函數有一個好處。我論文里面有提到。
            返回一個大結構體的函數,直接thunk成員函數可以,thunk自由函數不行……

            stdcall2stdcall 需要12字節
            cdecl2cdecl 需要23字節
            thiscall2stdcal(msvc) 需要10字節
            其他記不清楚了


            設計好thunk結構體,只是關鍵技術之一。
            之后還有很多很多的麻煩…… 主要是DEP(數據執行保護)

            在win32下可以用VirtualAlloc來分配可執行內存,posix下使用mmap。
            但是…… 不可能為了這么小一塊內存,就去分配1整頁吧? win32下還要保留64k……
            內存池是必須做的…… 而且現有內存池都無法被復用

            所以,那個gcode后來就沒有維護了。
            svn管理實驗代碼很麻煩…… 轉git了……

            最后的版本還沒有敲定,等過年后我會發一個版本出來……

            named template parameter就是為了解決callback currying中模板參數混亂(不僅是多,真的是混亂,論文里面好像有詳細解釋)而設計的。



            跑題了…… 上面的中心思想是: thunk是一種通用技術,但麻煩多多。
            如果僅僅是為了解決WndProc, 有專有技術。
            Set/GetWindowLongPtr
            還有前幾天學到的…… Set/GetProp
            見這里:http://www.shnenglu.com/kyelin/archive/2009/11/13/100880.aspx

            需要注意的是,Set/GetWindowLongPtr網上很多文章都說錯了。
            具體的使用方式去查msdn。
            cbExtra是一個0 based array —— 我記得msdn是有這么一句話的。

            GWL_USERDATA是另外一回事。


            用這2種方案之一就可以了(或者win32還提供了其他附加數據的方案?)。
            mfc的線程局部映射表…… 哎……

            # re: GUI框架:談談框架,寫寫代碼[未登錄]  回復  更多評論   

            2009-11-18 13:36 by Loaden
            @ OwnWaterloo
            因為要兼容x64平臺,所以統一使用__stdcall。
            原因是x64下只有__stdcall。

            DEP不是問題,很容易解決:
            void* operator new(size_t size)
            {
            return ::VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
            }

            void operator delete(void* p)
            {
            ::VirtualFree(p, 0, MEM_RELEASE);
            }

            而:
            Set/GetWindowLongPtr
            還有前幾天學到的…… Set/GetProp
            由于使用static函數,效率是一個大問題,而且有潛在的危險性。

            謝謝你分享:callback-currying

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-18 13:42 by OwnWaterloo
            補充一下: 關于DEP ,做thunk可能會用到。

            win32可以用win32 堆(HeapAlloc那套),創建時可以指定內存權限。
            這幾乎是最省心的做法了。

            不過我想做posix移植(論文啊…… thunk已經爛大街,不拿點新東西出來怎么過得去……)
            所以就要自己實現 ……
            做著做著嘛…… 發現內存池可以再分出幾部分……

            比如其中的一部分是intrusive data structure。
            小對象內存池其實只是intrusive date structure的使用方式之一。

            然后打算重構,然后…… 就畢業了…… 然后就無限延期了……

            過年之后再做吧……

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-18 13:44 by OwnWaterloo
            @Loaden
            兄弟,你知道這會分配多少嗎?

            DEP不是問題,很容易解決:
            void* operator new(size_t size)
            {
            return ::VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
            }


            這里用new placement也不太妥當。

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-18 13:54 by OwnWaterloo
            @Loaden

            Set/GetWindowLongPtr
            還有前幾天學到的…… Set/GetProp
            由于使用static函數,效率是一個大問題,而且有潛在的危險性。


            static 函數怎么影響效率了? 多傳遞了一次參數?
            有缺陷的回調肯定是C的,參數中肯定都是bitwise-copy。
            所以,還好吧……

            其實你需要的就是一個context而已。
            thunk, Set/Get WindowLongPtr/Prop 都是設置/得到context的手段。

            thunk的效率不見得比Set/Get WindowLongPtr高,而且用著很麻煩。

            WndProc(HWND hwnd,UINT msg,WPARAM w,LPARAM l)
            {

            CWindow* w = (CWindow*)GetWindowLongPtr( ... );
            // 編譯器知道這個調用處的上下文,可以進行優化
            return w->handle(hwnd, msg, w, l );

            }


            thunk的話,要么每個窗口對應一個窗口類,該窗口類對應一個thunk。
            要么若干窗口對應同一個窗口類,每個窗口再
            SetWindowLongPtr(hwnd, GWL_PROC , ) 去改窗口處理過程。

            調用一個chunk是絕對得不到優化的,因為編譯器根本就不可能知道thunk的目的地在哪。

            # re: GUI框架:談談框架,寫寫代碼[未登錄]  回復  更多評論   

            2009-11-18 14:10 by Loaden
            @ OwnWaterloo
            CWindow* w = (CWindow*)GetWindowLongPtr( ... );
            這個調用不僅僅是返回一個值而已。
            而且不同的窗口類,要返回不同的值。
            所以,這里編譯器如何優化呢?
            另外,static函數、全局函數也不利于C++封裝。
            而thunk,從其匯編代碼可以看到,代價只是多花1~3條CPU指令而已。
            顯然要高效。

            DEP的問題,我想過內存池來實現。
            不過由于技術不過硬,暫時還沒想過去實現它。

            先把GUI框架穩定下來再說。
            謝謝你的指點!如果有好的解決thunk的內存池(ATL就是這么干的,只是不開源),請介紹一個給我。

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-18 14:31 by OwnWaterloo
            @Loaden
            忘寫了…… 聯系方式,同名gmail郵箱……


            CWindow* w = (CWindow*)GetWindowLongPtr( ... );
            這個調用不僅僅是返回一個值而已。
            而且不同的窗口類,要返回不同的值。

            你的意思應該是,不同窗口“實例”,返回不同的值,是這樣嗎?
            那就對了,GetWindowLongPtr( hwnd, ... );本來就是不同窗口實例返回不同的值嘛……



            所以,這里編譯器如何優化呢?

            我說的優化是它下面的一個調用。
            w->handle( ... ); // 可能會被inline,避免參數傳遞。

            因為編譯器知道這里肯定調用的是handle,不會是其他函數。

            但thunk,調用的地址是運行時的,所以編譯器無法優化。



            DEP的問題,我想過內存池來實現。
            不過由于技術不過硬,暫時還沒想過去實現它。

            先把GUI框架穩定下來再說。
            謝謝你的指點!如果有好的解決thunk的內存池(ATL就是這么干的,只是不開源),請介紹一個給我。

            其實 …… atl的源代碼是能看到的…… 至少thunk部分是這樣。
            我上面有一個回帖, 估計你看掉了。
            如果只想支持win32, 可以用HeapAlloc那套。
            一個版本中的atl就是這么干的,還有個版本是自己實現的池 —— 具體記不太清楚了。


            如果想在posix平臺下,得到分配可執行內存的高效的池 ……
            等過年吧,哈哈哈



            而thunk,從其匯編代碼可以看到,代價只是多花1~3條CPU指令而已。
            顯然要高效。

            你看到thunk的分配了嗎? 考慮到thunk的復雜性了嗎?
            要全局的看~_~
            cbExtra可以被實現得很高效。當然,我只是猜……



            另外,static函數、全局函數也不利于C++封裝。

            free-function才是范化形式……
            member-function只是free-function的一個特例。
            是否封裝,是看你是否僅通過被數據認可的操作去使用數據,而不是自己去撥弄數據。
            跟語法無關。 語法只是個便利。

            這個話題…… 估計要扯很遠了……
            你不接受很正常,我可以理解……
            我只是提一下……

            # re: GUI框架:談談框架,寫寫代碼[未登錄]  回復  更多評論   

            2009-11-18 14:49 by Loaden
            因為編譯器知道這里肯定調用的是handle,不會是其他函數。
            但thunk,調用的地址是運行時的,所以編譯器無法優化。
            -----
            thunk不用調用,直接處理消息后,調用默認窗口過程。
            比如:
            LRESULT CALLBACK Frame::WndProc(UINT msg, WPARAM wpa, LPARAM lpa)
            {
            LRESULT res = 0;
            bool ret = ProcessMessage(msg, wpa, lpa, res);
            if (ret) return res;

            if (msg == WM_SIZE && m_layout != NULL)
            {
            m_layout->DoLayout(0.0f, 0.0f, static_cast<float>(X_LPARAM(lpa)),
            static_cast<float>(Y_LPARAM(lpa)), true);
            }

            switch (msg)
            {
            EVTCREATE(m_wnd, wpa, lpa);
            }

            return ::CallWindowProc(m_defWndProc, m_wnd, msg, wpa, lpa);
            }

            static函數...
            -----
            可能是個人喜好吧。
            我喜歡盡最大可能的 不 在C++中使用全局函數和靜態函數。
            我喜歡將什么東西都放在類里。
            哪怕是無法實例化的靜態類。

            我對你在前面給的“低代價”例子非常感興趣,下午準備好好學習你的例子。
            謝謝!!

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-18 15:05 by OwnWaterloo
            @Loaden

            thunk不用調用,直接處理消息后,調用默認窗口過程。

            肯定要調用的……
            不然怎么處理消息?

            只是我沒看出你的代碼中哪句是調用……


            我喜歡將什么東西都放在類里。

            最大的毛病:這會將優秀的算法埋葬到類中。


            強調一下,是"零"代價,不是低代價~_~
            union base, union member是低代價的。

            # re: GUI框架:談談框架,寫寫代碼[未登錄]  回復  更多評論   

            2009-11-18 15:37 by Loaden
            @ OwnWaterloo

            肯定要調用的……
            不然怎么處理消息?
            -----

            消息處理在這個宏里。
            寫宏的原因是:不同消息處理類,可以通用。

            // WM_CREATE
            #define EVTCREATE(wnd, wpa, lpa) \
            case WM_CREATE: \
            { \
            evt::Create evt = {wnd, this, reinterpret_cast<LPCREATESTRUCT>(lpa), false}; \
            EvtCreate(evt); \
            } \
            return 0;
            struct Create
            {
            HWND wnd;
            Object* owner;
            LPCREATESTRUCT createStruct;
            bool handled;
            };

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-18 15:45 by OwnWaterloo
            果然沒有錯誤處理是邪惡的……

            經過Loaden的強力測試(其實也就隨手一測),崩了……

            先上代碼:
            time_t t1 = time(0);
            tm t2 = *localtime(&t1);
            t2.tm_hour = 348294832;
            t1 = mktime(&t2);
            printf("%d\n",(int)t1);
            tm* pt3 = localtime(&t1);
            printf("%p\n",(void*)pt3);

            t2.tm_hour = 348294832; // 這里對于32位的int來說,沒有溢出。
            但hour的"權值"比較重,最后轉化為time_t的表達可能會溢出。


            t1 = mktime(&t2);
            printf("%d\n",(int)t1);
            C89要求:mktime如果不能將tm轉換到正確的time_t格式,就返回(time_t)-1。

            而vc8的運行庫不是這么處理的,它會直接崩掉,而不是返回-1。
            觸發的斷言是:
            loctime64.c (78)
            (*ptime <= _MAX_TIME64_T)

            所以,在vc8下,mktime那行代碼就會崩。
            在vc9、mingw3.4.x下,返回-1。


            tm* pt3 = localtime(&t1);
            printf("%p\n",(void*)pt3);
            如果time_t不是正確格式,C89好像沒說應該怎么做……
            vc8、vc9、mingw3.4.x一致返回空指針。


            再看date的代碼:

            int get_(int tm::*field) const
            {

            return localtime(&impl_.time_)->*field;
            //當time_t不正確時,返回空指針,然后被解引用,崩。

            }

            date& set_(int tm::*field,int v)
            {

            tm t = *localtime(&impl_.time_);
            t.*field = v;
            impl_.time_ = mktime(&t);
            // 當t格式不正確時,vc8不會返回-1,而是直接崩。
            return *this;

            }


            錯誤處理還是需要的……
            不過這僅僅是示例代碼,還是不加了吧……
            以免影響清晰…… 突出重點……

            感謝Loaden的測試~_~

            # re: GUI框架:談談框架,寫寫代碼[未登錄]  回復  更多評論   

            2009-11-18 15:56 by Loaden
            @ OwnWaterloo
            糾正一下,是隨手 三 測 ^)^
            第三次改輸出代碼時崩潰的。

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-18 15:59 by OwnWaterloo
            @Loaden

            #define EVTCREATE(wnd, wpa, lpa) \
            case WM_CREATE: \
            { \
            evt::Create evt = {wnd, this, reinterpret_cast<LPCREATESTRUCT>(lpa), false}; \
            ...

            this是怎么得到的?


            LRESULT CALLBACK Frame::WndProc(UINT msg, WPARAM wpa, LPARAM lpa)
            {
            ...

            這是靜態還是非靜態的? 函數定義看不出來,只有聲明才行。
            估計也是非靜態的吧? 因為有m_wnd這些東西。
            那么,this是怎么得到的??



            追根究底,要從【注冊窗口類】時給它的【WndProc】查起。
            如果你傳遞給它的不是一個真正的函數,而是一個thunk,這個thunk就會被調用。

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-18 16:00 by OwnWaterloo
            @Loaden

            糾正一下,是隨手 三 測 ^)^
            第三次改輸出代碼時崩潰的。


            隨手1測就錯了,非常不靠譜……
            隨手3測就錯了,稍微靠譜一點點……
            反正代碼很不靠譜的,免責聲明我也寫了的……
            出了任何問題我不負責哦~_~

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-18 16:06 by 陳梓瀚(vczh)
            不容易啊,變成水爐跟羅登在聊了……咱才走開了一個上午……工作啊……

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-18 16:07 by 陳梓瀚(vczh)
            @OwnWaterloo
            我就繼續做我的combinator去了,這是一個專門對付上下文無關語法分析的庫,可以在C++里面寫文法,綁定functor和error-recover來做錯誤恢復或者輸出更加貼心的錯誤信息等等。完了會搞一個小demo,也是有用的,造一套比宏順眼的代碼生成器,就用combinator做……

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-18 16:08 by OwnWaterloo
            @陳梓瀚(vczh)
            我就一閑人……

            我和羅登在gtalk上聊得更歡……
            要不要來插一腳?

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-19 00:51 by 空明流轉
            @陳梓瀚(vczh)
            唉,你們這版聊的。。。我現在還是用Python做Code Generator,這樣省心。。。不過就是測試起來很麻煩。。。。

            # re: GUI框架:談談框架,寫寫代碼[未登錄]  回復  更多評論   

            2009-11-19 09:07 by Loaden
            @ vczh
            我的網名叫老鄧(非羅登^_^),曾經在Live上向你請教過GUI框架設計。

            @ OwnWaterloo


            evt::Create evt = {wnd, this, reinterpret_cast<LPCREATESTRUCT>(lpa), false}; \
            ...
            this是怎么得到的?


            這是一個宏替換。宏所在的類的this就是這里的this。


            LRESULT CALLBACK Frame::WndProc(UINT msg, WPARAM wpa, LPARAM lpa)
            {
            ...
            估計也是非靜態的吧? 因為有m_wnd這些東西。
            那么,this是怎么得到的??

            非靜態。但只有三個參數,第一個參數通過匯編,寫到了m_wnd成員變量上了。沒辦法:兼容x64平臺,只能這樣thunk。

            另,昨天你教了我一下午,晚上我受到了啟發,想到了一個利用bitset的方法來降低成本。
            由于是動態注冊,所以運行成本增加了。

            f.Get<EvtCreate>(Frame::EvtCreate)->Bind(this, &A::test);

            而且,看起來不舒服。
            所以,今天準備結合你的union設計,把Get模板函數搞成屬性來訪問。
            如果Demo通過的話,我發gmail給你。

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-19 17:48 by cexer
            @Loaden
            thunk 完成的功能只是消息從系統到窗口類的很小一步,也是最需要完成的第一步。在這一步上我嘗試過很多方法,包括這種 thunk 技術,甚至還用過TLS做映射。我目前用的方法很簡單就是 SetProp,GetProp 兩個函數,有點重劍換木劍的感覺。效率上肯定沒有 thunk 的直接調用高,但是心里更踏實一點,在內存當中寫二進制代碼總有在犯罪的感覺。

            “過早的優化是一切罪惡的根源”。基于這一步只是整個GUI框架當中是很微小的一步,幾乎不影響框架設計,可以先把框架搭好,再來從安全,效率,可移值性各個方面進行考慮。反正不要選擇 GetWindowLong ,GWLP_USERDATA 那一套,如果發布出去后客戶也使用這個東西就一切全完了,“過時的悲觀毫無益處”。

            你的消息封裝看起來很舒服,肯定在 GUI 框架上也是下過很多功夫,喜歡重復制造車輪的的同志,我對這個興趣也比較大,希望以后能多與你多交流互相學習,革命路上并肩攜手一同進步!

            # re: GUI框架:談談框架,寫寫代碼[未登錄]  回復  更多評論   

            2009-11-19 17:50 by megax
            我認為WTL是目前最LITE最高效,簡潔易懂,易于使用和維護的基于windows的GUI框架,其它的要么就是技巧用的太多,要么就是不直觀。要么就是效率不好。

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-19 17:56 by OwnWaterloo
            @Loaden

            非靜態。但只有三個參數,第一個參數通過匯編,寫到了m_wnd成員變量上了。沒辦法:兼容x64平臺,只能這樣thunk。

            你用的是和atl一樣的方式?
            將hwnd"替換"為this? 而非"插入"一個this?
            是嗎?

            1~3cpu指令只是"調整棧"而已,調整完了,肯定會繼續執行thunk中的call,來到你的函數。
            這個call或者jmp是免不到的。
            同時,編譯器得不到thunk的目的地的信息,所以不可能進行任何優化。
            本來也沒什么優化的余地了,都機器碼了……


            x64的我也有點想做……
            但對x64上的匯編和調用約定不熟悉,而且也沒有x64的測試平臺……

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-19 18:07 by OwnWaterloo
            @cexer

            thunk 完成的功能只是消息從系統到窗口類的很小一步,也是最需要完成的第一步。在這一步上我嘗試過很多方法,包括這種 thunk 技術,甚至還用過TLS做映射。我目前用的方法很簡單就是 SetProp,GetProp 兩個函數,有點重劍換木劍的感覺。效率上肯定沒有 thunk 的直接調用高,但是心里更踏實一點,在內存當中寫二進制代碼總有在犯罪的感覺。

            “過早的優化是一切罪惡的根源”。基于這一步只是整個GUI框架當中是很微小的一步,幾乎不影響框架設計,可以先把框架搭好,再來從安全,效率,可移值性各個方面進行考慮。反正不要選擇 GetWindowLong ,GWLP_USERDATA 那一套,如果發布出去后客戶也使用這個東西就一切全完了,“過時的悲觀毫無益處”。


            我就知道……
            大多數人對GetWindowLongPtr/GWL_USERDATA的理解是有誤的。
            你先仔細看看msdn是怎么介紹cbExtra、GetWindowLongPtr,還有GWL_USERDATA再考慮要不要否定這套方案。


            線程局部存儲映射表?
            先不說效率,mfc不能在線程之間傳遞CWnd*,就是一個使用上的劣勢。


            mfc需要在文檔中記錄: 不能將CWnd*傳遞到另一個線程。
            使用SetProp需要在文檔中記錄:不要SetProp("cexer" , ...)
            使用GetWindowLongPtr需要在文檔中記錄:不要SetWindowLongPtr( hwnd, 0, ... );


            3種方案有區別嗎?文檔中都必須有記錄 —— 什么是庫使用的,用戶不能隨意使用 —— 沒一個逃得掉。
            可能只有thunk,不需要在文檔中特別說明什么,比如ATL/WTL。

            區別還是有的,效率。
            tss-table肯定是最低,編程也最復雜的,完全不值得使用。
            SetProp需要查找,效率肯定沒有另外兩種高,而且,它很可能也需要分配內存。

            thunk和GetWindowLongPtr旗鼓相當。很難說誰高誰低。
            但后者要簡單很多,而且是在所有Windows上都可以使用,不用考慮匯編的問題。

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-19 21:50 by cexer
            @OwnWaterloo
            就像你把火箭的點火系統裝到拖拉機上,轟轟烈烈地點燃了拖拉機,它還是不能以宇宙第一速度脫離地球獲得自由一樣。Windows 系統維護消息隊列的時間遠遠多于消息到窗口那一彈指一揮間,我們再努力地擠時間出來也只是尋求心理上的安慰。所以在寫 GUI 框架時我盡量避免的是空間消耗而不是時間。

            不過最佩服C++程序員們的一大優點就是,就算只有一點效率也是要打破腦袋擠出來的,有總比沒有好嘛。你們討論的我都認真看到,在效率和安全上 thunk 確實是很明顯的最佳選擇,我在考慮以后用這個了。在利益的誘惑下,心里的負罪感是可以克服的!

            還是不敢選 GetWinowLongPtr 。它的好用是世人皆知的,就像一個大美女,人人都想染指一把,很難說在哪個月黑風高的晚上,哪個不愛看文檔的程序員就忍不住用了它,于是崩潰藍屏,整個世界清靜了。。。這樣的事一定會發生的,因為不愛看文檔的人太多了。

            SetProp 雖然也有危險,但是那概率小得多。世上的 Windows 程序員有多少,這些 Windows 程序員當中寫 GUI 的又有多少,寫 GUI 的程序員們直接用 API 寫的又有多少,直接用 API 寫的用到 SetProp 的又有多少,用到 SetProp 偏偏又用到和我一樣參數的又有多少呢。我感覺遇到這樣的概率比走路時被不明飛行物砸中的概率還要小。

            當然作為一個庫的作者,不能把完全安全性交給概率去解決。不像 GetWindowLongPtr 你只能眼睜等著崩潰,用 SetProp 為了增大安全性可以有很多手段。可以用一個 GUID 來 SetProp( guid .... ) ,可以用圓周率3.14159265。。。。,甚至可以用對女朋友的愛情宣言全文,愛是獨一無二的嘛!

            MFC的窗口指針可以在線程間傳遞,只是除了 SendMessage 之外能干的事不多。這跟那個 TSS 表沒多大關系,GUI 很多 API 本身就是不能跨線程。真正不能在線程間傳遞的是 TSS 表本身,我們用 TSS 的目的就是避免線程間的牽扯,當然是不會在線程間傳來傳去的,而且這個表對 GUI 框架的客戶而言是看不到的,想傳也傳不了。TSS 映射表的速度也不如想像中的慢,一個再巨型的 GUI 軟件,能有多少窗口可供映射的呢。

            這些方法我都用過。權衡起來,還是覺得 SetProp 是最簡單好用的一個,有興趣的同志可以測試一下在 SetProp 和 thunk 的實現效率差別有多大。我個人覺得在消息隊列吊車尾的情況下,差別不大。

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-19 22:07 by OwnWaterloo
            @cexer
            如果不考慮效率,最好的辦法就是不用C++做gui了。
            開發又慢又容易出錯,何必呢? 圖什么?


            GetWindowLongPtr的問題真的很容易解決。
            只要程序員用這個函數之前,看過GetWindowLongPtr的文檔。
            如果一個庫的用戶繞過庫并且不看文檔而直接使用win32api,庫是怎么都做不到,也花太多心思防止他用錯的。

            他甚至可以不用看這個gui庫的文檔 —— 因為你可以把this放在array的最后,而不是最前。
            甚至還可以在this前后設置一些guard來檢測使用庫的人員是否出軌。
            并且,如果將this放在最后,正好也可能需要一些padding來對齊。



            SetProp和thunk的效率就不用比了……
            同樣要分配內存。
            thunk是直接調用,SetProp調用之后還有活要干……
            當然,效率不是最主要的…… 也不是可以完全不顧的……


            我覺得將this放在GetWindowLongPtr所在array最后,既安全又快速又簡便……


            當然,最終權衡在你。
            而且…… 只要這個方式不對用戶公開……
            隨時可以改……

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-19 22:18 by OwnWaterloo
            @cexer
            void f( CWnd* w)
            {

            CWnd* d = w->GetItem( ... );
            d->GetWindowTitle( ... );

            }

            具體是不是叫這個名字不記得了。
            由一個窗口得到上面的控件的窗口,然后取一下標題。
            如果w來自另一個線程 …… 等著程序掛吧……


            d是一個指針,有主動釋放嗎?
            mfc的消息循環還會在odle時清除這種"臨時對象"的指針。
            就為了滿足它的table機制,需要在動態內存中創建對象,并使用類似gc的機制去清理……
            mfc的大、慢就是這么一點一點的積累出來的。
            table這種方式絕不可取……


            這個例子只是舉例,可能并不正確,因為我很久都不用mfc了……
            太out……

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-19 22:38 by cexer
            @OwnWaterloo
            我可沒說不考慮效率哈。別說C++,就算用石頭寫程序,都得考慮效率的。只是效率不是影響一切的標準,特別是當效率的差別微乎其微的時候。我不是不考慮效率,而是覺得用 thunk 實現 GUI 框架的效率不一定就能高多少,因為真正吊車尾的是消息隊列, SetProp 和 thunk 這點微末的時間,相對系統得到事件,生成消息,翻譯消息,發送消息,排隊消息的這一大堆的內部操作的時間來說,短得不值一提。GUI 線程只是用來跑 GUI 的,不能用 GUI 線程來完成分步式計算啊。

            用 GetWindowLongPtr 來作為實現框架確實也很簡單,效率也最高。但因為GWL_USERDATAR 的眾所周知而又獨一無二,這個問題是很嚴重的,而且很明顯,OwnWaterloo兄啊,何苦這么執著地為它辯護。。。。要是在廣告中說道,“此 GUI 框架物美價廉童叟無欺,但請不要使用 GWL_USERDATA 因為本框架要使用”,怎么賣得出去啊。

            三種方式當中 thunk 和 SetProp 確實是前者優一點,我個人放棄效率而選擇標準一點的實現。至于 GetWindowLongPtr ,現在你就算用左輪手槍指著我的腦袋,我也還是堅持不能用的哈 。

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-19 22:48 by cexer
            @OwnWaterloo
            你說的:“
            oid f( CWnd* w)
            {
            CWnd* d = w->GetItem( ... );
            d->GetWindowTitle( ... );
            }
            具體是不是叫這個名字不記得了。
            由一個窗口得到上面的控件的窗口,然后取一下標題。
            如果w來自另一個線程 …… 等著程序掛吧……
            d是一個指針,有主動釋放嗎?
            mfc的消息循環還會在odle時清除這種"臨時對象"的指針。
            就為了滿足它的table機制,需要在動態內存中創建對象,并使用類似gc的機制去清理……
            mfc的大、慢就是這么一點一點的積累出來的。
            table這種方式絕不可取……


            你說的這個是 MFC 的實現嘛,MFC 還在系統中加了鉤子呢。它的這個指針之所以有諸如線程的限制,也是因為實現的功能太雜。我們的 TSS 表可是壓根沒想過要實現這么個“臨時指針”的功能。關于跨線程調用,應該是 API 是怎么樣,跨線程調用成員函數也應該能干啥。TSS 表只是消息到窗口類當中很小的一步,不該影響到窗口類本身的工作。所以函數調用跟 TSS 表一點關系都沒有的,如果一個函數調用因為 TSS 表而崩潰,那就是有問題的實現了。以前用過這種方式實現的,正是考慮到多線程才使用 TSS。

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-19 22:57 by OwnWaterloo
            @cexer
            我一直都在強調……
            GetWindowLongPtr, GWL_USERDATA是普遍被誤解了的……


            我從學Windows開始…… 就把這個用錯了……
            因為我的老師給我們的課件上用錯了……
            網絡上【我沒見過用對的文章】,也全都用錯了:
            p = (Window*)GetWindowLongPtr( hwnd , GWL_USERDATA );



            不是這樣的……
            GWL_USERDATA有其本身的含義(在對話框中)。
            應該怎么用還是怎么用。
            庫是不需要占用它的……


            GetWindowLongPtr返回的是一個array中的元素。
            1.
            它可以是一個以0開始的,大小為cbExtra / sizeof(void*)的數組。
            2.
            或者是以0開始的,大小為cbExtra / sizeof(long) —— 如果以GetWindowLong取得。
            3.
            或者是一些系統定義的index, 比如GWL_USERDATA, GWL_WNDPROC —— 這些系統定義的index全是負數。

            我上面寫的代碼全是:
            GetWindowLongPtr( hwnd, 0 );
            或者:
            GetWindowLongPtr( hwnd, index );
            而不是:
            GetWindowLongPtr( hwnd, GWL_USERDATA );是錯的 —— 對于塞this來說的話 —— 確實會占用掉用可以留給用戶的機制。


            庫只要將注冊窗口類的過程截獲,并且給cbExtra增加一點點 —— padding + sizeof(this) + guard。

            庫的用戶還是可以自由使用GetWindowLongPtr, 只要他沒有越軌 —— 不超過他填入的cbExtra。
            用戶要越軌,那就責任自負……


            內存分配:
            cbExtra應該是和window一起被分配,不需要專門分配thunk結構體或者SetProp用的數據。
            速度:
            直接通過index尋址array,也幾乎是最快的查找方式。


            所以我說從整體上thunk和GetWindowLongPtr的效率可能差不多。
            thunk如果one window one WNDCLASS,會注冊很多WNDCLASS。
            如果多個window共享一個WNDCLASS,就需要轉發一次。
            這些都是有效率損耗的。

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-19 23:03 by OwnWaterloo
            @cexer
            我沒有細想過 HWND -> this的映射的方案……
            因為它一開始就在我排除的范圍內……

            ——【一個設計缺陷不應該由另一個設計缺陷來彌補】。

            幸好,最后我發現WndProc(hwnd, ... )還不算徹底的設計缺陷。
            因為hwnd可以有cbExtra。


            我再想想table(hwnd,this)是否一定需要mfc那樣的機制……

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-19 23:04 by cexer
            @OwnWaterloo
            你說:“我一直都在強調……
            GetWindowLongPtr, GWL_USERDATA是普遍被誤解了的……”

            哈哈,原來如此。看來我對 GetWindowLongPtr 真是有深深的誤解!和你一番討論挺有收獲的,這樣看來我得再多權衡一下了!這樣的 GetWindowLongPtr 是一個誘人的選擇。看來不看書,只聽道聽途說來的東西真是不行的。可以回頭去睡覺了,多謝了!

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-19 23:12 by OwnWaterloo
            @cexer
            哈哈,性感誘人吧? 所以我極力推薦這妞……
            也正因為誤解很嚴重……
            所以用戶代碼訪問 [0, length ) 的幾率還真不大……


            抄一下msdn……

            The GetWindowLongPtr function retrieves information about the specified window. 【The function also retrieves the value at a specified offset into the extra window memory.】

            LONG_PTR GetWindowLongPtr
            (
            HWND hWnd,
            int nIndex
            );

            nIndex
            [in] Specifies the zero-based offset to the value to be retrieved. 【Valid values are in the range zero through the number of bytes of extra window memory, minus the size of an integer.】
            To retrieve any other value, specify one of the following values.
            ... 后面就是其他很多系統定義index了。


            GetWindowLongPtr的另一個作用 —— 0-based array —— 只有這么2句話……
            還隱藏在對系統自定義index的大篇幅的介紹之中……

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-19 23:14 by OwnWaterloo
            還有一句:

            Remarks
            Reserve extra window memory by specifying a nonzero value in the cbWndExtra member of the WNDCLASSEX structure used with the RegisterClassEx function.


            原來叫cbWndExtra…… 不叫cbExtra……
            很有沒用…… 寫錯了……
            也對,因為相對的還有cbClassExtra……

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-19 23:21 by cexer
            @OwnWaterloo
            你說:“性感誘人吧? 所以我極力推薦這妞……
            也正因為誤解很嚴重……
            所以用戶代碼訪問 [0, length ) 的幾率還真不大……”

            確實非常性感誘惑人,我們不要宣揚了,好東西我們兩知道就好了,哈哈!MSDN真是有點猥瑣。。。實現這么好個功能,卻不知道大書特書,讓那 GWL_USERDATA 忽悠了不知道多少程序員。OwnWaterloo 兄弟,該睡覺了,身體是革命的本錢啊,再聊。

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-20 00:13 by OwnWaterloo
            @cexer
            確實有點猥瑣……

            沒這功能,WndProc的設計真的就是缺陷了……
            有這功能,WndProc還是很麻煩……


            我剛吃完晚飯…… 囧……
            習慣沒養好…… 夜貓慣了…… 慢慢改吧……
            可能哪天不寫代碼了,生活會規律一些……


            那個array的index到底是怎樣,msdn我沒看怎么明白……
            待會驗證一下……

            tss + table是否可以調用成員函數, 我覺得有困難……
            我再想想……
            畢竟,要證明很容易 —— 想出一個實現。
            要證偽 —— 證明不可能實現出來 —— 要困難不少……

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-20 00:44 by OwnWaterloo
            我說一下思路吧,不算證明 tss+table不行,只是說一下這個方案可能遇到的陷阱。

            假設這就是注冊窗口類時使用的函數:
            LRESULT CALLBACK proc(HWND hwnd, UINT msg, WPARAM, LPARAM )
            {

            if ( msg == WM_XXX ) // 處理某條消息時需要得到this
            {
            table = get_tss_table(); // 線程相關映射表
            w = table.lookup( hwnd ); // 查找hwnd對應的object。
            w->f() // 調用一個成員函數...
            }

            }

            如果另一個成員函數:
            C::g()
            {

            SendMessage( this->m_hwnd , WM_XXX , ... );
            // 而且這個消息、WM_XXX、在proc處理過程中需要取得this

            }

            那這個成員函數g,就不能在另一個線程中使用。
            因為table = get_tss_table();是線程相關的。


            是否會有這種成員函數…… 就不知道了…… 沒這方面的經驗,也想不出例子……



            mfc返回一個CWnd* ,可能也是基于這種考慮:
            CWnd* w = father->getChild (...);
            w->g(); // 這里可能需要查表。

            mfc中也可以這樣:
            HWND hwnd = ::getChind( father->m_hwnd, ... );
            CWnd w;
            w.Attach( hwnd ); // 插入表
            w.g(); // 可查詢
            w.Detach(); // 從表中刪除

            可能afx考慮到最后的w.Detach會被忘記調用。一旦w離開作用域,那個hwnd -> object的表,就指向一個無效對象。

            所以就引入了一個類似gc的方式…… 返回CWnd* 同時插入表中。

            純猜測……


            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-20 01:53 by OwnWaterloo
            關于GetWindowLongPtr。


            1. 系統定義index
            有配套的GWLP_xxx系列…… 以前真沒注意……
            在32位上GWL_xxx和GWLP_xxx的值可能相同…… 但還是應該使用配套的……
            自從我用Get/SetWindowLongPtr開始,就沒寫對一次過……
            囧……

            幸好cexer的某個回復中有GWLP_USERDATA……
            順手再查了一下……


            2. array index
            array就是一個byte的array。
            這個array的地址是不會返回給用戶的 —— 應該如此。

            大致應該是如下偽代碼:
            LONG_PTR GetWindowLongPtr( hwnd , index )
            {

            char* array = get_extra(hwnd);
            LONG_PTR r;
            if ( index + sizeof(r) > get_extra_size(hwnd) )
            {
            SetLastError(ERROR_INVALID_INDEX); // 1413
            return 0;
            }

            memcpy(&r , array+index , sizeof(r) );
            return r;

            }

            越界會返回0,LastError是無效索引,1413。

            有效索引應該是:
            GetWindowLong, [0, cbWndExtra - sizeof(LONG) ], msdn說-4,也算對。
            GetWindowLongPtr, [0, cbWndExtra - sizeof(LONG_PTR) ] , msdn說-sizeof integer, 這就是我困惑的地方……
            integer是啥? short算不算…… 應該是sizeof(LONG_PTR)才對……


            對齊用戶(winapi的用戶)也不需要特別關心。


            反正array的地址是不可能暴露出來的,上面有些地方可能說錯了。

            SetWindowLong/Ptr也是會檢查無效索引。


            3. 創建窗口時收到的消息:

            測試時順便得到的:
            WM_GETMINMAXINFO = 0x24;
            WM_NCCREATE = 0x81;
            WM_NCCALCSIZE = 0x83;
            WM_CREATE = 0x1;

            可能還和窗口風格什么的有關,以上不準,以msdn為準。


            4. GetWindowLongPtr的其他限制
            cbWndExtra在對話框中有一些規定。
            MDI也有一些特殊規定 —— 針對CreateWindow是傳入context。
            可能還有一些特殊情況……

            總之…… win32 gui是很浩瀚的事情…………
            cexer…… 精神上支持你……


            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-20 10:00 by cexer
            @OwnWaterloo
            你說“那這個成員函數g,就不能在另一個線程中使用。因為table = get_tss_table();是線程相關的。”
            首先用表肯定是可以的,比如說我們一個全局表。針對映射表的操作有三種,一種窗口創建時的插入,一種窗口銷毀時的刪除,還有就是窗口消息來時的查詢。創建和銷毀是在調用的 GUI 線程當中進行的,做不到跨線程的創建和銷毀。而查詢時是在 WNDPROC 中進行的,這個函數是由系統調用,也是在 GUI 線程中運行的。既然所有的操作都在同一個 GUI 線程,那何必要用全局表呢,還要加鎖,這不正是 TSS 派上用場的地方嘛。

            你說:“GetWindowLongPtr的其他限制”
            GetWindowLongPtr 確實有比較大的限制。除了你所說的,對話框,按鈕這類的已經注冊過的窗口類,其 cbWndExtra 都已經是固定的,要想使用 GetWindowLongPtr 來存取額外數據的話,就必須要超類化,這樣的就又麻煩了。所以綜合考慮,SetProp 和 thunk 是最優選擇。

            你說:“總之…… win32 gui是很浩瀚的事情…………“
            當然是的,不浩翰就沒有辟波斬浪的快感嘛。OwnWaterloo 晚上兩點還堅持在前線?深更半夜的,都在研究些什么高深課題呢?多好的睡覺時間啊,晚上睡得香,白天不嗑睡,早起早睡效率才更高!

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-20 14:47 by OwnWaterloo
            @cexer
            關于tss+table, 我理解錯了……

            SendMessage( hwnd , .... );
            如果調用SendMessage所屬線程T1和hwnd所屬線程T2不是同一個線程,則依然是由T2,而不是T1去執行hwnd的wndproc。
            我想成T1去了……

            哦,還有個GetWindowThreadProcessId……
            如果tss真有問題,可以不用那么自動的tss,手動+GetWindowThreadProcessId也行。

            那mfc又是怎么掛掉的呢……

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-20 15:06 by OwnWaterloo
            @cexer

            GetWindowLongPtr 確實有比較大的限制。除了你所說的,對話框,按鈕這類的已經注冊過的窗口類,其 cbWndExtra 都已經是固定的,要想使用 GetWindowLongPtr 來存取額外數據的話,就必須要超類化,這樣的就又麻煩了。所以綜合考慮,SetProp 和 thunk 是最優選擇。


            已經注冊過的窗口類,如果要塞context,都需要subclassing。
            SetWindowLongPtr( hwnd, GWLP_WNDPROC, insert_context );
            然后在insert_context中將context塞進去,SetProp或thunk。
            只是GetWindowLongPtr塞不了。
            對窗口類注冊不由自己控制的情況,GetWindowLongPtr確實不適合。


            對這些預定義的窗口類需要做一些什么工作,有什么需求,我沒怎么細想……
            用win32 api寫gui的事,我沒做幾回……

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-20 15:08 by OwnWaterloo
            @cexer
            白天思維很亂…… 晚上思維清晰一些。
            在想C++中怎么弄出匿名函數…… 無果……

            如果沒有這種東西, gui的語法是不會漂亮的……

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-20 15:16 by OwnWaterloo
            也不算匿名函數吧。
            就是,如果一個調用需要傳入一個函數作為回調,如何在調用點上創建這個回調函數,而不是在調用點所在函數外。

            void handle_xxx( callback );

            void f()
            {


            handle_xxx
            ( /* 1. 如何就地創建一個函數,傳遞給handle_xxx */
            { ... }
            /* 估計C++0x才行 */
            );


            /* 2. 或者次一點,再handle_xxx的調用點所在函數中創建 */
            my_handler () { ... }
            handle_xxx( my_handler );

            }


            /* 3.最次的,就是在調用處所在函數外定義 */
            my_handler() { ... }
            void f()
            {

            handle_xxx( my_handler );

            }

            C++可以在函數內創建一個嵌套class, 并定義該class的函數 —— 可以是static的,但就是不能直接定義一個嵌套函數。
            這可能是C++98、03的唯一手段了。

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-20 16:42 by cexer
            @OwnWaterloo

            你說:“已經注冊過的窗口類,如果要塞context,都需要subclassing。
            SetWindowLongPtr( hwnd, GWLP_WNDPROC, insert_context );
            然后在insert_context中將context塞進去,SetProp或thunk。
            只是GetWindowLongPtr塞不了。”

            可以塞的,只是需要超類化(superclassing)之后再塞。子類化(subclassing)在這里不行。但超類化是更需要謹慎使用的東西。


            你說:“如何在調用點上創建這個回調函數,而不是在調用點所在函數外。“

            C++不能創嵌套函數的,但可以在函數當中創建一個函數對象。你試試看?

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-20 16:52 by OwnWaterloo
            @cexer
            超類化?


            可以創建嵌套類,嵌套類中可以有靜態或非靜態成員。比較間接一點。
            直接寫嵌套函數是不行的。

            跟是否可以函數對象沒什么關系。
            主要需求是定義這個回調(或者函數對象)的地方最好和使用這個回調的地方比較接近。

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-20 16:57 by OwnWaterloo
            @cexer
            GetClassInfo( DIALOG , &wc );
            wc.lpProc = my_proc;
            不修改wc.name
            RegClass( &wc );

            這樣?

            這……

            如果在修改wc并注冊之前,已經創建了一些窗口呢?
            會怎樣? 也會跟著被修改? 不會吧……?

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-20 16:58 by cexer
            @OwnWaterloo
            你說:“超類化?”

            隨手找了篇文章:http://hi.baidu.com/combojiang/blog/item/45e968b7b8a510f131add11c.html@OwnWaterloo


            你說:“跟是否可以函數對象沒什么關系。主要需求是定義這個回調(或者函數對象)的地方最好和使用這個回調的地方比較接近。”

            我的意思是,可以定義函數對象代替函數來作為回調。實在是需要純粹的回調函數,也可以在外面定義一個公用的模板,然后在內部定義一個函數對象為參數來具現化這個模板,就獲得了真正的函數地址。不知這個是否與你的需求有所出入?

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-20 17:05 by cexer
            @OwnWaterloo

            你說:“如果在修改wc并注冊之前,已經創建了一些窗口呢?
            會怎樣? 也會跟著被修改? 不會吧……?”

            確實不會。Windows 還沒實現那么神奇的功能,而且沒必要。如果需要同時修改已經創建的窗口,就只能列舉出來一個個地子類化。

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-20 17:19 by OwnWaterloo
            @cexer
            那篇文章我晚點再看看。

            我覺得是函數指針還是函數對象關系都不大,我并不排斥函數指針~_~

            關鍵是那個共用的模板定義不出來啊……

            假設,一個xxx控件,用來調節一個整數,它提供這么一個通知:

            void xxx::on_change_add( void (*listener)(void* context,int pos), void* context );

            我覺得最好的語法:
            void f1()
            {

            xxx x;
            int i = 0;
            x.on_change_add( void (void* c,int pos){ *(int*)c = pos; }, &i );
            wait();
            printf("%d\n",i);

            }
            應該只有等C++0x的lambda才可以這樣寫了。


            次之:

            void f2()
            {

            xxx x;
            int i = 0;
            struct local { static void set_pos(void* c,int pos) { *(int*)c = pos; } };
            x.on_change_add( &local::set_pos, &i );
            wait();
            printf("%d\n",i);

            }

            這是符合C++語法的,比較丑陋了。
            但如果on_change_add是函數模板,就會很麻煩。
            函數模板的實際類型參數不能是這種內嵌的類型。


            最難看的:
            void set_pos(void* c,int pos) { *(int*)c = pos; }
            void f3()
            {

            xxx x;
            int i = 0;
            x.on_change_add( &set_pos, &i );
            wait();
            printf("%d\n",i);

            }


            set_pos只是一個示例而已。
            代表的是一簇簡單(代碼就2、3行),但邏輯多變 ——根本不可能預定義一些函數對象或者函數模板 —— 只能是隨寫隨用、一處使用、不被復用的代碼來表現需要的邏輯。

            類似的例子, std::for_each如果沒有lambda的支持, 就是雞肋。


            當然,這只是個審美的問題……
            也有人覺得第3種(定義到外部)還不錯……

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-20 17:33 by cexer
            @OwnWaterloo

            你的這個需求有點像 java 的 AWT 的 添加 listener 的方式
            addWindowListener ( new WindowListener(){
              void windowOpened(){ ...... }
              void windowClosing(){ ...... }
            } );

            也有點類似自動化腳本的
            onclick = "javascript:{......}"

            或者 lua 的
            window.onClick = function( arg )
            begin
              --......
            end

            確實是很誘人的語法。可惜三種 C++ 都不能實現,boost::lamda 生成的函數對象好像是臨時的吧?也不能這樣保存起來以后再使用。

            # re: GUI框架:談談框架,寫寫代碼[未登錄]  回復  更多評論   

            2009-11-20 17:59 by OwnWaterloo
            boost.lambda好像只是表達式。 沒仔細研究過。
            要保存可以有auto。。。 或者boost.typeof。

            本來我需要的僅僅是一個函數,卻不得不定義一個類…… 很……
            被其他語言慣壞了,就覺得c++寫gui非常不方便……

            我打算等c++0x流行之后再考慮是不是要用c++寫gui了~_~
            在哪之前,如果有得選擇,c++就不是第1人選……

            # re: GUI框架:談談框架,寫寫代碼[未登錄]  回復  更多評論   

            2009-11-20 23:29 by Loaden
            @ OwnWaterloo
            你用的是和atl一樣的方式?
            將hwnd"替換"為this? 而非"插入"一個this?
            是嗎?
            ====================
            是的。因為這樣才能兼容x64平臺:x64前8個參數通過寄存器傳遞。

            @ cexer
            SetProp 結合 GUID更理想,而GUID可以用API獲取。
            以后多交流!期待你的GUI框架下一篇...^_^

            ================
            |||||||||||||||||||||

            發現一個類中有6萬個指針時,如果指針初始化了,還是占用內存。
            沒辦法,為了降低成本,不能在類中放那么多指針了。
            所以,使用bitset來幫忙。

            先看消息注冊(比原來的難看多了,沒找到好方法,詳見:http://topic.csdn.net/u/20091119/15/07d7e755-c733-4195-8cc4-306560d6fbc4.html

            class MainFrm : public Frame
            {
            public:
            MainFrm() : Frame(_T("Test Demo!"), _T("MainFrame")), m_testBtn(_T("Test!"))
            {
            Add(&m_testBtn);
            m_testBtn.Get<EvtLButtonDown>(m_testBtn.IdLButtonDown).Bind(this, &MainFrm::OnLBtnClicked);
            Get<EvtCreate>(IdCreate).Bind(this, &MainFrm::OnCreate);
            }

            ~MainFrm()
            {
            Get<EvtCreate>(IdCreate).UnBind(this, &MainFrm::OnCreate);
            }

            qpEvt OnCreate(EvtCreate& evt)
            {
            qpDbgInt(evt.handled);
            }

            qpEvt OnLBtnClicked(EvtLButtonDown& evt)
            {
            qpDbgInt(evt.owner);
            }

            protected:
            Button m_testBtn;
            };

            用bitset + map<short, void*> + enum來降低成本:
            #define IMPLEMENT_EVENT(X, Y) \
            public: \
            template <typename T> \
            Event<T>& Get(X id) \
            { \
            if (m_evtMap.get() == NULL) \
            { \
            qpNewPtrEx(p, EventMap); \
            m_evtMap.reset(p); \
            } \
            if (m_evtFlag[id] == 0) \
            { \
            qpNewPtrEx(p, Event<T>); \
            if (p != NULL) \
            { \
            m_evtFlag[id] = 1; \
            m_evtMap->insert(std::make_pair(static_cast<short>(id), p)); \
            return *static_cast<Event<T>*>(p); \
            } \
            } \
            else \
            { \
            EventMap::iterator it = m_evtMap->find(static_cast<short>(id)); \
            if (it != m_evtMap->end()) return *static_cast<Event<T>*>(it->second); \
            } \
            qpASSERT(false); \
            qpNewPtrEx(p, Event<T>); \
            return *std::auto_ptr<Event<T>>(p); \
            } \
            protected: \
            typedef std::map<short, void*> EventMap; \
            std::auto_ptr<EventMap> m_evtMap; \
            std::bitset<Y> m_evtFlag;

            std::bitset的使用,可以判斷相應消息是否注冊,不用每一個消息都進入一次Event了,但由于使用map來查找消息,效率上還是下降了。
            沒辦法:只能用時間換空間!

            # re: GUI框架:談談框架,寫寫代碼[未登錄]  回復  更多評論   

            2009-11-20 23:33 by Loaden
            相應類的消息注冊類
            class ButtonEvt
            {
            public:
            enum EventId
            {
            IdLButtonDown,
            TotalEvent
            };

            IMPLEMENT_EVENT(EventId, TotalEvent);
            };

            class FrameEvt : public EvtSender
            {
            protected:
            FrameEvt(const String& keyEvt) : EvtSender(keyEvt) {}

            public:
            enum EventId
            {
            IdCreate,
            TotalEvent
            };

            IMPLEMENT_EVENT(EventId, TotalEvent);
            };

            這樣,只需要往enum里放不占內存的消息ID就行了。

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-21 02:04 by OwnWaterloo
            @Loaden
            同學生日,聚會喝了點酒…… 看代碼很暈…… 像不認識C++似的……

            關于你在csdn上的問題。

            函數模板可以通過實際參數來推導類型。
            template<typename T>
            T max(T v1,T v2);

            max(1212 , 326); // max<int> T=int
            max(1212.0 , 326.0); // max<double> T=double

            max(1212, 326.0); // 歧義 T=int or double
            max<double>(1212, 326.0); // 顯示指定 T=double


            在模板參數列表中,最后一個不能被推導的參數以及它之前的所有參數都必須顯示指定:
            template<typename D,typename S>
            D union_cast(S src)
            {

            union
            {
            S src;
            D dst;
            } u = { src };
            return u.dst;

            }

            union_cast<long long>(&C::f); // D= 一個成員指針,但S不能通過實際參數推導出來,必須顯式指定。


            操作符重載,其實也是一個函數,只是通常不以函數的形式調用。
            所以你那個需求,可能真的只能這么寫:
            a.operator[]<T,U>( index );


            如果真的想實現:
            T* p = a[ i ];
            肯定能實現,不過很惡心…… 也可能很危險……

            class A;

            struct proxy
            {
            A* a_;

            template<typename D>
            operator D*() const { return new D; }

            };

            class A
            {

            template<typename T>
            proxy operator[](T i)
            {
            proxy p = { this };
            return p;
            }

            };

            A a;
            proxy p = a[ i ]; // 調用a.operator[]<int>; 返回一個proxy
            E<TBase>* o = p; // proxy.operator<D>

            E<TBase>* o = a[ i ]; // 連起來就是這樣……


            template<typename T>
            operator T() 是很邪惡的東西,盡量少用……



            是的。因為這樣才能兼容x64平臺:x64前8個參數通過寄存器傳遞。

            確實,i386是個特例,主要通過棧來傳遞參數。
            這樣,無論被partial application的函數的參數如何 —— 只要不是返回大結構體 —— 插入一個指針參數都可以使用相同的thunk結構體。

            我考慮過ppc(我唯一能搞到的非i386的機器),主要通過寄存器傳遞。
            視被partial application的函數的參數,插入一個指針需要不同的thunk結構體。
            但替換某個參數,thunk結構體就和其他參數的數目無關了。

            如果被partial application的函數的參數已經確定,為其設計一個thunk結構體,插入一個指針參數也應該是可行的。

            # re: GUI框架:談談框架,寫寫代碼[未登錄]  回復  更多評論   

            2009-11-21 08:53 by Loaden
            @ OwnWaterloo
            謝謝!
            我目前的方案,實現了每個可以發送消息的類,例如Button、Frame,至少需要8byte的代價,但:

            50個事件供Bind:至少12字節
            100個事件供Bind:至少20字節
            1000個事件供Bind:至少132字節
            10000個事件供Bind:至少1256字節
            而我之前每個事件用一個指針的方法,分別對應:
            200
            400
            4000
            40000
            代價下降了20倍。

            不過每個注冊的事件,還要多一個short + void*。
            由于用戶在一個程序中處理的事件不會很多,估計是總事件1/1000左右。
            所以也是很值得的。

            就算是一種低代價的實現吧。

            # re: GUI框架:談談框架,寫寫代碼[未登錄]  回復  更多評論   

            2009-11-21 10:06 by Loaden
            @ OwnWaterloo
            現在在理論上還可以有一種辦法將:
            m_testBtn.Get<EvtLButtonDown>(m_testBtn.IdLButtonDown).Bind(this, &MainFrm::OnLBtnClicked);
            Get<EvtCreate>(IdCreate).Bind(this, &MainFrm::OnCreate);
            簡化為:
            m_testBtn.Get<EvtLButtonDown>().Bind(this, &MainFrm::OnLBtnClicked);
            Get<EvtCreate>().Bind(this, &MainFrm::OnCreate);

            即不傳入enum的id,改由Get函數根據typeid(T).name去判斷id的值。
            但如何去實現呢?

            struct Create
            {
            bool handled;
            };

            class A
            {
            public:
            enum
            {
            IdCreate,
            TotalEvent
            };

            template <typename T>
            void Get()
            {
            // 這條語句輸出:struct Create
            printf("%s", typeid(T).name());
            // 可是,如何通過上面語句的輸出,來訪問: IdCreate呢?
            // 從“struct Create中獲取Create,在前面再加上Id,可以得到字符串IdCreate
            // 如何將這個字符串轉化為枚舉型變量的一個值,例如:IdCreate?
            //
            // 這兩行語句如何實現?
            // int i = IdCreate;
            // printf("%d", i);
            }
            };

            int main()
            {
            A a;
            a.Get<Create>();
            return 0;
            }

            一種比較容易想到的方案是:
            if (name=="A") id = ;
            else if (name=="B") id = ;
            但如果事件很多,這樣去比較,代碼寫起來很麻煩。
            而且編譯后的程序體積也是個問題。

            放到一個const字符串數組里,根據index判斷,由于const數組是占用內存的(VC占用,測試過,GCC不占用),所以這樣反而得不償失了。

            # re: GUI框架:談談框架,寫寫代碼[未登錄]  回復  更多評論   

            2009-11-21 11:30 by Loaden
            typeid.name()的問題,通過CSDN解決了。
            帖一下Demo。

            struct Create
            {
            bool handled;
            template<class T>
            static int GetEventId()
            {
            return T::IdCreate;
            };
            };

            struct Close
            {
            bool handled;
            template<typename T>
            static int GetEventId()
            {
            return T::IdClose;
            };
            };

            class A
            {
            public:
            enum
            {
            IdCreate,
            IdClose,
            TotalEvent
            };

            template <typename T>
            void Get()
            {
            printf("%d\n", T::GetEventId<A>());
            }
            };

            int main()
            {
            A a;
            a.Get<Create>();
            a.Get<Close>();
            return 0;
            }

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2009-11-22 20:56 by 暗涌
            @OwnWaterloo
            那么,ATL-style繼承的作用就是:實現靜態的"template method"?

            建議看看boost里Singleton的模板,類似。

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2010-04-29 09:04 by ValentineANA
            All people deserve wealthy life and <a href="http://lowest-rate-loans.com/topics/credit-loans">http://www.lowest-rate-loans.com</a> or just small business loan will make it better. Because people's freedom is grounded on money state.

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2010-05-09 10:43 by iwobz
            牛人啊,膜拜了,回頭我也去嘗試著寫一個

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2010-08-25 12:24 by 法官
            牛人啊,膜拜了,學習了

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2010-12-19 01:12 by 溪流
            來考古了,太多了~~~~囫圇看一遍都花這么久。

            # re: GUI框架:談談框架,寫寫代碼  回復  更多評論   

            2014-12-11 19:08 by lx20050809
            真的很精彩!
            評論共2頁: 1 2 
            精品久久久久久无码中文字幕| 狠狠色丁香久久婷婷综合图片| 精品久久综合1区2区3区激情| 久久久久一本毛久久久| 7777久久久国产精品消防器材| 久久99国产精品一区二区| 色综合合久久天天给综看| 国产精品久久久亚洲| 欧美大战日韩91综合一区婷婷久久青草| 狠狠色婷婷久久一区二区| 国産精品久久久久久久| 亚洲AV无一区二区三区久久| 久久99精品久久久久久野外| 久久精品国产亚洲AV无码偷窥| 久久免费99精品国产自在现线 | 国产精品亚洲综合专区片高清久久久 | 亚洲精品综合久久| 精品久久久久久国产| 超级碰碰碰碰97久久久久| 久久精品国产欧美日韩| 久久精品国产99国产精偷| 久久久精品2019免费观看| 九九精品久久久久久噜噜| 久久久久久久综合综合狠狠| 久久青草国产手机看片福利盒子| 日韩精品久久久久久免费| 亚洲?V乱码久久精品蜜桃| 国产精品无码久久久久| 97精品国产97久久久久久免费| 国产亚洲欧美精品久久久| 亚洲精品无码专区久久久| 久久亚洲AV无码精品色午夜| 久久亚洲精品无码播放| 久久综合色之久久综合| 久久精品国产福利国产琪琪| 久久精品亚洲男人的天堂| 精品欧美一区二区三区久久久| 精品免费久久久久国产一区| 久久国产免费直播| 香蕉久久久久久狠狠色| 亚洲精品久久久www|