Build lightweight ATL COM objects with the first part, the author: Zhao Xiangning
This article assumes that you are familiar with C and COM. Summary: The ATL - the Active Template library, its design is designed to make people develop COM objects with C conveniently. ATL itself is quite small and flexible, this is its biggest advantage. Use it to create a lightweight, self-contained, can be reused, without any additional runtime DLLS support. Due to a good reputation in COM technology, more and more programmers have entered or enter COM's programming world. It is like the ice beer in the summer, never disappoints you. Unfortunately, as a C programmer, C never share the ultimate of COM, and I have a unique momentum of coming to COM. If it is, if it is left, peaceful coexistence between C and COM, IUNKNOWN is implemented in each object again. I will definite the future C compiler and linker to realize the natural unconscious correspondence and mapping between C objects and COM objects. The current environment is only in the lab, so it is definitely not a product you can purchase today. It is the event template library --ATL. Why use the ATL? ATL is gradually expanded in a single-tier application, and distributed applications have gradually become born in such an environment such as mainstream. Its original version is in four C header files, one is still empty . The excellent architecture it formed is specifically used to develop a lightweight COM component required for modern distributed applications. As a modular standard component, ATL is unlike the MFC has a thick infrastructure, saving the library that makes hundreds of programmers to easily implement iUnknown and iClassFactory once again. The ATL architecture is not intended to include all, nothing. The first version provides very in place for implementing IUNKNOWN, ICLASSFACTORY, IDISPATCH, ICONNECTIONPOINTCONTAINER and COM envoys. In addition to writing ActiveX controls, the second version is also enhanced in the initial version of the ATL class. ATL does not provide a collection of collections and strings, which assumes these processes you use standard C libraries; do not support ODBC - this world is moving to COM-based unwanted packaging; Supporting Winsock packages - Sockets itself is also new; ATL does not support full Win32 API package class -ATL2.0 implementation mechanism provides dialogs and WNDPROCS support. In addition, there is no document / view model in the MFC in the ATL. Instead, ATL is more flexible and flexible through COM interfaces (such as ActiveX controls) and communication modes between UI-based objects. It is very important to use the right tool. If you are writing an invisible COM component, ATL is more likely to match the MFC, from the development efficiency, scalability, runtime performance, and executable size, ATL may be the best choice. The code generated by the ATL is more faster than the MFC based on the user interface of the modern ActiveX control. On the other hand, ATL needs more COM knowledge than the MFC's class wizard. ATL is the same as STL, which has no help to single-layer applications, while MFC maintains its advantage in this regard. The ATL design is largely inspired by STL, and STL has been included in a part of a standard C library with all ANSI / ISO compatible C compilers.
Like STL, ATL boldly uses C templates. The template is one of the more controversial features in C . Each use of improper use can cause confusion, reduce performance, and difficult to understand. The versatility effect and type safety characteristics generated by the template are not to be in other ways. ATL is in two extremes like STL. Fortunately, while l Gold Gold C templates, compilers and linker technology are also developing forward in the same pace. STL and ATL reasonable selection for current and future development. Although the template is widely used inside, you don't have to use an ATL technology, you don't have to use it or care about the sharp arc in those templates. Because the ATL itself comes with an ATL Object Wizard (see Figure 1): Figure An ATL Object Wizard The Object Wizard generates a large amount of ATL template-based object implementation code (ie, frame code). These default object types are listed in Schedule. The ATL Object Wizard allows anyone to quickly build COM objects and let it run within minutes, don't take into account the details of COM or ATL. Of course, in order to fully control the ATL, you must master C , templates, and COM programming technology. For large object classes, the custom interface is output as long as the method implementation is added to the default implementation generated by the ATL object (frame code), which is also the focus of most developers to implement the COM object. When you first contact ATL, its architecture gives people feel mysterious and incredible. Helloatl is the simplest ATL-based process server source code and the same process in the same process implemented in SDK (purely C ). Before you really build a COM component, the code needs to be repeated and modified multiple times. For mainstream component developers who want to accelerate the development of COM components, the ATL architecture is not a big problem, because the object wizard produces all the frame code required, just want to join the method definition. For serious COM developers and system programmers, ATL provides an advanced, scalable architecture that establishes COM components with C . Once you understand and master this architecture and drive the object wizard, you will see ATL's performance and powerful features, it can be comparable to the original COM programming technology. Another reason for using ATL development COM components is the Visual C 5.0 Integrated Development Environment (IDE) to ATL's height support. Microsoft integrates the interface definition language (IDL) of ATL to the C editor in Visual C 5.0 . (to be continued)
The second part of the establishment with a lightweight ATL COM objects Author: Zhao Xiangning
Starting in the first part of this article, we briefly introduce some background knowledge of ATL and the development technology and environment facing ATL. In this part will begin into the ATL, telling the basic methods, principles, and issues that must be paid to the ATL programming. Understanding ATL is the easiest way to investigate its support for client programming. For COM programming novices, one of a tricky main problem is to correctly manage the reference count of the interface pointer. The reference count of COM is not runtably, that is to say that each client must guarantee the commitment to the object. Experienced COM programmers are often accustomed to using the standard mode as proposed in the document (such as "Inside Ole"). Call a function or method, return the interface pointer, use this interface pointer within a certain time range, then release it. Below is an example of code using this mode:
Void f (void) {
IUNKNOWN * PUNK = 0;
// transfer
HRESULT HR = GetSomeObject (& Punk);
IF (successted (HR)) {//
UsesomeObject (punk);
// freed
punk-> release ();
}
}
This mode is so deep in the heart of COM programmer, so that they often do not use the statement of the pointer, but first tap the Release statement at the end of the code block. This is very like the C programmer reflects the conditions when the Switch statement is reflected, first knocking into Break. In fact, calling Release is not a terrible burden, however, client programmers face two fairly serious problems. The first question is related to obtaining a multi-interface pointer. If a function needs to get three interface pointers before doing any actual work, that is, the three call statements must be called before the first use of the pointer. When writing code, this often means that programmers need to write a number of nested conditional statements, such as:
Void f (void) {
IUNKNOWN * RGPUNK [3];
HRESULT HR = GetObject (RGPUNK);
IF (succeededed (hr)) {
HR = getObject (RGPUNK 1);
IF (succeededed (hr)) {
HR = getObject (RGPUNK 2);
IF (succeededed (hr)) {
Usebjects (RGPUNK [0], RGPUNK [1],
RGPUNK [2]);
RGPUNK [2] -> Release ();
}
RGPUNK [1] -> Release ();
}
RGPUNK [0] -> Release ();
}
}
A statement like this often causes the programmer to set the TAB button into one or two spaces, and even want to use a big display. But things don't always press what you imagine, due to the COM component programmers in the project team often do not have the hardware support you want, and before the company determines the use standard for the Tab key, programmers often help "GOTO" statement with great controversy but still effectively:
Void f (void) {
IUNKNOWN * RGPUNK [3];
ZeromeMory (RgPunk, Sizeof (RGPUNK));
IF (Failed (GetObject (RGPunk)))
Goto cleanup;
IF (Failed (GetObject (RGPUNK 1))))
Goto cleanup;
IF (Failed (GetObject (RGPunk 2))))
Goto cleanup;
Usebjects (RGPUNK [0], RGPUNK [1], RGPUNK [2]);
Cleanup:
IF (RGPUNK [0]) RGPUNK [0] -> Release ();
IF (RGPUNK [1]) RGPUNK [1] -> Release ();
IF (RGPUNK [2]) RGPUNK [2] -> Release ();
}
Although this code is not so professional, at least reduce the horizontal scroll of the screen. Using these code segments potentially more tricky problems, that is, when you encounter C anomalies. If the function useObjects throws out an exception, the code released the pointer is completely blocked. One way to solve this problem is to use Win32's structured exception handling (SEH) to end operation:
Void f (void) {
IUNKNOWN * RGPUNK [3];
ZeromeMory (RgPunk, Sizeof (RGPUNK));
__Try {
IF (Failed (GetObject (RgPunk))).
IF (Failed (GetObject (RGPunk 1)))
IF (Failed (GetObject (RGPUNK 2))).
Usebjects (RGPUNK [0], RGPUNK [1], RGPUNK [2]);} __finally {
IF (RGPUNK [0]) RGPUNK [0] -> Release ();
IF (RGPUNK [1]) RGPUNK [1] -> Release ();
IF (RGPUNK [2]) RGPUNK [2] -> Release ();
}
Unfortunately, the performance of Win32 She in C is not as good as imagining. A preferred method is to use built-in C abnormal processing model while stopping the use of no processed pointers. The standard C library has a class: Auto_PTR, which is died in its destructive function, which is died in the DELETE call (even if there is an exception). Similarly, ATL has a COM smart pointer, CComptr, which correctly calls Release Release correctly. The CCompTR class implements the client basic COM reference count model. CComptr has a data member, which is a COM interface pointer that has not been processed. Its type is passed as a template parameter:
CComptr
CCIMPTR
The default constructor initials this original pointer data member to NULL. The smart pointer also has a constructor, and its parameters either the original pointer or is the same type of intelligent parameters. No matter which case, the intelligent pointer calls the AddRef control reference. The assignment operator of CCompTR can handle both the original pointer or handle the smart pointer, and automatically release the saved pointer before calling the addRef of the new allocated pointer. Most importantly, CComptr's destructor releases the saved interface (if not empty). Please see the following code:
Void F (IUNKNOWN * PUNK1, IUNKNOWN * PUNK2) {
// If it is non-empty, constructor calls Punk1 AddRef
CComptr
Unk1 (Punk1);
// If you are not empty, constructor call unk1.p's addRef
CComptr
UNK2 = UNK1;
// If it is empty, operator = Release unk1.p and
// If you are empty, call unk2.p's AddRef
UNK1 = UNK2;
// If you are empty, the destructor releases Unk1 and Unk2
}
In addition to properly implementing the COM's AddRef and Release rules, CComptr also allows transparent operations for realizing the original and smart pointers, see Two shown in Schedule. That is to say, the following code will run in a way you think:
Void F (IUNKNOWN * PUNKCO) {
CComptr
CF;
HRESULT HR;
// Use operator & get access to & cf.p
HR = punkco-> queryinterface (IID_ICLASSFAACTORY, (VOID **) & CF);
IF (Failed (HR)) throw HR;
CComptr
Unk;
// Operator -> Get access to CF.P
// Operator & Get Access to & Unk.p
HR = CF-> CreateInstance (0, IID_IUNKNOWN, (VOID **) & unk);
IF (Failed (HR)) throw HR;
// Operator IUNKNOWN * Return unk.p
Usebject (unk);
// Destructor release unk.p and cf.p
}
In addition to lack of explicit calls to Release, this code is a pure COM code. With the armed armed for CCompTR, the troubles encountered in front suddenly become simple: void f (void) {ccomptr
}
Due to the expansion of the operator overload usage, the CCompTR is intertwinding. Assume that the template class knows the type of pointer it operates, you may ask: Why can't you automatically call QueryInterface in its functional operator or constructor, is more efficient to pack iUnknown? Before Visual C 5.0, there is no way to associate a GUID of an interface with its C type of itself - Visual C 5.0 uses private decspec to define a IID with an interface definition. Because ATL's design takes into account it to work with a large number of different C compilers, it needs to provide GUID with a means that is unrelated to the compiler. Below let's explore another class - CCMQIPTR class. CCOMQIPTR is closely related to CComptr relationship (in fact, it only adds two member functions). CCOMQIPTR must be two template parameters: one is a pointer type and the other is a GUID corresponding to this pointer type. For example, the following code declare the smart pointer for manipulating the IDATAOBJECT and IPERSIST interface:
CCOMQIPTR
CCOMQIPTR
The advantage of CCOMQIPTR is that it has an overloaded constructor and assignment operator. Similar versions (for example, accepted the same type of interface) only the assignment / initialization operations of the ADDREF right. This is actually the function of CComptr. The heterogeneous version (inconsistent interface) correctly invokes QueryInterface to determine if this object does support the requested interface:
Void F (IPERSIST * PPERSIST) {
CCOMQIPTR
// Type assignment - AddRef ''s
p = pPersist;
CCOMQIPTR
// 异 类 - Queryinterface ''s
Do = pPersist;
}
In the second assignment statement, because PPERSIST is a non-IdataObject * type, it is derived from the interface pointer of IUNKNOWN, and CCOMQIPTR calls QueryInterface through PPERSIST to try to get the IdataObject interface pointer of this object. This smart pointer will contain the original IDATAOBJECT pointer as the result. If the queryinterface call fails, DO.P will be set to NULL. If the HRESULT value returned by QueryInterface is important, there is no way to get its value from the assignment operation, QueryInterface must be explicitly invoked. Since there is Ccomqiptr, why do you have CCOMPTR? From several reasons: First, the initial release version of the ATL only supports CComptr, so it keeps legally reserved. Its bid (also the most important reason) is illegal for iUnknown using ccomqiptr for iUnknown due to heavy-duty constructor and assignment operation. Because all COM interfaces are defined to be compatible with IUNKNOWN. CComptr
Equipped with it
CCOMQIPTR
The former is correct. The latter is a wrong usage. If you wrote this, the C compiler will remind you to correct. As a preferred reason for ccomptr, some developers are confident that quietly call QueryInterface, no warning, weaken the type of C system. After all, C does not allow the original pointer to the type of inconsistency without the mandatory type conversion, so why use the intelligent pointer in this, fortunately, developers can choose the most demanding pointer type. . Many developers regard smart pointers as a simplification of excess complex programming tasks. I originally think so. But as long as you pay attention to how they use COM smart pointers. It will gradually realize that the potential hazards they introduce are as much as they solve. In this regard, I use an off-the-shelf function using the original pointer:
Void f (void) {
IFOO * PFOO = 0;
HRESULT HR = GetSomeObject (& Pfoo);
IF (succeededed (hr)) {
UsesomeObject (pfoo);
Pfoo-> Release ();
}
}
Convert it naturally to use CComptr.
Void f (void) {
CComptr
HRESULT HR = GetSomeObject (& Pfoo);
IF (succeededed (hr)) {
UsesomeObject (pfoo);
Pfoo-> Release ();
}
}
Note CComptr and CCOMQipTR output all controlled interface members, including AddRef and Release. Unfortunately, when the client is called through the operator->, the smart pointer is forgetful, and the build function will be called secondary. Obviously this is wrong, the compiler and linker have also accepted this code. If you are lucky, the debugger will quickly capture this error. Another risk to use the ATL smart pointer is the type of access to the original pointer to the original pointer. If you implicitly enforce the use of the operator, there is a controversy. When the ANSI / ISO C committee decides to use a C string class, they explicitly prohibit implicit type conversion. It is required to explicitly use the C_STR function to deliver standard C strings in the required constant char * (const char *). ATL provides an implicit type conversion operator to successfully solve this problem. Usually, this conversion operator can be used according to your preference, allowing you to pass a smart pointer to a function that needs to use the original pointer. Void F (IUNKNOWN * PUNK) {
CComptr
Unk = punk;
// Implicit call operator iUnknown * ()
ColockObjectExternal (unk, true, true);
}
This code can run correctly, but the following code does not generate warning information, compiling normal passes:
HRESULT CFOO :: Clone (iUnknown ** ppunk) {
CComptr
Unk;
CoCreateInstance (CLSID_FOO, 0, CLSCTX_ALL,
IID_IUNKNOWN, (VOID **) & unk);
// Implicit call operator iUnknown * ()
* PPUNK = UNK;
Return S_OK;
}
In this case, the intelligent pointer (UNK) triggered the assignment of the original value needle ** PPUNK with the same forced type conversion as the front code segment. In the first example, addRef is not required. In the second example, you must use AddRef. For more detailed general information about using a smart pointer, see Scott Meyer '"More Effective C " (published in 1995). There is currently no Chinese translation or photocopy of this book. For more specific information about the COM smart pointer, see a special article on the smart pointer http://www.develop.com/dbox/cxx/smartpointer.htm. (to be continued)