For example, a programmer needs to write the following code to use the IHello interface pointer.
View plaincopy to clipboardprint? Void SomeApp (IHello * pHello)
{
IHello * pCopy = pHello;
PCopy-> AddRef ();
OtherApp ();
PCopy-> Hello ();
PCopy-> Release ();
}
Void SomeApp (IHello * pHello)
{
IHello * pCopy = pHello;
PCopy-> AddRef ();
OtherApp ();
PCopy-> Hello ();
PCopy-> Release ();
}
Such code does not seem to have many problems, but if exceptions are taken into account, the above Code is not so optimistic.
If an exception is thrown in OtherApp (), pCopy-> Release () will never be executed. COM components cannot be released, and resource leakage occurs. Some companies do not allow exceptions in the code, but it is very difficult to eliminate all exceptions in the Code even if there are no throw statements in the code. The reasons are as follows:
1. You cannot guarantee that the third-party class library will not throw an exception. Or you will spend a considerable amount of time reading the document and are sure it will not throw an exception.
2. Some operations in C ++ will throw an exception by default. For example, the default stream operation, dynamically applied memory in value transfer, or dynamically transformed dynamic_cast operation.
To address the above two points, I would like to add that it is very difficult to eliminate exceptions, rather than impossible. However, these problems are always well solved when we are concerned. For example, you can require that you do not use stream operations or add nothrow before the new keyword to prevent these exceptions, even changing the compiler options completely disables exceptions. However, if negligence occurs, exceptions become unavoidable.
Maybe you will continue to argue about other ways to eliminate exceptions. But the fact is that you only want to eliminate the side effects of hanging exceptions, rather than the inherent ones. Before you come up with a better solution, let's take a look at how smart pointers solve this problem:
View plaincopy to clipboardprint? Void SomeApp (IHello * pHello)
{
CComPtr <IHello> spHello = pHello;
OtherApp ();
SpHello-> Hello ();
}
Void SomeApp (IHello * pHello)
{
CComPtr <IHello> spHello = pHello;
OtherApp ();
SpHello-> Hello ();
}
The usage of CComPtr is very simple. We will introduce it in detail in the next section. Now you only need to know how we use it. Here, IHello * is used as an example to replace all interface pointer types (except parameters) in the program with CComPtr <IHello>. That is to say, in addition to parameters in the program, do not use IHello *, all of which are replaced by CComPtr <IHello>. This may not cause too much trouble for your program, but it is very profitable.
First, the nasty AddRef () and Release () Operations disappear. Instead of disappearing, it is better to say that smart pointers help us deal with them at the right time. This shortens the number of lines of code and shows the logic clearly.
Return to the exception. What if the above Code is OtherApp () and then throw an exception? In this case, if the OtherApp () throws an exception, the smart pointer goes out of the stack from the function where the spHello is parsed, CComPtr objects automatically call the Release () function to reduce the reference count of COM during the destructor to avoid resource leakage.
In this way, all problems are solved elegantly. Maybe you will no longer focus on eliminating the side effects of exceptions, such as the root cause and end of your work.
You may have initially felt that smart pointers bring some convenience, but if these advantages are not enough to convince you, the following example may further change your mind:
View plaincopy to clipboardprint? IUnknown * PIUnknown = CreateInstance ();
IX * pIX = NULL;
HRESULT hr = pIUnknown-> QueryInterface (IID_IX, (void **) & pIX );
If (SUCCEEDED (hr ))
{
PIX-> Fx (); // The main logic section starts here.
IX * pIX2 = pIX; // The reference count operation occupies half of the Code.
PIX2-> AddRef ();
PIX2-> Fx ();
PIX2-> Release ();
PIX-> Release ();
}
IUnknown * PIUnknown = CreateInstance ();
IX * pIX = NULL;
HRESULT hr = pIUnknown-> QueryInterface (IID_IX, (void **) & pIX );
If (SUCCEEDED (hr ))
{
PIX-> Fx (); // The main logic section starts here.
IX * pIX2 = pIX; // The reference count operation occupies half of the Code.
PIX2-> AddRef ();
PIX2-> Fx ();
PIX2-> Release ();
PIX-> Release ();
}
In the above Code, in order to satisfy the three rules of reference counting, the pIX F () is called when the pIX is assigned to pIX2 (). However, since pIX2 has the same lifecycle as pIX1, it is not necessary to use AddRef () and Release () for pIX2 (). These redundant code greatly reduces the readability of the Code.
At the same time, if the program assigns too many operations to the interface pointer, it will also cause the programmer to miss the AddRef () and Release () operations, resulting in disastrous consequences. The debugging process for such errors is very troublesome.
Let's see how smart pointers solve this problem:
View plaincopy to clipboardprint? CComPtr <IX> spIX = NULL;
HRESULT hr = spIX. CoCreateInstance (CLSID_MYCOMPONENT );
If (SUCCEEDED (hr ))
{
SpIX-> Fx (); // The main logic section starts here.
CComPtr <IX> spIX2 = spIX; // with smart pointers, only logic is left. :)
SpIX2-> Fx ();
}
CComPtr <IX> spIX = NULL;
HRESULT hr = spIX. CoCreateInstance (CLSID_MYCOMPONENT );
If (SUCCEEDED (hr ))
{
SpIX-> Fx (); // The main logic section starts here.
CComPtr <IX> spIX2 = spIX; // with smart pointers, only logic is left. :)
SpIX2-> Fx ();
}
If this is not enough trouble, take a look at the example [5] below ]. After reading this example, you may be more eager to use smart pointers.
View plaincopy to clipboardprint? Void f (void ){
IUnknown * rgpUnk [3];
HRESULT hr = GetObject (& rgpUnk [0]);
If (SUCCEEDED (hr )){
Hr = GetObject (& rgpUnk [1]); // to simplify the code, use GetObject instead of QueryInterface.
If (SUCCEEDED (hr )){
Hr = GetObject (& rgpUnk [2]);
If (SUCCEEDED (hr )){
UseObjects (rgpUnk [0], rgpUnk [1], rgpUnk [2]);
RgpUnk [2]-> Release ();
}
RgpUnk [1]-> Release ();
}
RgpUnk [0]-> Release ();
}
}
Void f (void ){
IUnknown * rgpUnk [3];
HRESULT hr = GetObject (& rgpUnk [0]);
If (SUCCEEDED (hr )){
Hr = GetObject (& rgpUnk [1]); // to simplify the code, use GetObject instead of QueryInterface.
If (SUCCEEDED (hr )){
Hr = GetObject (& rgpUnk [2]);
If (SUCCEEDED (hr )){
UseObjects (rgpUnk [0], rgpUnk [1], rgpUnk [2]);
RgpUnk [2]-> Release ();
}
RgpUnk [1]-> Release ();
}
RgpUnk [0]-> Release ();
}
}
I don't think you can see the key points of the above Code at a glance. Only when you read one row at a time can you see the fog. "It was just to call the UseObjects (rgpUnk [0], rgpUnk [1], rgpUnk [2]) function ". What he needs is three COM interface pointers. As a result, this layer-by-layer nested code and the Release call after nesting appear. You may use a lot of energy to determine whether parentheses are paired and whether Release and GetObject are paired. You may have to drag the scroll bar below the IDE or on the right to view the subsequent code.
He is not only eye-catching, but more importantly, he cannot find the key logic code. Smart pointers simplify the writing process and are very elegant:
View plaincopy to clipboardprint? Void f (void ){
CComPtr <IUnknown> rgpUnk [3];
If (FAILED (GetObject (& rgpUnk [0]) return;
If (FAILED (GetObject (& rgpUnk [1]) return;
If (FAILED (GetObject (& rgpUnk [2]) return;
UseObjects (rgpUnk [0], rgpUnk [1], rgpUnk [2]);
}
Void f (void ){
CComPtr <IUnknown> rgpUnk [3];
If (FAILED (GetObject (& rgpUnk [0]) return;
If (FAILED (GetObject (& rgpUnk [1]) return;
If (FAILED (GetObject (& rgpUnk [2]) return;
UseObjects (rgpUnk [0], rgpUnk [1], rgpUnk [2]);
}
With the redundant AddRef () and Release () missing, the world is quiet. You can see the key logic of UserObjects,
Maybe you are already eager to try smart pointer, because it can get so many benefits, the cost is quite small (it only needs to open up a very small space on the function stack to store smart pointer objects, and the size is usually the same as that of common pointers ). But before that, let's look at some of the more exciting features.
Observe the following code:
View plaincopy to clipboardprint? HRESULT hrRetCode = E_FAIL;
IX * pIX = NULL;
HrRetCode = CoCreateInstance (
CLSID_MYCOMPONENT,
NULL,
CLSCTX_INPROC_SERVER,
IID_IY, // OH ~ This is a tragedy. The IID is wrong.
(Void **) & pIX
);
KG_COM_ASSERT_EXIT (hrRetCode );
PIX-> fun ();
HRESULT hrRetCode = E_FAIL;
IX * pIX = NULL;
HrRetCode = CoCreateInstance (
CLSID_MYCOMPONENT,
NULL,
CLSCTX_INPROC_SERVER,
IID_IY, // OH ~ This is a tragedy. The IID is wrong.
(Void **) & pIX
);
KG_COM_ASSERT_EXIT (hrRetCode );
PIX-> fun ();
If you check it carefully, you will find that IID_IY is used for the query interface, but the IX type pointer is used as the parameter for receiving. A similar error may occur when you query IX but use the IY interface to receive the error. What will happen after such an error code is executed? There is no need for further research. We should consider more methods to avoid this problem.
First, let's explore the key reasons for the above errors:
1. The IID and interface types are not statically bound together, which may lead to an incorrect combination of the IID and interface.
2. the outgoing parameter (the last parameter) of CoCreateInstance is of the void ** type. Therefore, it is of an insecure type and may pass in any type of interface error.
The solution is still smart pointer. Looking at this elegant solution in the workshop, the type security problem seems to be solved.
View plaincopy to clipboardprint? HRESULT hrRetCode = E_FAIL;
CComPtr <IX> spIX
HrRetCode = spIX. CoCreateInstance (CLSID_MYCOMPONENT); // IID and void do not exist **
KG_COM_ASSERT_EXIT (hrRetCode );
PIX-> fun ();
HRESULT hrRetCode = E_FAIL;
CComPtr <IX> spIX
HrRetCode = spIX. CoCreateInstance (CLSID_MYCOMPONENT); // IID and void do not exist **
KG_COM_ASSERT_EXIT (hrRetCode );
PIX-> fun ();
The usage of "." After the smart pointer in the above Code seems to make you confused about the concept of "Pointer. You may ask whether it should be a-> operator? We will discuss this issue in a later chapter. For the moment, you may consider smart pointers as a resource management object that fills in a secure method for creating COM components. Similar operations also exist in smart pointers for QueryInterface operations.
Some smart pointers provide us with a more convenient way to create COM components and Query Interfaces. For example, _ com_ptr_t can be used to create COM components as follows:
View plaincopy to clipboardprint? _ COM_SMARTPTR_TYPEDEF (ICalculator, _ uuidof (ICalculator ));
ICalculatorPtr spIX (CLSID_MYCOMPONENT );
KG_ASSERT_EXIT (spIX );
SpIX-> fun ();
_ COM_SMARTPTR_TYPEDEF (ICalculator, _ uuidof (ICalculator ));
ICalculatorPtr spIX (CLSID_MYCOMPONENT );
KG_ASSERT_EXIT (spIX );
SpIX-> fun ();
In the previous example, we will introduce in detail what _ COM_SMARTPTR_TYPEDEF is. For now, readers can regard it as a declaration. In this way, in addition to creating COM components, necessary assertions, and function calls, there is no redundant code. After comparing the previous practices, do you think we have some reason for choosing and using smart pointers?
It should be noted that what I mentioned above is "smart pointers provide type-safe operations", but it is not said that smart pointers are absolutely type-safe. This means that type security issues may still occur during the use of smart pointers. For further discussions, refer to "Using Smart pointers without interrupting Rules ".
-- Clause 13: You must release the COM component in advance.
Author's "liuchang5 column"