青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

woaidongmao

文章均收錄自他人博客,但不喜標題前加-[轉貼],因其丑陋,見諒!~
隨筆 - 1469, 文章 - 0, 評論 - 661, 引用 - 0
數據加載中……

直接調用類成員函數地址

下載源代碼

摘要:介紹了如何取成員函數的地址以及調用該地址.

關鍵字:C++成員函數 this指針 調用約定

一、成員函數指針的用法

  在C++中,成員函數的指針是個比較特殊的東西。對普通的函數指針來說,可以視為一個地址,在需要的時候可以任意轉換并直接調用。但對成員函數來說,常規類型轉換是通不過編譯的,調用的時候也必須采用特殊的語法。C++專門為成員指針準備了三個運算符: "::*"用于指針的聲明,而"->*"".*"用來調用指針指向的函數。比如:

   class tt
   {
       public: void foo(int x){ printf("\n %d \n",x); }
   };
 
   typedef   void  ( tt::* FUNCTYPE)(int );
 
 
    FUNCTYPE ptr = tt::foo;  //給一個成員函數指針賦值.
 
    tt a;
    (a.*ptr)(5);   //調用成員函數指針.
 
    tt *b = new tt;
    (b->*ptr)(6);  //調用成員函數指針.


  注意調用函數指針時括號的用法,因為 .* ->* 的優先級比較低,必須把它們和兩邊要結合的元素放到一個括號里面,否則通不過編譯。不僅如此,更重要的是,無法為成員函數指針進行任何的類型轉換,比如你想將一個成員函數的地址保存到一個整數中(就是取類成員函數的地址),按照一般的類型轉換方法是辦不到的.下面的代碼:

    DWORD dwFooAddrPtr= 0;
    dwFooAddrPtr = (DWORD) &tt::foo;  /* Error C2440 */
    dwFooAddrPtr = reinterpret_cast (&tt::foo); /* Error C2440 */

  你得到只是兩個c2440錯誤而已。當然你也無法將成員函數類型轉換為其它任何稍有不同的類型,簡單的說,每個成員函數指針都是一個獨有的類型,無法轉換到任何其它類型。即使兩個類的定義完全相同也不能在其對應成員函數指針之間做轉換。這有點類似于結構體的類型,每個結構體都是唯一的類型,但不同的是,結構體指針的類型是可以強制轉換的。有了這些特殊的用法和嚴格的限制之后,類成員函數的指針實際上是變得沒什么用了。這就是我們平常基本看不到代碼里有"::*", ".*" "->*"的原因。

二、取成員函數的地址

  當然,引用某位大師的話:"windows中,我們總是有辦法的"。同樣,在C++中,我們也總是有辦法的。這個問題,解決辦法已經存在了多年,并且廣為使用(在MFC中就使用了)。一般有兩個方法,一是使用內嵌的匯編語言直接取函數地址,二是使用union類型來逃避C++的類型轉換檢測。兩種方法都是利用了某種機制逃避C++的類型轉換檢測,為什么C++編譯器干脆不直接放開這個限制,一切讓程序員自己作主呢?當然是有原因的,因為類成員函數和普通函數還是有區別的,允許轉換后,很容易出錯,這個在后面會有詳細的說明。現在先看看取類成員函數地址的兩種方法:

第一種方法:

template 
void GetMemberFuncAddr_VC6(ToType& addr,FromType f)
{
    union 
    {
      FromType _f;
      ToType   _t;
    }ut;
 
    ut._f = f;
 
    addr = ut._t;
}


這樣使用:

DWORD dwAddrPtr;

GetMemberFuncAddr_VC6(dwAddrPtr,&tt::foo);

  為什么使用模版? 呵呵,如果不使用模版,第二個參數該怎么些,寫成函數指針且不說太繁瑣,關鍵是沒有通用性,每個成員函數都要單獨寫一個轉換函數。

第二種方法:

#define GetMemberFuncAddr_VC8(FuncAddr,FuncType)\
{                                               \
    __asm                                       \
    {                                           \
        mov eax,offset FuncType                 \
    };                                          \
    __asm                                       \
    {                                           \
        mov FuncAddr, eax                       \
    };                                          \
}
 

這樣使用:

DWORD dwAddrPtr;

GetMemberFuncAddr_VC8(dwAddrPtr,&tt::foo);

  本來是想寫成一個模版函數的,可惜雖然通過了編譯,卻不能正確運行。估計在匯編代碼中使用模版參數不太管用,用offset取偏移量直接就得0
  上面的宏是可以正確運行的,并且還有一個額外的好處,就是可以直接取私有成員函數的地址(大概在asm括號中,編譯器不再檢查代碼的可訪問性)。不過缺點是它在vc6下是無法通過編譯的,只能在VC8下使用。

三、調用成員函數地址

  通過上面兩個方法,我們可以取到成員函數的地址。不過,如果不能通過地址來調用成員函數的話,那也還是沒有任何用處。當然,這是可行的。不過在這之前,需要了解關于成員函數的一些知識。
  我們知道,成員函數和普通函數最大的區別就是成員函數包含一個隱藏的參數this指針,用來表明成員函數當前作用在那一個對象實例上。根據調用約定(Calling Convention)的不同,成員函數實現this指針的方式也不同。如果使用__thiscall調用約定,那么this指針保存在寄存器ECX中,VC編譯器缺省情況下就是這樣的。如果是__stdcall__cdecl調用約定,this指針將通過棧進行傳遞,且this指針是最后一個被壓入棧的參數,相當于編譯器在函數的參數列表中最左邊增加了一個this參數。
  這里還有件事不得不提,雖然vc__thiscall類型作為成員函數的默認類型,但是vc6卻沒有定義__thiscall關鍵字!如果你使用__thiscall來定義一個函數,編譯器報錯:'__thiscall' keyword reserved for future use

知道這些就好辦了,我們只要根據不同的調用約定,準備好this指針,然后象普通函數指針一樣的使用成員函數地址就可以了。

  對__thiscall類型的成員函數(注意,這個是VC的默認類型),我們在調用之前加一句: mov ecx, this; 然后就可以調用成員函數指針。例如:

class tt 
{
 public:
 
    void foo(int x,char c,char *s)//沒有指定類型,默認是__thiscall.
    {
        printf("\n m_a=%d, %d,%c,%s\n",m_a,x,c,s);
    }
 
    int m_a;
};
 
typedef  void (__stdcall *FUNCTYPE)(int x,char c,char *s);//定義對應的非成員函數指針類型,注意指定__stdcall.
 
 
    tt abc;
    abc.m_a = 123;
 
    DWORD ptr;
    DWORD This = (DWORD)&abc;
 
    GetMemberFuncAddr_VC6(ptr,tt::foo); //取成員函數地址.
 
    FUNCTYPE fnFooPtr  = (FUNCTYPE) ptr;//將函數地址轉化為普通函數的指針. 
 
    __asm //準備this指針.
    {
        mov ecx, This;
    }
 
    fnFooPtr(5,'a',"7xyz"); //象普通函數一樣調用成員函數的地址.


  對其它類型的成員函數,我們只要申明一個與原成員函數定義完全類似的普通函數指針,但在參數中最左邊加一個void * 參數。代碼如下:

class tt 
{
 
public:
 
    void __stdcall foo(int x,char c,char *s)//成員函數指定了__stdcall調用約定.
    {
        printf("\n m_a=%d, %d,%c,%s\n",m_a,x,c,s);
    }
 
    int m_a;
};
 
