Preface:
N years ago, we have been in the software development of such confusion, using VC to develop COM components too complex, with VB to develop COM components found inefficient, and can not achieve many object-oriented features, such as inheritance, polymorphism and so on. Furthermore, how to quickly encapsulate the large amount of C + + code left over from history is a big challenge.
At that time, members of the development team, through joint efforts, explored a set of C + + class dynamic component technology, a good solution to the above problems, through this technology, we inherited a large number of C + + code, while these C + + programs in the form of COM + components to be reborn. Through these years in the practical application of the test, the technology is mature and reliable.
Perhaps the new system is mostly built entirely on. Net or Java, but I think it's a good idea to study the components of C + + class objects, and from a software engineering perspective, it solves a problem that a daily programmer might encounter. We use a set of components programming specification implementation program, but also reduce the difficulty of code, improve maintenance. This is generally not familiar to COM technology programmers can also use C + + to make good COM components, and can realize the inheritance of components, polymorphism and so on.
Now I will the idea of this technology in the blog on the public, if it can also be useful to everyone's work now, it is an honor, hehe!
Dynamic Component Technology of C + + classKeywords
COM component Interface life cycle C + + class ATL Component Class C + + base class ATL template base class inheritance
Summary
In the era of component programming, how do you reuse a large number of C + + classes that have accumulated no component features? This paper discusses this problem from the point of view of engineering, and puts forward a complete solution to smooth transition of C + + class to COM component by using existing component technology.
1.The question is raised
The development paradigm on the Windows platform has changed dramatically since Microsoft unveiled the COM (Component object model, Component object models, short COM) technology. A series of COM-based component technologies bring Windows programming into the era of components, and traditional object-oriented software development methods have gradually been replaced by component-oriented approaches.
COM standards based on the binary executable code level, regardless of the tools, language development components, as long as the COM specification, can be reused in VC, VB, Delphi, BC and other development environments. COM's language independence pushes the level of software reuse from the source code level to the binary level, which is more convenient and safer to reuse.
However,COM technology brings new software design and development model, but also brings about the problem.
Many software companies have accumulated a large number of C + + classes in their development of their software products, which are well- designed, fully functional, and tested to be impeccable with object-oriented standards. However, this code does not support COM and will not continue to be reused in the COM era. If they are eliminated in the trend of software components, it is a huge loss to both software companies and developers.
COM experts don Box once said, "com is a super C + +". This gives us a hint as to whether a technology can be implemented that dynamically adds a layer of COM encapsulation to ordinary C + + classes. In this way, both the integrity and characteristics of the code itself can be maintained so that they can continue to be applied to the original system, or they can be dynamically transformed into components and reused for new systems when they need to be used as components.
A natural idea is to develop a COM component that exposes only one interface for each C + + class, and each public method of the original C + + class corresponds to a method of that interface, and the implementation of the interface method can simply call the corresponding C + + class method. And in general, C + + object instances are ordinary objects exist, only the external need to get the C + + object instance in a component way, the program will automatically add a component shell to the object. An external program implements the operation of the C + + object through the interface of the shell.
In this way, the program logic is controlled by the original C + + class, but the encapsulation of the COM layer is provided by the component. The basic idea is as follows:
This article discusses this technique and ultimately provides a complete solution for smoothing the transition from a common C + + class to a COM component. We use the ATL (Active Template Library, active templates libraries, or ATL) as a development tool for COM components. Without special instructions, the following "C + + class" means a C + + class without component attributes, "C + + object" means an instance of a C + + class; "ATL Component Class" refers to an ATL class for wrapping, "ATL object" refers to an instance of an ATL component class.
2.UseATL wrapper C + + classes
The resulting component is actually composed of two parts: the ATL Component Object and the bound C + + object after the C + + object is dynamically component as described above. The life cycle of the two is constrained, but consistent. The management of life cycle is the first difficulty of C + + class dynamic component.
C + + classes are divided into two types, one is simple C + + class, and the other is a collection-type C + + class. A collection of C + + objects manages a set of C + + objects that are responsible for their creation and deletion, maintaining their life cycle. The following is a description of the component technologies for simple C + + and aggregate C + + classes, demonstrating the core technology of the solution.
2.1.Simplethe component of C + + class
To make the ATL component classes free to invoke methods of the C + + class, you need:
L Placement A pointer member variable for the ATL component class, pointing to the C + + class
L provides binding mechanisms for ATL objects and C + + objects
We can create a C + + class at the time of initialization of the ATL component class, M_pcppobj records with member variables, and delete them on destruction, thus enabling the natural binding of the ATL component classes and C + + classes. But for the sake of flexibility, so that ATL component objects can bind objects of any C + + class, we add a binding function Link2cppobj (cimplement* POBJ) to the ATL component class.
within the constructor of the ATL component class, create a C + + object that is recorded with M_pcppobj. If Link2cppobj is called, the object pointed to by M_pcppobj is deleted and the incoming C + + object is used instead. Within the destructor of the ATL component class, remove its bound C + + object. The definition of the constructor and Link2cppobj function indicates that the m_pcppobj pointer is always meaningful. The idea of a simple C + + class component is as follows:
2.2.Set typethe component of C + + class
A collection C + + class manages other C + + objects in the form of arrays, lists, mapping tables (map), and so on. The collection object and the element object it manages, if packaged as a component using the above method, the aggregate ATL object may call a "Destroy" method, expecting to delete the collection-type ATL object and its corresponding element ATL object. But the essence of this operation is that when invoking the "Destroy" method of a collection-type ATL object, the "Destroy" method of the aggregate C + + object is called, the element C + + object is removed, and the ATL object corresponding to the element C + + object is not deleted. The result of this operation is that the ATL object of the element exists, and its bound C + + object is deleted, and the life cycle of the two is inconsistent.
To solve this problem, we need to delete the ATL object while the C + + object is deleted, and the C + + object can be deleted when the reference count of the ATL object is 0. The possible solutions are:
L Save an interface pointer in a C + + class, point to an ATL object that is bound together, and the best place to assign a value to the interface pointer is obviously inside the Link2cppobj function that provides the binding mechanism, and for that, add a iunknown* parameter to Link2cppobj
L in the C + + class destructor, determine whether the interface pointer is empty, if not empty, then release the reference to the interface, causing the ATL object itself to the destruction
Now, the technical scenario looks like this:
2.3.Internally created components and externally created components
The assembled C + + class is still a collection-type ATL component that can create and delete components that it manages. In this way, the creation of a component can occur in two ways:
L created directly by customer
• Indirect creation of an interface method by a customer calling a collection component
The differences in how they are created lead to the complexity of component lifecycle management. Generally, the creator of a component is responsible for maintaining the lifecycle of the component. In both cases, the lifecycle of the created component is maintained by the customer and the assembled component respectively. Of course, there is a case where a customer creates a component and then sends it to a collection-based component management, and now the responsibility for maintaining the component's life cycle is transferred from the customer to the assembled component.
Our solutions must provide such robustness and flexibility to maintain the lifecycle of components in a variety of situations. We add a BOOL member m_binnermanage to the ATL component class as a maintenance identifier for the component. Internal maintenance means that the life cycle of a component is maintained by other components (assembled components), while external maintenance is maintained by the customer.
By default, components are externally created and maintained, and external maintenance identities are set within the component's constructors. When a collection component creates an element, it needs to create a C + + object and an ATL object for the element, and then call the Link2cppobj function of the ATL object to bind the two together and modify the maintenance identity within the Link2cppobj function. For the third case, you can reset the maintenance identity within the corresponding method of the collection component when the externally created component is forwarded by the customer to the collection component.
2.4. C + + base class
To minimize changes to existing C + + classes, we designed a base class that encapsulates the functionality that needs to be added to the C + + class. All C + + classes that require dynamic assembly must derive from this base class to ensure that C + + objects in dynamic assemblies are consistent with the ATL object life cycle. As shown:
The implementation code looks like this:
Class Ccpp2atlobjbase
{
Ccpp2atlobjbase ();
Public
IUnknown pointer, pointing back to the interface that encapsulates the CPP class
iunknown* M_passociatlunk;
Protected
Virtual ~ ccpp2atlobjbase ();
};
Ccpp2atlobjbase::ccpp2atlobjbase ()
{
Initialize the IUnknown pointer to 0
M_passociatlunk = NULL;
}
Ccpp2atlobjbase::~ccpp2atlobjbase ()
{
When the CPP class object is refactored, release references to the interface
if (M_passociatlunk)
M_passociatlunk->release ();
}
Then, modify the existing individual C + + classes to derive them from Ccpp2atlobjbase, as shown in the following code snippet:
Class Cimplement:public Ccpp2atlobjbase
{
......
};
2.5. ATL template base class
From the above analysis, we find that all ATL component classes need to implement some of the same functionality:
L Keep a pointer to its bound C + + object
L provide a link2cppobj function
L Create an object in a constructor that is bound to a C + + class
To reduce the coding, we define a template base class with parameters that implements the public functions described above, and template parameters are bound C + + classes. All of the ATL component classes are then derived from the template base class. The current technical scenario is as follows:
The implementation code looks like this:
Template <class t>
Class Ccpp2atltemplatebase:
{
Protected
C + + class pointers
t* M_pcppobj;
Identifies whether the ATL object inheriting the template is maintained internally by the
BOOL M_binnermanage;
Public
/**********************************************************
A template constructor that implements the following functions:
1. New A C + + implementation class object
2. By default, ATL objects are externally maintained, and the internal maintenance identity is set to False
3. Set the anti-finger pointer to the ATL interface in the C + + class to null
**********************************************************/
Catlcpp2atltemplatebase ()
{
M_pcppobj = new T;
M_binnermanage = FALSE;
M_pcppobj->m_passociatlunk = NULL;
}
/**********************************************************
When you destructor an ATL object, if the ATL object is created externally, the
Delete the C + + object explicitly
If an ATL object is maintained internally, then nothing is done.
**********************************************************/
Virtual ~catlcpp2atltemplatebase ()
{
if (!m_binnermanage) {
if (m_pcppobj)
Delete m_pcppobj;
}
}
/**********************************************************
The Link2cppobj function, which is responsible for binding C + + objects and ATL interfaces
1. Delete the C + + object of new in the constructor, and use the externally imported C + + object
2. Set the internal maintenance identity of the ATL object to True
3. Set interface pointer members in C + + base classes
4. You need to increase the reference count because the ATL interface is being routed to external use
**********************************************************/
virtual void Link2cppobj (t* pObj, iunknown* pUnk)
{
ASSERT (POBJ! = NULL);
ASSERT (PUnk! = NULL);
if (m_pcppobj)
Delete m_pcppobj;
M_pcppobj = POBJ;
M_binnermanage = TRUE;
M_pcppobj->m_passociatlunk = PUnk;
M_pcppobj->m_passociatlunk->addref ();
}
};
Each ATL class is then derived from the template class, as shown in the following code snippet:
Class Atl_no_vtable CATLXX:
......,
Add an ATL template base class
Public ccpp2atltemplatebase<cimplementxx>
{
......
}
3.Automated Packaging for C + + parameter types
In the technical scenario of this article, the publicmethod of the C + + class corresponds to method one by one in the ATL component interface, and accordingly, the parameter types of the methods in the C + + class are also converted to the data types allowed by the COM specification.
In COM-based automation (Automation) technology, Microsoft provides a set of automation-compatible data types all simple data types can find corresponding definitions in the variant, but in most C + + based system designs, Method parameters do not simply appear as simple data types, class objects, object references, object pointers are frequently passed as parameters.
A parameter that exists as a class object, an object reference, or an object pointer, which we call a complex type parameter. In the technical scenario, all complex type parameters correspond to interface pointers in the ATL interface method, and we need to provide a dynamic transformation between C + + objects (or references, pointers) and ATL interface pointers.
When a complex type is passed in, how do you turn the interface pointer obtained by the ATL interface method into a C + + object pointer? For an ATL object, you can get the m_pcppobj variable directly, but the interface pointer does not. Therefore, it is necessary to provide a way to get the M_pcppobj variable value of an ATL component from an ATL interface pointer.
Our design is to provide a base interface Icppobjseeker for each ATL component that implements the Query method handlecppobj for binding C + + object pointers (that is, m_pcppobj). Any ATL interface is derived from the base interface and can call the Handlecppobj method.
In the previous discussion of life cycle management, it was mentioned that a customer created a component and then sent it to a collection of component management. When a collection component obtains externally created components, it needs to:
L GET the latter's C + + object pointer. The essence of assembly-to-element component management is the management of elements ' C + + objects through a collection of C + + objects, and there is no direct link between the aggregate ATL object and the element ATL object
L Modify the maintenance identity of the newly added element component
Therefore, we add the Postcppobj method for the Icppobjseeker interface to implement the above functions.
Complex types can only be converted from C + + pointers to interface pointers when they are outgoing.
4.Inheritance and polymorphism of interfaces
C + + classes inherit a wide range of applications, and dynamic components should retain the inheritance relationship between the original C + + classes. In our technical solution, C + + class and interface one by one correspond, C + + class inheritance relationship should also be reflected on each interface, as shown in:
The essence of implementing interface inheritance is to add a base interface for a derived ATL class, while the essence of adding an interface to an ATL class is:
L Modify IDL file to reflect the inheritance relationship of interface
• providing an interface implementation in an ATL class
Modifying the IDL file is simple and requires only changing the base interface of the derived interface. The implementation of adding a base interface to an ATL class is quite a consideration, and our approach is:
Extending the meaning of the ATL template base class, each of the ATL component classes corresponds to a template base class, derived from the template base class
The template base class of the derived class, derived from the template base class of the base class;ccpp2atltemplatebase is the root node of the template derivation tree, and all templates derive from Ccpp2atltemplatebase
L All interface methods are implemented in the corresponding template base class.
The ATL derived class inherits from its corresponding template base class, which inherits from the template base class corresponding to the ATL base class, and provides the implementation of the base interface in the template base class of the ATL base class. Therefore, the ATL derived class eventually inherits the implementation of the base interface. The inheritance relationships for C + + classes, ATL classes, and template base classes are as follows:
After implementing the inheritance of an interface, it is easy to show the polymorphism of the interface by simply adding the Base interface table entry in the interface map table declared by the ATL derived class.
5.Summarize
To smooth the transition to COM components for normal C + + classes, first using the ATL components to wrap C + + classes, adding a pointer to the ATL component interface in a C + + class when wrapping C + + classes, to address the consistency of ATL components and C + + class lifecycles, and then we took advantage of C + + base classes and the ATL template base classes simplify the implementation of these tasks, and then we automate the packaging of C + + parameter types, and finally we use templates to implement the inheritance and polymorphism of the interfaces. At this point, we have completely implemented the smooth transition of the ordinary C + + class to the COM component. In the above implementations, the C + + base class Ccpp2atlobjbase, the ATL template base class Ccpp2atltempbase, and the base interface Icppobjseeker are key points in this scenario. Ccpp2atlobjbase with Ccpp2atltempbase, perfect the management mechanism of the Component object life cycle; Through the base interface Icppobjseeker, we can reverse query C + + objects from any interface; Ccpp2atltempbase provides c+ + The free-binding function of the object and ATL components encapsulates the implementation of the IDispatch interface, while the further definition of the ATL template base class inheritance system greatly facilitates the free inheritance of the interface.
Of course, we have to specifically mention Microsoft's ". Net FrameWork". The introduction of the ". Net" Development Framework does address many of the confusion of COM technologies, as well as some of the technical issues to be addressed in this technical solution. However, the ". NET Framework" is a change in "Changing the World", and it is inconceivable that the effort to fully port a C + + based system, especially a large system, to a ". NET" platform, is no less than the amount of work to be re-developed. So Microsoft particularly recommends smooth porting from COM technology to the ". Net" platform. From this point of view, the dynamic component technology presented in this paper is more valuable, it looks at the practical application from the engineering angle, and solves many problems from object-oriented C + + to component-based COM technology, which not only fully protects the accumulation of the original system, but also provides the possibility for these systems to catch up with the developing ". Net" Express.
Reference documents
L "COM Principles and Applications", Pan, Tsinghua University Press
L "com essence" (Essential com), Don Box, Pan, China Power Press
L Deep analysis of ATL (ATL Internals), Brent Rector, Chris Sells, Pan, new language translation, China electric Power Press
L design mode-the basis of reusable object-oriented software (designpatterns-elements of reusable object-oriented software), Erich Gamma, Richard Helm, Ralph Johnson, John vlissides, Li Yingjun, Ma Xiaoxing, Cai Min, Liu Jianzhong, etc.
Dynamic component Technology of C + + class