Re: RTTI and exceptions between plugins (solution)
Re: RTTI and exceptions between plugins (solution)
- Subject: Re: RTTI and exceptions between plugins (solution)
- From: Steve Baxter <email@hidden>
- Date: Wed, 3 May 2006 13:36:46 +0100
Hi,
I've found a bit of a problem with my solution below.
On 10.4.6, this all works fine - dlopen( RTLD_GLOBAL ) loads my
plugins and correctly fixes up all the RTTI information etc. so that
the one definition rule is obeyed.
On 10.3.9 however, dlopen( RTLD_GLOBAL ) does not fix up the plugins
correctly - modules end up with different RTTI typeid() addresses for
the same types (which then causes dynamic_cast to fail).
Does anyone know what could be going on here? I know dlopen() was
completely changed for Tiger, but surely the behaviour isn't that
broken in Panther?!
Cheers,
Steve.
On 8 Feb 2006, at 0:18, Steve Baxter wrote:
Hi,
Some people (mostly me) have posted here concerning problems with
RTTI and exceptions in applications with a plugin architecture. I
think I may have found the solution (and possibly a bug in
CFBundle, but that kind of depends on your point of view).
Here is the problem:
The GCC C++ runtime support libraries compares type_info objects
by address rather than by using their name() value. This behaviour
is different to both Codewarrior and VC++. Comparing by pointer is
intended to offer better performance, however it introduces a
complication with applications that have a plugin architecture.
type_info symbols and other symbols with vague linkage are
instantiated as they are compiled:
http://developer.apple.com/documentation/DeveloperTools/gcc-3.3/gcc/
Vague-Linkage.html
In a standard application, the linker resolves all these symbols at
link time so that the application as a whole has only a single copy
of each symbol. With a plugin architecture however there is a
problem - it is possible for different plugins to have their own
copy of these symbols. The plugins do not link against each other,
so the linker does not get a chance to resolve the symbols. When
the plugins are loaded, it is possible for the application as a
whole to have multiple copies of a symbol. This can lead to RTTI
not working properly (an object of class X made by one plugin may
not be considered to have type X by another plugin) and exceptions
may not work properly, particularly where the exception is a
completely inline class.
Note that this situation is technically a bug in the application -
it is a violation of the one definition rule:
http://en.wikipedia.org/wiki/One_Definition_Rule
This is a particularly nasty C++ rule as there is no compiler error
you will see, your program may just subtly not work properly!
Anyway, the solution to all of this is here:
http://gcc.gnu.org/faq.html#dso
This page says that for this to work:
- Your plugins must export all their symbols
- You must load your plugins using dlopen() with RTLD_GLOBAL
This causes the symbols in each plugin to be added to the
application's symbol table as the plugin is loaded. As further
plugins are loaded, their symbols will be resolved against the
symbols in the already running application, and the one definition
rule will be obeyed. If plugin A has symbol X, when plugin B loads
that also has symbol X, it will link against plugin A's copy, not
its own.
Now for the problem. It is easy to export all the symbols in your
plugin - this is the default behaviour in Xcode. If however you
load your plugin using CFBundle, it will do the wrong thing.
CFBundle loads the plugin (dylib) with private (local) symbols.
From CFBundle.c:
NSModule module = NSLinkModule(image, buff,
(NSLINKMODULE_OPTION_BINDNOW | NSLINKMODULE_OPTION_PRIVATE |
NSLINKMODULE_OPTION_RETURN_ON_ERROR));
NSLINKMODULE_OPTION_PRIVATE appears to have the same behaviour as
RTLD_LOCAL (as far as I can tell, dlopen() actually calls
NSLinkModule()). This will prevent symbols being resolved properly
across plugins if you use CFBundle. This may be a bug in CFBundle,
I'm not sure though (sometimes you might want private visibility
for plugins). Possibly the bug is that CFBundle does not give you
the choice.
A solution to this is to call dlopen() on the bundle executable
with RTLD_GLOBAL before calling CFBundleLoadExecutable(). This
forces the dylib to be loaded with global visibility.
OK, hope that helps someone. It's taken me a long time to figure
this out for our application! If anyone can think of any
simplifications or anything I've missed, I'd be keen to hear them!
Cheers,
Steve.
Steve Baxter
Software Development Manager
Improvision
+44-2476-692229
_______________________________________________
Do not post admin requests to the list. They will be ignored.
Xcode-users mailing list (email@hidden)
Help/Unsubscribe/Update your Subscription:
40improvision.com
This email sent to email@hidden
Steve Baxter
Software Development Manager
Improvision
+44-2476-692229
_______________________________________________
Do not post admin requests to the list. They will be ignored.
Xcode-users mailing list (email@hidden)
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden