The C++ Programmer's Road Deep Dive into QueryInterface

A Preliminary Study on the Essence of QueryInterface

This article is written for beginners of COM!

The importance of the QueryInterface interface to COM is self-evident. There is a rule for the implementation of this interface - the IUnknow interface pointer returned by QueryInterface must be the same. My question is how does Microsoft use C++ to implement this COM rule? Please note that I am only discussing COM components implemented by Microsoft using C++, not COM components implemented by other companies using other languages. Of course, no matter what company uses a very language, it must follow the rules given by COM.

In order to realize this interface, COM gives 5 rules, you can refer to the book "COM Technology Insider" for details.

In order to get the answer to this question, we must first analyze the virtual functions in C++ classes and how these virtual functions are inherited in subclasses.

The most convincing one is the real example. I still give the example written in VS2005. I will give an example first and answer the questions I raised above.

// Give a pure virtual base class

class CBase{
public:
virtual int func(int param) = 0;
private:
   
};

// give the first subclass of the base class

class CTestA:public CBase{
public:
CTestA();
~CTestA();
virtual int func(int param);
private:
int m_value;
};
int CTestA::func(int param)
{
m_value += param;
this;     / /Add
return 0 to see this value;
}
CTestA::CTestA()
{
m_value = 0;
}
CTestA::~CTestA()
{
}

// give the second subclass of the base class

class CTestB:public CBase{
public:
CTestB();
~CTestB();
virtual int func(int param);
private:
int m_valueb;
};

int CTestB::func(int param)
{
m_valueb += param;
this;  //Add
return 0 to see this value;
}
CTestB::CTestB()
{
m_valueb = 0;
}
CTestB::~CTestB()
{
}

//Give the public subclasses of CTestA and CTestB
class CTestSub:public CTestA,public CTestB
{
public:
virtual int func(int param);
private:
};

int CTestSub::func(int param)
{
void *temPtr = NULL;
temPtr = static_cast<CTestA*>(this);
temPtr = static_cast<CTestB*>(this);
return 0;
}

//test the main function

int _tmain(int argc, _TCHAR* argv[])
{
   CTestSub sub;
CBase* ptr = static_cast<CTestA*>(&sub); //(1)
CTestA* aPtr = static_cast<CTestA*>(&sub); //(2)
CTestB* bPtr = static_cast<CTestB*>(&sub); //(3)
    
ptr->func(1); //(4)
aPtr->func(2); //(5)
bPtr->func(3); //(6)

return 0;
}
If the subclass of the page node (eg CTestSub class) implements the virtual function declared in the base class (eg: CBase class), then in the function calls of (4)-(6), we enter It is the func function implemented by the leaf node class (CTestSub class) without calling the func function in its parent class CTestA or CTestB class, which conforms to the rules of C++ - the rules in C++ are like this, if a method It is declared as virtual in the class and has been reimplemented in the subclass. When we call the virtual function with a pointer to the parent class (the pointer is assigned to the address of the subclass object), the subclass is called. The function implemented in , this subclass is not another subclass, but a subclass whose address is assigned to the parent class pointer (here is the class corresponding to the sub object); the reason for this phenomenon is - in the subclass The address of the func function in the maintained virtual function table has been replaced with the address of the func function implemented in the subclass, so the func function implemented by the CTestSub class is really called; if CTestSub does not reimplement the func function, then the CTestSub virtual function table The address of func is still the address of func in the parent class. In this case, the function body that is actually called is of course the func function implemented in the parent class. If the function func in the parent class is called, then the function func in the func is called. Of course, the this pointer used points to the instance of the parent class, which is consistent with the results of our experiment.

When func is implemented in the CTestSub class, the following test results are obtained:

In the test program, we enter the func function three times, and the this value is the same: 0x12ff48, which is also the address of the object sub.

So what happens if we don't implement the func function in the CTestSub class? ? ? ? ? This question is very important!

After testing, if the func function in CTestSub is not implemented, when the functions (4)-(6) are called above, different functions are entered and the this pointer is different. (4) and (5) are implemented by CTestA. For the func function, the this pointer points to the memory address of the CTestA object contained in the sub object; (6) the entry is the func function implemented by CTestB, and the this pointer points to the memory address of the CTestB object contained in the sub object.

The fact of this experiment shows that each class object maintains a virtual function table (vtbl). CTestA, CTestB, and CTestSub maintain different vtbls. Each class implements virtual functions in the order of the virtual functions when the class is declared. The function pointer is filled in its own virtual table, so if the virtual function implemented in CTestA or CTestB is reimplemented in CTestSub, the virtual function address implemented by the subclass will be used in the virtual function table of the subclass to overwrite the implementation in the parent class If the subclass does not implement the virtual function implemented in the parent class, the virtual table is still filled with the address of the virtual function implemented in the parent class; if the virtual function func is not implemented in CTestSub, the instance sub of CTestSub will The address of the func function in the parent class is still stored in the virtual table, so the final call is the func function of CTestA or CTestB.

At this point, everyone has an understanding of why QueryInterface returns the same IUnkown interface pointer!

The answer to this question can be summed up as follows:

The class that implements the component implements the virtual function QueryInterface in IUnkown, which ensures that the virtual table VTBL maintained by the class implementing the component stores the address of the QueryInterface function implemented in the class, not the address of the QueryInterface function in the parent class. In this way, the this pointer used in QueryInterface is the instance address of the component's class, not the instance address of the component's parent class. If the returned IUnknow addresses are consistent, it is necessary for this pointer to point to an instance of the component class.

In fact, the name of this article can also be called "Who does the this pointer used in QueryInterface point to? ". Oh, of course, it points to the instance of the component class! As can be seen from the above example, when calling func, if the func implementation of the parent class is really called, then the this pointer used in func points to the instance space of the parent class in sub; if the func implemented by the CTestSub class is really called function, then the this pointer used in func is to point to the instance sub of CTestSub. The QueryInterface function of COM also uses this technical rule of C++ virtual function; as long as the COM component class implements the QueryInterface interface function, no matter how the function is called, the this pointer used in the function always points to the instance of the component class. In a word, which class implements the func function to call, then the this pointer used in func points to the instance of which class.

The above explanation may be rather long-winded, but the watcher has to settle down and slowly understand the essence of QueryInterface. To be familiar with the essence of COM, this must be understood, because QueryInterface is really important to COM.

Go to: http://hi.baidu.com/klcdyx2008/blog/item/da5800facd3407d1b48f31a0.html

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325574160&siteId=291194637