[C++-sig] Re: more cross-module problems
David Abrahams
dave at boost-consulting.com
Fri Jul 25 17:27:02 CEST 2003
greg Landrum <greglandrum at mindspring.com> writes:
> At 07:16 AM 7/24/2003, David Abrahams wrote:
>
>>None of these problems with boost::any have anything to do with
>>Boost.Python per-se. I'll try to explain what's going on if you'll
>>agree to write a FAQ entry or other documentation page for
>>Boost.Python which describes the cross-module issues in detail. Deal?
>
> you drive a hard bargain...
> yeah, it's a deal.
Now that I think about it, this is going to have to be a FAQ about
dynamic linking, RTTI, and exception-handling issues in general. I'll
help you get it into shape.
OK, here's the skinny.
There are three basic dynamic linking models; I'll call these
Global - "everything" is shared across the link boundary. This
is what usually happens on Unix systems when dynamic
linking; it's much like static linking (but not
exactly). The global model is not available on
Windows (this might be a good thing).
Explicit - explicitly marked symbols are shared across the link
boundary. This is what Windows compilers do with DLLs
using __declspec(dllexport)/__declspec(dllimport).
Although the explicit model is technically available
on many Unices, it's usually very cumbersome and
requires special support from scripts outside the
compiler.
Insular - This is the model used for plugins where no symbols are
shared. Instead, you open the shared library
explicitly and get (function) pointers to the symbols
you're looking for. Python uses this model to open
extension modules, so that extension module writers
don't need to know what symbols other extension
modules might be using.
On Unix systems, Python is an executable which opens extension modules
using the Insular model (dlopen), and on most systems the Python
symbols used by extension modules are simply left unresolved and, as
far as I know, because they are unresolved they automatically get
hooked up to the symbols in the Python executable. AIX is a bit
different - because it has true insulation Python explicitly hooks all
its symbols up to the extension module when it is loaded.
On Windows, that implicit back-linking is not available, so the Python
executable is just a shell which links Explicitly to the Python DLL.
Extension modules are loaded by Python using the Insular model but
also link to the Python DLL using the Explicit model. I think Python
doesn't do something similar for AIX only because of a failure of the
imagination ;->
On Windows, Boost.Python extension modules link to the Boost.Python
library using the Explicit model. On Unix systems, they use the Global
model. Note, however, that the fact that two extension modules are
linked to the same library with the Global model does not mean the
extension modules share all symbols. In fact (excepton Tru64 when
-tlocal is not used to link -- not recommended), the extension modules
will *only* share a symbol which is also present in a library they are
both linked to.
Most of the subtle problems with dynamic linking C++ have to do with
symbols C++ generates implicitly:
RTTI and EH information
virtual tables
static data members of class template instantiations
static variables in function template instantiations
Typically, in the Global model this information (known as "weak
symbols" on Linux) is generated in every translation unit where it is
used, and resolved away at link time. Of course, that leaves us with
a single copy per link unit (executable or shared library), and which
copy is used needs to be resolved at load time.**
The solutions generally come down to controlling where those symbols
are generated and how they are shared across boundaries.
Whether the first two things are a problem for your application
depends in part on the ABI (application binary interface) of your
compiler. The ABI encompasses issues such as function calling
convention, class layout, the format of EH tables, the format of RTTI
information, and of course, the procedures the compiler uses to
manipulate that information.
Your problem with boost::any was an interaction with the compiler's
implementation of dynamic_cast. Recent GCCs determine dynamic type
equivalence by comparing the *addresses* (not the contents) of their
RTTI information. Because your two extension modules are not sharing
any symbols other than those in the Boost.Python library, each one
gets its own copy of the RTTI for the class in the any object, and the
template instantiation of the any_cast on each side of the library
boundary uses a different copy. The solution is to get that RTTI
information generated in a place that both libraries can see. The
best way to achieve that is to link both extension modules to a common
shared library using the global model, and put the RTTI information
there. For the case in question, you might achieve that by explicitly
instantiating the any_cast template there.
The problem we had earlier with catching exceptions is an interaction
with the compiler's implementation of EH and the behavior of glibc
under dynamic linking. Recent glibc implementations have an
"inconvenient" (charitably speaking) behavior with weak symbols. The
result is that if the symbol appears in both extension modules *and*
in the Boost.Python shared library, the first extension module loaded
will share a copy with the Boost.Python library and each module loaded
thereafter will get its own copy. Fortunately, I knew that classes
with non-inline virtual functions are different, and usually a
*strong* symbol gets generated in the translation unit containing the
class' first non-inline virtual function. By adding a virtual
destructor in the Boost.Python library we ensured that both extension
modules got the same copy of the exception's EH info.
>>The Windows crash, FWIW, is a poor-QOI issue "bug" in the Dinkumware
>>standard library that ships with vc6. vc7 solves it:
This problem is that the vc6 associative containers use a special
static data member representing a "NULL" node, used as a sentinel to
tell the container implementation where the "edge" is, so it would
stop searching there. Because you instantiated the map<> template in
two separate extension modules, each one got it's own copy of the
"NULL" node, and a map passed across the boundary would contain the
wrong sentinel value for the code on the other side.
--
Dave Abrahams
Boost Consulting
www.boost-consulting.com
More information about the Cplusplus-sig
mailing list