程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> ATL布幔之下的秘密(2)

ATL布幔之下的秘密(2)

編輯:關於VC++

介紹

在本系列的教程中,我要討論一些ATL的內部工作方式以及它所 使用的技術,這是本系列的第二篇文章。

現在讓我們來探究一些虛函數 背後更加有趣的資料。為了和上文保持一致,在本文的討論中我將使用相同的順 序,程序的序號從20開始。

讓我們看看下面這個程序:

程序 20.

#include <iostream>
using namespace std;
class Base {
public:
  virtual void fun() {
     cout << "Base::fun" << endl;
  }
  void show() {
    fun();
  }
};
class Drive : public Base {
public:
  virtual void fun() {
    cout << "Drive::fun" << endl;
   }
};
int main() {
  Drive d;
  d.show();
  return 0;
}

程序的輸出為:

Drive::fun

這個 程序清楚地示范了基類的函數是如何調用派生類的虛函數的。這一技術被用於不 同的框架中,例如MFC和設計模式(比如Template Design Pattern)。現在你可 以修改一下這個程序來看看它的行為,我將要在基類的構造函數中調用虛函數, 而不是普通的成員函數。

程序21.

#include <iostream>
using namespace std;
class Base {
public:
  Base() {
    fun();
  }
   virtual void fun() {
    cout << "Base::fun" << endl;
  }
};
class Drive : public Base {
public:
  virtual void fun() {
    cout << "Drive::fun" << endl;
  }
};
int main() {
  Drive d;
  return 0;
}

程序的輸出 為:

ase::fun

這個程序表明,我們不能在基類的構造函數中調用 派生類的虛函數。好了,那就讓我們來看看著布幔之下到底做了什麼。我將會把 這些構造函數之中的指針值打印出來,為了簡便起見,我移除了類中其它的函數 。

程序22.

#include <iostream>
using namespace std;
class Base {
public:
  Base() {
     cout << "In Base" << endl;
    cout << "This Pointer = " << (int*)this << endl;
    cout << endl;
  }
virtual void f () { cout << "Base::f" << endl; }
};
class Drive : public Base {
public:
  Drive() {
     cout << "In Drive" << endl;
     cout << "This Pointer = " << (int*)this << endl;
    cout << endl;
  }
virtual void f () { cout << "Drive::f" << endl; }
};
int main() {
  Drive d;
  cout << "In Main" << endl;
  cout << (int*)&d << endl;
  return 0;
}

程序的輸出為:

In Base
This Pointer = 0012FF7C
In Drive
This Pointer = 0012FF7C
In Main
0012FF7C

這就表示,整個內存位置中,只 有一個對象的存在。那麼就讓我們把這個指針指向的值打印出來,也就是虛函數 表的指針vptr指向的值,VTable的地址。

程序23.

#include <iostream>
using namespace std;
class Base {
public:
  Base() {
    cout << "In Base" << endl;
    cout << "Virtual Pointer = " << (int*)this << endl;
    cout << "Address of Vtable = " << (int*)*(int*)this << endl;
    cout << "Value at Vtable = " << (int*)*(int*)*(int*)this << endl;
     cout << endl;
  }
  virtual void f1() { cout << "Base::f1" << endl; }
};
class Drive : public Base {
public:
  Drive() {
     cout << "In Drive" << endl;
    cout << "Virtual Pointer = " << (int*)this << endl;
    cout << "Address of Vtable = " << (int*)*(int*)this << endl;
    cout << "Value at Vtable = " << (int*)*(int*)*(int*)this << endl;
    cout << endl;
  }
   virtual void f1() { cout << "Drive::f2" << endl; }
};
int main() {
  Drive d;
  return 0;
}

程序的輸出為:

In Base
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C08C
Value at Vtable = 004010F0
In Drive
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C07C
Value at Vtable = 00401217

這個程序示范了 基類和派生類中不同的虛函數表地址。為了更好地弄懂這一問題,那就讓我們把 繼承層次加深,並添加一個繼承於Drive類的MostDrive類,然後構建一個它的對 象。

程序24.

#include <iostream>
using namespace std;
class Base {
public:
  Base() {
     cout << "In Base" << endl;
    cout << "Virtual Pointer = " << (int*)this << endl;
    cout << "Address of Vtable = " << (int*)*(int*)this << endl;
    cout << "Value at Vtable = " << (int*)*(int*)*(int*)this << endl;
    cout << endl;
  }
   virtual void f1() { cout << "Base::f1" << endl; }
};
class Drive : public Base {
public:
   Drive() {
    cout << "In Drive" << endl;
    cout << "Virtual Pointer = " << (int*)this << endl;
    cout << "Address of Vtable = " << (int*)*(int*)this << endl;
    cout << "Value at Vtable = " << (int*)*(int*)*(int*)this << endl;
    cout << endl;
  }
  virtual void f1() { cout << "Drive::f2" << endl; }
};
class MostDrive : public Drive {
public:
  MostDrive() {
    cout << "In MostDrive" << endl;
    cout << "Virtual Pointer = " << (int*)this << endl;
    cout << "Address of Vtable = " << (int*)*(int*)this << endl;
    cout << "Value at Vtable = " << (int*)*(int*)*(int*)this << endl;
    cout << endl;
  }
   virtual void f1() { cout << "MostDrive::f2" << endl; }
};
int main() {
  MostDrive d;
   return 0;
}

程序的輸出為:

In Base
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C0A0
Value at Vtable = 004010F5
In Drive
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C090
Value at Vtable = 00401221
In MostDrive
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C080
Value at Vtable = 00401186

這個程序示范了虛函數表 指針在每個類的構造函數中的初始化過程。這樣看來,每個類構造函數中虛函數 表的地址是不同的,main函數則使用了繼承鏈中的最底部的派生類來創建對象。

現在你可以看看虛函數表中各個類的構造函數的位置了,你可以將虛函 數表中的第一個入口存入一個函數指針,並嘗試運行它。

程序25.

#include <iostream>
using namespace std;
typedef void(*Fun)();
class Base {
public:
  Base() {
     cout << "In Base" << endl;
     cout << "Virtual Pointer = " << (int*)this << endl;
    cout << "Address of Vtable = " << (int*)*(int*)this << endl;
    cout << "Value at Vtable = " << (int*)*(int*)*(int*) this << endl;
    Fun pFun = (Fun)*(int*)*(int*) this;
    pFun();
    cout << endl;
   }
  virtual void f1() { cout << "Base::f1" << endl; }
};
class Drive : public Base {
public:
  Drive() {
    cout << "In Drive" << endl;
    cout << "Virtual Pointer = " << (int*)this << endl;
    cout << "Address of Vtable = " << (int*)*(int*)this << endl;
    cout << "Value at Vtable = " << (int*)*(int*)*(int*)this << endl;

     Fun pFun = (Fun)*(int*)*(int*)this;
    pFun();
     cout << endl;
  }
  virtual void f1() { cout << "Drive::f1" << endl; }
};
class MostDrive : public Drive {
public:
  MostDrive() {
     cout << "In MostDrive" << endl;
     cout << "Virtual Pointer = " << (int*)this << endl;
    cout << "Address of Vtable = " << (int*)*(int*)this << endl;
    cout << "Value at Vtable = " << (int*)*(int*)*(int*) this << endl;

    Fun pFun = (Fun)*(int*)*(int*) this;
    pFun();
    cout << endl;
   }
  virtual void f1() { cout << "MostDrive::f1" << endl; }
};
int main() {
  MostDrive d;
  return 0;
}

程序的輸出為:

In Base
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C098
Value at Vtable = 004010F5
Base::f1
In Drive
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C088
Value at Vtable = 00401221
Drive::f1
In MostDrive
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C078
Value at Vtable = 00401186
MostDrive::f1

這個 程序示范了每個類中的構造函數是如何用自己的虛函數來填充虛函數表中的各入 口的。所以,Base類使用Base類的虛函數地址來填充自己的虛函數表,當Drive 類的構造函數執行它的時候會創建另外一個虛函數表,並存儲自己的虛函數地址 。

現在,你會發現在基類中含有多個虛函數的情況下,派生類並不能完 全重寫它們。

程序26.

#include <iostream>
using namespace std;
class Base {
public:
  Base() {
    cout << "In Base" << endl;
     cout << "Virtual Pointer = " << (int*)this << endl;
    cout << "Address of Vtable = " << (int*)*(int*)this << endl;
    cout << "Value at Vtable 1st entry = " << (int*)* ((int*)*(int*)this+0) << endl;
    cout << "Value at Vtable 2nd entry = " << (int*)*((int*)* (int*)this+1) << endl;
    cout << "Value at Vtable 3rd entry = " << (int*)*((int*)*(int*)this+2) << endl;
cout << endl;
  }
  virtual void f1() { cout << "Base::f1" << endl; }
  virtual void f2() { cout << "Base::f2" << endl; }
};
class Drive : public Base {
public:
   Drive() {
    cout << "In Drive" << endl;
    cout << "Virtual Pointer = " << (int*)this << endl;
    cout << "Address of Vtable = " << (int*)*(int*)this << endl;
    cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;
     cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;
    cout << "Value at Vtable 3rd entry = " << (int*)*((int*)* (int*)this+2) << endl;
    cout << endl;
   }
  virtual void f1() { cout << "Drive::f1" << endl; }
};
int main() {
  Drive d;
   return 0;
}

程序的輸出為:

In Base
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C0E0
Value at Vtable 1st entry = 004010F0
Value at Vtable 2nd entry = 00401145
Value at Vtable 3rd entry = 00000000
In Drive
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C0C8
Value at Vtable 1st entry = 0040121C
Value at Vtable 2nd entry = 00401145
Value at Vtable 3rd entry = 00000000

這個程序的 輸出表明基類的虛函數在派生類中並未被重寫,然後,派生類的構造函數沒有對 虛函數的入口做任何的事情。

那麼現在,讓我邀請純虛函數來加入這一 游戲,再來看看它的行為吧。請看以下的程序:

程序27.

#include <iostream>
using namespace std;
class Base {
public:
  Base() {
    cout << "In Base" << endl;
    cout << "Virtual Pointer = " << (int*)this << endl;
    cout << "Address of Vtable = " << (int*)*(int*)this << endl;
    cout << "Value at Vtable 1st entry = " << (int*)*((int*)* (int*)this+0) << endl;
    cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;
    cout << endl;
  }
   virtual void f1() = 0;
  virtual void f2() = 0;
};
class Drive : public Base {
public:
  Drive() {
     cout << "In Drive" << endl;
     cout << "Virtual Pointer = " << (int*)this << endl;
    cout << "Address of Vtable = " << (int*)*(int*)this << endl;
    cout << "Value at Vtable 1st entry = " << (int*)* ((int*)*(int*)this+0) << endl;
    cout << "Value at Vtable 2nd entry = " << (int*)*((int*)* (int*)this+1) << endl;
    cout << endl;
   }
  virtual void f1() { cout << "Drive::f1" << endl; }
  virtual void f2() { cout << "Drive::f2" << endl; }
};
int main() {
  Drive d;
  return 0;
}

在debug和release模式下, 程序的輸出有所不同。下面是debug模式的輸出:

In Base
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C0BC
Value at Vtable 1st entry = 00420CB0
Value at Vtable 2nd entry = 00420CB0
In Drive
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C0A4
Value at Vtable 1st entry = 00401212
Value at Vtable 2nd entry = 0040128F

以下則是release模式的輸出:

In Base
Virtual Pointer = 0012FF80
Address of Vtable = 0042115C
Value at Vtable 1st entry = 0041245D
Value at Vtable 2nd entry = 0041245D
In Drive
Virtual Pointer = 0012FF80
Address of Vtable = 00421154
Value at Vtable 1st entry = 00401310
Value at Vtable 2nd entry = 00401380

為了 更好地弄懂這一原理,我們需要對程序作少許的改動,並嘗試使用函數指針來調 用虛函數。

程序28.

