Đầu tiên, một số nền tảng kỹ thuật: Trình biên dịch C++ thường tạo ra một thứ được gọi là "vtable" cho bất kỳ lớp nào có chức năng ảo. Điều này về cơ bản là một bảng các con trỏ hàm. Vtable chứa một con trỏ hàm cho mọi phương thức ảo được thực hiện bởi một lớp.
Trong COM, giao diện cơ bản lớp cơ sở trừu tượng mà một cụ thành phần, ví dụ .:
class CSomeComponent : IUnknown, ISomeOtherInterface { ... };
Các vtable cho CSomeComponent
sẽ bao gồm chức năng gợi ý cho tất cả các phương pháp quy định tại hai giao diện này.
struct __imaginary_vtable_for_CSomeComponent
{
// methods required by IUnknown
HRESULT (*QueryInterface)(const IID& iid, void** ppv);
ULONG (*AddRef)();
ULONG (*Release)();
// methods required by ISomeOtherInterface
void (*foo)();
...
};
Bất kỳ đối tượng instantiated nào cũng có tham chiếu đến vtable loại động của nó. Đây là cách chương trình biết làm thế nào để gọi phương thức thích hợp trong trường hợp một phương pháp cơ bản được ghi đè trong một lớp học có nguồn gốc:
class Base
{
public:
virtual void foo() { ... }
}
class Derived : public Base
{
public:
virtual void foo() { ... } // overrides Base::foo()
virtual void bar() { ... }
}
...
Base* X = new Derived;
X->foo();
Dòng cuối cùng nên gọi Derived::foo
. Điều này làm việc vì đối tượng X
có tham chiếu đến vtable cho lớp Derived
. Như đã nói, vtable giống như một danh sách các con trỏ hàm. Bây giờ, vtables có một bố cục cố định: Nếu lớp Derived
kế thừa từ lớp Base
, con trỏ hàm cho phương pháp foo
sẽ có mặt tại vị trí tương đối giống nhau trong Derived
's vtable hơn trong Base
' vtable s:
struct __imaginary_vtable_for_Base
{
void (*foo)();
};
// __imaginary_vtable_for_Base::foo = Base::foo
struct __imaginary_vtable_for_Derived
{
void (*foo)();
void (*bar)();
};
// __imaginary_vtable_for_Derived::foo = Derived::foo
bây giờ, nếu trình biên dịch nhìn thấy một cái gì đó giống như X->foo()
, nó biết rằng tất cả cho tất cả các lớp bắt nguồn từ Base
, phương thức foo
tương ứng với mục nhập đầu tiên trong vtable. Vì vậy, nó phát ra một cuộc gọi đến con trỏ hàm đầu tiên, trong trường hợp của X
là một cuộc gọi đến Derived::foo
.
Trả lời câu hỏi của bạn: Trình biên dịch chỉ có thể tạo thành phần COM nếu chúng tạo bố cục tương tự cho vtables mà đặc tả COM yêu cầu. vtables có thể được thực hiện theo nhiều cách khác nhau, đặc biệt là khi nói đến nhiều thừa kế (được yêu cầu với các thành phần COM). Việc tuân thủ định dạng vtable nhất định là cần thiết để khi bạn gọi phương thức của thành phần f
, bạn sẽ thực sự gọi gọi phương thức f
và không phải một số phương pháp khác g
xảy ra ở vị trí f
trong vtable của thành phần. Tôi cho rằng các trình biên dịch tuân thủ COM về cơ bản phải tạo ra các bố cục vtable giống như Microsoft Visual C++, vì công nghệ COM đã được Microsoft xác định.
P.S.: Xin lỗi vì đã quá kỹ thuật, tôi hy vọng các thông tin trên là một số cách sử dụng cho bạn.
Bài đăng trên blog MSDN này (http://blogs.msdn.com/oldnewthing/archive/2004/02/05/68017.aspx) cũng có thể hữu ích. – stakx
+1 để được giải thích rất hay. – Ashish