All of lore.kernel.org
 help / color / mirror / Atom feed
* [libgpiod] C++ Bindings ABI Compatibility
@ 2020-10-23 20:04 Jack Winch
  2020-10-24 13:19 ` Bartosz Golaszewski
  0 siblings, 1 reply; 5+ messages in thread
From: Jack Winch @ 2020-10-23 20:04 UTC (permalink / raw)
  To: open list:GPIO SUBSYSTEM, Bartosz Golaszewski,
	Bartosz Golaszewski, Kent Gibson

Good evening, Bartosz!

Starting another email thread on this subject for future
searchability, although some previous discussion was held under the
'[libgpiod] cxx bindings: time_point vs duration' thread.

In that thread, you last said:

> I started reading on my own and I think I now have a slightly better
> idea about C++ and its ABI. I also see what a mess the original
> libgpiod bindings are in terms of ABI compatibility but fear not!
> Right now (v2.0) is the time to make it better! :)
>
> At a personal level I'm not too concerned about the ABI compatibility
> of C++ bindings - I much more care about the API. This is because
> libgpiod is aimed mostly at bespoke embedded distros (yocto,
> buildroot, openwrt etc.) I understand however that it's an important
> issue for distros.
>
> I didn't know any better at the time of writing libgpiodcxx so I just
> put all private members in the main header, exposing them to the users
> of the library. I'm not sure why I didn't realize that C++ classes are
> basically C structs (and exposing them amounts to exposing struct in a
> C header) but I just didn't know any better.
>
> I assume that you'll either propose to use the Pimpl pattern or a
> header-only library. I noticed that Pimpl is what Qt5 uses while
> header-only is more of a boost thing. If so - the timing is great as
> I'm open to either solution for libgpiod v2.0.


If you did some general reading on the topic, you'll realise just how
fortunate we are to be talking about Linux here and not Microsoft
Windows.  It's an absolute minefield.  In fact, Microsoft now
instructs developers to not provide C++ ABIs and to use a stable
platform ABI such as C, COM or WinRT.  Many of Microsoft's libraries
are written in C++, but a C ABI is provided to avoid ABI related
issues.  And then there is the fact that a process can have multiple
'components' loaded using different versions of the Microsoft C
Runtime (CRT), each possessing different heaps, etc.  It causes a lot
of pain for a lot of individuals and parties.

You may or may not be surprised how many professional software
developers / engineers have no clue regarding ABI compatibility.  Most
of them learn the hard way (which is exactly how it was brought to my
attention and my what a hard lesson it was).  But none of us are born
knowing everything.

Generally speaking, if you have a shared library largely implemented
in C++ and ABI compatibility is of a concern to you, your options are
as follows:

1.  Provide a C ABI to the library, wrapping the C++ internals.
Resource management should be handled within the library and handles
or opaque pointers should be used to pass references to library
managed resources to the using application.  Exceptions and non plain
old data (POD) type objects should not cross the shared library -
application boundary.

2.  Provide a C++ ABI, but implement the PImpl pattern such that it is
possible to maintain ABI compatibility for the major version releases
of the library (implementation details may be changed to implement bug
fixes, etc, which is not possible without using the PImpl pattern).
Even with this approach, there are limitations as to what should be
passed over the shared library - application boundary.

3.  Opt to provide a static library instead of a shared library.  The
shared library should be built with the same compiler and compiler
version as that to be used for the application, etc, in order to avoid
issues.  If linking fails here, this is still preferable to
experiencing loading and linking issues with shared libraries, which
may only become apparent once the application and library is deployed
and executed at runtime.

4.  Provide a header-only library, which is a popular option for many
C++ libraries these days.  This option is not without its
disadvantages, but the worry of library interface ABI compatibility
disappears.  This approach, much like the static library approach,
allows the full feature set of C++ to be utilised within the library
interface (e.g., the use of exceptions, etc).

You're right in that Qt makes use of the PImpl pattern.  The framework
uses return codes for error handling, as exceptions are still
problematic with this approach, and mostly Qt interface types or POD C
types are exchanged over the shared library - application boundary.
Some standard library types are passed across the boundary and this is
usually where problems arise.

For libgpiod, I would personally recommend going down the header-only
library approach.  The typical structure of most boost libraries, in
my opinion, would serve as a good model for the implementation of the
libgpiod C++ binding as a header-only library.  What would your
thoughts on this be?

Best Regards,
Jack

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [libgpiod] C++ Bindings ABI Compatibility
  2020-10-23 20:04 [libgpiod] C++ Bindings ABI Compatibility Jack Winch
@ 2020-10-24 13:19 ` Bartosz Golaszewski
  2020-11-03 21:01   ` Jack Winch
  0 siblings, 1 reply; 5+ messages in thread
From: Bartosz Golaszewski @ 2020-10-24 13:19 UTC (permalink / raw)
  To: Jack Winch; +Cc: open list:GPIO SUBSYSTEM, Bartosz Golaszewski, Kent Gibson

On Fri, Oct 23, 2020 at 10:04 PM Jack Winch <sunt.un.morcov@gmail.com> wrote:
>
> Good evening, Bartosz!
>
> Starting another email thread on this subject for future
> searchability, although some previous discussion was held under the
> '[libgpiod] cxx bindings: time_point vs duration' thread.
>
> In that thread, you last said:
>
> > I started reading on my own and I think I now have a slightly better
> > idea about C++ and its ABI. I also see what a mess the original
> > libgpiod bindings are in terms of ABI compatibility but fear not!
> > Right now (v2.0) is the time to make it better! :)
> >
> > At a personal level I'm not too concerned about the ABI compatibility
> > of C++ bindings - I much more care about the API. This is because
> > libgpiod is aimed mostly at bespoke embedded distros (yocto,
> > buildroot, openwrt etc.) I understand however that it's an important
> > issue for distros.
> >
> > I didn't know any better at the time of writing libgpiodcxx so I just
> > put all private members in the main header, exposing them to the users
> > of the library. I'm not sure why I didn't realize that C++ classes are
> > basically C structs (and exposing them amounts to exposing struct in a
> > C header) but I just didn't know any better.
> >
> > I assume that you'll either propose to use the Pimpl pattern or a
> > header-only library. I noticed that Pimpl is what Qt5 uses while
> > header-only is more of a boost thing. If so - the timing is great as
> > I'm open to either solution for libgpiod v2.0.
>
>
> If you did some general reading on the topic, you'll realise just how
> fortunate we are to be talking about Linux here and not Microsoft
> Windows.  It's an absolute minefield.  In fact, Microsoft now
> instructs developers to not provide C++ ABIs and to use a stable
> platform ABI such as C, COM or WinRT.  Many of Microsoft's libraries
> are written in C++, but a C ABI is provided to avoid ABI related
> issues.  And then there is the fact that a process can have multiple
> 'components' loaded using different versions of the Microsoft C
> Runtime (CRT), each possessing different heaps, etc.  It causes a lot
> of pain for a lot of individuals and parties.
>
> You may or may not be surprised how many professional software
> developers / engineers have no clue regarding ABI compatibility.  Most
> of them learn the hard way (which is exactly how it was brought to my
> attention and my what a hard lesson it was).  But none of us are born
> knowing everything.
>

I've had some experience in enterprise C++ a couple years ago (high
availability systems for a big corporation) and I've worked with some
brilliant C++ developers and I believe it. It's due to the fact that
such systems are usually packaged together and rebuilt from scratch.
Add to it a strong "not-invented-here" syndrome and you can basically
disregard any ABI issues.

> Generally speaking, if you have a shared library largely implemented
> in C++ and ABI compatibility is of a concern to you, your options are
> as follows:
>
> 1.  Provide a C ABI to the library, wrapping the C++ internals.
> Resource management should be handled within the library and handles
> or opaque pointers should be used to pass references to library
> managed resources to the using application.  Exceptions and non plain
> old data (POD) type objects should not cross the shared library -
> application boundary.
>
> 2.  Provide a C++ ABI, but implement the PImpl pattern such that it is
> possible to maintain ABI compatibility for the major version releases
> of the library (implementation details may be changed to implement bug
> fixes, etc, which is not possible without using the PImpl pattern).
> Even with this approach, there are limitations as to what should be
> passed over the shared library - application boundary.
>

Could you elaborate on the last sentence? Since my gut is telling me
PImpl would be the right choice, I'd like to know what these
limitations are.

> 3.  Opt to provide a static library instead of a shared library.  The
> shared library should be built with the same compiler and compiler
> version as that to be used for the application, etc, in order to avoid
> issues.  If linking fails here, this is still preferable to
> experiencing loading and linking issues with shared libraries, which
> may only become apparent once the application and library is deployed
> and executed at runtime.
>
> 4.  Provide a header-only library, which is a popular option for many
> C++ libraries these days.  This option is not without its
> disadvantages, but the worry of library interface ABI compatibility
> disappears.  This approach, much like the static library approach,
> allows the full feature set of C++ to be utilised within the library
> interface (e.g., the use of exceptions, etc).
>
> You're right in that Qt makes use of the PImpl pattern.  The framework
> uses return codes for error handling, as exceptions are still
> problematic with this approach, and mostly Qt interface types or POD C
> types are exchanged over the shared library - application boundary.
> Some standard library types are passed across the boundary and this is
> usually where problems arise.
>
> For libgpiod, I would personally recommend going down the header-only
> library approach.  The typical structure of most boost libraries, in
> my opinion, would serve as a good model for the implementation of the
> libgpiod C++ binding as a header-only library.  What would your
> thoughts on this be?
>
> Best Regards,
> Jack

Thanks for taking the time to write this down, very helpful!

As I mentioned above - I'm leaning towards PImpl. The reasons I see
for that are: we don't expose any templates to the user and we don't
need any polymorphism. We also seem to have a rather well defined
scope for the library - I can't imagine huge changes happening after
the v2.0 release.

Header-only approach means every user includes everything and we still
need to recompile every user to update the library even with minor
changes. How do distros handle this anyway? Let's say boost gets a
bugfix - do all reverse dependencies get a bugfix release?

I've been looking at what C++ shared libraries I have installed on my
regular Debian 10 system and then also browsed their code a bit. It
turns out that many of them also put the entire implementation in the
header (libjsoncpp, libmpeg2encpp and several others) and they're
still at relatively low ABI major versions of the shared object - so
I'm wondering if that's really such an issue? Or do so few people
realize this is a problem?

Best regards,
Bartosz Golaszewski

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [libgpiod] C++ Bindings ABI Compatibility
  2020-10-24 13:19 ` Bartosz Golaszewski
@ 2020-11-03 21:01   ` Jack Winch
  2020-11-03 21:06     ` Jack Winch
  0 siblings, 1 reply; 5+ messages in thread
From: Jack Winch @ 2020-11-03 21:01 UTC (permalink / raw)
  To: Bartosz Golaszewski
  Cc: open list:GPIO SUBSYSTEM, Bartosz Golaszewski, Kent Gibson

Sorry for the delayed response.

    I've had some experience in enterprise C++ a couple years ago (high
    availability systems for a big corporation) and I've worked with some
    brilliant C++ developers and I believe it. It's due to the fact that
    such systems are usually packaged together and rebuilt from scratch.
    Add to it a strong "not-invented-here" syndrome and you can basically
    disregard any ABI issues.


That's exactly it.

    > 2.  Provide a C++ ABI, but  implement the PImpl pattern such that it is
    > possible to  maintain ABI compatibility for the major version releases
    > of the library  (implementation details may be changed to implement bug
    > fixes, etc, which  is not possible without using the PImpl pattern).
    > Even with this  approach, there are limitations as to what should be
    > passed over the  shared library - application boundary.
    >
    Could you elaborate on the last sentence? Since my gut is telling me
    PImpl would be the right choice, I'd like to know what these
    limitations are.


Sure.  The expansion of that comment is thus:

