Rules of Component Object Model
Charlie Kindel
Program Administrator, Windows NT
October 20, 1995
Summary
This article aims to provide a quick reference for using and implementing the Microsoft Component Object Model (COM. If you want to better understand what COM is and what motivation is hidden in its design and system, read the first two chapters. They are technical instructions on component object models (msdn library, Technical Instructions ). The first chapter is a brief introduction, while the second chapter provides a thorough summary. All the information here comes from the com Technical Manual.
Rule 1:RequiredIunknown
If an object does not implement at least one iunknown interface, it is not Microsoft's Component Object Model (COM ).
Interface Design Rules
The interface must be inherited directly or indirectly from iunknown.
The API must have a unique ID ).
The interface is unchanged. Once the IID is assigned and published, no factors in the interface definition can be changed.
The interface member functions should have the return value of the hresult type, so that the remote structure can report Remote Procedure Call (RPC) errors.
The string parameter of the interface member function should be Unicode.
Implement iunknown
Object Identity. This requires that the same physical pointer variable be returned for the QueryInterface call of a given object instance of any specific iunknown interface. This results in the comparison between the QueryInterface (iid_iunknown,...) of the so-called two interfaces and the results to determine whether they are the same object (COM Object Identity ).
Static interface settings. Any interface settings that access objects through QueryInterface must be static rather than dynamic. That is to say, if QueryInterface obtains a given IID, it will always call the same object (unless unexpected). If QueryInterface cannot obtain a given IID, then the call to objects with the same IID will inevitably fail.
Object Integrity. The interface settings that can be processed must have the anti-body, symmetry, and transition. The given Code is as follows:
Ia * pA = (some function returning an IA *);
IB * pb = NULL;
Hresult hr;
HR = pa-> QueryInterface (iid_ib, & Pb); // Line 4
Using Ric: Pa-> QueryInterface (iid_ia,...) must succeed (A>)
Reflexive: If, in line 4, Pb was successfully obtained, then
Pb-> QueryInterface (iid_ia ,...)
Must Succeed (A> B, then B> ).
Transitive: If, in line 4, Pb was successfully obtained, and we do
IC * Pc = NULL;
HR = Pb-> QueryInterface (iid_ic, & PC); // line 7
And PC is successfully obtained in line 7, then
Pa-> QueryInterface (iid_ic ,...)
Must Succeed (A> B, and B> C, then a> C ).
Minimum Reference Service size. We need to implement addref to maintain a service desk, which is large enough to support 2 31-1 of all interfaces of a given object with excellent overall Indication service. A 32-bit unsigned integer meets the requirements.
Release does not mean a failure. If you want to know the resource has been released, you must use the high semantics of some object interfaces before calling release.
Memory Management Rules
The lifecycle management of interface pointers is always implemented by the addref and release methods created on each COM interface. (See "reference counting rules" below ")
The following rules apply to parameters of the interface member function, including the return values that are not passed by "by value.
For parameters, the caller should allocate and release the memory.
Exit parameters must be allocated by the called program and released by the calling program using the standard com memory allocation program.
The incoming and outgoing parameters are first allocated by the caller. If necessary, the called program is released and re-allocated. As for exit parameters, the caller has the responsibility to release the final returned variable. In this case, the standard com memory allocation program must be used.
If the function returns code that fails to be called, the caller is usually unable to clear the egress and inbound parameters. This leads to some additional rules:
When an error is returned, exit parameters must be reliably set to clear variables, which cannot affect the calling program.
In addition, all exit pointer parameters (including call allocation, called appointment structure) must be explicitly set to null. The most direct method is to set it to null in the function description.
When an error is returned, all the inbound and outbound parameters must be put on hold by the caller (in this way, they must be the value initialized by the caller; if the caller has not initialized it, it is an exit parameter, is not an inbound/outbound parameter), or is explicitly set as an exit error.
Reference counting rules:
Rule 1: for each new copy of the interface pointer, addref must be called. Release is called when every corruption of the interface pointer, except that the sub-rule explicitly allows other situations.
The following rules correspond to the non-exception scenarios of rule 1.
Rule 1A: function Entry and Exit parameters. The caller must add the actual parameter because when the exit variable is stored on it, it will be released by the called program.
Rule 1b: obtain global variables. The local copy of the interface pointer obtained from an existing pointer copy of the global variable must be referenced and counted independently. If a local copy exists, the called function will destroy the global copy.
Rule 1c: there are not many resources required for new pointer synthesis. The function uses the internal knowledge synthesis interface pointer instead of obtaining from other resources. In this case, the new pointer must be initially addref. Such an important example includes the example generation rule, iunknown: Implementation of QueryInterface, and so on.
Rule 1D: returns the pointer copy stored internally. After the pointer is returned, the called program does not know how it relates to its life cycle and the internal storage copy of the pointer. Therefore, the called program must call addref on the pointer copy before returning.
Rule 2: For two or more copies of interface pointers, their specific knowledge of the starting and ending link code of the life cycle allows addref/release to be omitted.
From the perspective of COM customers, reference counting is a concept corresponding to interfaces. The customer should not consider that all interfaces of the object have the same reference count.
It should not depend on the return values of addref & release, but should be used for debugging purposes.
Pointer stability. See the sub-section in the OLE help file under "reference-counting rules": "stabilizing the this pointer and keeping it valid ".
For more information, see the excellent technical article "Managing object lifetimes in OLE" written by Douglas Hodes and the third chapter of inside Ole written by kraig brockschmidt (msdn library, books.
ComApplication responsibility:
Every process that uses com as a client, server, or object executor is responsible for three things:
Make sure that the COM library is of the same version as the com function cobuildversion.
Before using other functions, call coinitialize to initialize the com library.
Cancel the initialization of the COM library without couninitialize.
The in-process server can assume that the loaded process has performed these steps.
Server rules
The internal server must output dllgetclassobject and dllcanunloadnow.
The in-process server must support com self-registration.
Oleselfreg strings should be provided in the file version information of both processes and local servers.
The in-process server must output the dllregisterserver and dllunregisterserver.
Local servers should support the/regserver and/unregserver command line switch.
Generate collection object
It is optional to generate an object that can be aggregated. It is easy to operate and has many benefits. The following rules are used to create an object that can be aggregated (usually called an internal object ).
The QueryInterface, addref, and release independently control the reference count of the Internal interface on the internal objects of the iunknown interface, and cannot authorize the external unknown pointer. This type of iunknown execution is called implicit iunknown.
The implementation of QueryInterface, addref, and release members of the internal object execution interface must be authorized to external unknown pointers except iunknown itself. These implementations cannot directly affect the reference count of internal objects.
Implicit iunknown only performs the QueryInterface operation on internal objects.
Addref cannot be called when a collection object occupies external unknown pointer reference.
If you need any interface except iunknown when creating an object, the creation fails with e_ubknown.
The following code snippet illustrates an example of using a nested class to implement a set object interface:
// Csomeobject is an aggregatable object that implements
// Iunknown and isomeinterface
Class csomeobject: Public iunknown
{
PRIVATE:
DWORD m_cref; // object reference count
Iunknown * m_punkouter; // outer unknown, no addref
// Nested class to implement the isomeinterface Interface
Class cimpsomeinterface: Public isomeinterface
{
Friend class csomeobject;
PRIVATE:
PRIVATE: DWORD m_cref; // interface ref-count, for debugging
PRIVATE: iunknown * m_punkouter; // outerunknown, for Delegation
PRIVATE: public:
PRIVATE: cimpsomeinterface () {m_cref = 0 ;};
PRIVATE :~ Cimpsomeinterface (void ){};
PRIVATE: // iunknown members Delegate to the outer unknown
PRIVATE: // iunknown members do not control lifetime of Object
PRIVATE: stdmethodimp QueryInterface (refiid riid, void ** GMM)
PRIVATE: {return m_punkouter-> QueryInterface (riid, GMM );};
PRIVATE: stdmethodimp _ (DWORD) addref (void)
PRIVATE: {return m_punkouter-> addref ();};
PRIVATE: stdmethodimp _ (DWORD) release (void)
PRIVATE: {return m_punkouter-> release ();};
PRIVATE: // isomeinterface members
PRIVATE: stdmethodimp somemethod (void)
PRIVATE: {return s_ OK ;};
PRIVATE :};
PRIVATE: cimpsomeinterface m_impsomeinterface;
PRIVATE: public:
PRIVATE: csomeobject (iunknown * punkouter)
{
M_cref = 0;
// No addref necessary if non-null as we're aggregated.
M_punkouter = punkouter;
M_impsomeinterface.m_punkouter = punkouter;
};
// Static member function for creating new instances (don't use
// New directly). Protects against outer objects asking for interfaces
// Other than iunknown
Static hresult create (iunknown * punkouter, refiid riid, void ** GMM)
{
Csomeobject * pobj;
If (punkouter! = NULL & riid! = Iid_iunknown)
Return class_e_noaggregation;
Pobj = new csomeobject (punkouter );
If (pobj = NULL)
Return e_outofmemory;
// Set up the right unknown for delegation (the non-aggregation
Case)
If (punkouter = NULL)
Pobj-> m_punkouter = (iunknown *) pobj;
Hresult hr;
If (failed (hR = pobj-> QueryInterface (riid, (void **) GMM )))
Delete pobj;
Return hr;
}
// Implicit iunknown members, non-delegating
// Implicit QueryInterface only controls inner object
Stdmethodimp QueryInterface (refiid riid, void ** GMM)
{
* GMM = NULL;
If (riid = iid_iunknown)
* GMM = this;
If (riid = iid_isomeinterface)
* GMM = & m_impsomeinterface;
If (null = * GMM)
Return resultfromscode (e_nointerface );
(Iunknown *) * GMM)-> addref ();
Return noerror;
};
Stdmethodimp _ (DWORD) addref (void)
{Return ++ m_cref ;};
Stdmethodimp _ (DWORD) release (void)
{
If (-- m_cref! = 0)
Return m_cref;
Delete this;
Return 0;
};
};
Set object
When generating another set object on one object, you must follow the following rules:
- When creating an internal object, the external object must explicitly request iunknown.
- The external object must protect the release Implementation of manual reference that destroys the code during re-import.
- If an external object queries any internal object interface, it must call its own unknown release. When this pointer is released, the External Object follows the internal object pointer to call its own external unknown addref.
// Obtaining inner object interface pointer
Punkinner-> QueryInterface (iid_ifoo, & pifoo );
Punkouter-> release ();
// Releasing inner object interface pointer
Punkouter-> addref ();
Pifoo-> release ();
External objects cannot blindly query unrecognized interfaces of internal objects, unless the operation is for a specific purpose of external objects.
Room thread model
The details of the room model thread are actually very simple, but you must be careful to follow the following rules:
Each object exists in a single thread (in a separate room ).
All calls to an object must be based on its own thread (in its own room ). Calling objects directly from other threads is forbidden. If you try to use an object using a null thread, you will encounter a problem that cannot run correctly in future versions of the operating system. This rule means that all pointers of objects must be arranged between rooms.
To process calls from different processes or from different rooms of the same process, each room/thread in the object must have a message queue. This means that the thread's working function must have a getmessage/dispatchmessage loop. If there are other synchronization primitives between threads for communication, the sgwaitformultipleobjects of Microsoft Win32 will be used to wait for messages and thread synchronization events.
DLL-based or in-process objects must be marked as "room identification" in the registry. The variable "threadingmodel = apartment" is added to the inprocserver32 keyword of the registered database.
The room identification object should be carefully filled in the DLL table items. Each room that calls cocreateinstance for the room recognition object will call dllgetclassobject from its thread. Therefore, dllgetclassobject should support multi-level class objects or single-thread security objects. Calling cofreeunusedlibraries from any thread calls dllcanunloadnow through the main room thread ..