#include <iostream>
using namespace std;
typedef void(*Fun)();
class Base {
public:
  Base() {
    cout << "In Base" << endl;
    cout << "Virtual Pointer = " << (int*)this << endl;
    cout << "Address of Vtable = " << (int*)*(int*)this << endl;
    cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;
    cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;

     // 嘗試執行第一個虛函數
    Fun pFun = (Fun)*((int*)* (int*)this+0);
    pFun();
    cout << endl;
  }
  virtual void f1() = 0;
  virtual void f2() = 0;
};
class Drive : public Base {
public:
  Drive() {
    cout << "In Drive" << endl;
    cout << "Virtual Pointer = " << (int*)this << endl;
    cout << "Address of Vtable = " << (int*)*(int*)this << endl;
    cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;
     cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;
    cout << endl;
  }
  virtual void f1() { cout << "Drive::f1" << endl; }
  virtual void f2() { cout << "Drive::f2" << endl; }
};
int main() {
  Drive d;
  return 0;
}

現在程序的 行為在debug和release模式下仍然不同。在debug模式下,它會顯示一個運行時 錯誤的對話框:

並且,當你按下“忽略”按鈕之 後,它會顯示下面的對話框:

而在release模式下運行的話,它只會在控 制台窗口中輸出錯誤信息:

In Base
Virtual Pointer = 0012FF80
Address of Vtable = 0042115C
Value at Vtable 1st entry = 0041245D
Value at Vtable 2nd entry = 0041245D
runtime error R6025
- pure virtual function call

那麼這裡 的R6025是什麼?它定義於CMSGS.H頭文件中,這個頭文件定義了所有C Run Time Library的所有錯誤信息。

#define _RT_PUREVIRT_TXT  "R6025" EOL "- pure virtual function call" EOL

事實上,當我們定義了純虛函數後,編譯器就會放置一個名為 _purecall的C Run Time Library的函數地址。這個函數定義在PUREVIRT.C之中 ,它的原型如下:

void __cdecl _purecall(void); // 譯注:原文此處 無分號

我們可以在程序中直接調用這個函數來達到相同的效果,請看下 面這個小程序:

程序29.

int main() {
  _purecall ();
  return 0;
}

這個程序在debug模式和release模式下 的輸出和前一個是一樣的。為了更好的理解這個問題,讓我們把繼承鏈弄得更深 一些,並且從Drive類中再繼承一個類來看看效果吧。

程序 30.

#include <iostream>
using namespace std;
class Base {
public:
  Base() {
    cout << "In Base" << endl;
    cout << "Virtual Pointer = " << (int*)this << endl;
    cout << "Address of Vtable = " << (int*)*(int*)this << endl;
    cout << "Value at Vtable 1st entry = " << (int*)*((int*)* (int*)this+0) << endl;
    cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;
    cout << endl;
  }
   virtual void f1() = 0;
  virtual void f2() = 0;
};
class Drive : public Base {
public:
  Drive() {
     cout << "In Drive" << endl;
     cout << "Virtual Pointer = " << (int*)this << endl;
    cout << "Address of Vtable = " << (int*)*(int*)this << endl;
    cout << "Value at Vtable 1st entry = " << (int*)* ((int*)*(int*)this+0) << endl;
    cout << "Value at Vtable 2nd entry = " << (int*)*((int*)* (int*)this+1) << endl;
    cout << endl;
   }
};
class MostDrive : public Drive {
public:
   MostDrive() {
    cout << "In MostDrive" << endl;
    cout << "Virtual Pointer = " << (int*)this << endl;
    cout << "Address of Vtable = " << (int*)*(int*)this << endl;
    cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;
     cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;
    cout << endl;
  }
  virtual void f1() { cout << "MostDrive::f1" << endl; }
  virtual void f2() { cout << "MostDrive::f2" << endl; }
};
int main() {
  MostDrive d;
  return 0;
}

程序的輸出為:

In Base
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C0D8
Value at Vtable 1st entry = 00420F40
Value at Vtable 2nd entry = 00420F40
In Drive
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C0C0
Value at Vtable 1st entry = 00420F40
Value at Vtable 2nd entry = 00420F40
In MostDrive
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C0A8
Value at Vtable 1st entry = 00401186
Value at Vtable 2nd entry = 004010F5