In order to reduce ABI compatibility problems in this scenario, do not 
pass anything over the boundary unless you can be certain that the ABI 
of those items are stable and compatible.  This should be taken to mean 
objects where ABI stability is guaranteed by the provider or you have 
control of the ABI stability yourself.  On some platforms and with some 
C++ standard library implementations, the exposure of standard library 
types in the public interface is out of the question, as ABI 
compatibility is not retained between major releases of the standard 
library implementation.  These are the 'pain points' for the Qt 
libraries on certain platforms and with certain compiler collections.

You also have to consider the binary compatibility of how certain 
language features are implemented by the dependency and reverse 
dependency.  For example, the manner in which exceptions are handled may 
differ between the dependency and reverse dependency.  This issue in 
particular is why Qt makes exclusive use of return codes (and not 
exceptions) for error handling.  Of course, the implementation of such 
features is determined by the compiler collection being used to build 
each binary module (as well as any settings applied during the build).

Fortunately, both libc++ and libstdc++ are relatively stable and 
somewhat compatible from an ABI point of view.  Both gcc and clang 
attempt to implement ABI stability and compatibility, with both 
implementing portions of the Itanium C++ ABI.  There are still some 
issues though, but with time these issues are being resolved in later 
releases of the compiler collections.

The detailed status of compatibility between other compiler collections 
for GNU / Linux is beyond me at current, though it would be safe to 
assume that ABI compatibility is not completely guaranteed.

    Thanks for taking the time to write this down, very helpful!


You're very welcome. Obviously this is a very high-level and general 
overview.

    As I mentioned above - I'm leaning towards PImpl. The reasons I see
    for that are: we don't expose any templates to the user and we don't
    need any polymorphism. We also seem to have a rather well defined
    scope for the library - I can't imagine huge changes happening after
    the v2.0 release. 


I agree that the scope of the library is well-defined and relatively 
limited.  As you also point out, templates and polymorphism are not 
utilised in the current form of the library and its public interface.  
Of course, it is possible that future features might come along within 
the gpio subsystem / libgpiod, where making use of these language 
features within the C++ binding might be desired. That being said, it's 
not like we're developing a library of highly generic components.  If 
required, we can make use of Qt as a reference for implementing 
polymorphic inheritance hierarchies and limited [dynamic] strong data 
type variation with the PImpl pattern. So PImpl is a very viable option 
for the C++ binding, should you want to retain it being a shared library.

    Header-only approach means every user includes everything and we still
    need to recompile every user to update the library even with minor
    changes. How do distros handle this anyway? Let's say boost gets a
    bugfix - do all reverse dependencies get a bugfix release?


That's how it usually works.  And that fact means that the reverse 
dependency can be confident as to whether a library bug fix is available 
at runtime, because it is compiled into the reverse dependency.  This 
differs from the shared library approach, as whether or not the bug fix 
is available will depend on which version of the shared library is 
available and which gets loaded and linked at runtime.

With a lot of C++ developers, there is a cultural element around 
steering away from these types of issues if possible (and not for bad 
reason either).  So that is, in my opinion, one of the contributing 
reasons as to why this approach is seen as acceptable / preferred by a 
lot of this community.  If you make a bug fix to the library, you have 
to build the library and deploy it to the target anyway. Personally, I'd 
rather build the reverse dependency with the bug fix 'baked in' and 
deploy that.

Don't forget that if you make an ABI breaking change to the public 
interface of a shared library, you have to do a re-build and 
re-deployment of the library and reverse dependencies anyway.  At least 
if you go down the header-only library route, you remove the pain of 
having to worry about ABI compatibility and stability entirely (while 
being able to make full use of the features offered by the language).

    I've been looking at what C++ shared libraries I have installed on my
    regular Debian 10 system and then also browsed their code a bit. It
    turns out that many of them also put the entire implementation in the
    header (libjsoncpp, libmpeg2encpp and several others) and they're
    still at relatively low ABI major versions of the shared object - so
    I'm wondering if that's really such an issue? Or do so few people
    realize this is a problem?


ABI compatibility for C++ libraries is certainly less of an issue on GNU 
/ Linux (as compared to other platforms), but it is still an issue.  
I've not had experience with either of the packages you've mentioned, so 
it's possible they have not made ABI breaking changes or they just don't 
care (maybe through ignorance).

I guess, like everything, it all comes down to what you're trying to 
achieve and what you really care about.  I've met individuals who didn't 
give a hoot about this particular issue, as the carnage it caused 
downstream "wasn't their problem" and they "still get paid at the end of 
the month".

If you need me to expand on anything above, then let me know.  
Personally, I'm still for a header-only approach, but if you still want 
the C++ binding to be available as a shared library, the PImpl pattern 
would also be well suited in this case.  In either case, implementing 
the required changes should not be too taxing.

On a somewhat related note, I did a build of the libgpiod master branch 
yesterday.  Could you just confirm what the version should be for the 
resultant libgpiodcxx.so?  The built library has a version of 1.1.1.

Jack

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [libgpiod] C++ Bindings ABI Compatibility
  2020-11-03 21:01   ` Jack Winch
@ 2020-11-03 21:06     ` Jack Winch
  2020-11-27 10:14       ` Bartosz Golaszewski
  0 siblings, 1 reply; 5+ messages in thread
From: Jack Winch @ 2020-11-03 21:06 UTC (permalink / raw)
  To: Bartosz Golaszewski
  Cc: open list:GPIO SUBSYSTEM, Bartosz Golaszewski, Kent Gibson

[Re-sent to list with proper formatting]

Sorry for the delayed response.

> I've had some experience in enterprise C++ a couple years ago (high
> availability systems for a big corporation) and I've worked with some
> brilliant C++ developers and I believe it. It's due to the fact that
> such systems are usually packaged together and rebuilt from scratch.
> Add to it a strong "not-invented-here" syndrome and you can basically
> disregard any ABI issues.

That's exactly it.

> > 2.  Provide a C++ ABI, but implement the PImpl pattern such that it is
> > possible to maintain ABI compatibility for the major version releases
> > of the library (implementation details may be changed to implement bug
> > fixes, etc, which is not possible without using the PImpl pattern).
> > Even with this approach, there are limitations as to what should be
> > passed over the shared library - application boundary.
> >
> Could you elaborate on the last sentence? Since my gut is telling me
> PImpl would be the right choice, I'd like to know what these
> limitations are.

Sure.  The expansion of that comment is thus:  In order to reduce and
eradicate ABI compatibility problems in this scenario, do not pass
anything over the boundary unless you can be certain that the ABI of
those items are stable and compatible.  This should be taken to mean
objects where ABI stability is guaranteed by the provider or you have
control of the ABI stability yourself.  On some platforms and with
some standard library implementations, the exposure of standard
library types in the public interface is out of the question, as ABI
compatibility is not retained between major releases.

You also have to consider the binary compatibility of how certain
language features are implemented by the dependency and reverse
dependency.  For example, the manner in which exceptions are handled
may differ between the dependency and reverse dependency.  This issue
in particular is why Qt makes exclusive use of return codes (and not
exceptions) for error handling.  Of course, the implementation of such
features is determined by the compiler collection being used to build
each binary module (as well as any settings applied during the build).

Fortunately, both libc++ and libstdc++ are relatively stable and
somewhat compatible from an ABI point of view.  Both gcc and clang
attempt to implement ABI stability and compatibility, with both
implementing portions of the Itanium C++ ABI.  There are still issues
though, but with time these issues are being resolved.  The detailed
status of compatibility between other compiler collections for GNU /
Linux is beyond me at current, though it would be safe to assume that
ABI compatibility is not completely guaranteed.

> Thanks for taking the time to write this down, very helpful!

You're very welcome.  Obviously this is a very high-level and general
overview, with more to be said on each approach.

> As I mentioned above - I'm leaning towards PImpl. The reasons I see
> for that are: we don't expose any templates to the user and we don't
> need any polymorphism. We also seem to have a rather well defined
> scope for the library - I can't imagine huge changes happening after
> the v2.0 release.

I agree that the scope of the library is well-defined and relatively
limited.  As you also point out, templates and polymorphism are not
utilised in the current form of the library and its public interface.
Of course, it is possible that future features might come along within
the gpio subsystem / libgpiod, where making use of these language
features within the C++ binding might be desired.  That being said,
it's not like we're developing a library of highly generic components.
If required, we can make use of Qt as a reference for implementing
polymorphic inheritance hierarchies and limited [dynamic] strong data
type variation with the PImpl pattern.  So PImpl is a very viable
option for the C++ binding, should you want to retain it being a
shared library.

> Header-only approach means every user includes everything and we still
> need to recompile every user to update the library even with minor
> changes. How do distros handle this anyway? Let's say boost gets a
> bugfix - do all reverse dependencies get a bugfix release?

That's how it usually works.  And that fact means that the reverse
dependency can be confident as to whether a library bug fix is
available at runtime, because it is compiled into the reverse
dependency.  This differs from the shared library approach, as whether
or not the bug fix is available will depend on which version of the
shared library is available and which gets loaded and linked at
runtime.

With a lot of C++ developers, there is a cultural element around
steering away from these types of issues if possible (and not for bad
reason either).  So that is, in my opinion, one of the contributing
reasons as to why this approach is seen as acceptable / preferred by a
lot of this community.  If you make a bug fix to the library, you have
to build the library and deploy it to the target anyway.  Personally,
I'd rather build the reverse dependency with the bug fix 'baked in'
and deploy that.

Don't forget that if you make an ABI breaking change to the public
interface of the shared library, you have to do a re-build and
re-deployment of the library and reverse dependencies anyway.

> I've been looking at what C++ shared libraries I have installed on my
> regular Debian 10 system and then also browsed their code a bit. It
> turns out that many of them also put the entire implementation in the
> header (libjsoncpp, libmpeg2encpp and several others) and they're
> still at relatively low ABI major versions of the shared object - so
> I'm wondering if that's really such an issue? Or do so few people
> realize this is a problem?

ABI compatibility for C++ libraries is certainly less of an issue on
GNU / Linux (as compared to other platforms), but it is still an
issue.  I've not had experience with either of the packages you've
mentioned, so it's possible they have not made ABI breaking changes or
they just don't care (maybe through ignorance).

I guess, like everything, it all comes down to what you're trying to
achieve and what you really care about.  I've met individuals who
didn't give a hoot about this particular issue, as the carnage it
caused "wasn't their problem" and they still got paid at the end of
the month.

If you need me to expand on anything above, then let me know.
Personally, I'm still for a header-only approach (eradicate the ABI
concern entirely), but if you still want the C++ binding to be
available as a shared library, the PImpl pattern would also be well
suited in this case.  In either case, implementing the required
changes should not be taxing.

On a somewhat related note, I did a build of the libgpiod master
branch yesterday.  Could you just confirm what the version should be
for the resultant libgpiodcxx.so?  The built library has a version of
1.1.1.

Jack


On Tue, Nov 3, 2020 at 8:57 PM Jack Winch <sunt.un.morcov@gmail.com> wrote:
>
> Sorry for the delayed response.
>
>     I've had some experience in enterprise C++ a couple years ago (high
>     availability systems for a big corporation) and I've worked with some
>     brilliant C++ developers and I believe it. It's due to the fact that
>     such systems are usually packaged together and rebuilt from scratch.
>     Add to it a strong "not-invented-here" syndrome and you can basically
>     disregard any ABI issues.
>
>
> That's exactly it.
>
>     > 2.  Provide a C++ ABI, but  implement the PImpl pattern such that it is
>     > possible to  maintain ABI compatibility for the major version releases
>     > of the library  (implementation details may be changed to implement bug
>     > fixes, etc, which  is not possible without using the PImpl pattern).
>     > Even with this  approach, there are limitations as to what should be
>     > passed over the  shared library - application boundary.
>     >
>     Could you elaborate on the last sentence? Since my gut is telling me
>     PImpl would be the right choice, I'd like to know what these
>     limitations are.
>
>
> Sure.  The expansion of that comment is thus:
>
> In order to reduce ABI compatibility problems in this scenario, do not
> pass anything over the boundary unless you can be certain that the ABI
> of those items are stable and compatible.  This should be taken to mean
> objects where ABI stability is guaranteed by the provider or you have
> control of the ABI stability yourself.  On some platforms and with some
> C++ standard library implementations, the exposure of standard library
> types in the public interface is out of the question, as ABI
> compatibility is not retained between major releases of the standard
> library implementation.  These are the 'pain points' for the Qt
> libraries on certain platforms and with certain compiler collections.
>
> You also have to consider the binary compatibility of how certain
> language features are implemented by the dependency and reverse
> dependency.  For example, the manner in which exceptions are handled may
> differ between the dependency and reverse dependency.  This issue in
> particular is why Qt makes exclusive use of return codes (and not
> exceptions) for error handling.  Of course, the implementation of such
> features is determined by the compiler collection being used to build
> each binary module (as well as any settings applied during the build).
>
> Fortunately, both libc++ and libstdc++ are relatively stable and
> somewhat compatible from an ABI point of view.  Both gcc and clang
> attempt to implement ABI stability and compatibility, with both
> implementing portions of the Itanium C++ ABI.  There are still some
> issues though, but with time these issues are being resolved in later
> releases of the compiler collections.
>
> The detailed status of compatibility between other compiler collections
> for GNU / Linux is beyond me at current, though it would be safe to
> assume that ABI compatibility is not completely guaranteed.
>
>     Thanks for taking the time to write this down, very helpful!
>
>
> You're very welcome. Obviously this is a very high-level and general
> overview.
>
>     As I mentioned above - I'm leaning towards PImpl. The reasons I see
>     for that are: we don't expose any templates to the user and we don't
>     need any polymorphism. We also seem to have a rather well defined
>     scope for the library - I can't imagine huge changes happening after
>     the v2.0 release.
>
>
> I agree that the scope of the library is well-defined and relatively
> limited.  As you also point out, templates and polymorphism are not
> utilised in the current form of the library and its public interface.
> Of course, it is possible that future features might come along within
> the gpio subsystem / libgpiod, where making use of these language
> features within the C++ binding might be desired. That being said, it's
> not like we're developing a library of highly generic components.  If
> required, we can make use of Qt as a reference for implementing
> polymorphic inheritance hierarchies and limited [dynamic] strong data
> type variation with the PImpl pattern. So PImpl is a very viable option
> for the C++ binding, should you want to retain it being a shared library.
>
>     Header-only approach means every user includes everything and we still
>     need to recompile every user to update the library even with minor
>     changes. How do distros handle this anyway? Let's say boost gets a
>     bugfix - do all reverse dependencies get a bugfix release?
>
>
> That's how it usually works.  And that fact means that the reverse
> dependency can be confident as to whether a library bug fix is available
> at runtime, because it is compiled into the reverse dependency.  This
> differs from the shared library approach, as whether or not the bug fix
> is available will depend on which version of the shared library is
> available and which gets loaded and linked at runtime.
>
> With a lot of C++ developers, there is a cultural element around
> steering away from these types of issues if possible (and not for bad
> reason either).  So that is, in my opinion, one of the contributing
> reasons as to why this approach is seen as acceptable / preferred by a
> lot of this community.  If you make a bug fix to the library, you have
> to build the library and deploy it to the target anyway. Personally, I'd
> rather build the reverse dependency with the bug fix 'baked in' and
> deploy that.
>
> Don't forget that if you make an ABI breaking change to the public
> interface of a shared library, you have to do a re-build and
> re-deployment of the library and reverse dependencies anyway.  At least
> if you go down the header-only library route, you remove the pain of
> having to worry about ABI compatibility and stability entirely (while
> being able to make full use of the features offered by the language).
>
>     I've been looking at what C++ shared libraries I have installed on my
>     regular Debian 10 system and then also browsed their code a bit. It
>     turns out that many of them also put the entire implementation in the
>     header (libjsoncpp, libmpeg2encpp and several others) and they're
>     still at relatively low ABI major versions of the shared object - so
>     I'm wondering if that's really such an issue? Or do so few people
>     realize this is a problem?
>
>
> ABI compatibility for C++ libraries is certainly less of an issue on GNU
> / Linux (as compared to other platforms), but it is still an issue.
> I've not had experience with either of the packages you've mentioned, so
> it's possible they have not made ABI breaking changes or they just don't
> care (maybe through ignorance).
>
> I guess, like everything, it all comes down to what you're trying to
> achieve and what you really care about.  I've met individuals who didn't
> give a hoot about this particular issue, as the carnage it caused
> downstream "wasn't their problem" and they "still get paid at the end of
> the month".
>
> If you need me to expand on anything above, then let me know.
> Personally, I'm still for a header-only approach, but if you still want
> the C++ binding to be available as a shared library, the PImpl pattern
> would also be well suited in this case.  In either case, implementing
> the required changes should not be too taxing.
>
> On a somewhat related note, I did a build of the libgpiod master branch
> yesterday.  Could you just confirm what the version should be for the
> resultant libgpiodcxx.so?  The built library has a version of 1.1.1.
>
> Jack

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [libgpiod] C++ Bindings ABI Compatibility
  2020-11-03 21:06     ` Jack Winch
