|
The discrepancy between interfaces and dynamic_castSubtleties in the C++ implementation of COM interfaces.
IntroductionWhen working with COM interfaces, you cannot use dynamic_cast<> to cast an interface to a concrete class. If you try it, it might work, but it might just as well crash your application. Why is this? Because dynamic_cast<> violates the contract. The definition of a COM interfaces specifies the following layout requirements:
dynamic_cast<> on C++ classesNow what happens if you use dynamic_cast<> on an interface?
Usually, a C++ compiler generates a call to a function in the runtime library that performs the dynamic cast. In C++Builder, this function is declared as follows (it can be found in $(BDS)\source\cpprtl\Source\except\xxtype.cpp):
The first parameter points to the object to be casted. The second parameter is the VMT pointer (which is not necessarily placed at the beginning of the object layout for all C++ classes, although there is a compiler option to change this). srctypPtr and dsttypPtr point to the RTTI descriptor tables for the static types given (IMyInterface and MyClass), and reference specifies whether the cast was performed on a pointer or on a reference - reference casts throw a std::bad_cast exception on failure, while pointer casts simply return 0 in that case. When reading the implementation of __DynamicCast(), you might notice that it looks into the VMT and extracts the entries at negative offsets. First, it decreases the object pointer by the value in VMT[-2]. This is necessary as C++ permits multiple inheritance. Furthermore, objects can have multiple VMTs for the various base classes, and therefore, the -1 offset of each VMT redirects to the primary VMT, the VMT associated with the first specified base class. From the primary VMT, the offset -3 is taken as a pointer to the type descriptor for the object's actual type. This is what the object and its VMT might look like: To achieve the polymorphic cast, dynamic_cast<> first casts the pointer to the actual object's type and then searches for the class that was specified in the base classes list of the object's type descriptor. If no such base class exists, it returns either 0 or throws std::bad_alloc, depending on the reference parameter as mentioned above. Obviously, this is an implementation detail; pretty much every C++ compiler does it differently. But we cannot guarantee that the actual object behind an interface was compiled by our compiler! It does not even need to be a C++ class - it could have been implemented in C, in Delphi or in a .NET language. By using dynamic_cast<> on an interface, we lie to the compiler: we say "this is a C++ class, please cast it at runtime", and, as Henry Spencer rightfully stated: "If you lie to the compiler, it will get its revenge." COM in C++So why does the compiler not prevent this? Why can we dynamic_cast<> an interface at all? Because we lied to the compiler once before. Remember how COM interfaces are declared in C++:
(For additional comfort, most Windows compilers support an annotation mechanism to associate the interface ID with the class, e.g. __declspec(uuid()) in C++Builder and Visual C++, but that is not relevant here.) This declaration tells the compiler "IUnknown is an abstract base class in every sense". Therefore, it anticipates all the hidden information at the negative offsets of a VMT to be there. As it turns out, we do not have much of a choice here. We could alternatively use the C-style declaration:
But that would force us to do many explicit casts, thus omitting type safety. Also, we could accidentally call one object's method and pass another object to the This parameter. All this can be avoided by using the C++ variant. Other languages such as Delphi introduced an "interface" keyword to restrict interfaces to the requirements given by COM. Unfortunately, the C++ standards comittee usually is reluctant to add new keywords to the language. This has caused quite some confusion as much inconsistence arised due to the recycling of the existing keywords: The next standard will add even more confusing keyword reuses: enum class (scoped enumerations), auto (automatic type determination), sizeof (number of variadic template arguments), possibly default and delete (for default constructors, copy constructors and assignment operators) etc. Apparently, there is few hope for an interface keyword as no existing keyword could easily be used for that. (Wait, what about "register"? We don't it anyway these days.)dynamic_cast<> on Delphi classesBut, even if the internals of dynamic_cast<> are compiler-specific, it should be safe to use dynamic_cast<> on interfaces as long as all interfaces point to objects generated by the same compiler, shouldn't it? Again, no. Even a single compiler might know multiple variants of dynamic_cast<>. For example, both major C++ compilers for Windows, the Visual C++ compiler and the C++Builder compiler, are "hybrid" compilers in some way. Visual C++ is able to compile both managed and unmanaged code into a single project and provides sophisticated interaction techniques, while C++Builder supports both Delphi-style and C++-style classes. Although Delphi-style classes cannot use multiple inheritance, they are free to implement as many interfaces as they like. Interfaces are, as explained above, represented by abstract C++ classes, and in order to permit Delphi classes to derive from them, the compiler performs checks whether the abstract C++ classes meet all interface requirements (no data members, private functions, multiple bases). As I exposed in "Why Delphi classes must be created on the heap", a Delphi class has a VMT layout quite different from a C++ class: Obviously, dynamic_cast<> must use a different mechanism than the one described above to cast between Delphi classes. Tracing a dynamic_cast<> call on a Delphi class reveals that the compiler recognizes the difference and instead generates a call of the __DynamicCastVCLptr() or the __DynamicCastVCLref() function:
As Delphi classes do not feature multiple inheritance, a pointer to a Delphi class always points to the beginning of the object, and no pointer adjustment is required when doing polymorphic casts. Therefore, __DynamicCastVCLptr() and __DynamicCastVCLref() only check whether the destination class is a base class of the actual type or the actual type itself and plainly return the given pointer upon success. If the base class cannot be found, __DynamicCastVCLptr() returns 0 and __DynamicCastVCLref() throws a std::bad_cast exception. Mixing Delphi and C++ classes is one of the easiest ways to mess up with dynamic_cast<> and interfaces:
A pragmatic workaroundBut what to do if you need a way to cast to the actual class type? Although dynamic_cast<> cannot be used for COM interfaces, COM does specify a way to cast an interface at runtime: the QueryInterface() function. So if you want to be able to cast to your class, you could use the annotation facility of your compiler and give your class an ID for which you test in your implementation of QueryInterface():
The fact that GUIDs are, well, globally unique ensures that our extension to QueryInterface() does not interfere with any COM requirements. References[1] The Delphi and C++Builder Runtime Library source code (delivered with C++Builder) [2] Raymond Chen, The layout of a COM object, 05.02.2004 [3] The C++ Standards Committee, State of C++ Evolution (Post-Antipolis 2008 Mailing), 30.06.2008 CommentsDeprecated: mysql_connect(): The mysql extension is deprecated and will be removed in the future: use mysqli or PDO instead in /www/htdocs/w008ab83/ad/phputils/dbc_mysql.php on line 112
|