這個程序表 明,Base和Drive類是用相同的值來初始化各自的虛函數表的。那麼,如果繼承 更深一些,並且只有最底層的派生類重寫了純虛函數,在這種情況下又會發生什 麼呢?這就是在COM程序設計的情況下所發生的了——接口就是只擁 有純虛函數的類,並且一個接口是繼承自另一個接口的,只有實現類才會重寫接 口的純虛函數。這樣一來,每個基類的構造函數就會以相同的值來初始化它們自 己的虛函數表入口。所以,這就意味著相同的代碼會反復重復下去。

ATL 的主要思想就是讓COM組件盡可能的小,但是由於這一行為,接口類的構造函數 就會擁有很多不必要的代碼。為了解決這一問題,ATL引入了一個宏 ATL_NO_VTABLE,它定義在ATLDEF.H中:

#define ATL_NO_VTABLE __declspec(novtable)

__declspec(novtable)為Microsoft C++擴展的類 屬性。它會使編譯器不產生初始化虛函數表指針和虛函數表的代碼,這樣一來就 減少了生成代碼的尺寸。

那麼,我們來修改一下我們的代碼,這樣就能 更好的明白這一屬性究竟能為我們做什麼了。

程序31.

#include <iostream>
using namespace std;
class Base {
public:
  Base() {
    cout << "In Base" << endl;
    cout << "Virtual Pointer = " << (int*)this << endl;
    cout << "Address of Vtable = " << (int*)*(int*)this << endl;
    cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;
    cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;
    cout << endl;
  }
  virtual void f1() = 0;
   virtual void f2() = 0;
};
class Drive : public Base {
public:
  Drive() {
    cout << "In Drive" << endl;
    cout << "Virtual Pointer = " << (int*)this << endl;
    cout << "Address of Vtable = " << (int*)*(int*)this << endl;
    cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;
    cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;
    cout << endl;
  }
};
class __declspec(novtable) MostDrive : public Drive {
public:
  MostDrive() {
     cout << "In MostDrive" << endl;
     cout << "Virtual Pointer = " << (int*)this << endl;
    cout << "Address of Vtable = " << (int*)*(int*)this << endl;
    cout << "Value at Vtable 1st entry = " << (int*)* ((int*)*(int*)this+0) << endl;
    cout << "Value at Vtable 2nd entry = " << (int*)*((int*)* (int*)this+1) << endl;
    cout << endl;
   }
  virtual void f1() { cout << "MostDrive::f1" << endl; }
  virtual void f2() { cout << "MostDrive::f2" << endl; }
};
int main() {
  MostDrive d;
  return 0;
}

程序的輸出為:

In Base
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C0CC
Value at Vtable 1st entry = 00420E60
Value at Vtable 2nd entry = 00420E60
In Drive
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C0B4
Value at Vtable 1st entry = 00420E60
Value at Vtable 2nd entry = 00420E60
In MostDrive
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C0B4
Value at Vtable 1st entry = 00420E60
Value at Vtable 2nd entry = 00420E60

這個程序有另 外一個結果,也就是Drive和MostDrive類擁有相同的虛函數表地址,但是Base類 卻不同。事實上,這就是由於我們沒有對Base類使用__declspec(novtable)屬性 的緣故。現在,我們來對繼承的Drive類也使用相同的屬性,也就是__declspec (novtable)。

程序32.

#include <iostream>
using namespace std;
class Base {
public:
  Base() {
    cout << "In Base" << endl;
     cout << "Virtual Pointer = " << (int*)this << endl;
    cout << "Address of Vtable = " << (int*)*(int*)this << endl;
    cout << "Value at Vtable 1st entry = " << (int*)* ((int*)*(int*)this+0) << endl;
    cout << "Value at Vtable 2nd entry = " << (int*)*((int*)* (int*)this+1) << endl;
    cout << endl;
   }
  virtual void f1() = 0;
  virtual void f2() = 0;
};
class __declspec(novtable) Drive : public Base {
public:
  Drive() {
    cout << "In Drive" << endl;
    cout << "Virtual Pointer = " << (int*)this << endl;
    cout << "Address of Vtable = " << (int*)*(int*)this << endl;
    cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;
    cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;
    cout << endl;
  }
};
class __declspec(novtable) MostDrive : public Drive {
public:
  MostDrive() {
     cout << "In MostDrive" << endl;
     cout << "Virtual Pointer = " << (int*)this << endl;
    cout << "Address of Vtable = " << (int*)*(int*)this << endl;
    cout << "Value at Vtable 1st entry = " << (int*)* ((int*)*(int*)this+0) << endl;
    cout << "Value at Vtable 2nd entry = " << (int*)*((int*)* (int*)this+1) << endl;
    cout << endl;
   }
  virtual void f1() { cout << "MostDrive::f1" << endl; }
  virtual void f2() { cout << "MostDrive::f2" << endl; }
};
int main() {
  MostDrive d;
  return 0;
}