@ 2020-11-27 10:14       ` Bartosz Golaszewski
  0 siblings, 0 replies; 5+ messages in thread
From: Bartosz Golaszewski @ 2020-11-27 10:14 UTC (permalink / raw)
  To: Jack Winch; +Cc: open list:GPIO SUBSYSTEM, Bartosz Golaszewski, Kent Gibson

Hi Jack!

Sorry for the late reply.

On Tue, Nov 3, 2020 at 10:02 PM Jack Winch <sunt.un.morcov@gmail.com> wrote:
>
> [Re-sent to list with proper formatting]
>
> Sorry for the delayed response.
>
> > I've had some experience in enterprise C++ a couple years ago (high
> > availability systems for a big corporation) and I've worked with some
> > brilliant C++ developers and I believe it. It's due to the fact that
> > such systems are usually packaged together and rebuilt from scratch.
> > Add to it a strong "not-invented-here" syndrome and you can basically
> > disregard any ABI issues.
>
> That's exactly it.
>
> > > 2.  Provide a C++ ABI, but implement the PImpl pattern such that it is
> > > possible to maintain ABI compatibility for the major version releases
> > > of the library (implementation details may be changed to implement bug
> > > fixes, etc, which is not possible without using the PImpl pattern).
> > > Even with this approach, there are limitations as to what should be
> > > passed over the shared library - application boundary.
> > >
> > Could you elaborate on the last sentence? Since my gut is telling me
> > PImpl would be the right choice, I'd like to know what these
> > limitations are.
>
> Sure.  The expansion of that comment is thus:  In order to reduce and
> eradicate ABI compatibility problems in this scenario, do not pass
> anything over the boundary unless you can be certain that the ABI of
> those items are stable and compatible.  This should be taken to mean
> objects where ABI stability is guaranteed by the provider or you have
> control of the ABI stability yourself.  On some platforms and with
> some standard library implementations, the exposure of standard
> library types in the public interface is out of the question, as ABI
> compatibility is not retained between major releases.
>

But ABI is by definition not retained across major releases, right? At
least this is what is implied when you reset the age component of the
libtool ABI version.

> You also have to consider the binary compatibility of how certain
> language features are implemented by the dependency and reverse
> dependency.  For example, the manner in which exceptions are handled
> may differ between the dependency and reverse dependency.  This issue
> in particular is why Qt makes exclusive use of return codes (and not
> exceptions) for error handling.  Of course, the implementation of such
> features is determined by the compiler collection being used to build
> each binary module (as well as any settings applied during the build).
>
> Fortunately, both libc++ and libstdc++ are relatively stable and
> somewhat compatible from an ABI point of view.  Both gcc and clang
> attempt to implement ABI stability and compatibility, with both
> implementing portions of the Itanium C++ ABI.  There are still issues
> though, but with time these issues are being resolved.  The detailed
> status of compatibility between other compiler collections for GNU /
> Linux is beyond me at current, though it would be safe to assume that
> ABI compatibility is not completely guaranteed.
>
> > Thanks for taking the time to write this down, very helpful!
>
> You're very welcome.  Obviously this is a very high-level and general
> overview, with more to be said on each approach.
>
> > As I mentioned above - I'm leaning towards PImpl. The reasons I see
> > for that are: we don't expose any templates to the user and we don't
> > need any polymorphism. We also seem to have a rather well defined
> > scope for the library - I can't imagine huge changes happening after
> > the v2.0 release.
>
> I agree that the scope of the library is well-defined and relatively
> limited.  As you also point out, templates and polymorphism are not
> utilised in the current form of the library and its public interface.
> Of course, it is possible that future features might come along within
> the gpio subsystem / libgpiod, where making use of these language
> features within the C++ binding might be desired.  That being said,
> it's not like we're developing a library of highly generic components.
> If required, we can make use of Qt as a reference for implementing
> polymorphic inheritance hierarchies and limited [dynamic] strong data
> type variation with the PImpl pattern.  So PImpl is a very viable
> option for the C++ binding, should you want to retain it being a
> shared library.
>

Yes. With your answer my thinking is this: the C++ ABI in linux
distros seems to be "stable enough" for us to rely on binary
compatibility not changing within major distro releases (for example:
Debian 10 can break the compatibility against Debian 9 but all
packages should be rebuilt anyway, on the other hand there's no risk
of it changing within Debian 9 itself). This makes me think that PImpl
is the way to go in the end as I'd prefer to have the implementation
inside a shared library rather than in headers.

For yocto or buildroot systems ABI isn't a concern at all.

> > Header-only approach means every user includes everything and we still
> > need to recompile every user to update the library even with minor
> > changes. How do distros handle this anyway? Let's say boost gets a
> > bugfix - do all reverse dependencies get a bugfix release?
>
> That's how it usually works.  And that fact means that the reverse
> dependency can be confident as to whether a library bug fix is
> available at runtime, because it is compiled into the reverse
> dependency.  This differs from the shared library approach, as whether
> or not the bug fix is available will depend on which version of the
> shared library is available and which gets loaded and linked at
> runtime.
>
> With a lot of C++ developers, there is a cultural element around
> steering away from these types of issues if possible (and not for bad
> reason either).  So that is, in my opinion, one of the contributing
> reasons as to why this approach is seen as acceptable / preferred by a
> lot of this community.  If you make a bug fix to the library, you have
> to build the library and deploy it to the target anyway.  Personally,
> I'd rather build the reverse dependency with the bug fix 'baked in'
> and deploy that.
>
> Don't forget that if you make an ABI breaking change to the public
> interface of the shared library, you have to do a re-build and
> re-deployment of the library and reverse dependencies anyway.
>

Indeed but I intend to not break the ABI across all v2.x releases.

> > I've been looking at what C++ shared libraries I have installed on my
> > regular Debian 10 system and then also browsed their code a bit. It
> > turns out that many of them also put the entire implementation in the
> > header (libjsoncpp, libmpeg2encpp and several others) and they're
> > still at relatively low ABI major versions of the shared object - so
> > I'm wondering if that's really such an issue? Or do so few people
> > realize this is a problem?
>
> ABI compatibility for C++ libraries is certainly less of an issue on
> GNU / Linux (as compared to other platforms), but it is still an
> issue.  I've not had experience with either of the packages you've
> mentioned, so it's possible they have not made ABI breaking changes or
> they just don't care (maybe through ignorance).
>
> I guess, like everything, it all comes down to what you're trying to
> achieve and what you really care about.  I've met individuals who
> didn't give a hoot about this particular issue, as the carnage it
> caused "wasn't their problem" and they still got paid at the end of
> the month.
>
> If you need me to expand on anything above, then let me know.
> Personally, I'm still for a header-only approach (eradicate the ABI
> concern entirely), but if you still want the C++ binding to be
> available as a shared library, the PImpl pattern would also be well
> suited in this case.  In either case, implementing the required
> changes should not be taxing.
>
> On a somewhat related note, I did a build of the libgpiod master
> branch yesterday.  Could you just confirm what the version should be
> for the resultant libgpiodcxx.so?  The built library has a version of
> 1.1.1.
>

Thanks again Jack!

On a completely different note: For the initial implementation of C++
bindings I chose the C++11 standard because it's well established and
supported by g++. Now it's almost 2021 and C++17 seems to be
wide-spread enough to use it.

For the new version I was thinking about switching to C++17
specifically because we'll be dropping the chip iterators in C and
instead providing the user with a function that allows to check if
given path points to a GPIO character device. This is because with the
current implementation: if the user can't access every GPIO chip, the
iterator will throw an error. It may be better to just iterate over
paths to chips and let the user handle the opening and error handling.

This is where the std::filesystem comes into play. It's only available
in C++17 but it would be quite useful for iterating over directory
entries in /dev. The question is: is there any risk related to
switching to the latest standard?

Bartosz

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2020-11-27 10:14 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-10-23 20:04 [libgpiod] C++ Bindings ABI Compatibility Jack Winch
2020-10-24 13:19 ` Bartosz Golaszewski
2020-11-03 21:01   ` Jack Winch
2020-11-03 21:06     ` Jack Winch
2020-11-27 10:14       ` Bartosz Golaszewski

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.