• <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>
            隨筆-90  評論-947  文章-0  trackbacks-0

            由于 C++ 成員函數的調用機制問題,對C語言回調函數的 C++ 封裝是件比較棘手的事。為了保持C++對象的獨立性,理想情況是將回調函數設置到成員函數,而一般的回調函數格式通常是普通的C函數,尤其是 Windows API 中的。好在有些回調函數中留出了一個額外參數,這樣便可以由這個通道將 this 指針傳入。比如線程函數的定義為:

            typedef DWORD (WINAPI *PTHREAD_START_ROUTINE)(
                LPVOID lpThreadParameter
                );
            typedef PTHREAD_START_ROUTINE LPTHREAD_START_ROUTINE;

            這樣,當我們實現線程類的時候,就可以:

            class Thread
            {
            private:
                HANDLE m_hThread;

            public:
                BOOL Create()
                {
                    m_hThread = CreateThread(NULL, 0, StaticThreadProc, (LPVOID)this, 0, NULL);
                    return m_hThread != NULL;
                }

            private:
                DWORD WINAPI ThreadProc()
                {
                    // TODO
                    return 0;
                }

            private:
                static DWORD WINAPI StaticThreadProc(LPVOID lpThreadParameter)
                {
                    ((Thread *)lpThreadParameter)->ThreadProc();
                }
            };

            不過,這樣,成員函數 ThreadProc() 便喪失了一個參數,這通常無傷大雅,任何原本需要從參數傳入的信息都可以作為成員變量讓 ThreadProc 來讀寫。如果一定有些什么是非從參數傳入不可的,那也可以,一種做法,創建線程的時候傳入一個包含 this 指針信息的結構。第二種做法,對該 class 作單例限制——如果現實情況允許的話。

            所以,有額外參數的回調函數都好處理。不幸的是,Windows 的窗口回調函數沒有這樣一個額外參數:

            typedef LRESULT (CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM);

            這使得對窗口的 C++ 封裝變得困難。為了解決這個問題,一個很自然的想法是,維護一份全局的窗口句柄到窗口類的對應關系,如:

            #include <map>

            class Window
            {
            public:
                Window();
                ~Window();
               
            public:
                BOOL Create();

            protected:
                LRESULT WndProc(UINT message, WPARAM wParam, LPARAM lParam);

            protected:
                HWND m_hWnd;

            protected:
                static LRESULT CALLBACK StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
                static std::map<HWND, Window *> m_sWindows;
            };

            在 Create 的時候,指定 StaticWndProc 為窗口回調函數,并將 hWnd 與 this 存入 m_sWindows:

            BOOL Window::Create()
            {
                LPCTSTR lpszClassName = _T("ClassName");
                HINSTANCE hInstance = GetModuleHandle(NULL);

                WNDCLASSEX wcex    = { sizeof(WNDCLASSEX) };
                wcex.lpfnWndProc   = StaticWndProc;
                wcex.hInstance     = hInstance;
                wcex.lpszClassName = lpszClassName;

                RegisterClassEx(&wcex);

                m_hWnd = CreateWindow(lpszClassName, NULL, WS_OVERLAPPEDWINDOW,
                    CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

                if (m_hWnd == NULL)
                {
                    return FALSE;
                }

                m_sWindows.insert(std::make_pair(m_hWnd, this));

                ShowWindow(m_hWnd, SW_SHOW);
                UpdateWindow(m_hWnd);

                return TRUE;
            }

            在 StaticWindowProc 中,由 hWnd 找到 this,然后轉發給成員函數:

            LRESULT CALLBACK Window::StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
            {
                std::map<HWND, Window *>::iterator it = m_sWindows.find(hWnd);
                assert(it != m_sWindows.end() && it->second != NULL);

                return it->second->WndProc(message, wParam, lParam);
            }

            (m_sWindows 的多線程保護略過,下同)

            據說 MFC 采用的就是類似的做法。缺點是,每次 StaticWndProc 都要從 m_sWindows 中去找 this。由于窗口類一般會保存窗口句柄,回調函數里的 hWnd 就沒多大作用了,如果這個 hWnd 能夠被用來存 this 指針就好了,那么就能寫成這樣:

            LRESULT CALLBACK Window::StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
            {
                return ((Window *)hWnd)->WndProc(message, wParam, lParam);
            }

            這樣看上去就爽多了。傳說中 WTL 所采取的 thunk 技術就是這么干的。之前,只是聽過這遙遠的傳說,今天,終于有機會走進這個傳說去看一看。參考資料是一篇不知原始出處的文章《深入剖析WTL—WTL框架窗口分析》,以及部分 WTL 8.0 代碼,還有其他亂七八糟的文章。

            WTL 的思路是,每次在系統調用 WndProc 的時候,讓它鬼使神差地先走到我們的另一處代碼,讓我們有機會修改堆棧中的 hWnd。這處代碼可能是類似這樣的:

            __asm
            {
                mov dword ptr [esp+4], pThis  ;調用 WndProc 時,堆棧結構為:RetAddr, hWnd, message, wParam, lParam, ... 故 [esp+4]
                jmp WndProc
            }

            由于 pThis 和 WndProc 需要被事先修改(但又無法在編譯前定好),所以我們需要運行的時候去修改這部分代碼。先弄一個小程序探測下這兩行語句的機器碼:

            LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
            {
                return 0;
            }

            int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
            {
                MessageBox(NULL, NULL, NULL, MB_OK);

                __asm
                {
                    mov dword ptr [esp+4], 1
                    jmp WndProc
                }

                return 0;
            }

            最前面的 MessageBox 是為了等下調試的時候容易找到進入點。

            然后使用 OllyDbg,在 MessageBoxW 上設置斷點,執行到該函數返回:

            image

            這里我們看到,mov dword ptr [esp+4] 的機器碼為 C7 44 24 04,后面緊接著的一個 DWORD 是 mov 的第二個操作數。jmp 的機器碼是 e9,后面緊接著的一個 DWORD 是跳轉的相對地址。其中 00061000h - 0006102Bh = FFFFFFD5h。

            于是定義這樣一個結構:

            #pragma pack(push,1)
            typedef struct _StdCallThunk
            {
                DWORD   m_mov;          // = 0x042444C7
                DWORD   m_this;         // = this
                BYTE    m_jmp;          // = 0xe9
                DWORD   m_relproc;      // = relative distance
            } StdCallThunk;
            #pragma pack(pop)

            這個結構可以作為窗口類的成員變量存在。我們的窗口類現在變成了這樣子:

            class Window
            {
            public:
                Window();
                ~Window();

            public:
                BOOL Create();

            protected:
                LRESULT WndProc(UINT message, WPARAM wParam, LPARAM lParam);

            protected:
                HWND         m_hWnd;
                StdCallThunk m_thunk;

            protected:
                static LRESULT CALLBACK StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
            };

            似乎少了點什么……創建窗口的時候,我們是不能直接把回調函數設到 StaticWndPorc 中去的,因為這個函數是希望被寫成這樣子的:

            LRESULT CALLBACK Window::StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
            {
                return ((Window *)hWnd)->WndProc(message, wParam, lParam);
            }

            那么至少需要一個臨時的回調函數,在這個函數里去設置新的回調函數(設到 m_thunk 上),再由 m_thunk 來調用 StaticWndProc,StaticWndProc 再去調用 WndProc,這樣整個過程就通了。

            但是,臨時回調函數還是需要知道從 hWnd 到 this 的對應關系。可是現在我們不能照搬用剛才的 m_sWindows 了。因為窗口在創建過程中就會調用到回調函數,需要使用到 m_sWindows 里的 this,而窗口被成功創建之前,我們沒法提前拿到 HWND 存入 m_sWindows。現在,換個方法,存當前線程 ID 與 this 的對應關系。這樣,這個類變成了:

            #include <map>

            class Window
            {
            public:
                Window();
                ~Window();

            public:
                BOOL Create();

            protected:
                LRESULT WndProc(UINT message, WPARAM wParam, LPARAM lParam);

            protected:
                HWND         m_hWnd;
                StdCallThunk m_thunk;

            protected:
                static LRESULT CALLBACK TempWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

                static LRESULT CALLBACK StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
                static std::map<DWORD, Window *> m_sWindows;
            };

            然后實現 Create 和 TempWndProc:

            BOOL Window::Create()
            {
                LPCTSTR lpszClassName = _T("ClassName");
                HINSTANCE hInstance = GetModuleHandle(NULL);

                WNDCLASSEX wcex    = { sizeof(WNDCLASSEX) };
                wcex.lpfnWndProc   = TempWndProc;
                wcex.hInstance     = hInstance;
                wcex.lpszClassName = lpszClassName;

                RegisterClassEx(&wcex);

                DWORD dwThreadId = GetCurrentThreadId();
                m_sWindows.insert(std::make_pair(dwThreadId, this));

                m_thunk.m_mov = 0x042444c7;
                m_thunk.m_jmp = 0xe9;

                m_hWnd = CreateWindow(lpszClassName, NULL, WS_OVERLAPPEDWINDOW,
                    CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

                if (m_hWnd == NULL)
                {
                    return FALSE;
                }
               
                ShowWindow(m_hWnd, SW_SHOW);
                UpdateWindow(m_hWnd);

                return TRUE;
            }

            LRESULT CALLBACK Window::TempWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
            {
                std::map<DWORD, Window *>::iterator it = m_sWindows.find(GetCurrentThreadId());
                assert(it != m_sWindows.end() && it->second != NULL);

                Window *pThis = it->second;
                m_sWindows.erase(it);

                WNDPROC pWndProc = (WNDPROC)&pThis->m_thunk;

                pThis->m_thunk.m_this = (DWORD)pThis;
                pThis->m_thunk.m_relproc = (DWORD)&Window::StaticWndProc - ((DWORD)&pThis->m_thunk + sizeof(StdCallThunk));

                m_hWnd = hWnd;
                SetWindowLong(hWnd, GWL_WNDPROC, (LONG)pWndProc);

                return pWndProc(hWnd, message, wParam, lParam);
            }

            差不多可以了,調試一下。結果,在 thunk 的第一行出錯了。我原以為地址算錯了神馬的,嘗試把 thunk.m_mov 改為 0x90909090,再運行,還是出錯。于是傻掉了……過了好一會兒才意識到,可能是因為 thunk 在數據段,無法被執行。可是,很久很久以前偶滴一個敬愛的老師在 TC 中鼓搗程序運行時改變自身代碼時,貌似無此問題啊。。。然后查呀查,原來是 Windows 在的數據執行保護搞的鬼。于是,需要用 VirtualAlloc 來申請一段有執行權限的內存。WTL 里面也是這么做的,不過它似乎維護了一塊較大的可執行內存區作為 thunk 內存池,我們這里從簡。最后,整個流程終于跑通了。最終代碼清單如下:

            #include <Windows.h>
            #include <assert.h>
            #include <map>
            #include <tchar.h>

            #pragma pack(push,1)
            typedef struct _StdCallThunk
            {
                DWORD   m_mov;
                DWORD   m_this;
                BYTE    m_jmp;
                DWORD   m_relproc;

            } StdCallThunk;
            #pragma pack(pop)

            class Window
            {
            public:
                Window();
                ~Window();

            public:
                BOOL Create();

            protected:
                LRESULT WndProc(UINT message, WPARAM wParam, LPARAM lParam);

            protected:
                HWND          m_hWnd;
                StdCallThunk *m_pThunk;

            protected:
                static LRESULT CALLBACK TempWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
                static LRESULT CALLBACK StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
                static std::map<DWORD, Window *> m_sWindows;
            };

            std::map<DWORD, Window *> Window::m_sWindows;

            Window::Window()
            {

            }

            Window::~Window()
            {
                VirtualFree(m_pThunk, sizeof(StdCallThunk), MEM_RELEASE);
            }

            BOOL Window::Create()
            {
                LPCTSTR lpszClassName = _T("ClassName");
                HINSTANCE hInstance = GetModuleHandle(NULL);

                WNDCLASSEX wcex    = { sizeof(WNDCLASSEX) };
                wcex.lpfnWndProc   = TempWndProc;
                wcex.hInstance     = hInstance;
                wcex.lpszClassName = lpszClassName;
                wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);

                RegisterClassEx(&wcex);

                DWORD dwThreadId = GetCurrentThreadId();
                m_sWindows.insert(std::make_pair(dwThreadId, this));

                m_pThunk = (StdCallThunk *)VirtualAlloc(NULL, sizeof(StdCallThunk), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
                m_pThunk->m_mov = 0x042444c7;
                m_pThunk->m_jmp = 0xe9;

                m_hWnd = CreateWindow(lpszClassName, NULL, WS_OVERLAPPEDWINDOW,
                    CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

                if (m_hWnd == NULL)
                {
                    return FALSE;
                }
               
                ShowWindow(m_hWnd, SW_SHOW);
                UpdateWindow(m_hWnd);

                return TRUE;
            }

            LRESULT Window::WndProc(UINT message, WPARAM wParam, LPARAM lParam)
            {
                switch (message)
                {
                case WM_LBUTTONUP:
                    MessageBox(m_hWnd, _T("LButtonUp"), _T("Message"), MB_OK | MB_ICONINFORMATION);
                    break;
                case WM_RBUTTONUP:
                    MessageBox(m_hWnd, _T("RButtonUp"), _T("Message"), MB_OK | MB_ICONINFORMATION);
                    break;
                case WM_DESTROY:
                    PostQuitMessage(0);
                    break;
                default:
                    break;
                }

                return DefWindowProc(m_hWnd, message, wParam, lParam);
            }

            LRESULT CALLBACK Window::TempWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
            {
                std::map<DWORD, Window *>::iterator it = m_sWindows.find(GetCurrentThreadId());
                assert(it != m_sWindows.end() && it->second != NULL);

                Window *pThis = it->second;
                m_sWindows.erase(it);

                WNDPROC pWndProc = (WNDPROC)pThis->m_pThunk;

                pThis->m_pThunk->m_this = (DWORD)pThis;
                pThis->m_pThunk->m_relproc = (DWORD)&Window::StaticWndProc - ((DWORD)pThis->m_pThunk + sizeof(StdCallThunk));

                pThis->m_hWnd = hWnd;
                SetWindowLong(hWnd, GWL_WNDPROC, (LONG)pWndProc);

                return pWndProc(hWnd, message, wParam, lParam);
            }

            LRESULT CALLBACK Window::StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
            {
                return ((Window *)hWnd)->WndProc(message, wParam, lParam);
            }

            int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
            {
                Window wnd;
                wnd.Create();

                MSG msg;

                while (GetMessage(&msg, NULL, 0, 0))
                {
                    TranslateMessage(&msg);
                    DispatchMessage(&msg);
                }

                return (int)msg.wParam;
            }

            剛才有一處,存 this 指針的時候,我很武斷地把它與當前線程 ID 關聯起來了,其實這正是 WTL 本身的做法。它用 CAtlWinModule::AddCreateWndData 存的 this,最終會把當前線程 ID 和 this 作關聯。我是這么理解的吧,同一線程不可能同時有兩處在調用 CreateWindow,所以這樣取回來的 this 是可靠的。

            好了,到此為止,邊試驗邊記錄的,不知道理解是否正確。歡迎指出不當之處,也歡迎提出相關的問題來考我,歡迎介紹有關此問題的新方法、新思路,等等,總之,請各位看官多指教哈。

            posted on 2010-10-24 16:44 溪流 閱讀(6605) 評論(40)  編輯 收藏 引用 所屬分類: C++Windows

            評論:
            # re: 學習下 WTL 的 thunk 2010-10-24 17:43 | OwnWaterloo
            virtualalloc不是這樣用的啊, 同學……
            virtualalloc的粒度很大(保留按64k, 提交按頁)。
            也就是說, 如果直接用virtualalloc, 每個window instance, 要占用64k地址空間, 并使用至少4k內存。


            ATL不同版本處理這個問題好像采取的策略不同。
            有你提到的使用virtualalloc分配, 然后再劃分。
            還有直接使用HeapCreate創建一個分配可執行內存的win32 heap。


            這些技術很炫, 研究起來也很有成就感。
            玩具代碼當然可以隨便用, 但成熟代碼盡可能選取成熟的技術。

            比如全局table。它的效率問題真的無法忍受嗎? 或者全局帶來的其他問題(多線程相關)很要緊嗎?

            如果窗口類是自己注冊的, 可以用cbExtra。
            如果不是, 而且GWL_USERDATA是開放的(對話框和MDI是保留給自己用的), 那可以用GWL_USERDATA。

            還可以用Set/GetWindowProp。

            如果thunk機器相關的缺陷不要緊, 而且上述的這些成熟的方案都不合適, 也不一定非要自己寫內存池, HeapCreate在windows上總是可用的。  回復  更多評論
              
            # re: 學習下 WTL 的 thunk 2010-10-24 18:00 | 溪流
            @OwnWaterloo
            啊。。我真的沒打算寫這方面輪子~~~只是想順著 WTL 的做法自己跑一遍,以了解僅僅看別人文章所看不出的細節。。。VirtualAlloc確實沒用過哈,于是剛才胡亂搞了一通,只求拿到內存,慚愧~  回復  更多評論
              
            # re: 學習下 WTL 的 thunk 2010-10-24 18:02 | 溪流
            @OwnWaterloo
            嗯對了,你覺得WTL本身算成熟技術嗎?  回復  更多評論
              
            # re: 學習下 WTL 的 thunk 2010-10-24 18:08 | OwnWaterloo
            @溪流
            沒什么好慚愧的呀……
            實驗、 玩具代碼這很正常。 為了突出重點(機器代碼的構造)而非內存管理。
            造輪子也是很好的練習。

            只是, 輪子在投入正規使用前, 一定要謹慎。
            玩具代碼中忽略的非重點, 恰恰可能占了很多代碼, 繁瑣、 無趣、 bug叢生……
            如果真有恒心把這些問題處理好, 再和現有的輪子客觀的比較權衡之后, 再考慮使用。


            因為看過許多把玩具代碼 —— 而且是一些相當沒有技術含量, 僅僅是滿足個人審美觀而已 —— 的代碼放入實際工程中, 還很自鳴得意的……
            慘不忍睹啊……
              回復  更多評論
              
            # re: 學習下 WTL 的 thunk 2010-10-24 18:13 | OwnWaterloo
            @溪流
            WTL我不熟, 深入研究的也就是和thunk相關的東西……

            給人的感覺是模板使用過度。
            比如, 居然用一個模板參數去傳遞兩個窗口風格參數。
            感覺是C++的混沌年代, 大家都還不知道如何合理使用模板的情況下, 產生的東西……
            所以也不愿意多看……

            想研究gui的話, cppblog有個 cexer, 看過很多gui的框架, 可以向他咨詢。  回復  更多評論
              
            # re: 學習下 WTL 的 thunk 2010-10-24 21:33 | 陳梓瀚(vczh)
            @溪流
            好快……其實我是看錯了,所以把留言刪掉了你竟然在這幾秒鐘內……  回復  更多評論
              
            # re: 學習下 WTL 的 thunk 2010-10-24 21:34 | 溪流
            @陳梓瀚(vczh)
            哈哈,碰巧在刷~  回復  更多評論
              
            # re: 學習下 WTL 的 thunk 2010-10-24 21:36 | 陳梓瀚(vczh)
            一般來說,如果你自己想用滿足各種要求的函數對象,就去用std::function哈。當然這個實現起來比較麻煩,假設支持10個參數,你就要特化0-10個參數,而且返回void和其他的各一份,一共22個類,還得考慮寫程序或者用宏搞代碼生成……然后如果你想實現bind和curry,那就更多類了……

            幸好std已經有function了,哇哈哈  回復  更多評論
              
            # re: 學習下 WTL 的 thunk 2010-10-24 21:38 | 溪流
            @陳梓瀚(vczh)
            function 現在 std 了?我土了。。  回復  更多評論
              
            # re: 學習下 WTL 的 thunk 2010-10-24 21:53 | OwnWaterloo
            @陳梓瀚(vczh)
            這問題用bind解決不了。

            bind生成的對象, 需要template配合。
            比如: for_each(begin, end, bind( ... ) );
            for_each是一個函數模板。

            而WndProc必須是一個函數指針。
            lpWndProc = bind( ... ); 是不行的。
              回復  更多評論
              
            # re: 學習下 WTL 的 thunk 2010-10-24 21:57 | 溪流
            @OwnWaterloo
            剛才逛了下cexer的博客,開場白太好太強大了,盡看開場白去了哈  回復  更多評論
              
            # re: 學習下 WTL 的 thunk 2010-10-24 22:02 | 溪流
            @OwnWaterloo
            我以為你說 WTL 模板使用過度神馬的可能會引人打架,怎么這么久都沒有人來打醬油呢~ ^_^  回復  更多評論
              
            # re: 學習下 WTL 的 thunk 2010-10-24 22:10 | OwnWaterloo
            @溪流
            因為用的人少啊  回復  更多評論
              
            # re: 學習下 WTL 的 thunk 2010-10-25 09:26 | waiting4you
            打醬油的來了~~希望沒打攪幾位,呵呵.
            我只是有一點小疑問:這種方法在64位系統下可以工作嗎?StdCallThunk里放在代碼塊是否x64系統兼容?
            WTL應該可以編譯為64位程序,不知道WTL的源碼中有沒有針對x64的編譯開關  回復  更多評論
              
            # re: 學習下 WTL 的 thunk 2010-10-25 09:33 | OwnWaterloo
            @waiting4you
            有。
            Windows支持的體系結構里, ATL的stdthunk都有實現。
            編譯switch也不用自己操心, 都寫好了的。
              回復  更多評論
              
            # re: 學習下 WTL 的 thunk 2010-10-25 10:13 | waiting4you
            @OwnWaterloo
            謝謝,看來這種實現的可移植性不太好,了解一下它的實現可以開闊視野.只是自己應用這種技術來編碼就得好好考慮一下了.像ATL/WTL這樣有強大的微軟或開源社區(偶不確定這個thunk部分應該歸哪個"部門"管,開源的WTL基于商業的ATL~!@#$)支持的代碼來說,這不是問題.但是我們自己寫就...  回復  更多評論
              
            # re: 學習下 WTL 的 thunk 2010-10-25 10:29 | 空明流轉
            WTL的高手還沒發話沒得打架的。
            我沒覺得WTL模板用的多。。。  回復  更多評論
              
            # re: 學習下 WTL 的 thunk 2010-10-25 10:42 | 空明流轉
            順便說一下,那個什么所謂的Thunk,也就是一個JIT的雛形。。。  回復  更多評論
              
            # re: 學習下 WTL 的 thunk[未登錄] 2010-10-25 12:34 | dd
            請問下:(DWORD)&Window::StaticWndProc - ((DWORD)pThis->m_pThunk + sizeof(StdCallThunk));這里面的(DWORD)m_pThunk + sizeof(StdCallThunk)是正確的嗎?sizeof(StdCallThunk)得到的是這個結構體的字節數 13 byte,然后把字節數的單位變成DWORD加上,即 sizeof(DWORD)*13...  回復  更多評論
              
            # re: 學習下 WTL 的 thunk 2010-10-25 13:03 | 溪流
            @dd
            (DWORD)(pThis->m_pThunk),一個很原始很普通的數字,再加上 sizeof(StdCallThunk),也就是加上13。沒有sizeof(DWORD)*13。嗯。  回復  更多評論
              
            # re: 學習下 WTL 的 thunk 2010-10-25 22:36 | 路過
            boost::function即可  回復  更多評論
              
            # re: 學習下 WTL 的 thunk 2010-10-25 23:01 | 路過
            @OwnWaterloo

            class Thread
            {
            LONG ThreadProc(LPVOID lParam)
            {
            boost::function<void> *pFunc = (boost::function<void> *) lParam;
            (*pFunc)();
            delete pFunc;
            }

            template<typename Callable>
            void Run(Callable callable)
            {
            boost::function<void> *pFunc = new boost::function<void>;
            *pFunc = callable;
            ::CreateThread(NULL, 0, StaticThreadProc, (LPVOID) pFunc, 0, NULL);
            }
            }

            int _tmain(int argc, _TCHAR* argv[])
            {
            Task task;
            Thread thread;

            thread.Run(boost::bind(&Task::DoSomething, &task, 1, 2, ... n));
            }
            }  回復  更多評論
              
            # re: 學習下 WTL 的 thunk 2010-10-25 23:09 | OwnWaterloo
            @路過
            喔唷! 好華麗, 好耀眼哦!
            class, thread, callable, run什么的, 我看不懂也!

            我就問一句, 還望賜教賜教啊~

            如果CreateThread使用的線程函數的簽名是這個樣子:
            unsigned threadproc(void); // 注意, 是void, 而不是void*

            你用上面那些華麗的東西給我看看?
            全局變量什么的也丑的, 也不許用哦!


            WndProc就是這樣一個場景 —— 回調的簽名沒有void* context。
            您, 明白了嗎?  回復  更多評論
              
            # re: 學習下 WTL 的 thunk 2010-10-25 23:24 | 路過
            @OwnWaterloo

            >> 如果CreateThread使用的線程函數的簽名是這個樣子:
            那是如果,這樣的線程api可用嗎?windows,linux都沒有

            >> WndProc就是這樣一個場景 —— 回調的簽名沒有void* context。
            用GWLP_USERDATA不行?

            如果設計者真的設計了這種用戶無法擴展的api,那您是對的
              回復  更多評論
              
            # re: 學習下 WTL 的 thunk 2010-10-25 23:27 | 路過
            @OwnWaterloo

            多嘴說句,沒有如果;

            如果真有“如果“,我也不會回復你  回復  更多評論
              
            # re: 學習下 WTL 的 thunk 2010-10-26 00:10 | OwnWaterloo
            @路過
            作者討論的是“如何開啟一個線程”嗎?
            不, 作者討論的是如何給WndProc塞入一個context。

            哪誰在討論ThreadProc?
            是你, 將討論從WndProc轉移到ThreadProc。

            我怕你不懂WndProc和ThreadProc究竟有什么重大區別,
            以及, 也許你不熟悉WndProc,
            所以想就近從你熟悉的ThreadProc中給你一點啟發 —— 就是移除ThreadProc的context參數。
            設想一下ThreadProc如果沒有void*參數, 編程會是個什么樣子, api有多難擴展。
            以及, 問題的關鍵究竟是什么?

            你是不明白啊, 還是不明白啊, 還是不明白?


            多說一句, GWL_USERDATA我在上面已經提到過, 是誰回帖不看貼的?
            再多提醒一句, GWL_USERDATA并不是你想象的那么簡單。
            究竟是什么, 請繼續看我在樓上的回復。
              回復  更多評論
              
            # re: 學習下 WTL 的 thunk 2010-10-26 19:09 | 路過
            @OwnWaterloo

            >> 哪誰在討論ThreadProc?
            請看樓主發的第一個例程,

            我用threadproc來舉例一種靈活的方法而已

            照這種方法法不能推導出wndproc的用法?需要在這上面挑刺?
            你要挑刺我那例程語法也有問題,你可以繼續

            >>設想一下ThreadProc如果沒有void*參數, 編程會是個什么樣子, api有多難擴展。

            還是“如果”,有意義?

            >>多說一句, GWL_USERDATA我在上面已經提到過, 是誰回帖不看貼的?
            我知道你提過,既然你知道GWL_USERDATA,那我提的方法就是可行的;但你沒發現,所以告訴你



            最后,請你搞清楚一點,

            我之所以回復你,是有人說可以用bind,你說不行;我告訴怎么做可以行,明白不?都不知道你噴什么
              回復  更多評論
              
            # re: 學習下 WTL 的 thunk 2010-10-26 19:15 | OwnWaterloo
            @路過
            lz的標題是 WTL thunk。
            thunk你懂嗎? thunk解決的是華麗的語法, 還是缺少的context參數?
              回復  更多評論
              
            # re: 學習下 WTL 的 thunk 2010-10-26 19:20 | 溪流
            @路過
            @OwnWaterloo

            啊。。。熄火熄火~~~最開始提出bind可能原意并非完全針對我在頂樓說的情形,路過同學的例子確實也指出了一個用bind拐個彎設置到原始C函數指針上去的方法,不過用不用bind都沒法解決WndProc少一個參數的硬傷……lz是來打醬油的,請無視~  回復  更多評論
              
            # re: 學習下 WTL 的 thunk 2010-10-26 19:27 | OwnWaterloo
            >>照這種方法法不能推導出wndproc的用法?
            >>還是“如果”,有意義?
             
            你有點起碼的邏輯分析能力嗎?
            說threadproc是幫助你理解這兩者的不同, 既然不同, 那就是不可導出的。
            既然好心被當作驢肝肺, 那別什么"如果"了, 你自己動手導出wndproc試試。
             
            然后, 摸著自己的良心說: wndproc缺少的參數是靠什么傳入的
            是bind嗎? 是function嗎? 是華麗的語法嗎?
             
            還是我在前面提到的那些東西: table, cbExtra, GWL_USERDATA, Set/GetProp, thunk?
             
              回復  更多評論
              
            # re: 學習下 WTL 的 thunk 2010-10-26 19:30 | OwnWaterloo
            @路過
            最煩這種瞅住機會就迫不及待的秀一下自己似是而非的理解, 離題萬里
            浮躁
              回復  更多評論
              
            # re: 學習下 WTL 的 thunk 2010-10-26 19:35 | OwnWaterloo

            @溪流
            >> 路過同學的例子確實也指出了一個用bind拐個彎設置到原始C函數指針上去的方法

            bind生成的可調用體 —— 一個instance —— 依然需要通過某種方式傳遞到callback中
            而現在的問題就是如何將這個context傳遞進去
             
            所以我一直說, bind根本就不是解決這個問題的方法, 它本身需要這個問題解決之后才能使用
              回復  更多評論
              
            # re: 學習下 WTL 的 thunk 2011-03-17 23:11 | 溪流
            @OwnWaterloo
            又看了一些人對回調函數的理解,覺得你在這個問題上如此激烈的反應是必要的,太多人認為function之類的玩意兒就能解決回調函數少參數的問題了,甚至沒意識到這個問題是什么問題。。。  回復  更多評論
              
            # re: 學習下 WTL 的 thunk 2011-03-17 23:24 | OwnWaterloo
            @溪流
            嘿, 多謝理解~  回復  更多評論
              
            # re: 學習下 WTL 的 thunk 2011-03-17 23:51 | 溪流
            @OwnWaterloo
            http://topic.csdn.net/u/20110221/16/86608158-ac06-4a45-b3bb-7f53ba3008fc.html,好多自以為是的人吶,還有人說“去看chrome源碼吧”  回復  更多評論
              
            # re: 學習下 WTL 的 thunk 2013-04-27 17:37 | 溪流
            @OwnWaterloo
            最近有點想給TimerProc成員化的欲望~  回復  更多評論
              
            # re: 學習下 WTL 的 thunk[未登錄] 2015-08-26 11:05 | Mark
            是我看過分析Thunk技術最清晰的文章  回復  更多評論
              
            # re: 學習下 WTL 的 thunk 2015-10-25 00:46 | WXX
            這個做法還是有個全局的static std::map<DWORD, Window *> m_sWindows
            感覺沒有比mfc那種查找的做法好多少啊  回復  更多評論
              
            # re: 學習下 WTL 的 thunk 2015-10-26 00:27 | 溪流
            @WXX
            請看完全文吧  回復  更多評論
              
            # re: 學習下 WTL 的 thunk 2023-05-15 16:39 | 溪流
            @Mark
            謝謝~!  回復  更多評論
              
            免费精品久久天干天干| 国内精品伊人久久久久妇| 99久久99久久精品国产| 亚洲v国产v天堂a无码久久| 久久精品人人做人人爽电影| 久久精品www人人爽人人| 久久99精品国产麻豆婷婷| 99精品国产综合久久久久五月天 | 久久精品aⅴ无码中文字字幕不卡| 久久久噜噜噜久久熟女AA片| 久久久久99精品成人片三人毛片| 久久久久久亚洲AV无码专区| 性高湖久久久久久久久AAAAA| 97久久超碰国产精品2021| 国内高清久久久久久| 久久久久国产精品麻豆AR影院| 国产精品久久久久AV福利动漫| 久久无码人妻精品一区二区三区| AV狠狠色丁香婷婷综合久久| 午夜精品久久久久久毛片| 婷婷久久综合九色综合绿巨人| 国产精品无码久久四虎| 久久免费视频观看| 91精品国产高清91久久久久久| 欧美午夜精品久久久久免费视| 一本大道久久香蕉成人网| 久久精品无码一区二区三区免费 | 久久久亚洲裙底偷窥综合| 久久一区二区三区免费| 久久久久99精品成人片牛牛影视| 亚洲嫩草影院久久精品| 国产99精品久久| 久久久久人妻一区二区三区vr| 精产国品久久一二三产区区别| 久久精品国产99国产精品导航| 国产欧美久久久精品影院| 精品一二三区久久aaa片| 欧美一区二区三区久久综合| 国产成人综合久久综合| 国产—久久香蕉国产线看观看| 久久久黄片|