| Feed: Monopoly Notes Title: Study notes on "COM Principles and Applications" | Author: fgs_fgs Comments |
| "COM Principles and Applications" Study Notes - Part 1 COM Principles http://savetime.delphibs.com Start time: 2004.1.30 Last modified: 2004.2.1 The format of this article is: The text is automatically wrapped by the window; all codes are bounded by 80 characters; Chinese and English characters are separated by space characters. (The content of this article is basically excerpted from the book "COM Principles and Applications". The copyright is owned by the author Pan Aimin. Please do not use it in public media) Table of contents ===================================================================== ======================================== ⊙ Chapter 1 Overview What is COM COM Objects and Interfaces COM Process Model COM reusability ⊙ Chapter 2 COM Object Model Globally unique identifier GUID COM Object COM interface Interface Description Language IDL IUnknown interface Interface principles for COM objects ⊙ Chapter 3 Implementation of COM COM component registration information Register COM components Class factory and DllGetObjectClass function CoGetClassObject function CoCreateInstance / CoCreateInstanceEx functions Initialization of COM library Memory management of COM library Loading and uninstalling component programs Common functions for COM library HRESULT Type ⊙ Chapter 4 COM Features Reusability: Inclusion and Aggregation Process Transparency (to be learned) Safety (to be learned) Multithreading features (to be learned) ⊙ Chapter 5 Developing COM applications with Visual C++ Description of some header files provided by Win32 SDK Some macros related to COM interface ===================================================================== ======================================== text ===================================================================== ======================================== ⊙ Chapter 1 Overview ===================================================================== ======================================== What is COM -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- COM is a component standard proposed by Microsoft. It not only defines the standard for interaction between component programs, but also provides the environment required for component programs to run. In the COM standard, a component program is also called a module. It can be a dynamic link library called an in-process component or an executable program (ie, an EXE program), It is called an out-of-process component. A component program can contain one or more component objects. Because COM is a model with objects as the basic unit, when communicating between programs, both parties to the communication should be component objects, also called COM objects, and component programs (or COM program) is a code carrier that provides COM objects. COM objects are different from the object concept in general object-oriented languages (such as C++). COM objects are based on binary executable code level, while objects in languages such as C++ are based on source code level. Therefore, COM objects are language-independent. This feature is possible to interact with component objects developed by different programming languages. -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- COM Objects and Interfaces -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- Similar to the concept of an object in C++, an object is an instance of a class; and a class is a definition that combines a set of related data and functions. An application that uses an object (or another object) is called a customer, sometimes also called a user of the object. An interface is a set of logically related functions, and its functions are also called interface member functions. According to custom, interface names are often prefixed with "I". Objects provide customers with various forms of services through interface member functions. In the COM model, the object itself is invisible to the customer, and when the customer requests service, it can only be performed through the interface. Each interface is identified by a 128-bit globally unique identifier (GUID). The client obtains the interface pointer through the GUID, and then passes the interface pointer, and the client can call its corresponding member function. Similar to interfaces, each component is also identified by a 128-bit GUID, called CLSID (class identifier, class identifier or class ID). Using CLSID to identify objects can ensure (probably) uniqueness on a global scale. In fact, after the client successfully creates the object, it gets a pointer to an interface of the object. Because the COM object implements at least one interface (a COM object without an interface is meaningless), the client can call the interface to provide it. All services. According to the COM specification, if a COM object implements multiple interfaces, any other interface of the object can be obtained from a certain interface. From this process, we can also see that the client only deals with the COM object through interfaces, and the object is just a set of interfaces for the client. -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- COM Process Model -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- The service component objects provided by COM have two process models when implementing: in-process objects and out-of-process objects. If it is an in-process object, it runs in the client process space; if it is an out-of-process object, it runs in another process space on the same machine or in the remote machine space. In-process service program: The service program is loaded into the customer's process space. In the Windows environment, the code of the service program is usually implemented in the form of a dynamic connection library (DLL). Local service programs: The service program runs on the same machine as the client program. The service program is a separate application, usually it is an EXE file. Remote service program: The service program runs on a different machine than the client, and can be either a DLL module or an EXE file. If the remote service program is implemented in DLL form, the remote machine creates a proxy process. Although COM objects have different process models, this difference is transparent to client programs. Therefore, when client programs use component objects, they can ignore the existence of this difference, as long as they comply with COM specifications. However, when implementing COM objects, you should still carefully choose the process model. The advantage of in-process models is that they are efficient, but unstable components will cause the client process to crash, so the components may endanger the client; (savetime Note: There is a problem here. If the component is unstable, the out-of-process model will also have problems, which may be Because the components and customers in the process are in the same address space, there is a high possibility of conflict? ) The advantage of the off-process model is that it has good stability, and the component process will not endanger the client program. One component process can provide services to multiple client processes. , but the out-of-process components are overhead and the call efficiency is relatively low. -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- COM reusability -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- Since the COM standard is based on binary code level, the reusability of COM objects is different from the reuse process of objects in general object-oriented languages such as C++. For the client program of COM object, it only uses the services provided by the object through the interface, and it does not know the implementation process inside the object. Therefore, the reusability of component objects can be based on the behavior of component objects, rather than the specific implementation. On this, this is the key to building reuse. COM uses two mechanisms to realize the reuse of objects. We assume that there are two COM objects, and object 1 hopes to reuse the function of object 2. We call object 1 an external object and object 2 an internal object. (1) Inclusive way. Object 1 contains object 2. When object 1 needs to use the function of object 2, it can simply hand over the implementation to object 2. Although object 1 and object 2 support the same interface, object 1 is when implementing the interface. In fact, the implementation of object 2 is called. (2) Aggregation method. Object 1 simply submits the interface of object 2 to the client. Object 1 does not implement the interface of object 2, but it also exposes the interface of object 2 to the client program, and the client program does not know the internal object 2. exist. ===================================================================== ======================================== ⊙ Chapter 2 COM Object Model ===================================================================== ======================================== Globally unique identifier GUID -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- The COM specification uses a 128-bit globally unique identifier GUID to identify objects and interfaces, which are random numbers and do not require specialized agencies to allocate and manage. Because GUID is a random number, uniqueness is not absolutely guaranteed, but the possibility of identifiers being doubled is very small. In theory, if a machine generates 100,000,000 GUIDs per second, it can be guaranteed that 3240 years (in the sense of probability) will not be repeated. GUID can be described in C/C++ using this structure: typedef struct _GUID { DWORD Data1; WORD Data2; WORD Data3; BYTE Data4[8]; } GUID; Example: {64BF4372-1007-B0AA-444553540000} You can define a GUID as follows: extern "C" const GUID CLSID_MYSPELLCHECKER = { 0x54BF0093, 0x1048, 0x399D, { 0xB0, 0xA3, 0x45, 0x33, 0x43, 0x90, 0x47, 0x47} }; Visual C++ provides two programs to generate GUIDs: UUIDGen.exe (command line) and GUIDGen.exe (dialog). The COM library provides the following API functions that can generate GUIDs: HRESULT CoCreateGuid(GUID *pguid); If GUID is created successfully, the function returns S_OK and pguid points to the resulting GUID value. -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- COM Object -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- In the COM specification, COM objects are not strictly defined, but COM provides an object-oriented component model, and COM components provide customers with entities encapsulated in object form. The entity in which the client program interacts with the COM program is a COM object. It does not care about the name and location of the component model (i.e., location transparency), but it must know which COM object it is interacting with. -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- COM interface -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- Technically speaking, an interface is a data structure that contains a set of functions, through which client code can call the functions of component objects. The interface defines a set of member functions, which are all the information exposed by component objects. Client programs use these functions to obtain the services of component objects. Usually we call the interface function table virtual function table (vtable), and the pointer to the vtable is pVtable. For an interface, its virtual function table is determined, so the number of member functions of the interface is unchanged, and the order of member functions is also unchanged; for each member function, its parameters and The return value is also definite. In the definition of an interface, all this information must be determined at the binary level. No matter what language, the interface can be used as long as it can support such a memory structure description. Interface pointer ----> pVtable ----> Pointer function 1 -> |-----------| m_Data1 pointer function 2 -> | Object implementation | m_Data2 pointer function 3 -> |------------| The first parameter of each interface member function is a pointer to the object instance (=this). This is because the interface itself is not used independently and must exist on a certain COM object, so this pointer can provide the properties of the object instance. Information, when called, the interface can know which COM object is operating on. In interface member functions, string variables must use Unicode character pointers. The COM specification requires the use of Unicode characters, and the COM API functions provided in the COM library also use Unicode characters. Therefore, if ANSI characters are used inside the component program, two character expressions should be converted. Of course, in the case of establishing component programs and client programs, you can use the parameter types you define yourself as long as they are compatible with the parameter types that COM can recognize. Visual C++ provides two string conversions: namespace _com_util { BSTR ConvertStringToBSTR(const char *pSrc) throw(_com_error); BSTR ConvertBSTRToString(BSTR pSrc) throw(_com_error); } BSTR is a double-byte-width string, which is the most commonly used automated data type. -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- Interface Description Language IDL -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- The COM specification uses the DCE specification of OSF to describe the remote call interface IDL (interface description language) and expands to form the description language of the COM interface. Interface description language provides a description method for interfaces that do not depend on any language, so it can become a common language between component programs and client programs. The IDL interface description language used by the COM specification can not only be used to define the COM interface, but also define some commonly used data types and custom data structures. For interface member functions, we can define the type and input output of each parameter. Features, even support descriptions of variable-length arrays. IDL supports pointer types, which are very similar to C/C++. For example: interface IDictionary { HRESULT Initialize() HRESULT LoadLibrary([in] string); HRESULT InsertWord([in] string, [in] string); HRESULT DeleteWord([in] string); HRESULT LookupWord([in] string, [out] string *); HRESULT RestoreLibrary([in] string); HRESULT FreeLibrary(); } Microsoft Visual C++ provides MIDL tools that can compile IDL interface description files into C/C++-compatible interface description header files (.h). -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- IUnknown interface -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- IUnknown's IDL definition: interface IUnknown { HRESULT QueryInterface([in] REFIID iid, [out] void **ppv); ULONG AddRef(void); ULONG Release(void); } IUnkown's C++ definition: class IUnknown { virus HRESULT _stdcall QueryInterface(const IID& iid, void **ppv) = 0; virtual ULONG _stdcall AddRef() = 0; virus ULONG _stdcall Release() = 0; } -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- Interface principles for COM objects -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- The COM specification sets the following rules for the QueryInterface function: 1. For different interface pointers of the same object, the IUnknown interface obtained by query must be exactly the same. That is, the IUnknown interface pointer for each object is unique. Therefore, for two interface pointers, we can determine whether they point to the same object by judging whether the IUnknown interfaces they are querying are equal. 2. Interface reflexivity. Querying an interface itself should always succeed, for example: pIDictionary->QueryInterface(IID_Dictionary, ...) should return S_OK. 3. Interface symmetry. If you query from one interface pointer to another interface pointer, then returning from the second interface pointer to the first interface pointer must succeed, for example: pIDictionary->QueryInterface(IID_SpellCheck, (void **)&pISpellCheck); If the search is successful, then checking back the IID_Dictionary interface from pISpellCheck will definitely be successful. 4. Interface transitiveness. If you query the second interface pointer from the first interface pointer and the third interface pointer can be queryed from the second interface pointer, then you can definitely query the first interface pointer from the third interface pointer. 5. Interface query time irrelevant. If a certain interface pointer can be found at a certain moment, then the same interface pointer will be checked at any time in the future, and the query will definitely be successful. In short, no matter which interface we start from, we can always reach any interface, and we can always return to the original interface. ===================================================================== ======================================== ⊙ Chapter 3 Implementation of COM ===================================================================== ======================================== COM component registration information -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- Information about all components on the current machine HKEY_CLASS_ROOT/CLSID In-process component HKEY_CLASS_ROOT/CLSID/guid/InprocServer32 Out-of-process component HKEY_CLASS_ROOT/CLSID/guid/LocalServer32 Category to which component (CATID) HKEY_CLASS_ROOT/CLSID/guid/Implemented Categories COM interface configuration information HKEY_CLASS_ROOT/Interface Proxy DLL/stub DLL HKEY_CLASS_ROOT/CLSID/guid/ProxyStubClsid HKEY_CLASS_ROOT/CLSID/guid/ProxyStubClsid32 Information about type library HKEY_CLASS_ROOT/TypeLib String naming ProgID HKEY_CLASS_ROOT/ (for example "COMCTL.TreeCtrl") Component GUID HKEY_CLASS_ROOT/COMTRL.TreeControl/CLSID Default version number HKEY_CLASS_ROOT/COMTRL.TreeControl/CurVer (e.g. CurVer = "COMTRL.TreeCtrl.1", then HKEY_CLASS_ROOT/COMTRL.TreeControl.1 also exists) All component categories of the current machine HKEY_CLASS_ROOT/Component Categories COM provides two API functions CLSIDFromProgID and ProgIDFromCLSID to convert ProgID and CLSID. If the COM component supports the same set of interfaces, they can be classified into the same class, and a component can be classified into multiple classes. For example, if all automation objects support the IDispatch interface, they can be classified as "Automation Objects". Category information is also described by a GUID, called CATID. The main purpose of component categories is that customers can quickly discover specific types of component objects on the machine. Otherwise, they must check all component objects, load the component objects into memory and instantiate them, and then ask whether the necessary ones are implemented. The interface, now using component categories, can save the query process. -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- Register COM components -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- RegSrv32.exe is used to register an in-process component, which calls the DllRegisterServer and DllUnregisterServer functions of the DLL to complete the registration and cancellation of the component program. If the operation is successful, return TRUE, otherwise return FALSE. For out-of-process component programs, the situation is slightly different because it is an executable program itself and it cannot provide entry functions for other programs to use. Therefore, the COM specification stipulates that out-of-process components that support self-registration must support two command line parameters /RegServer and /UnregServer to complete the registration and cancellation operations. Command line parameters are case-dependent, and "/" can be replaced by "-". If the operation is successful, the program returns 0, otherwise, returning non-0 means failure. -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- Class factory and DllGetObjectClass function -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- Class factory is the production base of COM objects. The COM library creates COM objects through the class factory; corresponding to each COM class, there is a class factory specifically used for object creation operations of the COM class. The class factory itself is also a COM object, which supports a special interface IClassFactory: class IClassFactory : public IUnknown { virtual HRESULT _stdcall CreateInstance(IUnknown *pUnknownOuter, const IID& iid, void **ppv) = 0; virtual HRESULT _stdcall LockServer(BOOL bLock) = 0; } The CreateInstance member function is used to create the corresponding COM object. The first parameter pUnknownOuter is used for the case where the object class is aggregated, and is generally set to NULL; the second parameter iid is the initial interface IID that the customer should get after the object is created; the third parameter ppv stores the returned interface pointer. LockServer member functions are used to control the lifetime of a component. The class factory object is created by the DLL-derived function DllGetClassObject: HRESULT DllGetClassObject(const CLSID& clsid, const IID& iid, (void **)ppv); The first parameter of the DllGetClassObject function is the CLSID of the object to be created. Because a component may implement multiple COM object classes, it is necessary to specify the CLSID in the parameters of the DllGetClassObject function to create the correct class factory. The other two parameters iid and ppv refer to the specified interface IID and the storage class factory interface pointer, respectively. After receiving the instruction to create an object, the COM library calls the DllGetClassObject function of the component in the process, creates the class factory object from the function, and returns the interface pointer of the class factory object. Once a COM library or customer has an interface pointer to the class factory, they can create the corresponding COM object through the member function CreateInstance of the IClassFactory. -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- CoGetClassObject function -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- In the COM library, there are three APIs that can be used for object creation, namely CoGetClassObject, CoCreateInstnace, and CoCreateInstanceEx. Typically, the client program calls one of them to complete the creation of the object and returns the object's initial interface pointer. The COM library and class factory also interact through these three functions. HRESULT CoGetClassObject(const CLSID& clsid, DWORD dwClsContext, COSERVERINFO *pServerInfo, const IID& iid, (void **)ppv); The CoGetClassObject function first finds the class factory of the COM class specified by clsid, and then connects to the class factory object. If necessary, the CoGetClassObject function loads the component code. If it is an in-process component object, CoGetClassObject calls the DllGetClassObject of the DLL module to introduce the function, pass the parameters clsid, iid and ppv to the DllGetClassObject function, and returns the interface pointer of the class factory object. Normally, iid is the identifier IID_IClassFactory of IClassFactory. If the class factory object also supports other interfaces that can be used to create operations, other interface identifiers can also be used. For example, the IClassFactory2 interface may be requested to verify the user's license status at creation time. The IClassFactory2 interface is an extension to IClassFactory, which enhances the security of component creation. The parameter dwClsContext specifies the component category, which can be specified as an in-process component, an out-of-process component or an in-process control object (similar to the proxy object of an out-of-process component, mainly used in OLE technology). The parameters iid and ppv correspond to the parameters of DllGetClassObject, respectively, and are used to specify the interface IID and the interface pointer for storing the class object. The parameter pServerInfo is used to specify server information when creating remote objects. When creating in-process component objects or local out-of-process components, set NULL. The situation is much more complicated if the class factory object created by the CoGetClassObject function is located in an out-of-process component. First, the CoGetClassObject function starts the component process and then waits until the component process registers the class factory of the COM class object it supports to COM. So the CoGetClassObject function returns the corresponding class factory information in COM. Therefore, when an out-of-component process is started by the COM library (with the command line parameter "/Embedding"), it must register the supported COM class factory objects into the COM through the CoRegisterClassObject function so that the COM library can create COM objects for use. When the process exits, the CoRevokeClassObject function must be called to notify COM that the class factory object it registered is no longer valid. The component program calls the CoRegisterClassObject function and the CoRevokeClassObject function must be paired to ensure consistency of COM information. -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- CoCreateInstance / CoCreateInstanceEx functions -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- HRESULT CoCreateInstance(const CLSID& clsid, IUnknown *pUnknownOuter, DWORD dwClsContext, const IID& iid, (void **)ppv); CoCreateInstance is a wrapped helper function that actually calls the CoGetClassObject function inside it. The meaning of the parameters clsid and dwClsContext of CoCreateInstance are consistent with the corresponding parameters of CoGetClassObject (the iid and ppv parameters of CoCreateInstance are different from CoGetClassObject, one is the interface information representing the object, and the other is the interface information representing the class factory). The parameter pUnknownOuter is consistent with the corresponding parameters in CreateInstance of the class factory interface, and is mainly used in the case where objects are aggregated. The CoCreateInstance function encapsulates the process of creating objects through a class factory. The client program only needs to specify the CLSID of the object class and the interface pointer and interface ID to be output, and the client program can not deal with the class factory. CoCreateInstance can be implemented with the following code: (savetime note: The application of ppv pointer in the following code seems to be void **) HRESULT CoCreateInstance(const CLSID& clsid, IUnknown *pUnknownOuter, DWORD dwClsContext, const IID& iid, void *ppv) { IClassFactory *pCF; HRESULT hr; hr = CoGetClassObject(clsid, dwClsContext, NULL, IID_IClassFactory, (void *) pCF); if (FAILED(hr)) return hr; hr = pCF->CreateInstance(pUnknownOuter, iid, (void *)ppv); pFC->Release(); return hr; } From this code, we can see that the CoCreateInstance function first uses the CoGetClassObject function to create a class factory object, and then uses the interface pointer of the class factory object to create a real COM object. Finally, the class factory object is released and returned, so that the class is put into use. The factory is blocked. However, using CoCreateInstance does not create an object on the remote machine, because when calling CoGetClassObject, the third parameter used to specify the server information is set to NULL. If you want to create a remote object, you can use CoCreateInstance's extension function CoCreateInstanceEx: HRESULT CoCreateInstanceEx(const CLSID& clsid, IUnknown *pUnknownOuter, DWORD dwClsContext, COSERVERINFO *pServerInfo, DWORD dwCount, MULTI_QI *rgMultiQI); The first three parameters are the same as CoCreateInstance. The pServerInfo is the same as the CoGetClassOjbect parameter, which is used to specify server information. The last two parameters, dwCount and rgMultiQI, specify an array of structures that can be used to save multiple object interface pointers. The purpose is to obtain multiple objects at once. interface pointers to reduce frequent interactions between client programs and component programs, which is very meaningful for remote objects in network environments. -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- Initialization of COM library -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- Before calling the COM library's functions, in order to make the function valid, the COM library's initialization function must be called: HRESULT CoInitialize(IMalloc *pMalloc); pMalloc is used to specify a memory allocator, and the memory allocation principle can be specified by the application. Generally, if we directly set the parameter to NULL, the COM library will use the default provided memory allocator. Return value: S_OK means initialization is successful S_FALSE indicates that the initialization is successful, but this call is not the first time the initialization function is called in this process. S_UNEXPECTED indicates that an error occurred during the initialization process and the application cannot use the COM library. Usually, a process initializes the COM library only once, and it does not make sense to initialize the COM library multiple times in the same module unit. The only function that does not need to initialize the COM library is to get the COM library version: DWORD CoBuildVersion(); Return value: High 16-bit major version number Lower 16 digit version number After the COM program uses up the COM library service, usually before the program exits, it is necessary to call the terminated COM library service function to free the resources maintained by the COM library: void CoUninitialize(void); Note: Any process or program module that calls the CoInitialize function and returns S_OK must have a corresponding CoUninitialize function call to ensure that the COM library effectively utilizes resources. (? If CoInitialize is called in a module and returns S_OK, will other modules that are also using the COM library have errors after calling the CoUnitialize function? Or will the COM library automatically check which modules are used?) -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- Memory management of COM library -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- Since COM component programs and client programs are connected through binary-level standards, all operations involving memory interactions between clients, COM libraries and components (allocation and release are not in the same module) in COM applications must be used Consistent memory manager. The memory management standard provided by COM is actually an IMalloc interface: // IID_IMalloc: {00000002-0000-0000-C000-000000000046} class IMalloc: public IUnknown { void * Alloc(ULONG cb) = 0; void * Realloc(void *pv, ULONG cb) = 0; void Free(void *pv) = 0; ULONG GetSize(void *pv) = 0; // Returns the allocated memory size int DidAlloc(void *pv) = 0; // Determine whether the memory pointer is allocated by the memory manager void HeapMinimize() = 0; // Make the heap memory as low as possible and return the unused memory to // Operating system for performance optimization } Obtain the IMalloc interface pointer: HRESULT CoGetMalloc(DWORD dwMemContext, IMalloc **ppMalloc); The first parameter of the CoGetMalloc function dwMemContext is used to specify the type of the memory manager. The COM library contains two memory managers, one is the memory manager specified during initialization or its internal default manager, also known as the job manager (task allocator). This manager is valid in this process. , To obtain the manager, specify it as MEMCTX_TASK in the dwMemContext parameter; the other is a cross-process shared allocator, provided by the OLE system, to obtain this manager, specify it as MEMCTX_SHARED in the dwMemContext parameter, use the shared manager The convenience is that memory can be allocated within one process and passed to a second process, using this memory in the second process or even releasing it. As long as the return value of the function is S_OK, ppMalloc points to the memory manager interface pointer of the COM library, and you can use it to perform memory operations. After use, you should call the Release member function to release control. The COM library encapsulates three API functions that can be used for memory allocation and release: void * CoTaskMemAlloc(ULONG cb); void CoTaskFree(void *pv); void CoTaskMemRealloc(void *pv, ULONG cb); These three functions are assigned to three member functions corresponding to IMalloc: Alloc, Realloc, and Free. Example: How a COM program finds the corresponding ProgID value from the CLSID value: WCHAR *pwProgID; char pszProgID[128]; hResult = ::ProgIDFromCLSID(CLSID_Dictionary, &pwProgID); if (hResult != S_OK) { ... } wcstombs(pszProgID, pwProgID, 128); CoTaskMemFree(pwProgID); // Note: Memory must be released After calling the COM function ProgIDFromCLSID to return, because the COM library allocates memory space for the output variable pwProgID, the application must call the CoTaskMemFree function to free up memory after using up the pwProgID variable. This example illustrates a situation where memory is allocated in the COM library and memory is freed in the calling program. Some other functions in the COM library also have similar characteristics, especially some functions that contain indefinite length output parameters. -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- Loading and uninstalling component programs -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- Loading of components in process: The client program calls the CoCreateInstance or CoGetClassObject function of the COM library to create a COM object. In the CoGetClassObject function, the COM library finds the full path of the component program (DLL file) corresponding to the class identifier CLSID based on the information in the system registry, and then calls LoadLibrary( It is actually a CoLoadLibrary function, and calls the DllGetClassObject of the component program to introduce the function. The DllGetClassObject function creates the corresponding class factory object and returns the IClassFactory interface of the class factory object. At this point, the task of the CoGetClassObject function is completed, and the client program or the CoCreateInstance function continues to call the CreateInstance member function of the class factory object, which is responsible for the creation of the COM object. CoCreateInstance |-CoGetClassObject |-Get CLSID -> DLLfile path |-CoLoadLibrary |-DLLfile.DllGetClassObject |-return IClassFactory |-IClassFactory.CreateInstnace Loading of off-process components: In the CoGetClassObject function of the COM library, when it finds that the component program is an EXE file (specified by the LocalServer or LocalServer32 value in the registry component object information), the COM library creates a process to start the component program with the "/Embedding" command After the component program is started, when it checks the "/Embedding" command line parameter, it will create a class factory object, and then call the CoRegisterClassObject function to register the class factory object in COM. When the COM library checks the class factory of the component object, the CoGetClassObject function returns the class factory object. Since the class factory and the client program run in different processes, the client program gets the class factory's proxy object. Once the client program or COM library has a class factory object, it can complete the creation of component objects. The different creation processes of in-process objects and out-of-process objects only affect the implementation process of the CoGetClassObject function and are completely transparent to the client program. CoGetClassObject |-LocalServer/LocalServer32 |-Execute EXE /Embedding |-Create class factory |-CoRegisterClassObject ( class factory ) |-return class factory (proxy) Uninstallation of in-process components: It can only be uninstalled when the component program meets two conditions: the number of objects in the component is 0 and the lock count of the class factory is 0. When these two conditions are met, the DllCanUnloadNow elicitation function returns TRUE. COM provides a function CoFreeUnusedLibraries, which detects all component programs in the current process. When it is found that the DllCanUnloadNow function of a component program returns TRUE, the FreeLibrary function (actually the CoFreeLibrary function) is called to transfer the component from the program to the memory. Uninstall. Who should call the CoFreeUnusedLibraries function? Because it is impossible for it to discharge itself from memory during the execution of the component program, this task should be done by the client. The client program can call the CoFreeUnusedLibraries function at any time to complete the unloading work, but the usual practice is to call the CoFreeUnusedLibraries function during the idle processing of the program. This can not only prevent the program from considering calls to the CoFreeUnusedLibraries function everywhere, but also prevent the program from being considered. The component programs used are cleared in a timely manner to improve resource utilization, and this practice is also recommended by the COM specification. Uninstallation of off-process components: Uninstalling off-process components is relatively simple because the component program runs in a separate process. Once its exit condition is met, it only needs to return from the process's master function. In Windows systems, the main function of the process is WinMain. As mentioned earlier, when the component program starts and runs, it calls the CoRegisterClassObject function to register the class factory object into COM. After registration, the reference count of the class factory object is always greater than 0, so the reference count of the class factory object alone cannot be controlled. The lifetime of the process is also the reason why the locking and lock reduction operations of factory objects are introduced.进程外组件的载条件与DllCanUnloadNow 中的判断类似,也需要判断COM 对象是否还存在、以及判断是否锁计数器为0,只有当条件满足了,进程的主函数才可以退出。 从原则上讲,进程外组件程序的卸载就是这么简单,但实际上情况可能复杂一些,因为有些组件程序在运行过程中可以创建自己的对象,或者包含用户界面的程序在运行过程中,用户手工关闭了进程,那么进程对这些动作的处理要复杂一些。例如,组件程序在运行过程中,用户又打开了一个文件并进行操作,那么即使原先创建的对象被释放了,而且锁计数器也为0,进程也不能退出,它必须继续为用户服务,就像是用户打开的进程一样。对这种程序,可以增加一个“用户控制”标记flag,如果flag 为FALSE,则可以按简单的方法直接退出程序即可;如果flag 为TRUE,则表明用户参与了控制,组件进程不能马上退出,但应该调用CoRevokeClassObject 函数以便与CoRegisterClassObject 调用相响呼应,把进程留给用户继续进行。 如果组件程序在运行过程中,用户要关闭进程,而此时并不满足进程退出条件,那么进程可以采取两种办法:第一种方法,把应用隐藏起来,并把flag 标记设置为FALSE,然后组件程序继续运行直到卸载条件满足为止;另一种办法是,调用CoDisconnectObject 函数,强迫脱离对象与客户之间的关系,并强行终止进程,这种方法比较粗暴,不提倡采用,但不得已时可以也使用,以保证系统完成一些高优先级的操作。 -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- COM 库常用函数 -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- 初始化函数CoBuildVersion 获得COM 库的版本号 CoInitialize COM 库初始化 CoUninitialize COM 库功能服务终止 CoFreeUnusedLibraries 释放进程中所有不再使用的组件程序 GUID 相关函数IsEqualGUID 判断两个GUID 是否相等 IsEqualIID 判断两个IID 是否相等 IsEqualCLSID 判断两个CLSID 是否相等(*为什么要3个函数) CLSIDFromProgID 字符串组件标识转换为CLSID 形式 StringFromCLSID CLSID 形式标识转化为字符串形式 IIDFromString 字符串转换为IID 形式 StringFromIID IID 形式转换为字符串 StringFromGUID2 GUID 形式转换为字符串(*为什么有2) 对象创建函数CoGetClassObject 获取类厂对象 CoCreateInstance 创建COM 对象 CoCreateInstanceEx 创建COM 对象,可指定多个接口或远程对象 CoRegisterClassObject 登记一个对象,使其它应用程序可以连接到它 CoRevokeClassObject 取消对象的登记 CoDisconnectObject 断开其它应用与对象的连接 内存管理函数CoTaskMemAlloc 内存分配函数 CoTaskMemRealloc 内存重新分配函数 CoTaskMemFree 内存释放函数 CoGetMalloc 获取COM 库内存管理器接口 -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- HRESULT 类型 -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- 大多数COM 函数以及一些接口成员函数的返回值类型均为HRESULT 类型。HRESULT 类型的返回值反映了函数中的一些情况,其类型定义规范如下: 31 30 29 28 16 15 0 |-----|--|------------------------|-----------------------------------| 类别码(30-31) 反映函数调用结果: 00 调用成功 01 包含一些信息 10 警告 11 错误 自定义标记(29) 反映结果是否为自定义标识,1 为是,0 则不是; 操作码(16-28) 标识结果操作来源,在Windows 平台上,其定义如下: #define FACILITY_WINDOWS 8 #define FACILITY_STORAGE 3 #define FACILITY_RPC 1 #define FACILITY_SSPI 9 #define FACILITY_WIN32 7 #define FACILITY_CONTROL 10 #define FACILITY_NULL 0 #define FACILITY_INTERNET 12 #define FACILITY_ITF 4 #define FACILITY_DISPATCH 2 #define FACILITY_CERT 11 操作结果码(0-15) 反映操作的状态,WinError.h 定义了Win32 函数所有可能返回结果。 以下是一些经常用到的返回值和宏定义: S_OK 函数执行成功,其值为0 (注意,其值与TRUE 相反) S_FALSE 函数执行成功,其值为1 S_FAIL 函数执行失败,失败原因不确定 E_OUTOFMEMORY 函数执行失败,失败原因为内存分配不成功 E_NOTIMPL 函数执行失败,成员函数没有被实现 E_NOTINTERFACE 函数执行失败,组件没有实现指定的接口 不能简单地把返回值与S_OK 和S_FALSE 比较,而要用SECCEEDED 和FAILED 宏进行判断。 ===================================================================== ======================================== ⊙ 第四章COM 特性 ===================================================================== ======================================== 可重用性:包容和聚合 -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- 包容模型: 组件对象在接口的实现代码中执行自身创建的另一个组件对象的接口函数(客户/服务器模型)。这个对象同时实现了两个(或更多)接口的代码。 聚合模型: 组件对象在接口的查询代码中把接口传递给自已创建的另一个对象的接口查询函数,而不实现该接口的代码。另一个对象必须实现聚合模型(也就是说,它知道自己正在被另一个组件对象聚合),以便QueryInterface 函数能够正常运作。 在组件对象被聚合的情况下,当客户请求它所不支持的接口或者请求IUnknown 接口时,它必须把控制交给外部对象,由外部对象决定客户程序的请求结果。 聚合模型体现了组件软件真正意义上的重用。 聚合模型实现的关键在CoCreateInstance 函数和IClassFactory 接口: HRESULT CoCreateInstance(const CLSID& clsid, IUnknown *pUnknownOuter, DWORD dwClsContext, const IID& iid, (void **)ppv); // class IClassFactory : public IUnknown virtual HRESULT _stdcall CreateInstance(IUnknown *pUnknownOuter, const IID& iid, void **ppv) = 0; 其中pUnknownOuter 参数用于指定组件对象是否被聚合。如果pUnknownOuter 参数为NULL,说明组件对象正常使用,否则说明被聚合使用,pUnknownOuter 是外部组件对象的接口指针。 聚合模型下的被聚合对象的引用计数成员函数也要进行特别处理。在未被聚合的情况下,可以使用一般的引用计数方法。在被聚合时,由客户调用AddRef/Release 函数时,必须转向外部组件对象的AddRef/Release 方法。这时,外部组件对象要控制被聚合的对象必须采用其它的引用计数接口。 -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- 进程透明性(待学) 安全性(待学) 多线程特性(待学) -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- ===================================================================== ======================================== ⊙ 第五章用Visual C++ 开发COM 应用 ===================================================================== ======================================== Win32 SDK 提供的一些头文件的说明 -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- Unknwn.h 标准接口IUnknown 和IClassFacatory 的IID 及接口成员函数的定义 Wtypes.h 包含COM 使用的数据结构的说明 Objidl.h 所有标准接口的定义,即可用于C 语言风格的定义,也可用于C++ 语言 Comdef.h 所有标准接口以及COM 和OLE 内部对象的CLSID ObjBase.h 所有的COM API 函数的说明 Ole2.h 所有经过封装的OLE 辅助函数 -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- 与COM 接口有关的一些宏 -------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- DECLARE_INTERFACE(iface) 声明接口iface,它不从其他的接口派生 DECLARE_INTERFACE_(iface, baseiface) 声明接口iface,它从接口baseiface 派生 STDMETHOD(method) 声明接口成员函数method,函数返回类型为HRESULT STDMETHOD_(type, method) 声明接口成员函数method,函数返回类型为type ===================================================================== ======================================== ⊙ 结束 ===================================================================== ======================================== |