ATL interface mapping macro detailed

zhaozj2021-02-17  69

* This article is selected from: COM concentration

ATL interface mapping macro detailed

Lostall

Preamble:

In the past few days, I looked at the interface map of the ATL. I didn't know that it was deeply, I suddenly sprout the idea written. ATL defines a lot of interface mapping macro, there are still more important, although it is not necessary to make it very clear, but in the process of in-depth learning, you can learn other ATL classes. The mechanism for it can also be more clear, it should still be some benefit. I will write it out according to the process of learning, and I don't know if you can understand. I want to imitate the teacher's hand to explain the internal details, but I don't dare to say the beautiful name "in-depth shallow", huh, I only hope that I can help everyone.

Introduce the interface map macro macro in each form of COM_Interface_entry_xx in ATL, and will be explained in the order from an easy to difficult, and each part will be based on the previous part. Every part will analyze the actual call function stack, and the writing of the stack is from bottom to. The code involved in the article is a slight written, only listing the relevant part.

First, com_interface_entry (x)

First we start from a typical application:

Define a simplest ATL DLL:

Class ATL_NO_VTABLE CMYOBJECT:

Public CComobjectroTroTex,

Public CCOMCOCLASS,

Public IDispatchImpl

{

.....

Begin_COM_MAP (CMYOBJECT)

Com_interface_entry (iMyObject) // a double interface

COM_ITERFACE_ENTRY (Idispatch)

END_COM_MAP ()

.....

}

Write a shortest query interface code:

Iunknown * punk;

IMyObject * PMYOBJECT;

CoCreateInstance (CLSID_MYOBJECT, NULL, CLSCTX_INPROC_SERVER, IID_IUNKNOWN, (VOID **) & PUNK);

Punk-> queryinterface (IID_IMYOBJECT, (Void **) & PMYObject);

Execute a customer code, first we look at how the component object is created. Function call stack one:

4 ...........

3.atl :: ccomcreator > :: createInstance (...)

2.atl :: ccomcreator2 >,

Atl :: ccomcreator >> :: createInstance (...)

1.atl :: ccomclassfactory :: createInstance (...)

4.atl :: atlmodulegetClassObject (...)

9.atl :: atlinternalQueryInterface (...)

8.atl :: ccomobjectrootbase :: InternalQueryinterface (...)

7.atl :: ccomclassfactory :: _ InternalQueryinterface (...)

6.atl :: ccomobjectcached :: queryinterface (...)

5.atl :: ccomcreator> ::

CREATEINSTANCE (...)

4.atl :: atlmodulegetClassObject (...)

3.tl :: ccommodule :: getclassobject (...) 2.dllgetclassobject (...)

1.cocreateInstance (...) (client)

Explain as follows:

1:

CoCreateInstance (CLSID_MYOBJECT, NULL, CLSCTX_INPROC_SERVER, IID_IUNKNOWN, (VOID **) & PUNK);

The inside will call the OLE API function COGETCLASSOBJECT (), and COGETCLASSOBJECT will load DLL through LoadLibrary (...), and call the dllgetClassObject () function in the DLL.

2:

StDAPI DLLGETCLASSOBJECT (Refclsid Rclsid, Refiid Riid, LPVOID * PPV)

{

Return_Module.getClassObject (RCLSID, RIID, PPV);

}

Among them, it is worth noting that the _Module variable defines the global variable in the DLL: ccommodule _module; ATL passes a set of macros:

Begin_Object_map (ObjectMap)

Object_entry (CLSID_MYOBJECT, CMYOBJECT)

END_OBJECT_MAP ()

#define begin_object_map (x) static _tl_objmap_entry x [] = {

#define Object_ENTRY (CLSID, CLASS) {& CLSID, CLSS :: UpdateRegistry, /

Class :: _ classfactorycreatorclass :: createInstance, // / key

Class :: _ CreatorClass :: CreateInstance, /

Null, 0, Class :: GetObjectDescription, /

Class :: getcategorymap, class :: objectmain},

#define end_object_map () {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}};

Generate a static global _tl_objmap_entry type: ObjectMap []; then ATL is

Bool WinApi Dllmain (Hinstance Hinstance, DWORD DWREASON, LPVOID / * LPRESERVED * /

{

.....

_Module.init (ObjectMap, Hinstance, & libid_test2lib);

.....

}

Initialization _Module // Note Inly, in InitInstance (), _Module

So _Module initialization, in fact, he didn't do anything, in ccommodule :: INIT, it calls atlmoduleinit (_TL_Module * PM, _TL_OBJMAP_ENTRY * P, HINSTANCE H), in which only one sentence: PM- > m_pobjmap = p; Visible _Module is only given this global object mapping array ObjectMap []. So why can I get the class with _Module.getClassObject? In fact, the key is that our component CMYObject inherits another base class ccomcoclass! Defines a macro declare_classfactory () in ccomcoClass.

#define declare_classfactory () Declare_classfactory_ex (ccomclassfactory)

#define declare_classfactory_ex (cf) Typedef ccomcreator > _classfactorycreatorclass;

CCOMCREATOR, CCOMOBJECTCACHED We don't care, but see CCOMCLASSFAACTORY, as the name suggests, we know that our products have finally appeared! There is a class factory object inside each component. Wrought around a large circle, we have now known that _Module contains the objects of each component we want, which is already enough for the current, and now continue the route!

3:

HRESULT CCOMMODULE :: GetClassObject (Refclsid Rclsid, Refiid Riid, LPVOID * PPV)

{

Return AtlModulegetClassObject (this, RCLSID, RIID, PPV);

}

CCommodule :: getClassObject is very simple, just call ATL's API functions.

4:

AtlinetClassObject (_TL_Module * PM, Refclsid Rclsid, Refiid Riid, LPVOID * PPV)

{

_TL_OBJMAP_ENTRY * PENTRY = PM-> m_pobjmap; // Take an object mapping array from _Module

While (Pentry-> PCLSID! = NULL)

{

IF ((Pentry-> PfNgetClassObject! = null) && inlineisequalguid (rclsid, * pentry-> pclsid))

{

IF (PENTRY-> PCF == NULL)

{

HRES = PENTRY-> PfNgetClassObject (Pentry-> PFNCREATEINSTANCE,

IID_IUNKNOWN, (LPVOID *) & PENTRY-> PCF);

}

IF (Pentry-> PCF! = NULL)

HRES = PENTRY-> PCF-> Queryinterface (RIID, PPV);

Break;

}

PENTRY = _nextObjectMapenTry (PM, Pentry);

}

}

It's a bit not understanding now, it seems that we have to see the structure of _ATL_OBJMAP_ENTRY.

Struct _tl_objmap_ENTRY

{

Const CLSID * PCLSID;

HRESULT (BOOL BREGOSTER)

_TL_CREATORFUNC * PFNGETCLASSOBJECT;

_TL_CREATORFUNC * PFNCREATEINSTANCE;

IUNKNOWN * PCF;

DWORD DWREGOSTER;

_TL_DESCRIPTIONFUNC * PFNGETOBJECTDESCRIPTION;

_TL_catmapfunc * pfNgetcategoryMap;

}

PCLSID is clearly representative of the CLSID of our components; PfNgetClassObject We also know it is CMYObject :: _ ClassFactoryCreatorClass :: CreateInstance (the CreateInstance function of the class object included in our component); PCF can also guess it is pointing this The IUNKNOWN pointer of the class is created by this class factory object. If the factory object already exists, it will not have to create a new type of factory object. We still don't understand what is going on now. In fact, the answer is still in ccomcoClass! In CCOMCLASS, the macro declare_AGGREGATABLE (X) is defined. This macro indicates that this component can be both gathered or not gathering, and the concept of gathering We are temporarily ignored, first look at its definition:

#define declare_AGGREGATABLE (X) Public: /

Typedef ccomcreator2 >, / /

CCMCREATOR >> _creatorclass;

We saw a familiar string _CREATORCLASS, which still has a component containing objects. But there is still a problem, we don't know why _classfactorycreator and _CREATORCLASS have to follow a CREATEINSTANCE? It seems that we must first see what CCOMCREATOR is something.

Template

Class CComcreator

{

PUBLIC:

Static HRESULT WINAPI CREATEINSTANCE (Void * PV, Refiid Riid, LPVOID * PPV)

{.....

}

}

It turns out that there is only one createInstance function in it, and we now finally understand _classfactorycreatorclass :: createInstance, it means that it represents ccomclassfactory :: CreateInstance (..), almost like this. Then let's take a look at the difference between ccomcreator2:

Template

Class CComcreator2

{

PUBLIC:

Static HRESULT WINAPI CREATEINSTANCE (Void * PV, Refiid Riid, LPVOID * PPV)

{

RETURN (PV == NULL)?

T1 :: CreateInstance (NULL, RIID, PPV):

T2 :: CREATEINSTANCE (PV, RIID, PPV);

}

}

This class is very similar to CCMCREATOR, only a CREATEINSTANCE member function, from _CREATORCLASS we can know that it actually contains two class ccomobject, ccomaggobject's CreateInstance function (via ccomcreator), where ccomobject is used for non-aggregated objects, ccomaggobject is used for ccomagGObject The aggregation object is based on the situation it establishes the corresponding object. (The actually generated component objects in ATL is not CMYObject, but ccomobject, ccomaggobject, or ccompolyobile, this concept is very important, but now, don't talk now) Now we basically know what is going on, it It is created based on the address of the creation of the creation of the creation plant in the presence object mapping. PfNgetClassObject and PfncreateInstance We basically have already known what is going on, but there is a question why Pentry-> PFNCREATEINSTANCE is passed as a parameter in Pentry-> PfNgetClassObject (...)? The answer is below, let us continue to route! 5:

CCOMCREATOR :: CreateInstance (Void * PV, Refiid Riid, LPVOID * PPV)

{

T1 * P = NULL;

ATLTRY (P = New T1 (PV)) // Create a class factory object

IF (p! = null)

{

P-> setvoid (PV);

P-> InternalFinalConstructAddref ();

HRES = P-> Finalconstruct ();

P-> infinalconstructrelease ();

IF (HRES == S_OK)

HRES = P-> QueryInterface (RIID, PPV);

IF (hres! = s_ok)

Delete P;

}

}

Note that the T1 here is ccomobjectcached , this is the template parameter we give CComcreator. We have seen our familiar operators 'new'! Until now we have finally created a component of the component. But I haven't finished, continue to go down, what do you do in Setvoid (PV)?

Void ccomclassfactory :: setvoid (void * pv)

{

m_pfncreateInstance = (_TL_CREATORFUNC *) PV;

}

Everyone still remember that we used to pass CMYOBJECT :: _ CreatorClass :: CreateInstance as a parameter to Pentry-> PfNgetClassObject (...), then we don't understand what is going on, now it is clear! It turns out that the class is a factory that needs it to create a component object! Although we just guess this from literally, it is actually as it is expected, in CCOMCLASSFAACTORY: CREATEINSTANCE (...), we have seen M_PFNCREATEINSTANCE (Punkouter, Riid, PPVOBJ); now everything is already Understand, ATLs have been opened for us to create a class, and we have opened it. The rest of the process of creating components is already our very familiar process!

But now, now we need to query an IUNKNOWN pointer for the class object, this pointer exists in the PENTRY-> PCF seen in front.

6:

STDMETHOD (REFIID IID, VOID ** PPVObject) {return_internalQueryInterface (IID, PPVObject);}

Now calling ccomobjectcached :: queryinterface, as for this class, we don't need to know now, I am very tired, huh, huh.

7:

HRESULT _INTERNALQUERYINTERFACE (Refiid IID, Void ** PPVObject) /

