[Development] templated QObjects [was: Re: We are planning to upgrade qdoc to use clang for parsing C++]
Thiago Macieira
thiago.macieira at intel.com
Wed Mar 2 21:59:30 CET 2016
On quarta-feira, 2 de março de 2016 20:59:41 PST Milian Wolff wrote:
> Hey Thiago,
>
> what is "the runtime merging problem on Windows"?
Ever heard of the dynamic_cast problem on Windows? It's the same.
Here's the problem:
QMetaObjects are identified by their pointer addresses: two meta objects are
the same if their pointer addresses are the same (remember: QMetaObject has no
operator==). qobject_cast uses that feature to conclude whether a given object
derives from a given class.
The way it's currently designed in Qt, the meta object is exported
(Q_DECL_EXPORT) from the DLL in which the class is defined. Obviously, that can
only happen for concrete types, not for templates. As I explained, we could
add a feature to allow the class author to export all possible instantiations
of a template. Each and every instantiation would be exported from the DLL.
That's, in fact, why a full listing is required: to determine which
instantiations to export in the first place.
The other suggestion done here is that the user of a template create the meta
object. That's how typeinfo works for types without virtual tables: each place
where typeid() is called, the typeinfo is generated, then merged at runtime by
the dynamic linker. On ELF systems without any special compiler flags, this
works because symbols are global by default ("default" visibility) and all
references to any symbol name are accessed via the GOT, which ensures that
only one copy is active and all accesses get the same pointer address.
Where this breaks down:
1) Windows: the PE-COFF file format does not work like ELF. Symbols are by
local (the default), __declspec(dllexport), or __declspec(dllimport). If the
symbol is local or exported, then the compiler generates access assuming that
the copy in the current DLL is the active one; if it's imported, then the
compiler generates code assuming it exists in another DLL and will not emit a
copy.
This means that if a DLL has a copy, it assumes its copy is active. If it has
no copy, some other DLL must have it. What's more, imports are associated with
a particular DLL, so the compile-time linker needs to know which DLL contains
the symbol.
I don't know how or even if throwing template types or dynamic_cast'ing them
works on Windows. It's possible it doesn't work and will never work. I don't
care to find out.
2) "hidden" visibility: modern libraries on Unix today compile with
-fvisibility=hidden -fvisibility-inlines-hidden, like Qt does. Many of them,
like Qt, heavily pollute the global namespace if you don't use those flags, and
that's assuming they work at all. Every type with hidden visibility is, as the
name says, not exported to the ELF dynamic symbol table, which means the
dynamic linker doesn't see them and will not merge with other copies.
3) -Bsymbolic / "protected" visibility: this instructs the compiler and linker
that the exported symbols are not subject to preemption and that, like
Windows's __declspec(dllexport), the copy in this ELF module is always the
active one and local accesses need not go through the GOT (that's why we use
it). If this assumption fails, crazy things happen, as we've seen with
platforms other than x86 for our -Bsymbolic usage.
Summary: this is where the theory of the C++ Standard and reality do not
agree. When you take the ABIs into consideration, the C++ Standard's definition
of ODR is just wishful thinking.
In time: C++17 Modules do not solve this issue. Modules replace some use of
headers and #include; they have nothing to do with DLLs and symbol exporting/
importing.
Glossary:
* ELF: Executable and Linkable Format, the file format for all object files,
libraries, executables and core dumps on modern Unix systems, first deployed by
Sun on Solaris.
* PE-COFF: Portable Executable COFF, Microsoft's variant of the COFF object
file format, used for DLLs and executables (not for .obj files, that's OMF).
* GOT: Global Offset Table, a technique used to achieve position-
independence. Whenever a symbol "foo" is referenced, instead of writing its
address into the code, the compiler generates an indirect load of the symbol's
address from a fixed location in the GOT, which the dynamic linker will write
to once it has finished loading all modules and can resolve all symbols. This
can be used to make the code read-only and shareable. See also PLT and the GOT
pointer (which is the value that the PIC register carries).
--
Thiago Macieira - thiago.macieira (AT) intel.com
Software Architect - Intel Open Source Technology Center
More information about the Development
mailing list