typedef  void (__stdcall *FUNCTYPE)(void *This,int x,char c,char *s);//注意多了一個void *參數.
 
    tt abc;
    abc.m_a = 123;
 
    DWORD ptr;
 
    GetMemberFuncAddr_VC6(ptr,tt::foo); //取成員函數地址.
 
    FUNCTYPE fnFooPtr = (FUNCTYPE) ptr;//將函數地址轉化為普通函數的指針. 
 
    fnFooPtr(&abc,5,'a',"7xyz"); //象普通函數一樣調用成員函數的地址,注意第一個參數是this指針.

  每次都定義一個函數類型并且進行一次強制轉化,這個事是比較煩的,能不能將這些操作寫成一個函數,然后每次調用是指定函數地址和參數就可以了呢?當然是可以的,并且我已經寫了一個這樣的函數。

//調用類成員函數
//callflag:成員函數調用約定類型,0--thiscall,0--其它類型.
//funcaddr:成員函數地址.
//This:類對象的地址.
//count:成員函數參數個數.
//...:成員函數的參數列表.
DWORD CallMemberFunc(int callflag,DWORD funcaddr,void *This,int count,...)
{
      DWORD re;
 
      if(count>0)//有參數,將參數壓入棧.
      {
           __asm
           {
                 mov  ecx,count;//參數個數,ecx,循環計數器.
                 mov  edx,ecx;
                 shl  edx,2;    
                 add  edx,0x14;  edx = count*4+0x14;
 
        next:    push  dword ptr[ebp+edx];
                 sub   edx,0x4;
                 dec   ecx  
                 jnz   next;
           }
      }
 
      //處理this指針.
      if(callflag==0) //__thiscall,vc默認的成員函數調用類型.
      {
           __asm mov ecx,This;
      }
      else//__stdcall
      {
           __asm push This;
      }
 
      __asm//調用函數
      {
           call funcaddr;
           mov  re,eax;
      }
 
      return re;
}

使用這個函數,則上面的兩個調用可以這樣寫:

CallMemberFunc(0,ptr1,&abc,3,5,'a',"7xyz");//
第一個參數0,表示采用__thiscall調用.

CallMemberFunc(1,ptr2,&abc,3,5,'a',"7xyz");//
第一個參數1,表示采用非__thiscall調用.

  需要說明的是,CallMemberFunc是有很多限制的,它并不能對所有的情況都產生正確的調用序列。原因之一是它假定每個參數都使用了4個字節的棧空間。這在大多數情況下是正確的,比如參數為指針,char,short,int,long以及對應的無符號類型,這些參數確實都是每一個參數使用了4字節的棧空間。但是還有很多情況下,參數不使用4字棧空間,比如double,自定義的結構或類.float雖然是占了4字節,但編譯器還產生了一些浮點指令,而這些無法在CallMemberFunc被模擬出來,因此對float參數也是不行的。
  總結一下,如果成員函數的參數都是整型兼容類型,則可以使用CallMemberFunc調用函數地址。如果不是,那就只有按前面的方法,先定義對應的普通函數類型,強制轉化,準備this指針,然后調用普通函數指針。

四、進一步的討論

  到目前為止,已經討論了如何取成員函數的地址,然后如何使用這個地址。但是還有些重要的情況沒有討論,我們知道成員函數可分為三種:普通成員函數,靜態,虛擬。另外更重要的是,在繼承甚至多繼承下情況如何。

首先看看最簡單的單繼承,非虛擬函數的情況。
 

class tt1
{
public:
      void foo1(){ printf("\n hi, i am in tt1::foo1\n"); }
};
 
class tt2 : public tt1
{
public:
      void foo2(){ printf("\n hi, i am in tt2::foo2\n"); }
};

注意,tt2中沒有定義函數foo1,它的foo1函數是從tt1中繼承過來的。這種情況下,我們直接取tt2::foo1的地址行會發生什么?

DWORD tt2_foo1;
tt1 x;
 
GetMemberFuncAddr_VC6(tt2_foo1,&tt2::foo1);
CallMemberFunc(0,tt2_foo1,&x,0); // tt2::foo1 = tt1::foo1

  運行結果表明,一切正常!當我們寫下tt2::foo1的時候,編譯器知道那實際上是tt1::foo1,因此它會暗中作替換。編譯器(VC6)產生的代碼如下:

GetMemberFuncAddr_VC6(tt2_foo1,&tt2::foo1); //源代碼.
 
//VC6編譯器產生的匯編代碼:
 
push offset @ILT+235(tt1::foo1) (004010f0) //直接用tt1::foo1 替換 tt2::foo1.
...

再看看稍微復雜些的情況,繼承情況下的虛擬函數。

class tt1
{
public:
      void foo1(){ printf("\n hi, i am in tt1::foo1\n"); }
      virtual void foo3(){ printf("\n hi, i am in tt1::foo3\n"); }
};
 
class tt2 : public tt1
{
public:
      void foo2(){ printf("\n hi, i am in tt2::foo2\n"); }
      virtual void foo3(){ printf("\n hi, i am in tt2::foo3\n"); }
};

現在tt1tt2都定義了虛函數foo3,按C++語法,如果通過指針調用foo3,應該發生多態行為。下面的代碼:

DWORD tt1_foo3,tt2_foo3;
 
GetMemberFuncAddr_VC6(tt1_foo3,&tt1::foo3);
GetMemberFuncAddr_VC6(tt2_foo3,&tt2::foo3);
 
tt1 x;
tt2 y;
 
CallMemberFunc(0,tt1_foo3,&x,0); // tt1::foo3
CallMemberFunc(0,tt2_foo3,&x,0); // tt2::foo3
 
CallMemberFunc(0,tt1_foo3,&y,0); // tt1::foo3
CallMemberFunc(0,tt2_foo3,&y,0); // tt2::foo3

輸出如下:

hi, i am in tt1::foo3
hi, i am in tt1::foo3
hi, i am in tt2::foo3
hi, i am in tt2::foo3

  請注意第二行輸出,tt2_foo3取的是&tt2::foo3,但由于傳遞的this指針產生是&x,所以實際上調用了tt1::foo3。同樣,第三行輸出,取的是基類的函數地址,但由于實際對象是派生類,最后調用了派生類的函數。這說明取得的成員函數地址在虛擬函數的情況下仍然保持了正確的行為。
  你若真的理解了上面所說的,一定會覺得奇怪。取函數地址的時候就得到了一個整數(成員函數地址),為何調用的時候卻進了不同的函數? 只要看看匯編代碼就都清楚了,"源碼之前,了無秘密"。源代碼: GetMemberFuncAddr_VC6(tt1_foo3,&tt1::foo3); 產生的匯編代碼如下:

push offset @ILT+90(`vcall') (0040105f)
...

  原來取tt1::foo3地址的時候,并不是真的就將tt1::foo3的地址傳給了函數,而是傳了一個vcall函數的地址。顧名思義,vcall當然是虛擬調用的意思。我們找到地址0040105f,看看這個函數到底干了些什么。

@ILT+90(??_9@$BA@AE):
0040105F jmp `vcall' (00401380)

該地址只是ILT的一個項,直接跳轉到真正的vcall函數,00401380。找到00401380,就可以看到vcall的代碼。

`vcall':
00401380 mov eax,dword ptr [ecx] ;//this指針視為dword類型,并將指向的內容(對象的首個dword)放入eax.
00401382 jmp dword ptr [eax] ;//跳轉到eax所指向的地址.

  代碼執行的時候,ecx就是this指針,具體說就是上面對象xy的地址。而eax就是對象xy的第一個dword的值。我們知道,對于有虛擬函數的類對象,其對象的首地址處總是一個指針,該指針指向一個虛函數的地址表。上面的對象由于只有一個虛函數,所以虛函數表也只有一項。因此,直接跳轉到eax指向的地址就好。如果有多個虛函數,則eax還要加上一個偏移量,以定位到不同的虛函數。比如,如果有兩個虛函數,則會有兩個vcall代碼,分別對應不同的虛函數,其代碼大概是下面的樣子:

`vcall':
00401BE0 mov eax,dword ptr [ecx]
00401BE2 jmp dword ptr [eax]
 
`vcall':
00401190 mov eax,dword ptr [ecx]
00401192 jmp dword ptr [eax+4]

編譯器根據取的是哪個虛函數的地址,則相應的用對應的vcall地址代替。

  總結一下:用前面方法取得的成員函數地址在虛擬函數的情況下仍然保持正確的行為,是因為編譯器實際上傳遞了對應的vcall地址。而vcall代碼會根據上下文this指針定位到對應的虛函數表,進而調用正確的虛函數。
  最后,我們看一下多繼承情況。很明顯,現在情況要復雜得多。如果實際試一下,會碰到很多困難。首先,指定成員函數的時候可能會碰到沖突。其次,給定this指針的時候需要經過調整。另外,對虛擬繼承可能還要特別處理。解決所有這些問題已經超出了這篇文章的范圍,并且我想要的成員函數指針是一個真正的指針,而在多繼承的情況下,很多時候成員函數指針已經變成了一個結構體(見參考文獻),這時要正確調用該指針就變得格外困難。因此結論是,上面討論的方法并不適用于多繼承的情況,要想在多繼承的情況下直接調用成員函數地址,必須手工處理各種調整,沒有簡單的統一方法。

 

posted on 2010-03-11 18:03 肥仔 閱讀(1385) 評論(0)  編輯 收藏 引用 所屬分類: C++ 基礎

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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>
            久久久噜噜噜| 日韩亚洲成人av在线| 欧美黄色一级视频| 国产精品一区二区在线观看网站| 亚洲字幕一区二区| 亚洲国产激情| 夜夜嗨一区二区三区| 欧美成人r级一区二区三区| 国产精品久久久久久久久久免费| 日韩视频一区二区在线观看| 久久久综合香蕉尹人综合网| 欧美一区影院| 国产精品网站视频| 亚洲午夜一区二区三区| 亚洲成色www8888| 久久精品成人一区二区三区蜜臀| 国产日韩精品久久| 美日韩精品视频| 亚洲一区久久久| 亚洲欧美伊人| 久久超碰97人人做人人爱| 久久精品99久久香蕉国产色戒 | 亚洲第一天堂av| 亚洲激情成人在线| 99www免费人成精品| 在线视频日韩| 欧美亚洲一级片| 免费成人激情视频| 亚洲精品极品| 亚洲摸下面视频| 久久国产精品一区二区三区| 久久一日本道色综合久久| 欧美激情精品久久久六区热门| 欧美日韩亚洲一区在线观看| 国产精品视频导航| 又紧又大又爽精品一区二区| 亚洲精品一区二区网址| 午夜精品久久久久久久久 | 国产精品美女在线观看| 欧美日韩的一区二区| 欧美日本国产| 国产精品av免费在线观看| 欧美日韩国产首页| 欧美无乱码久久久免费午夜一区| 欧美激情一区二区| 免费精品视频| 亚洲高清在线| 另类成人小视频在线| 午夜伦欧美伦电影理论片| 欧美精品一区在线观看| 欧美制服丝袜| 久久久久久久综合| 亚洲系列中文字幕| 欧美一区二区三区在线视频 | 美女免费视频一区| 欧美成人三级在线| 欧美亚洲一区| 久久综合久色欧美综合狠狠| 亚洲国产一区二区三区高清 | 久久综合中文字幕| 一本色道久久综合亚洲二区三区| 亚洲第一福利在线观看| 国产一区二区三区日韩| 亚洲国产精品传媒在线观看| 国产在线精品一区二区中文| 亚洲国产精品va在线看黑人| 国产一区二区看久久| 亚洲成人在线网| 国产在线乱码一区二区三区| 91久久久久久| 亚洲福利在线视频| 在线视频亚洲一区| 欧美精品v日韩精品v韩国精品v| 亚洲制服少妇| 欧美一级大片在线观看| 午夜精品福利一区二区蜜股av| 久久av一区二区三区亚洲| 欧美一区二区视频观看视频| 另类天堂av| 美女主播视频一区| 亚洲视频在线观看免费| 久久99伊人| 韩国v欧美v日本v亚洲v| 欧美在线一二三| 性做久久久久久免费观看欧美| 国产精品美女久久久久久免费| 亚洲综合好骚| 午夜欧美大片免费观看| 欧美777四色影视在线| 国产亚洲激情视频在线| 亚洲精品久久7777| 99精品视频免费观看视频| 欧美一区二区三区四区在线| 欧美视频手机在线| 亚洲国产va精品久久久不卡综合| 亚洲第一在线综合网站| 欧美在线欧美在线| 老司机精品视频网站| 国产亚洲高清视频| 久久久久国产精品午夜一区| 久久精品人人做人人爽电影蜜月| 久久精品夜色噜噜亚洲a∨| 久久蜜桃av一区精品变态类天堂| 国产精品国产自产拍高清av王其 | 欧美看片网站| 午夜精品久久久久影视| 欧美在线观看www| 国产美女精品视频| 久久人91精品久久久久久不卡| 另类春色校园亚洲| 亚洲一区二区三区乱码aⅴ| 久久久久久欧美| 欧美刺激性大交免费视频| 欧美成人午夜77777| 亚洲免费观看| 亚洲激情不卡| 欧美国产亚洲另类动漫| 国产综合18久久久久久| 久久久久久亚洲精品中文字幕| 欧美大片专区| 欧美一区二区三区四区在线观看地址 | 久久久午夜视频| 亚洲一区日韩在线| 久久免费一区| 欧美国产一区二区在线观看| 亚洲免费中文字幕| 亚洲视屏一区| 一区二区激情| 在线一区观看| 欧美一区午夜精品| 久久成人免费网| 久久国产福利| 亚洲啪啪91| 亚洲免费一在线| 一区二区三区日韩欧美精品| 亚洲欧洲在线一区| 亚洲欧洲另类国产综合| 国产欧美综合一区二区三区| 欧美激情亚洲激情| 在线观看日韩av电影| 久久精品国产2020观看福利| 欧美一区影院| 欧美日韩你懂的| 亚洲欧美在线免费| 欧美一区二区在线免费播放| 国产亚洲在线观看| 免费看亚洲片| 欧美激情中文字幕一区二区| 亚洲自拍都市欧美小说| 久久这里只有| a4yy欧美一区二区三区| 国产精品自拍三区| 久久国产视频网| 亚洲国产精品嫩草影院| 欧美成人精品1314www| 欧美a级片网| 欧美一区二区视频97| 欧美激情aⅴ一区二区三区| 久久精品亚洲| 欧美日韩成人在线观看| 欧美老女人xx| 亚洲电影免费观看高清完整版| 欧美sm视频| 国产自产2019最新不卡| 国产精品99久久久久久www| 美女性感视频久久久| 老司机午夜精品视频在线观看| 国产亚洲一级| 久久久久久网址| 久久天堂国产精品| 欧美成人午夜视频| 激情久久久久久| 久久精品亚洲| 欧美自拍偷拍午夜视频| 欧美激情亚洲精品| 亚洲激情图片小说视频| 欧美精品国产精品| 亚洲一区国产| 夜夜爽99久久国产综合精品女不卡| 欧美人与禽猛交乱配视频| 中日韩视频在线观看| 亚洲永久免费av| 久久精品国产77777蜜臀| 欧美成人精品激情在线观看| 国产日韩精品在线播放| 国产精品亚洲成人| 99国产精品国产精品久久| 久久免费国产精品| 猛干欧美女孩| 在线视频欧美精品| 国产一区二区毛片| 欧美激情 亚洲a∨综合| 洋洋av久久久久久久一区| 性色av一区二区怡红| 在线观看欧美| 欧美三级韩国三级日本三斤| 欧美在线观看www| 亚洲日本成人| 久久久久久久久伊人| 国产精品久久久久毛片软件|