{Return InternalQueryInterface (this, _Getentries (), IID, PPVObject;

All classes of _internalqueryinterface (...) are defined in Begin_COM_MAP. CCOMOBJECTCACHED does not have a begin_com_map macro, so call now CCOMCLASSFAACTory. Note that the THIS pointer and interface mapping array _Getentries () is passed to INTERNALQUERYINTERFACE (), which is the basis for implementing queries (...). Define a static interface mapping array in Begin_COM_MAP (X):

_TL_INTMAP_ENTRY _ENTRIES [];

Each interface mapping macro is actually an increase in an array. An interface map macro includes three parts: the IID number of the interface, the offset value (most of the time), the function that needs to be executed, does not have other functions for the general interface. _Getentries () is returning to this array. There are still some details of the issue.

8:

Static HRESULT WINAPI INTERNALQUERYINTERFACE (void * pthis,

Const _tl_intmap_entry * pentries, refiid Iid, void ** ppvobject)

{

...

HRESULT HRES = AtlinternalQueryInterface (PTHIS, Pentries, IID, PPVObject);

...

}

Co-call now CComObjectRootBase :: InternalQueryinterface (...)

9: Now we finally arrived at QueryInterface's nose. AtlinternalQueryInterface (...) is the end point of the entire query process, which traverses the interface mapping table and makes the corresponding action according to each item. There are many kinds of news maps in ATL, there are many ways, but now we don't care about those, now we have to do it is to find an iUnknown interface, which is easy, we don't even need to traverse interface mapping tables.

Atlinline Atlapi AtlinternalQueryinterface (void * pthis,

Const _tl_intmap_entry * pentries, refiid Iid, void ** ppvobject)

{

Atlassert (Pentries-> pfunc == _tl_simplemapentry);

IF (PPVObject == Null)

Return E_POINTER;

* pPVObject = NULL;

IF (IISEQULUNKNOWN (IID)) // USE First Interface

{

IUNKNOWN * PUNK = (IUNKNOWN *) ((int) PTHIS PENTRIES-> DW);

punk-> addRef ();

* pPVObject = punk;

Return S_OK;

}

... // There is also a lot of piles, but now you can't use it, save some space}

Here is a provision that the first interface of the interface mapping table must be _ATL_SIMPLEENTRY. As for why this requirement, and pthis pentries-> DW, let's talk later, it is also a bunch of problems. In short, we are now having easy access to the class objects we need and the iUnknown pointer.

4: I think we can get back to the first step, but in atl :: atlmodulegetclassobject, stop, look at its source code, but also query the iClassFactory pointer by the iUnknown pointer you just received. . It is also the same call, just as in step 6 to 9, we will make the same call. Note that in step 9, we are no longer the iUnknown pointer, so we need to see the code I have not listed, but this is left to the next function stack and look at it.

1: Finally, we have completed all the operations of the creation of a class object. Now we have to do it is the process of creating components of the CreateInstance (...) function we are familiar with the CREA object. As we see, now OLE starts calling ccomclassfactory :: createInstance (), we haven't forgotten, and the CreateInstance () function for creating components has been reserved in the class factory object, which is already very clear.

2. Don't repeat it, see step 4.

3. Don't repeat it, see step 4.

4. If we continue to route, our stack can be very long, but this is just a repeated mood. I didn't continue to go, I was very tired, hehe.

Function call stack 2:

0: ............

5.atl :: atlinternalQueryInterface (...)

4.atl :: ccomobjectRootbase :: InternalQueryInterface (...)

3.cmyObject :: _ InternalQueryinterface (...)

2.atl :: ccomobject :: queryinterface (...)

1.Punk-> queryinterface (IID_IMYOBJECT, (Void **) & PMYObject; (client)

Explain as follows:

1. We query the iMyObject pointer through the iUnknown interface pointer of the components that have just been obtained. This is the pointer we really need.

2. Remember that we said that the ATL really created components is not CMYObject, but ccomobject, ccomaggobject or ccompolyobile, here we create ccomobject. So we must call CComobject :: queryinterface (...), and indeed CComobject Implement this function.

STDMETHOD (QueryInterface) (Refiid IID, Void ** PPVObject)

{RETURN _INTERNALQUERYINTERFACE (IID, PPVObject);} It just simply calls _internalQueryinterface (...), we also said that only the class has declared Begin_COM_MAP macro will have _InternalQueryinterface (...), so now do it now Go to its parent class CMYOBJECT, so CMYObject :: _ interfaceQueryinterface (...)

3. In the future, we are already very familiar, but also to say it again, huh, huh

4. This call is also very familiar. Don't say more. 5. Now we will query a non-iunknown interface, so let's take a look at the code we have not listed before.

Atlinline Atlapi AtlinternalQueryinterface (void * pthis,

Const _tl_intmap_entry * pentries, refiid Iid, void ** ppvobject)

{

/ / Make sure the first item of the interface map is a simple interface

/ / If you are querying the iUnknown interface, perform the corresponding operation

// The following will traverse the interface mapping table, try to find the corresponding interface

While (pentries-> pfunc! = null)

{

Bool bblind = (Pentries-> PIID == NULL);

IF (Bblind || Inlineisequalguid (* (Pentries-> PIID), IID)

{

/ / _ ATL_SIMPLEMAPENTRY indicates a simple interface

IF (Pentries-> Pfunc == _TL_SIMPLEMAPENTRY) // offset

{

Atlassert (! BBLIND);

IUNKNOWN * PUNK = (IUNKNOWN *) ((int) PTHIS PENTRIES-> DW);

punk-> addRef ();

* pPVObject = punk;

Return S_OK;

}

ELSE // If not a simple interface, you need to perform the corresponding function

{

HRESULT HRES = Pentries-> Pfunc (PTHIS, IID, PPVObject, Pentries-> DW);

IF (hres == s_ok || (! BBLIND && FAILED (HRES))))

Return HRES;

}

}

PENTRIES ;

}

Return E_NOINTERFACE;

}

}

The logic of functions is very clear, only two points may not understand, one is (iUnknown *) (INT) PTHIS PENTRIES-> DW) What does it mean? The other is Pentries-> PFUNC to do something. The previous problem will tell in COM_ITERFACE_ENTRY2, and the latter problem will explain the different types of interfaces later. The meal is always a bit to eat, huh, huh.

Now we just need to care about how our iMyObject is found. Look at its macro We will unwind the com_interface_entry (iMyObject) to follow:

{& _TL_IIDOF (iMyObject), // Get the IID value of IMYObject

Offsetofclass (iMyObject, cmyObject), // Define offset

_TL_SIMPLEMAPENTRY}, // indicates a simple interface

We will also stay again later for OffsetOfClass (ImyObject, CMYOBJECT). According to this structure, we can easily get the IMYOBJECT interface pointer.

0: OK, IT is over. Return it in turn. In fact, the process of this query also happened in the call sequence just in the call, and there is a similar process when querying the iClassFactory interface, but it is still a separately, just to take a look at the typical situation, huh, huh.

Second, COM_ITERFACE_ENTRY2 (X, X2) Part 2 ATL RROR: Commap

The ATL is a multi-inheritance method to implement the components, but if there are multiple branches in the inheritance tree to implement the same interface, when querying this interface, you need to know which branch is returned to it. This macro is that this macro is used for the iDispatch interface. Let's take a look at its typical usage: Class Couter:

Public IDispatchImpl , // IOUTER1 are a dual interface

Public IDispatchImpl , // IOUTER2 are also a double interface

PUBLIC ...

{

PUBLIC:

COUTER () {}

...

Begin_COM_MAP (COUTER)

COM_ISPATCH_ENTRY2 (Idispatch, IOUTER2), // will expose the route inherited by IOUTER2,

COM_ITERFACE_ENTRY (IOUTER1)

COM_Interface_ENTRY (IOUTER2)

...

END_COM_MAP

}

IdispatchImpl <...> This class implements the IDispatch interface, so there are two iDispatch implementations in the components. Which implementation is returned when querying the idispatch interface?

Let's take a look at the definition of COM_ITERFACE_ENTRY2 (X, X2)

#define begin_com_map (x) public: /

Typedef x _commapclass; /

....................

#DEFINE COM_ITERFACE_ENTRY2 (X, X2) /

{& _ATL_IIDOF (x), // Get the IID value of the interface

(DWORD) ((x2 *) ((_COMMAPCLASS *) 8)) - 8, /

_TL_SIMPLEMAPENTRY}, // shows a simple interface

The problem is in (dword) ((x *) (_ command *) 8) - 8 What does it mean?

Let's take a look at the following code:

Class A1

{

PUBLIC:

Virtual void test () {}

}

Class A2: Public A1

{

PUBLIC:

Virtual void test () {}

}

Class A3: Public A1

{

PUBLIC:

Virtual void test () {}

}

Class A: Public A2, PUBLIC A3

{

}

{

DWORD DW; DW = (DWORD) ((a *) 8); // dw = 0x08

DW = (DWORD) ((A3 *) (A *) 8); // dw = 0x0c

DW = (DWORD) ((A1 *) (A3 *) (A *) 8); // dw = 0x0c

DW = (DWORD) ((A1 *) (A3 *) (A *) 8) - 8; // dw = 4

}

This inheritance is a typical diamond structure, and two virtual function table pointers are saved in class A, which represents two branches of it. When an object is declared for class a, the system will allocate memory. The top of this memory retains two virtual function table pointers. The results of the analysis program run can be seen that the last result 4 represents the offset between the virtual function table pointer to the interface A3 and the tip of the memory block of the class A object.

Let's take a more complex point inheritance:

Class B1

{

PUBLIC:

Virtual void test () {}

}

Class B2

{

PUBLIC:

Virtual void test () {}};

Class B3

{

PUBLIC:

PUBLIC:

Virtual void test () {}

}

Class B4: Public B1, Public B2

{

PUBLIC:

Virtual void test () {}

}

Class B5: Public B2, Public B3

{

PUBLIC:

Virtual void test () {}

}

Class B: Public B4, PUBLIC B5

{

}

{

DWORD DW; DW = (DWORD) ((b *) 8); // dw = 0x08

DW = (DWORD) ((b5 *) (b *) 8); // dw = 0x10

DW = (DWORD) ((b2 *) (b5 *) (b *) 8); // dw = 0x10

DW = (DWORD) ((b2 *) (b5 *) (b *) 8) - 8; // dw = 8

}

Class B will retain four virtual function table pointers because it has four branches. Our goal is to get B2 interface in this branch in this branch, and the last result 8 is what we need, it represents the offset of the block B memory block.

From the above two examples, we have understood (DWORD) ((x *) (xMMAPCLASS *) 8)) - 8 The role we can get the interface we need through this value.

Below we analyze the IdispatchImpl template class from class T, ...> template class from class T, ...., IOUTER1, IOUTER2 It is a double interface, which is classified from idispatch, so you can have two branches, which is also a diamond structure, so according to our example, this offset value should be 4. To prove our ideas, let's verify our results through the function stack.

Function Stack:

5.atl :: atlinternalQueryInterface (...)

4.atl :: ccomobjectRootbase :: InternalQueryInterface (...)

3.cmyObject :: _ InternalQueryinterface (...)

2.atl :: ccomobject :: queryinterface (...)

1.Punk-> queryinterface (IID_IDISPATCH, (Void **) & pdispatch)

Explanation:

1: This is our verification code, PUNK is the iUnknown pointer of the component.

2 - 5: These code we are now very familiar, we only need to see the specific implementation of AtlinternalQueryInterface.

Atlinline Atlapi AtlinternalQueryinterface (void * pthis,

Const _tl_intmap_entry * pentries, refiid Iid, void ** ppvobject)

{

...........

While (pentries-> pfunc! = null)

{

Bool bblind = (Pentries-> PIID == NULL);

IF (Bblind || Inlineisequalguid (* (Pentries-> PIID), IID)

{

IF (Pentries-> Pfunc == _TL_SIMPLEMAPENTRY) // offset

{

Atlassert (! BBLIND); IUNKNOWN * PUNK = (IUNKNOWN *) ((int) PTHIS PENTRIES-> DW);

punk-> addRef ();

* pPVObject = punk;

Return S_OK;

}

...../ If it is not simple interface ...

}

PENTRIES ;

}

Return E_NOINTERFACE;

}

The key sentence is iUnknown * punk = (iUnknown *) ((int) PTHIS PENTRIES-> DW);

By observing the variable, just as we expected to Pentries-> DW = 4. (int) PTHIS PENTRIES-> DW) guarantees that we can get the virtual functions of the IOUTER2 branch, but because iDispatch is inherited from iUnknown, putting the virtual function pointer of iUnknown in the virtual function table, so (IUNKNOWN) *) Forced conversion, you can get the top address of this virtual function table, which is what we need. Maybe I will ask why the address of the virtual function table is not the address of a class instance? Don't forget, the interface is no data, it only has a pure virtual function. For customers, it can only access it through the virtual function defined by the interface, and it is impossible to access the member variables of the class that implement the interface, and the components are not visible to the customer, so only the virtual function table is used. The address is OK.