現在,程序的輸出為:

In Base
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C0C0
Value at Vtable 1st entry = 00420E50
Value at Vtable 2nd entry = 00420E50
In Drive
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C0C0
Value at Vtable 1st entry = 00420E50
Value at Vtable 2nd entry = 00420E50
In MostDrive
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C0C0
Value at Vtable 1st entry = 00420E50
Value at Vtable 2nd entry = 00420E50

在MSDN中,對 __declspec(novtable)的解釋是:它應該使用在純虛類中。那麼,讓我們再做一 個實驗來更好地弄懂這一含義吧。

程序33.

#include <iostream>
using namespace std;
class Base {
public:
  Base() {
    cout << "In Base" << endl;
    cout << "Virtual Pointer = " << (int*)this << endl;
    cout << "Address of Vtable = " << (int*)*(int*)this << endl;
    cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;
    cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;
    cout << endl;
  }
  virtual void f1() = 0;
   virtual void f2() = 0;
};
class __declspec(novtable) Drive : public Base {
public:
  Drive() {
    cout << "In Drive" << endl;
    cout << "Virtual Pointer = " << (int*)this << endl;
    cout << "Address of Vtable = " << (int*)*(int*)this << endl;
    cout << "Value at Vtable 1st entry = " << (int*)*((int*)* (int*)this+0) << endl;
    cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;
    cout << endl;
  }
};
class __declspec(novtable) MostDrive : public Drive {
public:
  MostDrive() {
    cout << "In MostDrive" << endl;
    cout << "Virtual Pointer = " << (int*)this << endl;
    cout << "Address of Vtable = " << (int*)*(int*)this << endl;
    cout << "Value at Vtable 1st entry = " << (int*)*((int*)* (int*)this+0) << endl;
    cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;
    cout << endl;
    // 嘗試調 用第一個虛函數
    typedef void (*Fun)();
    Fun pFun = (Fun)*((int*)*(int*)this+0);
    pFun();
  }
  virtual void f1() { cout << "MostDrive::f1" << endl; }
  virtual void f2() { cout << "MostDrive::f2" << endl; }
};
int main() {
  MostDrive d;
  return 0;
}

我們在程序中添 加的新東西是:

// 嘗試調用第一個虛函數
typedef void (*Fun) ();
Fun pFun = (Fun)*((int*)*(int*)this+0);
pFun ();

並且,當我們運行這個應用程序的時候,我們會面對與前一個程 序相同的問題——也就是嘗試調用虛函數發生的錯誤。這就意味著虛 函數表並未初始化。MostDrive類並不是一個抽象類,所以我們應該從類中移除 __declspec(novtable)。

程序34.

#include <iostream>
using namespace std;
class Base {
public:
  virtual void f1() = 0;
  virtual void f2() = 0;
};
class __declspec(novtable) Drive : public Base {
};
class MostDrive : public Drive {
public:
   MostDrive() {
    // 嘗試調用第一個虛函數
     typedef void (*Fun)();
    Fun pFun = (Fun)*((int*)*(int*) this+0);
    pFun();
  }
  virtual void f1() { cout << "MostDrive::f1" << endl; }
   virtual void f2() { cout << "MostDrive::f2" << endl; }
};
int main() {
  MostDrive d;
   return 0;
}

現在,程序就可以正常工作了。它的輸出為:

MostDrive::f1

這個屬性並不一定只用在ATL的類中,它可以對任 何不能創建對象的類使用。同樣,它也並不一定非要用在ATL類中,也就是說它 可以從ATL類中省略,不過這就意味著這樣會產生更多的代碼。

我希望能 在下篇文章中探究更多ATL的秘密。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved