Basic Sample Senario:
The Client requires a component to provide a FastString class, which has the int length () method
Solution:
Static link: The component manufacturer delivers the source code to the client, and the client compiles and runs the component code. If the component Code requires a fix bug or update, the client code needs to recompile the connection. In addition, different instances of the client software use their respective Component Memory objects.
Dynamic Link: component vendors use DLL to issue components. In this case, different client instances can share component code segments in the memory.
DLL problems: 1. export name problem: Different compiler can use different mangle names to differentiate c ++ functions, therefore, different compiler clients and components cannot be linked (you can use extern "C" to solve the global function name problem. DEF file to solve the problem of exporting member functions)
2. Upgrade problem: if the component is initially defined
Class FastString
{
Char * m_psz;
Public:
FastString (const char * psz) {strcpy (m_psz, psz );}
};
Then the vendor changes the component implementation.
Class FastString
{
Int newmember;
Char * psz;
Public:
FastString (const char * psz) {strcpy (m_psz, psz );}
}
Originally, the size of the FastString object was 4 bytes, which is now 8 bytes. However, when the client allocates an object based on 4 bytes, the dll needs to store a pointer to the four bytes behind it. This behavior is unpredictable!
One way to solve this problem is to change the dll name every time it is released, such as 1.0, 2.0, and x.0. But this is relatively weak !!
What is the root cause of this problem?
The key to the class is to encapsulate the implementation details, that is, the user knows which services (public methods) the class provides, and does not need to use any member variables inside the class. In this way, as long as the interface is not changed (the class provides functions), the user can use any version of the implementation with peace of mind. How can C ++ not work? C ++ also tells the user interface about the implementation (Object layout) of the user class ). For example, how big is the class object and the offset of each member (For details, refer to Inside c ++ object model ). With this in mind, the client's interface code is closely coupled with the specific implementation in the DLL ~
What should we do? You only need to prevent the client from directly creating FastString, so that the client code will not be affected by the FastString Implementation changes. Add a Wrapper class to FastString and nest a FastString internally. All calls to FastString are foward to the internal FastString member. The task of creating FastString is completed in dll, the client only knows that the Wrapper size is 4 bytes-pointer to FastString. The problem is solved in this way, but it is too troublesome. All interfaces must be packaged with one layer !! There is an additional layer of calling!
What else can I do? To ensure binary compatibility of the c ++ interface class, only compiler-independent features can be used: 1. suppose the composite type has the same representation (struct) 2. the sequence of passing parameters is the same. You can use an indicator to specify 3. the Calling mechanism of virtual functions is the same, that is, based on vtbl and vptr. based on these assumptions, all the functions of the c ++ interface class we created are set to virtual functions, so different compiler will generate the same machine code for client method calls. When an interface is defined, it is stipulated that the memory structure of all classes inherited from it must be compatible with it. However, you cannot tell the definition of the user class at this time. Otherwise, you will be back on the old road. What should I do? Only interface customers cannot create class definitions, and only export users can create class objects. Like wrapper above, class creation operations are called only within the dll, this means that the size and layout of the actually built Class Object Code are created using the same compiler as the code for compiling the implementation class method (that is, the code of the coder and the call coder is compiled by the same compiler at the same time ). Because the location of the virtual destructor in vtbl is related to compiler, you cannot set it as a virtual function. You only need to add a Delete function to complete the destructor.
OK. Currently, only functions that create class objects in the DLL need to use extern "C" export to the client. virtual functions in other interfaces are accessed through virtual tables, you do not need to rely on symbolic name links.
Further, what if we want to add a function to the interface? If you add a new method directly after declaring the method in the existing interface, this method will appear in the last column of vtbl, and the old client will not call the new method, but what if the new client accesses the old object? Unfortunately, this happened! The problem with this is that modifying public interfaces breaks the encapsulation of objects.
The added interface function can only be implemented by designing one interface to inherit from another interface or inheriting multiple interfaces to the class. You can use RTTI to query objects during running. This function is not supported? However, RTTI is also related to compiler. Well, let each class implement RTTI by itself, that is, implement a dynamic_cast method to make its own cast an interface, if not, 0 is returned.
For example:
Void * CFastString: Dynamic_Cast (const char * pszTypename)
{
Void * pRev;
If (strcmp (pszTypename, "IFastString") = 0)
{
PRev = static_cast <IFastString *> (this );
}
Else if (strcmp (pszTypename, "IOut") = 0)
{
PRev = static_cast <IOut *> (this );
}
Else if (strcmp (pszTypename, "IExtent") = 0)
{
PRev = static_cast <IFastString *> (this );
}
Else
{
Return 0;
}
Return pRev;
}
Note that IFastString is used for cast to IExtent, because both IFastString and IOut are inherited from IExtent. If you write IExtent, you do not know which one to use. Using Virtual inheritance, you can make the CFastString object have only one IExtent, why not? You know... As with the previous answer, the compiler is related.
The last problem is the delete problem. You need to remember to call the delete method for each object and use the cast pointer to cast. It is not difficult to remember that the object is deleted! What should I do? Use reference counting to treat each pointer as an object with a lifecycle. When creating a pointer, the pointer is counted plus +, and the pointer is destroyed. When the pointer is 0, the object is deleted.
By using the vptr and vtbl binary firewalls, we have implemented reusable binary components, and customers do not need to re-compile the components.