1. 程式人生 > >一種巧妙的取類的虛擬函式指標的方法

一種巧妙的取類的虛擬函式指標的方法

 

        熟悉C++開發的朋友們都知道,每一個包含虛擬函式的類的物件的前四個位元組(32位系統中,以下例子都是在32位系統下)的記憶體中存放著該物件的虛擬函式表的指標。虛擬函式表中依次存放著該物件的每個虛擬函式的地址。

舉個例子:

class TestA

{

public:

       TestA();

       ~TestA();

       virtual void  _cdecl testAFunc();//給該函式加上_cdecl約定是為了保證成員函式的第一個引數this指標可以在該成員函式被呼叫時壓入引數棧

       virtual void  _cdecl testAFunc1(); //理由同上

};

TestA::TestA()

{

}

TestA::~TestA()

{

}

void TestA::testAFunc()

{

       cout<<"testAFunc is called!"<<endl;

}

void TestA::testAFunc1()

{

       cout<<"testAFunc1 is called!"<<endl;

}

int main()

{

TestA a;

return 0;

}

對於TestA這個類的物件a來說,它在記憶體中的佈局如下:

 

我們可以採用取記憶體的方法來獲得某一個虛擬函式的指標,方法如下:

typedef  void(*Func) (void);

在main函式中加入以下程式碼:

Func pFunc = NULL;

pFunc = (Func)*((int *)(*(int*)(&a)) + 0);

pFunc();                    //實際呼叫的是testAFunc()

pFunc = (Func)*((int *)(*(int*)(&a)) + 1);

pFunc();                    //實際呼叫的是testAFunc1()

輸出為:

testAFunc is called!

testAFunc1 is called!

但是通過記憶體來取虛擬函式指標的方法有點繁瑣,稍微一個不留神忘了指標轉換或者解除引用就會導致錯誤,這裡,我給大家介紹一種清晰簡單的方法來取虛擬函式。

如上圖所示,虛擬函式表其實是一塊連續的記憶體,裡面每個元素(每四個位元組)都是一個函式指標,這樣的話,我們完全可以把它看做一個結構體,該結構體的每個成員是一個函式指標。

struct TestA_Vtpr

{

       void (*Hook)(TestA *This); //該函式指標的引數列表和返回值必須與類的對應的虛擬函式的引數列表和返回值一致

       void (*Hook1)(TestA *This);//同上

};    

如此這樣的話,我們完全可以用以下方式來引用TestA 類的虛擬函式

(將以下程式碼加入main函式)

TestA a;

TestA_Vtpr **pVtable = (TestA_Vtpr**)(void*)&a;

 TestA_Vtpr *pVtpr = *pVtable;

  pVtpr->Hook(&a);

  pVtpr->Hook1(&a);

   (&a)->testAFunc();  //必須用指向a物件的指標來引用testAFunc,否則不會去查詢虛表

   (&a)->testAFunc1(); //同上

程式輸出如下:

testAFunc is called!

testAFunc1 is called!

testAFunc is called!

testAFunc1 is called!

可以看到pVtpr->Hook(&a); 和 (&a)->testAFunc();執行的結果一樣的。

親們,你們明白了麼?