ATL interface mapping macro detailed
-
Lostall
Preamble:
In the past few days, I looked at the ATL's interface mapping macro. I didn't know that it was deeply inserted. I suddenly wrote it.
idea. ATL defines a lot of interface mapping macro, there are several more important, although it is not necessary to put it
Some details are very clear, but in the process of in-depth learning, they can learn other ATL classes in the process of studying.
The system can be more clear, it should still be some benefit. I put it out according to the process of learning, and I also
I don't know if you can understand. I want to imitate the teacher's hand to explain its internal details, but not
The name of the beauty of the big words, "deeply shallow", huh, hunts, I only hope that everyone can help.
Introduce the interface map macro in each form of the COM_Interface_entry_xx in the ATL, respectively.
And will be explained in the orderless order, 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 ()
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.atl :: ccommodule :: getClassObject (...)
2.DLLGETCLASSOBJECT (...)
1.cocreateInstance (...) (client)
Explain as follows:
1: CoCreateInstance (CLSID_MYOBJECT, NULL, CLSCTX_INPROC_SERVER, IID_IUNKNOWN, (VOID **) & PUNK);
It will call the OLE API function COGETCLASSOBJECT (), while CogetherClassObject passes
LoadLibrary (...) is loaded into the DLL and calls 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 _Module variables define global variables in DLL:
CCOMMODULE _MODULE;
ATL passes a group 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 end_object_map () {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}};
#define object_entry (clsid, class) {& clsid, class :: updateRegistry,
Class :: _ ClassFactoryCreatorclass :: CreateInstance,
//The essential
Class :: _ Creatance ,: CreateInstance,
Null, 0, Class :: GetObjectDescription,
Class :: getcategorymap, class :: objectmain},
Generate a static global _ATL_OBJMAP_ENTRY type: Objectmap [];
Then ATL is
Bool WinApi Dllmain (Hinstance Hinstance, DWORD DWREASON, LPVOID
/ * LPRESERVED * /
{
.....
_Module.init (ObjectMap, Hinstance, & libid_test2lib);
.....
} Initialization_Module
// Notice in the case of initInstance () _Module
So _module initialization, in fact, he didn't do anything, in ccommodule :: init, it
Call atlmoduleinit (_ATL_MODULE * PM, _ATL_OBJMAP_ENTRY * P, HINSTANCE H), in
There is only one sentence in the middle: PM-> m_pobjmap = p; visible _Module is just a group of this global object mapping
Objectmap [] is given. So why can I get the class with _Module.getClassObject?
In fact, the key is that our component CMYObject inherits another base class ccomcoClass!
Define a macro declare_classfactory () in ccomcoClass
#define declare_classfactory () Declare_classfactory_ex (ccomclassfactory)
#define declare_classfactory_ex (cf)
Typedef CComcreator
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.
Above a large circle, we now already know that _Module contains the class objects of each component we want,
This is already enough for the current, and now you will continue to 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: atlinline atlapi atlmodulegetClassObject (_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 class contained in our components)
The CreateInstance function of the factory object); PCF we can also guess it is pointing to this type of IUNKNOWN finger
Needle, representing this class factory object is created, if the class object already exists, you don't 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
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 CCMCREATOR2
CCOMCREATOR
We saw a familiar string _CREATORCLASS, which still has a component containing objects. But also
There is a problem, we don't know why, is why _classfactorycreator and _creatorclass
Do you have to follow a createInstance? It seems that we must first take a look at CCMCREATOR 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 will finally understand _classfactorycre
Atorclass :: CreateInstance means what it means, it represents ccomclassfactory :: Createin
Stace (..), almost the same. 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, there is only one CreateInstance member function, from _creatorclass
We can know that it actually contains two CComObject, ccomaggobject's CreateInstance letters
Number (via ccomcreator), where ccomobject is used for non-aggregated objects, ccomaggobject is used to aggregate objects
Establish a corresponding object according to the situation. (The actual components actually generated in the ATL are not CMYObject, but
CCOMOBJECT, CCOMAGGOBJECT or CCompolyObject object, this concept is very important, but now you don't talk about it now)
Now we basically know what is going on, it is based on it.
There is an address of the creation of a function of the creation of the creation plant in an object map to create a class. PfNgetClassObject and
PfncreateInstance We basically know what is going on, but there is a problem why
Pentry-> PFNCREATEINSTANCE as a parameter in PENTRY-> PFNGETCLASSOBJECT (...)
transfer? 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 object
IF (p! = null)
{
P-> setvoid (PV);
P-> infinalconstructaddref (); hres = p-> finalconstruct ();
P-> infinalconstructrelease ();
IF (HRES == S_OK)
HRES = P-> QueryInterface (RIID, PPV);
IF (hres! = s_ok)
Delete P;
}
}
}
Note that T1 here is ccomobjectcached, this is our giving ccomcreator
Template parameters. We once again saw our familiar operator 'new'! Until now we finally created a group
Piece of the class. 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 remembers that we have passed CMYOBJECT :: _ CreatorClass :: CreateInstance
PENTRY-> PfNgetClassObject (...), we don't understand what is going on, now it has already opened
! It turns out that the class is a factory that needs it to create a component object! Although we just guess this from the literal meaning,
As we expected, in CCOMCLASSFAACTORY :: CreateInstance (...), we
I saw M_PFNCREATEINSTANCE (PUNKOUTER, RIID, PPVOBJ); now everything has already understood,
The layer packaging of ATL for our creation of a class has been opened, and the process of the rest of the creation is already
We are familiar with the process!
But now it is not finished, we also need to query an IUNKNOWN pointer for the class object, this pointer exists
We are in the PENTRY-> PCF seen earlier.
6: stdmethod (queryinterface) (REFIID IID, VOID ** PPVOBJECT)
{RETURN _INTERNALQUERYINTERFACE (IID, PPVObject);
The CComobjectCached :: queryinterface is now called, as for this class, we are now
I don't need to know if I seem, I am also 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 (),
This is the basis for the internalQueryInterface (...) implementation query.
The following static interface mapping arrays are defined 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
Points: the IID number of the interface, the offset value (most of the time), the function that needs to be executed, not for general interface
Perform other functions. _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 whole
The end point of the query process, which traverses the interface mapping table and makes the corresponding action according to each item. The elimination in the ATL
There are many kinds of shammurs, there are many corresponding movements, but now we don't care, now we have to do it.
Check an IUnknown interface, which is easy, we don't even need to traverse the interface mapping table.
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 (IISEQUNKNOWN (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. So as for
What is the meaning of this request, and pthis pentries-> dw, let's talk later, it is also a bunch of
problem. In short, we are now having easy access to the objects we need and IUNKNOWN finger.
needle.
4: I think we can get back to the first step, but in atl :: AtlmodulegetClassObject
But stop, look at its source code, I have to query the iUnknown pointer you just got.
IclassFactory pointer. Another way of calling, as in step 6 to 9, we will do
The same call. But note that in step 9, we are no longer an iUnknown pointer, so we need
See the code I have not listed yet, but this is left to the next function stack.
1: Finally, we have completed all the operations of the creation of a class, now we have to do it is what we are familiar.
Creating the CreateInstance (...) function of the class factory object creates the process of the component. As we see, now
In the OLE start call ccomclassfactory :: createInstance (), we have not forgotten, in the class factory object
The CreateInstance () function for creating components is retained, 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. so I
I don't want to go on, I am 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 by the iUnknown interface pointer of the components that have just been obtained. This is our
Really needed pointer.
2. Remember that we said that the components that ATL really created are not CMYOBJECT, but ccomobject, ccomaggobject.
Or ccompolyobile, here we create ccomobject. Therefore, of course we want to call
CComObject :: queryinterface (...), and confidence CCOMOBJECT also implements this function.
STDMETHOD (QueryInterface) (Refiid IID, Void ** PPVObject)
{RETURN _INTERNALQUERYINTERFACE (IID, PPVObject);
It is just simply call _internalQueryinterface (...), we also said that only the class is declared
Begin_com_map macro will have _internalQueryInterface (...), so now do it to its father
Go to 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 is 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 the IUNKNOWN interface is queried, the corresponding operation // below will be traversed 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 it is 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 is the meaning, the other is Pentries-> PFUNC in the end
What to do. The previous question will tell the story in com_interface_entry2, the latter problem will be
The different types of interfaces will be explained 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 it's macro
After we unlock the com_interface_entry (iMyObject) in the form:
{& _TL_IIDOF (ImyObject),
// Get the IMYObject IID value
Offsetofclass (iMyObject, CMYObject),
/ / Define offset
_TL_SIMPLEMAPENTRY},
// Indicates that it is 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, when querying the IclassFactory interface
Have a similar process, but still put it separately, just to look at the typical situation, huh, huh.
Second, COM_ITERFACE_ENTRY2 (X, X2) Part 2 ATL RROR: Commap
The ATL is to achieve components in a multi-inheritance, but if there are multiple branches in inherited trees to achieve the same
Interface, you need to know which branch is returned to it when you query this interface. This macro is doing this job.
Usually this macro is used for iDispatch interfaces. Let's take a look at its typical usage:
Class Couter:
Public IDispatchImpl & libid_commaplib>,
Public IDispatchImpl & libid_commaplib>,
PUBLIC ...
{
PUBLIC:
COUTER () {}
...
Begin_COM_MAP (COUTER)
COM_Interface_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 in the component.
Realization. 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) /
{& _TL_IIDOF (x), /
/ / Get the IID value of the interface
(DWORD) ((x2 *) ((_COMMAPCLASS *) 8)) - 8, /
_TL_SIMPLEMAPENTRY},
// Indicates is 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, representing it.
Branch. When an object is declared for class a, the system will allocate memory. Top of this memory
Keep two virtual function table pointers. Analyze the results of the program run, it can be seen that the last result 4 represents the finger
The offset between the virtual function table pointer to the interface A3 is the offset between the top 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 purpose is to get B :: b5 :: b2
The B2 interface in the branch, the last result 8 is what we need, it represents the offset of the block B memory block.
From the above two examples, we already understand (DWORD) ((x *) (_MMAPCLASS *) 8) - 8
We can get the interface we need through this value.
Below we analyze our actual situation COM_ITERFACE_ENTRY2 (Idispatch, IOUTER2)
The IDispatchImpl template class is derived from the class T, so COUTER wants to be in the two template classes.
Inherit, IOUTER1, IOUTER2 are both interface, namely the class derived from idispatch, so you can get the COUTER
There are two branches, and it is also a rhombus 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:
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 atlinternalQueryInterface
Specific implementation.
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 us
You can get the virtual function table of the IOUTER2 branch, but because iDispatch is also inherited from iUnknown, in the virtual function table
The topmost is put the virtual function pointer of IUNKNOWN, so it can be achieved (IUNKNOWN *) forced conversion, you can get this
The top address of the virtual function table is exactly what we need. Maybe I will ask why you get the virtual function table
Address, not the address of a class instance? Don't forget, the interface is no data, it only has a pure virtual function. for
Customer, it can only access it through the virtual function defined by the interface, and it is not possible to access the members of the class that implements the interface.
Variables, components' data is invisible to customers, so only the address of the virtual function table is used.
Third, com_interface_entry_tear_off (iid, x) Reference ATL routine Beeper, Commap
The purpose of using this macro is to achieve some of the very few interfaces in a separate component, only
This component is created when querying this interface, and it will be released when its reference count is reduced to 0. We know that components in ATL are implemented by multiple inheritance, each inheriting an interface, in the memory block allocated.
More virtual function table pointers, use this macro to save this virtual function table pointer for instances of each component.
(4 bytes of pointers, not much, huh, huh)
Let's take a typical usage:
Class CTearoff1:
// This class is specifically used to implement the split interface 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. Unique
It is inherited from CCOMTEAROFFOBJECTBASE, and CCOMTEAROFFOBJECTBASE is defined as follows:
Template
Class CCOMTEAROFFOBJECTBASE: PUBLIC CCOMOBJECTROOTEX
{
PUBLIC:
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
It can be found that the CCOMTEAROFFOBJECTBASE main feature is to include a point to the external object (here is us here.
The pointer of the component class ccomobject). Its features will be seen later.
We continue to track our execution process with our old way. Suppose Pouter is we have obtained
IOUTER interface pointer for the component.
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.atl :: 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, because it is not a simple interface,
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 compilation. 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. just now
We can finally create our split components, it is not ctearoff1, it is also packaged, it 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 change
The amount m_powner, now it is assigned to the pointer to its external object.
7. Now finally create the components of this implementation of the interface, and the remaining in ctearoff1 is querying ITEAROFF1
Work is already repeated labor, and will not be described again.
Execute PTEAR1-> QueryInterface (itemoff1, (void **) & ptear2)
A component that implements a split interface may contain multiple segmentation 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 execute query operations
It is its member function. Its implementation is simple, in fact it doesn't do anything, just give it to it.
The subject (ie ccomobject) is done. 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 duplicated in calling the first query operation.
all. This process is very simple, but it also implies a point: if a component that implements the segmentation interface is checked
When you ask another 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
This macro is the most important difference between the COM_ITERFACE_ENTRY_TEAR_OFF macro in the previous section.
Inquiry when other interfaces in the segmentation object, new objects will not be created. 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, and its class definition is the same as the CTearoff1 seen by the previous section.
It can be seen which divided interface is the same, the realization is the same, and the different places lies in couter. Added in couter
A member variable m_punktearoff2 as a parameter of the macro.
We continue to track its internal implementation process with the old way, assume that the POUTER is the components that have been obtained Interface
IOUTER pointer.
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> :: createInstance ()
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 the definition of this macro to know what PFUNC is performing.
#define com_interface_entry_cached_tear_off (IID, X, PUNK) /
{& IID, /
(DWORD) & _ CCOMCACHEDATA ccomcreator
(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 ccomcreator
(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 CComObjectroTrootex
{
PUBLIC:
Typedef contained _baseclass;
CCOMCHEDTEAROFFOBJECT (VOID * PV):
m_contained ((Contained :: _ OwnerClass *) PV) -> getControllingunkNown ())
{
m_contained.m_powner =
Reinterpret_cast *> (PV);
}
CCOMCONTAINEDObject M_Contained;
}
CCOMCACHEDTEAROFFOBJECT is the key to this macro and the previous section, because it contains one
Object of CCOMCONTAINEDOBJECT. 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) Offsetof (_CommapClass, punk) is the offset in the _CommapClass class
value.
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
I know (DWORD) PV PCD-> dwoffsetvar is the M_PunkTearoff2 defined in class couter
In the offset, IUNKNOWN ** PP is a pointer to a pointer to M_PunkTearoff2. and
PDC-> PFUNC () will call CComcreator> :: CreateInstance
8: CCOMCREATOR :: CreateInstance will be called, you will create a ccomcachedtearoffObject <>
Object instance. Its constructor is defined as follows:
CCOMCHEDTEAROFFOBJECT (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 COUTER, visible M_Contained
The pointer to the external object is saved.
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 query is the iUnknown pointer, don't forget, we also define an iUternown finger in COUTER.
Needle, now query is it! !
8: After a series of decall, back to _Cache (), now continue to check the ITEAROFF2 interface. Yes according to us
ITEAROFF2 is queried by IUNKNOWN pointer. So once again
Atl :: ccomcachedtearoffObject :: queryinterface (...), but this will be called
Is m_contained._internalQueryInterface (...).
9: Because the base class of ccomcontainedObject m_contained is CTEAROFF2, it will call
CTEAROFF2 :: _ InternalQueryInterface (...)
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 naturally guess, here should be executed is the function of ccomcachedtearoffObject. but
Let's take a look at the definition of ccomcachedtearoffObject:
Template
Class CComphedtearoffObject:
Public IUNKNOWN,
Public CComObjectroTrootex
{...};
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 possible.
Object of CCOMCHEDTEAROFFOBJECT. In fact, ccomcontainedObject is from CTEAROFF2 followers
In the case, when ITEAROFF2 interface, ITEAROFF2 interface is queried, and the work is given to m_contained.
This is a ccomcontainedObject object, so I actually query ITEAROFF2
Point to the 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 = _internalQueryInterface (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 iUternown pointer m_punktearoff2, we are in the first query
When ITEAROFF2 interface, create a CTEAROFF2 object and query an iUnknown pointer to give it. Now it will send
Way the role, judge in _Cache If m_punktearoff2 does not equal empty, it indicates that CTEAROFF2 has been created.
Will you create it again, but use it directly to query the interface.
9: So now call ccomcachedtearoffObject :: queryinterface (...), on
In step 9 in a function stack, we have seen this queryinterface (...) code, it puts queries
Work is handed over to m_contained._internalQueryInterface (.), In fact, because ccomcontainedObject
There is no Begin_COM_MAP macro, so there is no definition _internalQueryInterface (), so actually
The function is called the function it contains, that is, ctearoff2 :: _ internalQueryinterface (...)
10-12: The following work is very simple, and will not be described again.
to sum up:
COM_ITERFACE_ENTRY_CACHED_TEAR_OFF is a relatively troublesome macro, it is a macro introduced in the previous section.
Compared with the difference in creating a process of creating a split interface object is only used, if the object has been created,
A new split object will not be created again when you query the interface of the object. In order to achieve this, it is outside
The object contains an iUnknown pointer and queries this IUNKNOWN pointer when you first create a split object.
This will learn whether this division object has been created by judging whether this pointer is empty.
No Create a new split object and query other interfaces within the split object by it. Particularly, it is important to note that
In fact, two objects are created, one is ccomcachedtearoffObject, the other is
CCOMCONTAINEDOBJECT. And the second object is implemented inside the first object.
Inquiry work is also handed over to the second object. COUTER :: M_PunkTearoff2 is an iUnknown in front of an object
Pointer, when you use it to query itemoff2, it is actually handed over to its internal object m_contained, this
It can be seen very clear on the 8th and 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
There is no real feature 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 pay attention to it in its class definition.
To join Declare_NO_AGGREGATABLE.
Class Couter:
Public cchainbase,
Public IDispatchImpl,
Public CCOMCOCLASS
{
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. Also
It is the location of m_punkagg in 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 must also continue to go abroad, you can see this function will return to the component's iUnknown from literally.
pointer.
(3) Add a member variable in the COUTER: ccomptr m_punkagg;
M_PUnkagg will be used to obtain an IUNKNOWN pointer for the aggregated component.
(4) Heavy loaded FinalConstruct, FinalRelease
HRESULT COUTER :: FinalConstruct ()
{
Iunknown * punkouter = getControllingunkNown ();
HRESULT HRES = CocreateInstance (CLSID_CAGG, PUNKOUTER,
CLSCTX_INPROC_SERVER, IID_IUNKNOWN, (VOID **) & m_PUNKAGG);
IF (hres! = s_ok)
Return HRES;
Return S_OK;
}
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 with components that contain it. Aggregate components
Creating is nothing special, just pay attention to it will query the iUnknown pointer and return to m_punkagg. External
Components will be operated through the M_PUnkagg operation. Also note that PUNKOUTER is used as CocreateInstance
Parameters, which will cause CCOMAGGOBJECT objects that include an object of ccomcontainedObject. CCOMCACHEDTEAROFF <> in the first section, ccomaggobject is not
Delicated from couter, so the real component object is not a ccomaggobject object, but it is internal package
CCOMCONTAINEDOBJECT object. The same PUNKOUTER will be ccomaggobject <>
IUNKNOWN pointer, also calls its QueryInterface to turn CCOMCONTAINEDOBJECT
_InternalQueryInterface function (Oh, now I am still guess, see what I guess is right)
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.atl :: ccomobject :: queryinterface (...)
Explanation:
1-5: These steps have been called many times, because using _dlegate in this macro, so
CCOMOBJECTROOTBASE :: _ delegate (...) will be called.
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: The same as the above section, will be handed over to CAGG :: _ InternalQueryInterface (...), the rest of the work will be by CAGG
finished. The finally returned pointer will actually be 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, it's too similar to the stack of our previous section, because it is used to include
Icon. Include the role of an agent, he first handed the query to the external object (step 1, 2),
When the external object finds that the interface to query is the interface (IAGG), the query will be retained to it.
The pointer of the assembly (M_PUnkagg, step 7, pay attention to this is not a real aggregation component), m_punkagg retries
Inquiry to include objects (step 8), including objects to pass the query to a class CAGG that is really realizing the interface.
If the external object is found to query the interface of the external component, it is very simple, and the direct query will. This
Sample prevents inconsistencies of external components and aggregated components 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. Everyone can refer to the previous section
The content 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 to perform the following operation, it can be seen, even if it is not what we need, will also execute
_Delegate.
As can be seen, this macro is suitable for a case where a gathering component has multiple interfaces, as long as it is querying this gathering group.
The interface of the piece 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_ITOAGGGREGATE (IID_IAUTOAGG, M_PUNKAUTOAGGG.P,
CLSID_CAUTOAGG)
END_COM_MAP ()
CComptr M_PunkautoAgg;
}
Unlike the macro COM_ITERFACE_ENTRY_AGGREGRATE (_), the couter does not have to create a gathering in FinalConstruct.
Set components. External components will automatically create aggregated components! ! !
1.
Template
_TL_Cachedata :: Data = {DWVAR, CREATOR :: Creat
Einstence};
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_INPROC, IID_IUNKNOWN, PPV);
}
}
Because _Cache, _ccomcachedata, ccomaggrecatecreator these classes and functions we have been see
I have seen it or have seen it, so I will not talk more. In short, we can see that if m_punkautoagg.p is not empty
Then query directly, otherwise create a gathering component.
Compared with the macro COM_INTERFACE_ENTRY_AGGREGATE, this macro seems to be better, only when needed
Use it simpler.
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 COM_ITERFACE_ENTRY_AUTOAGGREGATE () and
COM_ITERFACE_ENTRY_AGGREGATE_BLIND () Features, which can be automatically created or easy to query
A plurality of interfaces in the assembly. 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:
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 :: 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 familiar, _Chain brizes the base class's pointer and the base class interface map macro, actually query
The base class interface! ! !
Under normal circumstances, this macro is in the end of Begin_COM_MAP and END_COM_MAP, which means that only is
The front of the front class does not check the interface when the interface is not checked. However, it often puts it first, then go check first.
The parent class interface, only check itself when the parent class does not implement this interface. Components in ATL are inheritance
Now, ATL defines a number of categories to achieve some common interfaces, which are often used as the base class of components, so this
Macro is used in a large number.
All of the important macros have all been told, and the rest is a very simple macro. Oh, still put them all
Next, 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 compared to com_interface_entry () and com_interface_entry2 ().
A "IID". There is no other benefit, but it is clearly pointed out by the user to the interface IID, without the system according to the interface.
The name is converted.
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 we found us in the interface mapping table
The interface to find, and this interface is not _ATL_SIMPLEETRY, then performs the specified functions in the macro definition.
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, RIID
Is the interface to query, the PPV is the interface pointer to return to the query, and DW is the parameter specified in the macro definition.
Also if you don't intend to return the interface pointer in the function, the PPV should be assigned to null and return S_FALSE or
E_nointerface. Returns S_FALSE just continues to find it, if you return to E_NOIInterface, the query will be terminated.
If the interface pointer is returned, S_OK should be returned.
Thirteen, 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 fifteen interface map, we have already finished, 唉, it is not easy, especially the front of several macros.
I am very troublesome. Because of the limitations of text, many things are not easy to express. Some call is also me
It's called this, it may be different from the habits of others. No way, everyone will be just, huh, huh.
----------------------------
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
CCOMCREATOR
2. The macro declare_AGGREGATABLE () is also defined in CCOMCOCLASS, which contains object_creatorclass. This object is actually the component object we have to create (detailed in detail), it also has one
CREATEINSTANCE, this function is used to create this component! ! When creating a component of the component, this will
The address of a function tells to the class.
3. When we succeed in obtaining a class object (at this time, the class is already created), we will then call the class factory.
CreateInstance (), in this function, call the component's CREATEINSTANCE to 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