• <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>

            隨感而發

            雜七雜八

            統計

            留言簿(13)

            閱讀排行榜

            評論排行榜

            API回調成員函數 THUNK

            想來想去還是羅嗦一下,API只能回調全局函數,而我們有時候希望他能回調成員函數。最常用的就是Timmer和窗口回調。要實現這個需求,就會用到THUNK技術。THUNK 我查了一下是:thunk  名詞 n.  ;,鏘 。跟這個完全沒有關系嘛(看來英文太爛是壞處還是挺多的)。學習了一下之后,我理解的意思就是:貍貓換太子。替換原來意圖,轉調我們需要的地址。

            Thunk的原理其實說起來很簡單:巧妙的將數據段的幾個字節的數據設為特殊的值,然后告訴系統,這幾個字節的數據是代碼(即將一個函數指針指向這幾個字節的第一個字節),讓系統來執行

            API回調成員函數:

            直接用成員函數的地址傳給作為API的回調函數顯然會編譯出錯的,原因是他們的調用規則不一致,C++編譯器不允許這樣做。具體可以參考:

            http://hi.baidu.com/shongbee2/blog/item/7867de9744e3c26155fb9611.html

            而剛好THUNK技術就是讓數據段當做代碼斷用,如果我把回調函數地址用一個數據段給他,然后在數據段中再跳轉到成員函數的地址。這樣就可以間接的調用成員函數了。不錯,我就是學習的這個方法。嘻嘻。。

            大致方向知道了,還得了解一下細節,函數調用的規則:

            建議看一下http://hi.baidu.com/shongbee2/blog/item/7867de9744e3c26155fb9611.html(也就是上一篇文章啦。)需要注意的:調用者怎么處理棧,被調用者怎么使用棧和處理棧。系統回調函數基本上都是_stdcall的調用方式,成員函數是__thiscall的調用方式。他們的區別為:

            關鍵字

            堆棧清除

            參數傳遞

            __stdcall

            被調用者

            將參數倒序壓入堆棧(自右向左)

            __thiscall

            被調用者

            壓入堆棧,this指針保存在 ECX 寄存器中

            發現他們唯一不同的就是__thiscallthis指針保存到了ECX的寄存器中。其他都是一樣的。這種情況我們就方便了,我們只需在他調用我們的時候,我們吧this指針保存到ECX,然后跳轉到期望的成員函數地址就可以了。

            //我認為思路就是這樣了。接下來是實現,貼源代碼:

            #include "stdafx.h"
            #include "wtypes.h"

            #include <iostream>
            using namespace std;

            typedef void (*FUNC)(DWORD dwThis);
            typedef int (_stdcall *FUNC1)(int a, int b);
            #pragma pack(push,1)
            typedef struct tagTHUNK
            {
                BYTE    bMovEcx;   //MOVE ECX 將this指針移動到ECX的指令
                DWORD    dwThis;   // this   this指針的地址
                BYTE    bJmp;    //jmp   跳轉指令
                DWORD    dwRealProc; //proc offset 跳轉偏移

                void Init(DWORD proc,void* pThis)
                {
               bMovEcx = 0xB9;        //注釋見下面說明^_^
                    dwThis = (DWORD)pThis;
                    bJmp = 0xE9;
                    dwRealProc = DWORD((INT_PTR)proc - ((INT_PTR)this+sizeof(THUNK)));
                    FlushInstructionCache(GetCurrentProcess(),this,sizeof(THUNK));
                }
            }THUNK;
            #pragma pack(pop)
            /**************************************************************************************
            void Init(DWORD proc,void* pThis)里面的說明:
            0xB9 為MOVE ECX的指令, 0xE9 跳轉的指令,這段初始化表示:
            0013FF54 mov         ecx, ptr [this]
            0013FF59 jmp         dwRealProc
            這個單步一下便知。
            下面那個API :FlushInstructionCache,查MSDN,表示刷新緩存,
            因為我們修改了數據,建議他重新載入一下。

            我最不能理解的是jmp的偏移是為什么是那樣計算,所以這里也著重說明一下:
            jmp跳轉的是當前指令地址的偏移,我們參數中proc是實際函數的地址,我們需要
            把他轉為jmp的偏移: 實際函數地址-jmp指令地址。
            實際函數地址就是proc,jmp地址就是((INT_PTR)this+sizeof(THUNK)),所以就得到
            dwRealProc = DWORD((INT_PTR)proc - ((INT_PTR)this+sizeof(THUNK)));這行代碼
            還有一點,我對匯編不了解,下面是YY:為什么不是:
            dwRealProc = DWORD((INT_PTR)proc - ((INT_PTR)this+sizeof(THUNK)) - sizeof(dwRealProc))
            直觀上看jmp地址不是:this + sizeof(bMoveEcx) + sizeof(dwThis) + sizeof(bJmp)嗎?
            也就是((INT_PTR)this+sizeof(THUNK)) - sizeof(dwRealProc) 啊。可是我看了一下編譯的結果,
            發現0013FF59 jmp         dwRealProc 是一行的,也就是jmp地址實際就是:
            ((INT_PTR)this+sizeof(THUNK)) 這個地址。經過測試也沒有問題,我就認為是這樣了,不對的還
            忘多指出。嘻嘻。
            還有一個容易混淆的,就是我們會傳入this指針,在dwRealProc里面和 FlushInstructionCache
            里面都用到了this。這里要注意啦:如果你不知道傳入的參數this指針和使用的這個this的話,你就該
            重新復習一下C++基礎了。解釋一下:傳入的this指針變為參數pThis,使用的this是THUNK的this。^_^
            *****************************************************************************************/

            template<typename dst_type,typename src_type>
            dst_type pointer_cast(src_type src)
            {
            return *static_cast<dst_type*>( static_cast<void*>(&src) );
            }

            class Test
            {
            public:
            int m_nFirst;
                THUNK m_thunk;
                int      m_nTest;

                //構造函數中初始化為3,僅為測試,以便查看外面的方法JmpedTest是否可以正確取得這個值
                Test() : m_nTest(3),m_nFirst(4)
                {}

                void TestThunk()
                {
               m_thunk.Init(pointer_cast<int>(&Test::Test2),this);
                    FUNC1 f = (FUNC1)&m_thunk;
                    f(1,2);
                    cout << "Test::TestThunk()" << endl;
                }

            int Test2(int a, int b)
            {
               cout << a << " " << b << " " << m_nFirst << " " << m_nTest << endl;
               return 0;
            }
            };

            int _tmain(int argc, _TCHAR* argv[])
            {
                Test t;
                t.TestThunk();
                system("pause");
                return 0;
            }

            總結:

            這個明顯是暴力的去強制跳轉,直接把指令寫入到數據段中,增加了出錯的風險,而且可移植性變的很差。所以盡量少用。

            要弄清楚函數調用規則和堆棧的平衡。如果你用_cedcl規則的函數調用的話,就會出錯啦。

            學習代碼中只是處理了簡單的情況,還有幾種方式,例如不是強制跳轉,而是用call的方式調用,也可以實現。對于其他的函數規則例如成員函數是_stdcall,他是參數壓棧的,這個THUNK的寫法也不一樣了。。

            因為數據段中用到了this,函數回調中會用到它,所以一定要保證這個this有效。特別是窗口回調函數,如果釋放了變量,但是窗口沒有銷毀是很容易出問題的。窗口回調函數也有比較喜歡用一個靜態的分配器,通過窗口識別,把他分配到不同的成員處理函數中的方式。

            這個只是初學,原因是發現ATL的窗口回調是這樣做的。覺得很神奇,所以學習了一下,有不對的地方還望多多指教。嘻嘻。。。

            找到的資料:

            http://www.vckbase.com/document/viewdoc/?id=1821

            http://www.codeproject.com/KB/cpp/GenericThunks.aspx

            http://blog.csdn.net/superarhow/archive/2006/07/10/898261.aspx

            http://www.cnblogs.com/homeofish/archive/2009/02/20/1395208.html

            posted on 2010-12-11 16:14 shongbee2 閱讀(1591) 評論(0)  編輯 收藏 引用 所屬分類: c/c++

            一本大道久久a久久精品综合| 亚洲成色www久久网站夜月| 国产精品亚洲综合专区片高清久久久| 91精品国产乱码久久久久久 | 久久国产亚洲精品无码| 久久精品国产亚洲AV无码娇色| 99久久国产主播综合精品| 亚洲国产婷婷香蕉久久久久久| 色偷偷偷久久伊人大杳蕉| 国产精品无码久久久久| 天天躁日日躁狠狠久久| 日韩欧美亚洲国产精品字幕久久久| 精品国产乱码久久久久软件| 久久久精品免费国产四虎| 久久综合亚洲色一区二区三区| 免费观看成人久久网免费观看| 18禁黄久久久AAA片| 国产高潮久久免费观看| 欧美午夜精品久久久久免费视| 久久精品无码一区二区日韩AV| 久久无码人妻一区二区三区午夜 | 九九精品99久久久香蕉| 亚洲国产成人久久综合碰| 99久久人妻无码精品系列| 无码八A片人妻少妇久久| 激情五月综合综合久久69| 色偷偷888欧美精品久久久| 久久夜色精品国产噜噜亚洲AV| 奇米影视7777久久精品人人爽| 久久99精品久久久久久不卡| 久久久精品一区二区三区| 国产精品久久99| 国产精品久久毛片完整版| 久久精品国产亚洲AV蜜臀色欲| 欧美国产精品久久高清| 久久午夜福利电影| 久久99精品国产麻豆不卡| 久久精品国产亚洲av瑜伽| 久久精品国产福利国产琪琪| 欧美与黑人午夜性猛交久久久| 午夜精品久久影院蜜桃|