Third, com_interface_entry_tear_off (iid, x) Reference ATL routine Beeper, Commap

The purpose of using this macro is to implement some of the very few interfaces in a separate component, only this component is created only when the interface is queried, and when its reference count is reduced to 0, it will be Release. We know that the components in the ATL are implemented by multiple inheritance, each inherit an interface, will make more virtual function table pointers in the memory block allocated, and this macro can save this virtual for each component. Function table pointer comes (a pointer 4 bytes, it seems to be not much, huh, huh) Let's look at its typical usage:

Class CTearoff1: // This class is specifically used to implement the split interface item ITEAROFF1

Public IDispatchImpl ,

Public ccomtearoffObjectBase // external object

{

PUBLIC:

CTEAROFF1 () {}

~ Ctearoff1 () {}

Begin_COM_MAP (CTEAROFF1)

COM_ITERFACE_ENTRY (ITEAROFF1)

END_COM_MAP ()

HRESULT stdmethodcalltype get_name (bstr * pbstrname)

{

* PBSTRNAME = :: SysallocString (l "itemoff1");

Return S_OK;

}

}

Class Couter: public ..... / We really want to implement the components

{

PUBLIC:

...........

Begin_COM_MAP (COUTER)

...........

COM_INTERFACE_ENTRY_TEAR_OFF (IID_ITEAROFF1, CTEAROFF1)

END_COM_MAP ()

...........

}

Ctearoff1 implements the Tear-OFF interface ITEAROFF1, and the implementation method is not different from other components. The only difference is that it is inherited from ccomtearoffObjectBase, and CCOMTEAROFFOBJECTBASE is defined as follows: Template

Class CCOMTEAROFFOBJECTBASE: PUBLIC CCOMOBJECTROOTEX

{

PUBLIC:

Typedef owner; oWNERCLASS

CCOMOBJECT * M_POWNER;

CCOMTEAROFFOBJECTBASE () {m_powner = NULL;}

}

We also saw a class CComobject we are familiar with, which is the real generation class of the component. From the above definition, you can know that the CCOMTEAROFFOBJECTBASE main feature is to include a pointer to an external object (here is our component CComObject). Its features will be seen later.

We continue to track our execution process with our old way. Suppose Pouter is the IOUTER interface pointer of the components we have gained.

Perform Pouter-> QueryInterface (IID_ITEAROFF1, (Void **) & PTEAR1);

Function Stack 1:

7.ctearoff1 :: _ InternalQueryinterface (...)

6.atl :: ccominternalcreator > :: createInstance (...)

5.atl :: ccomobjectrootbase :: _ creator (...)

4.atl :: atlinternalQueryInterface (...)

3.atl :: ccomobjectrootbase :: InternalQueryInterface (...)

2.couter :: _ internalQueryinterface (...)

1.ttl :: ccomobject :: queryinterface (...)

Explanation:

1--4: These codes have been encountered many times, we still concentrate to look at the core code:

Atlinline Atlapi AtlinternalQueryinterface (void * pthis,

Const _tl_intmap_entry * pentries, refiid Iid, void ** ppvobject)

{

// ..........

While (pentries-> pfunc! = null)

{

Bool bblind = (Pentries-> PIID == NULL);

IF (Bblind || Inlineisequalguid (* (Pentries-> PIID), IID)

{

IF (Pentries-> Pfunc == _TL_SIMPLEMAPENTRY) // offset

{

// If it is a simple interface, ....

}

Else // Actual Function Call

{

HRESULT HRES = Pentries-> Pfunc (PTHIS, IID, PPVObject, Pentries-> DW);

IF (hres == s_ok || (! BBLIND && FAILED (HRES))))

Return HRES;

}

}

PENTRIES ;

}

Return E_NOINTERFACE;

}

When ITEAROFF1 is found in the interface mapping array in the interface, because it is not a simple interface, it is necessary to perform Pentries-> PFUNC (....). Let's take a look at the definition of com_interface_entry_tear_off:

#define com_interface_entry_tear_off (IID, X) /

{& IID, /

(DWORD) & _ CCOMCREATORDATA

CCominternalCreator > /

> :: data, /

_CREATOR},

I don't quite understand, or continue our route.

5: The original _Creator is a static member function of CComobjectRootBase, which is a base class of the COUTER, so you can write this way without compiling an error. Take a look at its implementation:

Static HRESULT WINAPI _CREATOR (Void * PV, Refiid IID, VOID ** PPVObject, DWORD)

{

_TL_CREATORDATA * PCD = (_TL_CREATORDATA *) DW;

Return PCD-> PFUNC (PV, IID, PPVObject);

}

Struct _atl_creatordata

{

_TL_CREATORFUNC * PFUNC;

}

TypedEf HRESULT (WinAPI _TL_CREATORFUNC) (Void * PV, REFIID RIID, LPVOID * PPV);

Template

_TL_CREATORDATA _CCOMCREATORDATA :: DATA = {CREATOR :: CreateInstance};

The source code is listed, don't use me more, everyone can understand. Continue routing

6: Above a large circle, now we call should be ccominternalcreator <...> :: CreateInstance

Template

Class CCominternalCreator

{

PUBLIC:

Static HRESULT WINAPI CREATEINSTANCE (Void * PV, Refiid Riid, LPVOID * PPV)

{

Atlassert (* ppv == null);

HRESULT HRES = E_OUTOFMEMORY;

T1 * P = NULL;

ATLTRY (P = New T1 (PV))

IF (p! = null)

{

P-> setvoid (PV);

P-> InternalFinalConstructAddref ();

HRES = P-> Finalconstruct ();

P-> infinalconstructrelease ();

IF (hres == s_ok) hres = p -> _ InternalQueryinterface (RIID, PPV);

IF (hres! = s_ok) delete P;

}

Return HRES;

}

}

Like most Creator classes we have seen, it also has only one static CREATEINSTANCE function. Now we can finally create our split component, it is not ctearoff1, it is also a pack of packaging, is ccomtearoffObject! Now let's take a look at its constructor, what is going on:

CCOMTEAROFFOBJECT (VOID * PV)

{

Atlassert (m_powner == null); m_powner = reinterpret_cast *> (pv);

M_Powner-> addRef ();

}

Remember that CTEAROFF1 is inherited from CCOMTEAROFFOBJECTBASE, this base class contains a member variable m_powner, now it is assigned to point to its external object.

7. Now, the component of this implementation segmentation interface is now created, and the rest of the work in ctearoff1 is already repeated labor, and will not be described again.

Perform PTEAR1-> QueryInterface (ITEAROFF1, (Void **) & PTEAR2) A component that implements a split interface may contain multiple split interfaces, let's detect its query process.

Function Stack 2:

4 ............

3.couter :: _ InternalQueryInterface (...)

2.atl :: ccomobject :: queryinterface (...)

1.atl :: ccomtearoffObject :: queryinterface (...)

Explanation:

1:

STDMETHOD (QueryInterface) (Refiid IID, Void ** PPVObject)

{

Return M_Powner-> QueryInterface (IID, PPVObject);

}

Remember the segmentation component we created is CCOMTEAROFFOBJECT ? Now performing query operations is its member function. It is very simple, in fact it doesn't do anything, just hand it over to its external object (ie ccomobject ). Remember that m_powner assigns a value in the constructor. Do you feel bad now? Ha ha

2, 3: Sure enough, now no longer have to look down, the rest will be repeated everything we do in calling the first query operation. This process is simple, but it also implies a point: If you query a component of the partial interface, you will call a new instance! ! ! In the above example, the last result of PTEAR2 and PTEAR1 are different! ! This is obviously a waste! In the next section, we will introduce a macro that can solve this problem!

Four .com_interface_entry_cached_tear_off (iid, x, punk) ATL routine Commap

The most important difference between this macro with the com_interface_entry_tear_off macro in the previous section is that new objects will not be created when the query is queried in other interfaces in the split object. Let's take a look at its typical usage:

Class CTearoff2:

Public IDispatchImpl ,

Public CCOMTEAROFFOBJECTBASE

{

PUBLIC:

Ctearoff2 () {}

~ Ctearoff2 () {}

Begin_COM_MAP (CTearoff2)

COM_INTERFACE_ENTRY (ITEAROFF2)

END_COM_MAP ()

HRESULT stdmethodcalltype get_name (bstr * pbstrname)

{

* pbstrname = :: sysallocstring (l "itemoff2"); return s_ok;

}

}

Class Couter: public ....

{

PUBLIC:

Begin_COM_MAP (COUTER)

COM_INTERFACE_ENTRY_CACHED_TEAR_OFF (IID_ITEAROFF2, CTEAROFF2, M_PUNKTEAROFF2.P)

......

END_COM_MAP ()

CComptr m_punktearoff2;

.....

}

CTearoff2 implements the split interface ITEAROFF2. Its class definition is the same as the CTearoff1 seen by the previous section, regardless of which division interface is the same, different places lies in couter. A member variable m_punktearoff2 is added to a parameter of the macro in the COUTER. We continue to track its internal execution with the old way, assuming that the POUTER is the components that have been obtained have interface IOUTER pointers.

Perform Pouter-> Queryinterface (IID_ITEAROFF2, (Void **) & PTEAR1);

Function Stack 1:

9.ctearoff2 :: _ InternalQueryinterface (...)

8.atl :: ccomcachedtearoffObject :: queryinterface (...) (second call)

9.atl :: ccomcachedtearoffObject :: queryinterface (...)

8.atl :: ccomcreator > :: creteinstance ()

7.atl :: ccomobjectrootbase :: _ cache (...)

6.couter :: _ cache (...)

5.atl :: atlinternalQueryInterface (...)

4.atl :: ccomobjectRootbase :: InternalQueryInterface (...)

3, couter :: _ internalQueryinterface (...)

2.atl :: ccomobject :: queryinterface (...)

1.ctestdlg :: onbutton1 () line 187 22 Bytes

Explanation:

1: Pouter-> QueryInterface (IID_ITEAROFF2, (Void **) & PTEAR1);

2-5: This code saw many times, no need to talk, now the program is executed

HRESULT HRES = Pentries-> PFUNC (PTHIS, IID, PPVObject, Pentries-> DW); It seems that we have to see this macro definition to know what PFUNC is performing.

#define com_interface_entry_cached_tear_off (IID, X, PUNK) /

{& IID, /

(DWORD) & _ CCOMCACHEDATA

CCMCREATOR >, / /

(DWORD) OFFSTOF (_CommapClass, PUNK) /

> :: data, /

_Cache},

The definition of the macro we came in the previous section is not too the same, or then track it first.

6: It turns out in Begin_COM_MAP also defined _cache functions:

Static HRESULT WINAPI _CACHE (Void * PV, Refiid IID, VOID ** PPVObject, DWORD DW) / {/

......

HRESULT HRES = CComobject HRES = CComobjectRootBase :: _ Cache (PV, IID, PPVObject, DW); /

......

} /

7: Take a look at CComobjectRootBase :: _ cache source code:

Static HRESULT WINAPI _CACHE (Void * PV, Refiid IID, VOID ** PPVObject, DWORD DW)

{

HRESULT HRES = E_NOINTERFACE;

_TL_CacheData * PCD = (_TL_CACHEDATA *) DW;

IUNKNOWN ** PP = (iUnknown **) ((DWORD) PV PCD-> dwoffsetvar);

IF (* pp == null) hres = pcd-> pfunc (PV, IID_IUNKNOWN, (VOID **) PP);

IF (* pp! = null) hres = (* pp) -> QueryInterface (IID, PPVObject);

Return HRES;

}

The key to the problem is DW, DW is passed from Pentries-> DW. We have to look at the macro definition

(DWORD) & _ CCOMCACHEDATA

CCMCREATOR >, / /

(DWORD) OFFSTOF (_CommapClass, PUNK) /

> :: data, /

What means.

Template

_TL_CacheData _ccomcachedata :: data = {dwvar, creator :: createinstance};

CCOMCREATOR We have seen its definition in front, it has only one member function createInstance.

Template

Class CComphedtearoffObject:

Public IUNKNOWN,

Public ccomobjectrootex

{

PUBLIC:

Typedef contained _baseclass;

CcomcachedtearoffObject (Void * PV)

: m_contained ((Contained :: _ OwnerClass *) PV) -> getControllingunknown ())

{

m_contained.m_powner = reinterpret_cast *> (PV);

}

CCOMCONTAINEDOBJECT m_contained;

}

CCOMCAMCHEDTEAROFFOBJECT is the key to this macro and the previous section, because it contains a CCOMCONTAINEDOBJECT object. The role of this object is in the query.

Let's take a look at the definition of Offsetof:

#define offsetof (s, m) ((SIZE_T) & (((S *) 0) -> M) For DWORD OFFSTOF (_CommapClass, Punk) is the offset value in the _CommapClass class. Let's take a look at _ATL_CACHEDDATA.

Struct _tl_cachedata

{

DWORD DWOFFSETVAR;

_TL_CREATORFUNC * PFUNC;

}

TypedEf HRESULT (WinAPI _TL_CREATORFUNC) (Void * PV, REFIID RIID, LPVOID * PPV);

It should be noted that the parameter DW from the _Cached () function is the variable of the _ATL_CACHEDATA structure, so you can know

(DWORD) PV PCD-> DWOFFSetVar is the offset value of M_PunkTearoff2 defined in class couter, so IUNKNOWN ** PP is a pointer to a pointing pointer to M_PunkTearoff2.

PDC-> PFUNC () will call CCOMCREATOR > :: CreateInstance

8: CCOMCREATOR :: CreateInstance will create a CCOMCACHEDTEAROFFOBJECT <> object instance. Its constructor is defined as follows:

CcomcachedtearoffObject (Void * PV)

: m_contained ((Contained :: _ OwnerClass *) PV) -> getControllingunknown ())

{

Atlassert (m_contained.m_powner == null);

m_contained.m_powner = reinterpret_cast *> (PV);

}

Here Contained is CTEAROFF2, Contained :: _ OwnerClass is the couter, you can see M_Contained to save the pointer of the external object.

9: After the object is created, the interface item ITEAROFF2 will be queried.

STDMETHOD (QueryInterface) (Refiid IID, Void ** PPVObject)

{

// If it is iUnknown, ..., return to the iUnknwon interface pointer

Else

HRES = m_contained._internalQueryinterface (IID, PPVObject);

.....

}

Note that the query work is handed over to m_contained, that is, a ccomcontainedObject object. But now the IUNKNOWN pointer is now, don't forget, we also define an iunknown pointer in the couter, now query is it! !

8: After a series of decall, back to _Cache (), now continue to check the ITEAROFF2 interface. Yes ITEAROFF2 is queried according to the iUnknown pointer we just queried. So once again entered atl :: ccomcachedtearoffObject :: queryinterface (...), but this will call M_Contained._internalQueryInterface (...).

9: Because the base class of ccomcontainedObject m_contained is ctearoff2, CTEAROFF2 :: _ InternalQueryinterface will be called (...)

The rest of the operation is nothing special, and only general query operations. Perform PTEAR1-> QueryInterface (ITEAROFF2, (Void **) & PTEAR2);

Function Stack 2:

12.atl :: atlinternalQueryInterface (...)

11.atl :: ccomobjectRootbase :: InternalQueryInterface (...)

10.ctearoff2 :: _ InternalQueryinterface (...)

9.atl :: ccomcachedtearoffObject :: queryinterface (...)

8.atl :: ccomobjectrootbase :: _ cache (...)

7.couter :: _ cache (...)

6.atl :: atlinternalQueryInterface (...)

5.atl :: ccomobjectrootbase :: InternalQueryInterface (...)

4.couter :: _ InternalQueryInterface (...)

3.atl :: ccomobject :: queryinterface (...)

2.atl :: ccomobjectrootbase :: OuterQueryInterface (...)

1.atl :: ccomcontainedObject :: queryinterface (...)

Explanation:

1: The first step may make us confused, why do ccomcontainedObject :: queryinterface in the previous section, execute atl :: ccomtearoffObject :: queryinterface (...), so we also naturally Conjective, here should be executed is a function of ccomcachedtearoffObject. But let's take a look at the definition of ccomcachedtearoffObject:

Template

Class CComphedtearoffObject:

Public IUNKNOWN,

Public ccomobjectrootex

{...};

It turns out that ccomcachedtearoffObject is not inherited from the Contained class (here is CTearoff2), and CCOMTEAROFFOBJECT is inherited from ctearoff1! So the PTEAR1 we just got will not be the object of CCOMCHEDTEAROFFOBJECT. In fact, ccomcontainedObject is inherited from ctearoff2, and query the ITEAROFF2 interface in step 9 in the above function stack, the work is handed over to m_contained, this is a ccomcontainedObject object, so I actually query ITEAROFF2 pointing Is CCOMCONTAINEDOBJECT object. So now the execution will be ccomcontainedObject :: queryinterface (...)! ! !

STDMETHOD (QueryInterface) (Refiid IID, Void ** PPVObject)

{

HRESULT HR = OuterQueryInterface (IID, PPVObject);

IF (Failed (HR) && _GetrawunkNown ()! = m_pouterunknown) hr = _internalquryinterface (IID, PPVObject);

Return HR;

}

//? m_pouterunknown

2:

HRESULT OTERQUERYINTERFACE (Refiid IID, VOID ** PPVObject)

{

Return m_pouterunknown-> queryinterface (IID, PPVObject);

}

Turn the query work to the external object, that is, COUTER.

First, the functionality of the second step is the same as that of the previous section, is handed over to the external object to process, the difference below

3-8: The query process in couter is no different from the above example, we can jump directly to step 8

Static HRESULT WINAPI _CACHE (Void * PV, Refiid IID, VOID ** PPVObject, DWORD DW)

{

....

IF (* pp == null) hres = pcd-> pfunc (PV, IID_IUNKNOWN, (VOID **) PP);

IF (* pp! = null) hres = (* pp) -> QueryInterface (IID, PPVObject);

Return HRES;

}

I still remember that we define an iUTerOwn pointer m_punktearoff2, we created a CTEAROFF2 object when I first queryed the ITEAROFF2 interface, and queried an IUnknown pointer to it. Now it works, in _Cache Judgment If m_punktearoff2 does not equal empty, it indicates that CTEAROFF2 has been created, but it is directly used to use it to query the interface.

9: So now call CCOMCachedTearoffObject :: queryinterface (...), in step 9 in the last function stack, we have seen this queryinterface code, which handed the query work to m_contained._internalQueryInterface (.), because there is no Begin_COM_MAP macro in CCOMContainedObject, so there is no _internalQueryinterface (), so actually calling the function of the class it contains, that is, ctearoff2 :: _ InternalQueryInterface (...)

10-12: The following work is very simple, and will not be described again.

to sum up:

COM_TERFACE_ENTRY_CACHED_TEAR_OFF is a relatively troublesome macro, which is different from the macro described in the previous section is that the process of creating a split interface object is only once. If the object has been created, the next query is not the interface of the object. Create a new split object. To achieve this, it contains an IUNKNOWN pointer in the external object and queries this IUNKNOWN pointer when you first create a split object, so you can know if this pointer is empty, you can know if this split object has been created, thus Decide whether a new split object is created and the other interfaces within the split object are queried. It is particularly important to note that there are actually two objects being created, one is ccomcachedtearoffObject , the other is CCOMCONTAINEDOBJECT . And the second object is implemented inside the first object, and the real query work is also handed over to the second object. COUTER:: M_PUNKTEAROFF2 is an iUnknown pointer for an object. When you use it to query itemoff2, it is actually handed over to its internal object M_Contained, which can be seen very clear on the 8th, 9 steps. I finally finished this macro, I feel that this macro may be the most complicated in the ATL interface map macro, in fact it does not use the true function of CCOMCONTAINEDObject. I feel that this macro may not be so troublesome. Fives. COM_INTERFACE_ENTRY_AGGREGATE (IID, PUNK) ATL routine Commap

This section will be introduced to the macro used to aggregate objects in ATL. Please refer to other reference books for the concept of gathering objects. Let's take a look at this macro test now:

Class CAGG:

Public IDispatchImpl ,

Public isupporterrorinfo,

Public CComObjectRoot,

Public ccomcoclass

{

.....

}

CAGG is a gathering class, which does not differ from the general ATL component, just notice that do not join DECLARE_NO_AGGREGATABLE in its class definition.

Class Couter:

Public cchainbase,

Public IDispatchImpl ,

Public CCOMCLASS

{

HRESULT FINALCONSTRUCT ();

Void finalrelease ();

Begin_COM_MAP (COUTER)

COM_INTERFACE_ENTRY_AGGREGATE (IID_IAGG, M_PUNKAGG.P)

END_COM_MAP ()

Declare_get_controlling_unknown ()

CComptr m_punkagg;

}

The COUTER contains the aggregated component CAGG, which contains several differences:

(1) Join Com_Interface_entry_aggregate (IID_IAGG, M_PUNKAGG.P) macro.

#define com_interface_entry_aggregate (IID, PUNK) /

{& IID, /

(DWORD) OFFSTOF (_CommapClass, Punk), /

_Delegate},

Offsetof We have seen in the previous section, you can guess it is the position in PUNK in the class. That is, M_Punkagg is in the position of the COUTER. (2) Join the macro Declare_Get_Controlling_unkNown (), which is defined as:

#define declare_get_controlling_unknown () public: /

Virtual iUnknown * getControllingunknown () {return getunknown ();

We don't have to continue to go abroad, you can see this function will return to the component's iUnknown pointer from literally.

(3) Add a member variable in the COUTER: ccomptr m_punkagg; m_punkagg will be used to obtain an IUNKNOWN pointer to the aggregated component.

(4) Heavy loaded FinalConstruct, FinalRelease

HRESULT COUTER :: FinalConstruct ()

{

Iunknown * punkouter = getControllingunkNown ();

HRESULT HRES = CocreateInstance (CLSID_CAGG, PUNKOUTER, CLSCTX_ALL, IID_IUNKNOWN, (void **) & m_punkagg);

Return HRES;

}

Void couter :: finalrelease ()

{

m_punkagg.release ();

.....

}

FinalConstruct will be called after creating component couter, so you will create a gathering component here. In principle, the aggregation component can be created only when needed, but can also be created along with the components that contain it. There is no special place for the creation of the aggregation components, just pay attention to it will query the IUnknown pointer and return to m_punkagg. External components will operate the components via the M_PUnkagg operation. Also note that Punkouter as a parameter of CocreateInstance, which will cause CCOMAGGGOBJECT object, which contains a CCOMCONTAINEDOBJECT containing objects. With ccomcachedtearoff <> in the previous section, ccomaggobject is not derived from COUTER, so the real component object is not a ccomaggobject object, but it is included in the ccomcontainedObject object. The same PUNKOUTER will be the iUnknown pointer of ccomaggobject <>, and also calling its QueryInterface will turn the _ternalQueryinterface function (Oh, now I am guess now, see what I guess is right now.

Run Pouter-> queryinterface (IID_IAGG, (Void **) & PAGG1)

Function Stack 1:

9.atl :: atlinternalQueryInterface (...)

8.atl :: ccomobjectrootbase :: InternalQueryinterface (...)

7.cagg :: _ InternalQueryInterface (...)

6.atl :: ccomaggobject :: queryinterface (...)

5.atl :: ccomobjectrootbase :: _ delegate (...)

4.atl :: atlinternalQueryInterface (...)

3.atl :: ccomobjectrootbase :: InternalQueryInterface (...) 2.couter :: _ InternalQueryinterface (...)

1.ttl :: ccomobject :: queryinterface (...)

Explanation:

1-5: These steps have been called many times because of this macro definition using _Delegate, so CComObjectRootBase :: _ delegate (...).

Static HRESULT _DELEGATE (Void * PV, Refiid IID, Void ** PPVObject, DWORD DW)

{

HRESULT HRES = E_NOINTERFACE;

IUNKNOWN * P = * (iUnknown **) ((DWORD) PV DW);

IF (p! = null) hres = p-> queryinterface (IID, PPVObject);

Return HRES;

}

The meaning of the second sentence we have seen in the previous section, the last result p = couter :: m_punkagg.

6: As we just expected, call now ccomaggobject :: queryinterface ()

STDMETHOD (QueryInterface) (Refiid IID, Void ** PPVObject)

{

// If the query is iUnknown, then ....

Else

HRES = m_contained._internalQueryinterface (IID, PPVObject);

Return HRES;

}

Just as we expected, it will be handed over to it. (This code seems to be seen in the previous section, huh, huh)

7-9: Like the same section, it will be handed over to CAGG :: _ InternalQueryInterface (...), and the remaining work will be done by CAGG. The final returned pointer is actually the interface pointer of the CCOMCONTAINEDOBJECT component.

Run PAGG1-> Queryinterface (IID_IAGG, (Void **) & PAGG2)

Function Stack 2:

9.cagg :: _ InternalQueryInterface (...)

8.atl :: ccomaggobject :: queryinterface (...)

7.atl :: ccomobjectRootbase :: _ delegate (...)

6.atl :: atlinternalQueryInterface (...)

5.atl :: ccomobjectrootbase :: InternalQueryInterface (...)

4.couter :: _ InternalQueryInterface (...)

3.atl :: ccomobject :: queryinterface (...)

2.atl :: ccomobjectrootbase :: OuterQueryInterface (...)

1.atl :: ccomcontainedObject :: queryinterface (...)

Explanation:

1-9: Browse the entire stack, with the stack two of our previous section, because it is used to include objects. The role of the object has gone a proxy, he first handed the query to the external object (step 1, 2), when the external object is found to query the interface of the aggregated component (IAGG), The query is returned to the pointer to the aggregated component (M_PUnkagg, step 7, pay attention to this is not a real aggregation component), M_PUnkagg will hand over the query to the included object (step 8), including the object and give the query That's that the external object finds that the external object is found to query the interface of the external component, it is very simple, and directly queries. This prevents inconsistencies of external components and aggregated component query operations. Hey, the truth is really troublesome, but it is ok, it is very similar to the macro of the previous section. Related source code can be found in the previous section. Sixth, com_interface_entry_aggregate_blind Accommoding ATL routine Commap

In the previous section, we told com_interface_entry_aggregate, which is very similar to it.

#DEFINE COM_ITERFACE_ENTRY_AGGREGATE_BLIND (PUNK) /

{NULL, /

(DWORD) OFFSTOF (_CommapClass, Punk), /

_Delegate},

As can be seen from the definition, it is the only difference between the introduction to the previous section is that it does not specify the interface ID! !

So the first item in its definition is NULL.

This macro is used in the same way as our com_interface_entry_aggregate. You can refer to the previous section and the ATL routine COMMAP.

Let's take a look at the related code in atlinternalQueryInterface ().

Atlinline Atlapi AtlinternalQueryinterface (void * pthis,

Const _tl_intmap_entry * pentries, refiid Iid, void ** ppvobject)

{

// If it is iUnknown, ....

While (pentries-> pfunc! = null)

{

Bool bblind = (Pentries-> PIID == NULL);

IF (Bblind || Inlineisequalguid (* (Pentries-> PIID), IID)

{

IF (Pentries-> Pfunc == _TL_SIMPLEMAPENTRY) // offset

{

Atlassert (! BBLIND);

IUNKNOWN * PUNK = (IUNKNOWN *) ((int) PTHIS PENTRIES-> DW);

punk-> addRef ();

* pPVObject = punk;

Return S_OK;

}

Else

{

HRESULT HRES = Pentries-> Pfunc (PTHIS, IID, PPVObject, Pentries-> DW);

IF (hres == s_ok || (! BBLIND && Failed (HRES))) Return HRES;

}

}

PENTRIES ;

}

Return E_NOINTERFACE;

}

Pay attention to the variable BBLIND:

Bool bblind = (Pentries-> PIID == NULL); If the interface ID is not specified, continue after the operation, it can be seen that even if the IID we need is not _delegate.

As can be seen from the above, this macro is suitable for a case where a gathering component has multiple interfaces, so as long as it is querying the interface of this aggregation component, it will enter the _dlegate function. But special attention is the location of this macro! ! For example, if this is the order: Begin_COM_MAP

COM_INTERFACE_ENTRY_AGGREGATE_BLIND (m_punkaggblind.p)

COM_ITERFACE_ENTRY (IOUTER)

END_COM_MAP

When you query the IOUTER interface, you will be wrong! ! !

7. COM_ITERFACE_ENTRY_AUTOAGGREGATE (IID, PUNK, CLSID) Part of ATL routine Commap

Let's take a look at this macro:

#define COM_ITERFACE_ENTRY_AUTOAGGREGATE (IID, PUNK, CLSID) /

{& IID, /

(DWORD) & _ CCOMCACHEDATA

Ccomaggregatecreator <_commapclass, & clsid>, /

(DWORD) OFFSTOF (_CommapClass, PUNK) /

> :: data, /

_Cache},

Let's take a look at its typical usage:

Class CautoAgg:

Public IDispatchImpl ,

Public isupporterrorinfo,

Public CComObjectRoot,

Public ccomcoClass

{

......

}

With the general components and no numbers.

Class Couter:

Public cchainbase,

Public IDispatchImpl,

Public CCOMCOCLASS

{

Begin_COM_MAP (COUTER)

COM_INTERFACE_ENTRY_AUTOAGGREGATE (IID_IAUTOAGG, M_PUNKAUTOAGGG.P, CLSID_CAUTOAGG)

END_COM_MAP ()

CComptr M_PunkautoAgg;

}

Unlike the macro COM_ITERFACE_ENTRY_AGGREGRATE (_), COUTER does not have to create a gathering component in FinalConstruct. External components will automatically create aggregated components! ! !

1.

Template

_TL_CacheData _ccomcachedata :: data = {dwvar, creat :: creat einstance};

2.

Static HRESULT WINAPI _CACHE (Void * PV, Refiid IID, VOID ** PPVObject, DWORD DW)

{

HRESULT HRES = E_NOINTERFACE;

_TL_CacheData * PCD = (_TL_CACHEDATA *) DW;

IUNKNOWN ** PP = (iUnknown **) ((DWORD) PV PCD-> dwoffsetvar);

IF (* pp == null) hres = pcd-> pfunc (PV, IID_IUNKNOWN, (VOID **) PP);

IF (* pp! = null) hres = (* pp) -> QueryInterface (IID, PPVObject);

Return HRES;

}

3.

Template

Class CComaggregatecreator

{

PUBLIC:

Static HRESULT WINAPI CREATEINSTANCE (Void * Pv, Refiid / * riid * /, lpvoid * ppv)

{

Atlassert (* ppv == null);

Atlassert (PV! = NULL);

T * p = (t *) PV;

Return CocreateInstance (* PCLSID, P-> getControllingunkNown (), CLSCTX_ALL, IID_IUNKNOWN, PPV);

}

}

Because _Cache, _ccomcrachedata, CCOMAGGREGATECREATOR these few classes and functions we have seen in front or have seen it, so no more. In short, we can see that if m_punkautoagg.p does not directly query the flight, create a gathering component.

Compared with the macro COM_INTERFACE_ENTRY_AGGREGATE, this macro seems to be better, only when it is needed, it is easier to use.

8. COM_INTERFACE_ENTRY_AUTOAGGREGATE_BLIND (PUNK, CLSID) Accommap ATL RRC Commap

Take a look at its definition:

#DEFINE COM_ITERFACE_ENTRY_AUTOAGGREGATE_BLIND (PUNK, CLSID) /

{NULL, /

(DWORD) & _ CCOMCACHEDATA

Ccomaggregatecreator <_commapclass, & clsid>, /

(DWORD) OFFSTOF (_CommapClass, PUNK) /

> :: data, /

_Cache},

Oh, this macro combines the characteristics of com_interface_entry_autoaggregate () and COM_ITERFACE_ENTRY_AGGREGATE_BLIND (), which can automatically create multiple interfaces in the aggregated component. No longer! !

Nine, com_interface_entry_chain (classname) Part of the ATL routine Commap

Let's take a look at its definition:

#define com_interface_entry_chain (classname) /

{NULL, /

(DWORD) & _ ccomchaindata :: data, /

_Chain},

Typical usage:

Class cchain:

Public IDispatchImpl ,

Public isupporterrorinfo,

Public CComObjectRoot,

Public ccomcoClass

{

........

}

It is not different from the general components.

Class Couter:

Public cchain,

....

{

Begin_COM_MAP (COUTER)

......

COM_ITERFACE_ENTRY_CHAIN ​​(CChain)

END_COM_MAP ()

}

We are very familiar with the process of queries, you can take a look at _Chain's function. _Chain () is a member function of CComobjectRootBase:

Static HRESULT WINAPI _CHAIN ​​(Void * PV, REFIID IID, VOID ** PPVObject, DWORD DW) {

_TL_Chaindata * PCD = (_ATL_CHAINDATA *) DW;

Void * p = (void *) ((DWORD) PV PCD-> DWOFFSET);

Return INTERQUERYINTERFACE (P, PCD-> PFUNC (), IID, PPVObject;

}

Struct _atl_chaindata

{

DWORD DWOFFSET;

Const _TL_INTMAP_ENTRY * (WINAPI * PFUNC) ();

}

Let's take a look at the DW section in the macro:

Template

_TL_Chaindata _ccomchaindata :: data =

{OffsetOfClass (Base, Derived), Base ::_ getENTRIES};

Basically, we have already understood what is going on. Void * p will get the base class pointer, Interalexryinterface we are already very familiar, _chain gives the base class pointer and the base class interface mapping macro to it, actually query The base class interface! ! !

Under normal circumstances, this macro is placed between BeGin_COM_MAP and END_COM_MAP, which means that only the interface of the Father class is only when the interface is not checked in the current class. However, it is often placed in the first place. At this time, I will first check the father-class interface. Only when the parent class does not implement this interface. In ATL components are implemented in multiple inheritance, ATL defines a number of categories to implement some common interfaces, which are often used as the base class of components, so this macro is used in a large number of use.

All of our important macros have been said, the rest is very simple macro. Oh, still put them all, good start.

Ten, com_interface_entry_iid (IID, X)

#define COM_ITERFACE_ENTRY_IID (IID, X) /

{& IID, /

OffsetofClass (X, _CommapClass), /

_TL_SIMPLEMAPENTRY},

11. COM_ITERFACE_ENTRY2_IID (IID, X, X2)

#define COM_ITERFACE_ENTRY2_IID (IID, X, X2) /

{& IID, /

(DWORD) ((x2 *) ((_COMMAPCLASS *) 8)) - 8, /

_TL_SIMPLEMAPENTRY},

From the definition, these two macros are just a "IID" compared with COM_ITERFACE_ENTRY () and COM_INTERFACE_ENTRY2 (). There is no other benefit, but it is clearly pointed out by the user to the interface IID, without the system to convert according to the interface name.

12. COM_ITERFACE_ENTRY_FUNC (IID, DW, FUNC)

#define com_interface_entry_func (IID, DW, FUNC) /

{& IID, /

DW, /

Func},

Remember the code in atlinternalQueryInterface ()? If you find the interface we have to find in the interface mapping table, and this interface is not _ATL_SIMPLEENTRY, the specified function in the macro definition is executed.

This macro gives us a function of writing a handset. This function must be defined below:

HRESULT WINAPI FUNC (Void * PV, REFIID RIID, LPVOID * PPV, DWORD DW); When the AtlinternalQueryInterface calls FUNC, the relevant information will be passed. PV is the pointer of the class object, the RIID is the interface to query, the PPV is the interface pointer to which the query is obtained, DW is the parameters specified in the macro definition. Also if you do not intend to return the interface pointer in the function, PPV should be assigned to null and return S_FALSE or E_NOIInterface. Returns S_FALSE just continues to find it, if you return to E_NOIInterface, the query will be terminated. If you return the interface pointer, you should return S_OK. 13, com_interface_entry_func_blind (dw, func)

#define COM_ITERFACE_ENTRY_FUNC_BLIND (DW, FUNC) /

{NULL, /

DW, /

Func},

As for the characteristics of _blind type, you can see a few sections.

14, com_interface_entry_nointerface (x)

#define com_interface_ENTRY_NOINTERFACE (X) /

{& _TL_IIDOF (x), /

NULL, /

_Nointerface},

_Nointerface is a member function of CComobjectRootBase to see its definition:

Static hResult winapi _nointerface (...)

{

Return E_NOINTERFACE;

}

It turns out that it just returns E_NOINTERFACE and terminates the query.

Haha, it seems that I don't want someone to find this interface! ! !

Fifteen, com_interface_entry_break (x)

#DEFINE COM_INTERFACE_ENTRY_BREAK (X) /

{& _TL_IIDOF (x), /

NULL, /

_Break},

_Break is also a member function of CComobjectRootBase to see its definition:

Static HRESULT WINAPI _BREAK (...)

{

IID;

_Tldumpiid (IID, _T ("Break Due To Qi for Interface", S_OK);

DEBUGBREAK ();

Return S_FALSE;

}

If you check this interface, you will call DEBUGBREAK () and return S_FALSE and continue to query. DEBUGBREAK () What is the effect of everyone try it, must be familiar, huh, huh.

At this time, all fifteen interface maps have all been finished, 唉, it is not easy, especially the previous macro tracks. Because of the limitations of text, many things are not easy to express. Some calls are also what I call it myself, may be different from the habits of others. No way, everyone will be just, huh, huh.

--- Full text ---

2000/3/31 early morning

Note: About the implementation of ATL in China

1. When you create a component, you must first create its class, and then call the CREATEINSTANCE () of the class factory to create the component.

Macro Declare_ClassFactory () is defined in CCOMCOCLASS, which contains a class factory object for the component.

_CLASSFAACTORYCREATORCLASS, its CREATEINSTANCE is a class factory used to create components. That is

CCMCREATOR > :: createInstance ();

2. The macro Declare_AGGREGATABLE () is also defined in CCOMCOCLASS, which contains the object_creatorclass,

This object is actually the component object we have to create (detailed in detail), it also has a createInstance, which is used to create this component! ! When creating a component of the component, tell the address of this function to the class.

3. When we succeed in obtaining a class object (at this time, we have created), we will then call the CREATEINSTANCE () of the class factory, in this function,

Create component's CREATEINSTANCE will create components.

4. So, you can see the total of three CreateInstance here:

(1) _CLASSFAACTORYCREATORCLASS :: CreateInstance () // Used to create a component of the component

(2) CCOMCLASSFAACTORY :: CreateInstance () // Used to call _CreatorClass :: CreateInstance

(3) _CREATORCLASS :: CreateInstance () // Used to create components

* This article is selected from: COM concentration

转载请注明原文地址:https://www.9cbs.com/read-31132.html

New Post(0)