|  | How to maintain a c++ abi |  | |
| | | Brendan Miller |  |
| Posted: Fri Aug 22, 2008 6:49 am Post subject: How to maintain a c++ abi |  |
| |  | |
My question, is how does one engineer a library so as to provide a stable c++ abi.
I was thinking that the pimpl idiom would solve a lot of problems and allow the implementation of exposed classes to be changed easily.
Obviously templates are burned into the executable and should be made thin wrappers around ABI stable non template code.
What isn't entirely obvious to me is how to deal with classes that export virtual functions. If virtual functions are added, won't this change the layout of the virtual function table and break virtual function calls? Is there some way to add entries to the vtable without breaking old vtable calls (i.e., adding new virtual functions after all other virtual functions). Vtable layout is implementation specific, but I suspect many people here have experience with how the major implementations do it (gcc, visual studios, etc) and could provide that information for reference.
-- [ See LINK for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ] |
| |
| | | Martin Bonner |  |
| Posted: Fri Aug 22, 2008 1:03 pm Post subject: Re: How to maintain a c++ abi |  |
| |  | |
On Aug 22, 7:49 am, Brendan Miller <catph...@catphive.net> wrote:
| Quote: | My question, is how does one engineer a library so as to provide a stable c++ abi. [snip] What isn't entirely obvious to me is how to deal with classes that export virtual functions. If virtual functions are added, won't this change the layout of the virtual function table and break virtual function calls? Is there some way to add entries to the vtable without breaking old vtable calls (i.e., adding new virtual functions after all other virtual functions).
|
You can't (in theory at least) change a class and have a stable ABI (the reason is that the old client code would have one definition of the class, and the new implementation would have another, and that violates the one-definition-rule - which means all bets are off).
The safe way to do this, is to start with class Foo, then in version 2 you write class Foo2 : public Foo {...}; and in version 3 class Foo3 : public Foo2 { ...}
That gets tedious very quickly (but it is reliable). Note that you can change the definition of the Foo pimpl class, because that is held entirely within the implementation code, and so you will replace all definitions.
The slightly dodgy method is to add new virtual function declarations at the end of the class definition. This will work in practise, unless you try and make a destructor virtual in a later release (moral: you have to decide once-and-for-all whether the destructor is to be virtual or protected in release 1)
Note that you will /have/ to play the Foo2 game if you want to add another constructor.
-- [ See LINK for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ] |
| |
| | | Marco Manfredini |  |
| Posted: Fri Aug 22, 2008 1:04 pm Post subject: Re: How to maintain a c++ abi |  |
| |  | |
Brendan Miller wrote:
| Quote: | What isn't entirely obvious to me is how to deal with classes that export virtual functions. If virtual functions are added, won't this change the layout of the virtual function table and break virtual function calls? Is there some way to add entries to the vtable without breaking old vtable calls (i.e., adding new virtual functions after all other virtual functions). Vtable layout is implementation specific, but I suspect many people here have experience with how the major implementations do it (gcc, visual studios, etc) and could provide that information for reference.
|
The compilers I know implement virtual function calls by indexing a function table with a compile time constant. Adding a new member to the end of a class definition is most likely not feasible, even if you assume FIFO layout: Any change to the virtual layout of a base class will shift the vtbl-slots of the derived classes.
People have various means to deal with that and other ABI-issues, for example by defining the API as a set of abstract interface classes (which never change once published), but may expose enhanced functionality or different interface versions through a dynamic cast. As a matter of fact, using this technique with C++ is a big pain in the intestinal outlet if the interface versions are not in a clean inheritance order, because C++ thinks that interface types are unnecessary.
A different solution would be to have runtime-safe virtual function calls, i.e. instead of calling member function directly from the vtbl, a compiler generated "thunk" would be called which has a relocation entry to resolve the right vtbl index. This is usually simulated with the pimpl idiom, where the thunks are hand-knitted by the library maintainer.
M -- IYesNo yes=YesNoFactory.getFactoryInstance().YES; yes.getDescription().equals(array[0].toUpperCase());
[ See LINK for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ] |
| |
| | | blargg |  |
| Posted: Thu Aug 28, 2008 8:58 am Post subject: Re: How to maintain a c++ abi |  |
| |  | |
In article <1984751.OZrQRRRG3p@technoboredom.net>, Marco Manfredini <ok_nospam_ok@phoyd.net> wrote:
| Quote: | [...] A different solution would be to have runtime-safe virtual function calls, i.e. instead of calling member function directly from the vtbl, a compiler generated "thunk" would be called which has a relocation entry to resolve the right vtbl index. This is usually simulated with the pimpl idiom, where the thunks are hand-knitted by the library maintainer.
|
So you're describing something like this? No pimpl required here.
// Interface
class Base { public: void f(); void g(); // can add new functions without breaking already-compiled clients private: Base() { } friend class Base_; };
Base* new_derived();
// Implementation
class Base_ : public Base { public: Base_() { } virtual void f(); virtual void g(); };
void Base::f() { static_cast<Base_*> (this)->f(); } void Base::g() { static_cast<Base_*> (this)->g(); }
// except for using Base_, implementation isn't affected by insulation
class Derived : public Base_ { public: virtual void f(); };
Derived* new_derived() { return new Derived; }
-- [ See LINK for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ] |
| |
| | | Yechezkel Mett |  |
| Posted: Thu Aug 28, 2008 8:55 pm Post subject: Re: How to maintain a c++ abi |  |
| |  | |
On Aug 22, 9:49 am, Brendan Miller <catph...@catphive.net> wrote:
| Quote: | My question, is how does one engineer a library so as to provide a stable c++ abi.
|
My preferred method is to reduce everything to a C abi and provide a thin inline C++ wrapper on that.
e.g.:
// private to the library
class C { public: void f(); void g(); };
// exported from the library
extern "C" { C* create_C() { return new C; } void destroy_C(C* c) { delete c; } void C_f(C* c) { c->f(); } void C_g(C* c) { c->g(); } }
// thin wrapper for the user side
class C : boost::noncopyable { C* c; public: C() : c(create_C()) {} ~C() { destroy_C(c) } void f() { c->f(); } void g() { c-g(); } };
Of course, if you want the class to be copyable you'll have to export the equivalent of at least a copy constructor (you can probably do without an assignment operator but you might want one as an optimisation). Some care is required with the naming to avoid clashes -- it's probably best to use a long prefix on the exported C functions, the user doesn't use those names anyway. Take care only to export the C functions from the library.
Concrete types such as std::string will need to be converted to C equivalents when passing them; inward is generally not a problem, but returning them can be a pain because you need to copy them then free them -- you might need to return structure with a pointer to a release function.
There is a certain amount of work involved, but it does help to solidify the interface properly, and separate it from the implementation, so it does have it's benefits.
This system also allows other languages to be used for one side or the other -- I most recently used such a system for a library written in Delphi with a program written in C++.
Yechezkel Mett
-- [ See LINK for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ] |
| |
|
|