Pages

Tuesday, May 1, 2018

Shared Libraries - Example #1

Let's try a first example of a fictitious dynamic application (one that links to dynamic objects ‒ a shared library in this case). The key aspects I wish to address on this post are how to build these artifacts under Solaris 11.3 using GCC 4.8.2 and properly adhering to the versioning naming convention. I would like to emphasize that in the name of straightforward software engineering there will be no excessively elaborated interfaces strategies, for instance by using crazy things such as GUIDs (or so) or any other related / similar interface infrastructure (if you like GUIDs you may perhaps have some fun playing with a GUID generator). My strategy is pure C++ resulting in a much more powerful, efficient and higher-quality artifact than some sort of software crazy and buggy Frankenstein monster such as COM and related. I'll be using C++ namespaces and a main C++ class on each library module.

The software engineering implementation/deployment depicted on this example consists (for the sake of simplicity) on a single (do-nothing) shared library and a (stub) dynamic application linking to it just for testing and demonstration. I'll build 2 major versions of the library (libgreeting.so.1 and libgreeting.so.2) the first of them with some different minor versions and releases (for demonstration purposes). For each major version of the library I'll show a backward compatibility tehcnique for avoiding or minimizing the rebuild of the application (myapp-1). Accompanying each new major version of the library there will be (for the sake of simplicity) a new / updated header-file which as I said will provide for backward compatibility with previous major version(s). The header-files shouldn't be modified within minor versions and releases as this would be a clear deviation of whole strategy. For demonstration purposes, the implementation of each interface will consist on multiple source-code files. Besides the interface mechanism the library will be able to "report" its hard-coded versioning information (as a kind of reflection) which will always correspond to the name of the shared library version final binary object, although (again for the sake of simplicity) there will be no provisions to enforce this correspondence.

I can only hope that the software engineering aspects I'll be exploring side-by-side do not hinder the main goals of this post about building and dealing with shared libraries!

So let me start by the implementation of version reflection.

$ cat lib-version.h

 1 #if !defined( ACME_LIB_VERSION_H )
 2 #define ACME_LIB_VERSION_H
 3 
 4 namespace ACME
 5 {
 6 
 7 namespace Library // {{{1
 8 {
 9 
10 struct Version // {{{2
11 {
12   Version( unsigned major, unsigned minor, unsigned release ) :
13     major( major ), minor( minor ), release( release )
14   {
15   }
16 
17   const unsigned major;
18   const unsigned minor;
19   const unsigned release;
20 };
21 // }}}2
22 
23 }
24 // namespace Library }}}1
25 
26 }
27 // namespace ACME
28 
29 #endif // ACME_LIB_VERSION_H
30 

NOTE
Unfortunately I still don't get a C++17 compiler in order to declare all these namespaces in a more compact way such as:

namespace ACME::Library::Greeting {}
$ cat lib-greeting.h

 1 #if !defined( ACME_LIB_GREETING_H )
 2 #define ACME_LIB_GREETING_H
 3 
 4 // headers {{{1
 5 
 6 // standard headers {{{2
 7 #include <string>
 8 // }}}2
 9 
10 // custom headers {{{2
11 #include "lib-version.h"
12 // }}}2
13 
14 // }}}1
15 
16 namespace ACME
17 {
18 
19 namespace Library
20 {
21 
22 namespace Greeting // {{{1
23 {
24 
25 extern Version version;
26 
27 struct Main // {{{2
28 {
29    std::string hello() const;
30 };
31 // }}}2
32 
33 }
34 // namespace Greeting }}}1
35 
36 }
37 // namespace Library
38 
39 }
40 // namespace ACME  
41 
42 #endif // ACME_LIB_GREETING_H
43 

$ cat lib-greeting-main.cpp

 1 // headers {{{1
 2 
 3 // standard headers {{{2
 4 // }}}2
 5 
 6 // custom headers {{{2
 7 #include "lib-greeting.h"
 8 // }}}2
 9 
10 // }}}1
11 
12 namespace ACME
13 {
14 
15 namespace Library
16 {
17 
18 namespace Greeting // {{{1
19 {
20 
21 // externals {{{2
22 Version version( 1, 0, 0 );
23 // }}}2
24 
25 }
26 // namespace Greeting }}}1
27 
28 }
29 // namespace Library
30 
31 }
32 // namespace ACME
33 

$ cat lib-greeting-hello.cpp

 1 // headers {{{1
 2 
 3 // standard headers {{{2
 4 #include <sstream>
 5 // }}}2
 6 
 7 // custom headers {{{2
 8 #include "lib-greeting.h"
 9 // }}}2
10 
11 // }}}1
12 
13 namespace ACME
14 {
15 
16 namespace Library
17 {
18 
19 namespace Greeting // {{{1
20 {
21 
22 std::string Main::hello() const // {{{2
23 {
24   std::ostringstream oss;
25 
26   oss 
27     << "Hello"
28     << " (" 
29     << version.major << "." 
30     << version.minor << "." 
31     << version.release 
32     << ")."
33     << std::ends;
34 
35   return oss.str();
36 }
37 // }}}2
38 
39 }
40 // namespace Greeting }}}1
41 
42 }
43 // namespace Library
44 
45 }
46 // namespace ACME
47 

The above source-code is the version 1.0.0 of the Greeting shared library. One way of building this code into a shared library using GCC 4.8.2 under Solaris 11.3 is:

$ cd ~/project/greeting/1.0.0

$ g++ \
  -m64 -std=c++11 -march=core2 -mtune=core2 -fPIC \
  -shared -Wl,-soname=libgreeting.so.1 -o libgreeting.so.1.0.0 \
  lib-greeting-main.cpp \
  lib-greeting-hello.cpp

NOTE
I use core2 just because it makes sense to my particular machine.
$ file libgreeting.so.1.0.0
...: ELF 64-bit LSB dynamic lib AMD64 ..., dynamically linked, ...

$ ldd libgreeting.so.1.0.0  
  libstdc++.so.6 => /usr/lib/64/libstdc++.so.6
  libm.so.2 => /lib/64/libm.so.2
  libgcc_s.so.1 => /usr/lib/64/libgcc_s.so.1
  libc.so.1 => /lib/64/libc.so.1


NOTE
The further dynamic linking above (in red) seems natural and is harmless and probably the safest approach. But fact is the application executable process must also link to them as well, so they seem to be an unnecessary stuff duplication.

By looking at GCC(1) man page one may consider using -nostdlib to remove those linkings as well as startup code. By experimentation, at least on this example, I've verified that -nostdlib has a bad side-effect on global data defined on the shared library. In particular, my shared library defines an ACME::Library::Greeting::version object which wasn't being correctly resolved in the myapp-1 executable. Hence I've discarded this option.

Next I've tried with -nodefaultlibs which stripes out the presumed unnecessary linkings but keeps startup code. Hopefully that fixed the global data resolution issues and everything seemed to have worked fine, though I can't guarantee no other hidden issue may remain, such as some exception handling issue or so. But anyway it never seems good practice to let exceptions propagate across modules, isn't it?

When striping libraries, the man page warns about the possibility of introducing undesired side-effects such as to the resolution of memcmp, memset and memcpy which otherwise would be usually resolved by entries in libc. But so far I believe one may successfully use the -nodefaultlibs option though with care. As I said, on the particular example I present on this post I had no issues in spite of being a virtually do-nothing application.
 
Nevertheless, in order to inspect this possible issue a little further I have intentionally inserted the following probing code at / near ACME::Library::Greeting::Main::hello:34:
 
 char buffer[ 128 ];
 ::memset( buffer, '\0', sizeof( buffer ) );
 ::sprintf( buffer, "Oops!\n" );
 ::printf( "%s", buffer );


If I compile the library with the -g (debugging) option  I'll certainly see (under GDB for instance) the explicitly call to memset, although with absolutely no linking issues; that is, it works perfectly.
 
If on the other hand I compile the library with the -O2 (optimize even more) option  I'll be amazingly pleased to see that the call to memset has been successfully inlined by a rep stos of 16 (ecx == 0x10) zeroed 8-bytes (eax == 0) cycles as follows:
 
0x...df0 <+544>: lea    -0x210(%rbp),%rsi
0x...df7 <+551>: mov    $0x10,%ecx
0x...dfc <+556>: xor    %eax,%eax
0x...dfe <+558>: mov    %rsi,%rdi
0x...e01 <+561>: rep stos %rax,%es:(%rdi)
0x...e04 <+564>: mov    $0xa21,%edi
0x...e09 <+569>: movl   $0x73706f4f,-0x210(%rbp)
0x...e13 <+579>: mov    %di,-0x20c(%rbp)
0x...e1a <+586>: lea    -0x40d(%rip),%rdi  # 0x...a14
0x...e21 <+593>: callq  0x...a90 
<printf@plt>
An alternative way of building the library is to perform separate steps for compilation and linking as follows:

$ g++ \
  -m64 -std=c++11 -march=core2 -mtune=core2 -fPIC \
  -c lib-greeting-main.cpp

$ g++ \

  -m64 -std=c++11 -march=core2 -mtune=core2 -fPIC \
  -c lib-greeting-hello.cpp

NOTE
Of course, the compilation can combined into a single command:
(which will still generate separated object-files for each source-file)
 
$ g++ \
  -m64 -std=c++11 -march=core2 -mtune=core2 -fPIC -c \
  lib-greeting-main.cpp \
  lib-greeting-hello.cpp

 
A lib-greeting-*.cpp in the tail of the above would also work.
$ g++ \
  -m64 \
  -nodefaultlibs -shared \
  -Wl,-soname=libgreeting.so.1 -o libgreeting.so.1.0.0 \
  lib-greeting-main.o \
  lib-greeting-hello.o

Just for curiosity, a variation of the above command would be:

$ MACH=$(g++ -dumpmachine) # same as $MACHTYPE

$ /usr/bin/ld \
  -m64 \
  -shared \
  -Qy -soname=libgreeting.so.1 -o libgreeting.so.1.0.0 \
  /usr/lib/amd64/crti.o \
  /usr/gcc/4.8/lib/gcc/$MACH/4.8.2/amd64/crtbegin.o
\

  lib-greeting-main.o \
  lib-greeting-hello.o \
  /usr/gcc/4.8/lib/gcc/$MACH/4.8.2/amd64/crtend.o \
  /usr/lib/amd64/crtn.o


NOTE
By default GCC linking (collect2) uses the Solaris linker /usr/bin/ld. Be aware there's also an /usr/gnu/bin/ld which's another story. If one decides to use the GNU linker instead then one shall do similarly to the when explicitly invoking the default Solaris linker:
  
$ /usr/gnu/bin/ld \
  -m elf_x86_64_sol2
  -shared \
  -soname=libgreeting.so.1 -o libgreeting.so.1.0.0 \

  /usr/lib/amd64/crti.o \
  /usr/gcc/4.8/lib/gcc/$MACH/4.8.2/amd64/crtbegin.o
\

  lib-greeting-main.o \
  lib-greeting-hello.o \

  /usr/gcc/4.8/lib/gcc/$MACH/4.8.2/amd64/crtend.o \
  /usr/lib/amd64/crtn.o
 
 
Note that by using the GNU linker on the shared library will require that the executable be also linked with the GNU linker.
An important versioning procedure which should immediately follow the build of the shared library is adjusting  or creating the associated symbolic links. For the very first time, when no other versions yet exist, it suffices to create them:

$ cd ~/project/myapp/1/lib
$ cp ~/project/greeting/1.0.0/libgreeting.so.1.0.0 .

$ ln -s libgreeting.so.1.0.0 libgreeting.so.1
$ ln -s libgreeting.so.1 libgreeting.so

The application source-code is as follows:
 
$ cat myapp-main.cpp

 1 // headers {{{1
 2 
 3 // standard headers {{{2
 4 #include <cstdlib>
 5 #include <iostream>
 6 // }}}2
 7 
 8 // custom headers {{{2
 9 #include "lib-greeting.h"
10 // }}}2
11 
12 // }}}1
13 
14 // {{{1
15 
16 int main() // {{{2
17 {
18   using namespace ACME::Library;
19 
20   using Greeting::version;
21 
22   std::cout << std::endl;
23   std::cout 
24     << "MyApp - Greeting version: " 
25     << version.major << "." 
26     << version.minor << "." 
27     << version.release 
28     << std::endl;
29 
30   Greeting::Main greeting;
31 
32   std::cout << std::endl;
33   std::cout << greeting.hello() << std::endl;
34 
35   return EXIT_SUCCESS;
36 }
37 // }}}2
38 
39 // }}}1
40 

Assuming that the dynamic library will be deployed no a well-known relative path from the binary executable (for instance on a subdirectory such as lib), one way of building the dynamic application using GCC 4.8.2 under Solaris 11.3 is:

$ cd ~/project/myapp/1

$ g++ \
  -m64 -std=c++11 -march=core2 -mtune=core2 \
  -L./lib -R./lib -lgreeting \
  -o myapp-1 \
  myapp-main.cpp

$ file myapp-1
...: ELF 64-bit LSB executable AMD64 ..., dynamically linked, ...


$ ldd myapp-1
    libgreeting.so.1 => ./lib/libgreeting.so.1
    libstdc++.so.6   => /usr/lib/64/libstdc++.so.6
    libm.so.2        => /lib/64/libm.so.2
    libgcc_s.so.1    => /usr/lib/64/libgcc_s.so.1
    libc.so.1        => /lib/64/libc.so.1


NOTE
Note that I avoid LD_LIBRARY_PATH by using the -R option. But the problem is that I'm using a relative path, which is not recommended when using the -R option. Nevertheless, I'll go on like this just for the sake of simplicity of this post and assume I'll always make the executable directory the current directory before running the application.
NOTE
If the shared libraries remains on the same directory as the executable this shall work, of course, but it may cause some pitfalls related to the runtime-linker dynamic object search resolution if the libraries are moved to other place. I shall discuss other options for this requirements and considering the runtime-linker configuration on another post.
So far, running myapp-1 should produce:

$ ./myapp-1

MyApp - Greeting version: 1.0.0

Hello (1.0.0).


NOTE
If perhaps some different segmented building strategy is needed, specially if the executable source-code is split in many files (not the case of this particular example), the final step could be adjusted as follows:
 
$ g++ \
  -m64 -std=c++11 -march=core2 -mtune=core2 \
  -c myapp-main.cpp

  
$ g++ \
  -m 64 \
  -L./lib -R./lib -lgreeting \
  -o myapp-1 \
  myapp-main.o 
  
 
Case the executable had multiple object-files it would be just a matter of listing them in sequence right after myapp-main.o.
NOTE
Again if one decides to use the GNU linker (/usr/gnu/bni/ld) instead of the Solaris linker (/usr/bin/ld) then one will have considerably more work to do on the final steps as follows:
 
$ g++ \
  -m64 -std=c++11 -march=core2 -mtune=core2 \
  -c myapp-main.cpp


The following should yield the same as $MACHTYPE:  

$ MACH=$(g++ -dumpmachine)
 
On what follows the order seems important:

$ /usr/gnu/bin/ld \
  -m elf_x86_64_sol2 \
  -o myapp-1 \

  /usr/lib/amd64/crt1.o \
  /usr/lib/amd64/crti.o \
  /usr/lib/amd64/values-Xc.o \
  /usr/lib/amd64/values-xpg6.o \
  /usr/gcc/4.8/lib/gcc/$MACH/4.8.2/amd64/crtbegin.o
\
  myapp-main.o \

  -L./lib -R./lib \
  -lgreeting

 
-L/lib/amd64:/usr/lib/amd64 \
 
-lstdc++ -lm -lgcc_s -lc \
  /usr/gcc/4.8/lib/gcc/$MACH/4.8.2/amd64/crtend.o \
  /usr/lib/amd64/crtn.o
 
  
 
All this extra work of specifying libraries and startup/exit-codes would be automatically taken care of by the g++ front-end, except that it would invoke the Solaris linker because it was configured as so out-of-the-box.

Now suppose time has gone by and the major version 1 of the Greeting library will get some implementation change or fix. As the scope of change is strictly internal it will just get a new minor version and release. Had the change or fix altered the the interface (lib-greeting.h) then it should get a new major version. For the sake of this example I'll (arbitrarily) choose the new version as 1.2.3.

Line 22 of lib-greeting-main.cpp will become:

...
17 
18 namespace Greeting // {{{1
19 {
20 
21 // externals {{{2
22 Version version( 1, 2, 3 );
23 // }}}2
24 
25 }
26 // namespace Greeting }}}1
27 
... 

Line 27 of lib-greeting-hello.cpp and will become:

...
21 
22 std::string Main::hello() const // {{{2
23 {
24   std::ostringstream oss;
25 
26   oss 
27     << "Hello, world!"
28     << " (" 
29     << version.major << "." 
30     << version.minor << "." 
31     << version.release 
32     << ")."
33     << std::ends;
34 
35   return oss.str();
36 }
37 // }}}2
38 
... 

I've chosen to rebuild the changes as follows:

$ cd ~/project/greeting/1.2.3

$ g++ \
  -m64 -std=c++11 -march=core2 -mtune=core2 -fPIC -nodefaultlibs \
  -shared -Wl,-soname=libgreeting.so.1 -o libgreeting.so.1.2.3 \
  lib-greeting-main.cpp \
  lib-greeting-hello.cpp

Next I copy the new library libgreeting.so.1.2.3 next to the previous version, say libgreeting.so.1.0.0 and just update the libgreeting.so.1 symbolic-link as follows:

$ cd ~/project/myapp/1/lib
$ cp ~/project/greeting/1.2.3/libgreeting.so.1.2.3 .
 
$ rm libgreeting.so.1
$ ln -s libgreeting.so.1.2.3 libgreeting.so.1

Hence, in ~/project/myapp/v1/lib, where I previously had:

$ l libgreeting.so*
lrwxrwxrwx 1 ... libgreeting.so -> libgreeting.so.1
lrwxrwxrwx 1 ...
libgreeting.so.1 -> libgreeting.so.1.0.0
-rwxr-xr-x 1 ...
libgreeting.so.1.0.0


Now I have:

$ l libgreeting.so*
lrwxrwxrwx 1 ... libgreeting.so -> libgreeting.so.1
lrwxrwxrwx 1 ...
libgreeting.so.1 -> libgreeting.so.1.2.3
-rwxr-xr-x 1 ...
libgreeting.so.1.0.0
-rwxr-xr-x 1 ... libgreeting.so.1.2.3


The application remains untouched, because it's dynamically pointing to major version 1 of the Greeting library which remains at 1 meaning that whatever new minor version or release the libgreeting.so.1 symbolic-link points it won't break the application (the library interface hasn't changed).

$ cd ~/project/myapp/1

$ ldd myapp-1
    libgreeting.so.1 => ./lib/libgreeting.so.1
    libstdc++.so.6   => /usr/lib/64/libstdc++.so.6
    libm.so.2        => /lib/64/libm.so.2
    libgcc_s.so.1    => /usr/lib/64/libgcc_s.so.1
    libc.so.1        => /lib/64/libc.so.1


Running the unchanged application how results in:

$ ./myapp-1

MyApp - Greeting version: 1.2.3

Hello, world! (1
.2.3).

So far this all seems great! I was able to build a new minor version and release of the shared library which was seemless used by the application which wasn't rebuild. This is extremelly powerful and simple. There's no "DLL hell".

Now, the last part of this post is the advent of major version 2 of the Greetings shared library. The new major version number is justified because the library publishes changes (enhancements and / or new features) through its public interfaces (lib-greeting.h).

As a good practice, although not required, the new public interface will remain backward compatible which means that the application doesn't need to be changed, not even recompiled, but just relinked in order to point to the new major version of the library. Case backward compatibility wasn't a requirement, then the application would probably have to be reengineered and hence recompiled as well.

WARNING
Note that for the public interface to remain backward compatible it's absolutely necessary that all previous entry-point symbols remain the same. Not to mention other binary compatibility issues such as the same compiler and linker versions and so on.

This means for instance that previous non-inline member functions must not become inlined on the newer version. As a consequence be careful on the backward compatibility implementation in order to not introduce an additional (overhead) call to the legacy code.

The only safe C++ way of achieving this (interface preservation while keeping binary compatibility and not incurring on extra indirections) that comes to my mind is through (kind of reverse) C++ inheritance. What I mean by reverse is that in (major) version n of the library, the previous interface from (major) version n-1 must acutally inherit from the new interface of (major) version n. This is kind of the opposite of the usual derivation of version n from version n-1. I'll give an example of this in what follows.

If none of this is possible the backward compatibility will only become possible by recompiling the whole application, not just relinking to the new shared library version.
For the sake of exemplification, at my arbitrary discretion, the new version of the libgreeting.so.2 will be 2.1.5. I'll exemplify some library implementation changes but keeping backward compatibility and just requiring the relink of the application.

The lib-greeting.h will have some changes, but of course they will be mostly relevant to newer applications which could take advantage of new features.

NOTE
One thing perhaps I've not realized in time for major version 1 is that I could have emcopassed its struct Main by a namespace V1 and have inserted an using alias within its parent Greeting namespace in order to keep the application from knowing about V1.

For instance:

...

namespace Greeting
{

namespace V1
{
 
...

struct Main
{
...
};
 
...
 
}
// namespace V1
 
using V1::Main;
 
}
// namespace Greeting

...

 1 #if !defined( ACME_LIB_GREETING_H )
 2 #define ACME_LIB_GREETING_H
 3 
 4 // headers {{{1
 5 
 6 // standard headers {{{2
 7 #include <string>
 8 // }}}2
 9 
10 // custom headers {{{2
11 #include "lib-version.h"
12 #include "lib-greeting-v2.h"
13 // }}}2
14 
15 // }}}1
16 
17 namespace ACME
18 {
19 
20 namespace Library
21 {
22 
23 namespace Greeting // {{{1
24 {
25 
26 extern Version version;
27 
28 struct Main : public V2::Main // {{{2
29 {
30   std::string hello() const;
31 };
32 // }}}2
33 
34 }
35 // namespace Greeting }}}1
36 
37 }
38 // namespace Library
39 
40 }
41 // namespace ACME  
42 
43 #endif // ACME_LIB_GREETING_H
44 

Next I shall show the interface for the new features of major version 2. For the sake of organization I've decided to keep it on a separated header lib-greeting-v2.h:

 1 #if !defined( ACME_LIB_GREETING_V2_H )
 2 #define ACME_LIB_GREETING_V2_H
 3 
 4 // headers {{{1
 5 
 6 // standard headers {{{2
 7 #include <string>
 8 // }}}2
 9 
10 // custom headers {{{2
11 // }}}2
12 
13 // }}}1
14 
15 namespace ACME
16 {
17 
18 namespace Library
19 {
20 
21 namespace Greeting // {{{1
22 {
23 
24 namespace V2 // {{{2
25 {
26 
27 struct Main // {{{3
28 {
29    std::string hi( const std::string & name ) const;
30 };
31 // }}}3
32 
33 }
34 // namespace V2 }}}2
35 
36 }
37 // namespace Greeting }}}1
38 
39 }
40 // namespace Library
41 
42 }
43 // namespace ACME  
44 
45 #endif // ACME_LIB_GREETING_V2_H
46 

Line 22 of lib-greeting-main.cpp will become:

...
17 
18 namespace Greeting // {{{1
19 {
20 
21 // externals {{{2
22 Version version( 2, 1, 5 );
23 // }}}2
24 
25 }
26 // namespace Greeting }}}1
27 
... 

For the sake of simplification of this post I've decided to put all the implementation on the source-file lib-greeting-goodwill.cpp which has become as follows:

 1 // headers {{{1
 2 
 3 // standard headers {{{2
 4 #include <string>
 5 #include <sstream>
 6 // }}}2
 7 
 8 // custom headers {{{2
 9 #include "lib-greeting.h"
10 // }}}2
11 
12 // }}}1
13 
14 // {{{1
15 
16 namespace LIB = ACME::Library::Greeting;
17 
18 std::string 
19 LIB::Main::hello() const // {{{2
20 {
21   try
22   {
23     std::ostringstream oss;
24 
25     oss 
26       << "Hello, world!"
27       << " (" 
28       << version.major << "." 
29       << version.minor << "." 
30       << version.release 
31       << ")."
32       << std::ends;
33 
34     return oss.str();
35   }
36   catch ( ... )
37   {
38     return "";
39   }
40 }
41 // }}}2
42 
43 std::string
44 LIB::V2::Main::hi( const std::string & name ) const // {{{2
45 {
46   try
47   {
48     std::ostringstream oss;
49 
50     oss 
51       << "Hi ["
52       << ( name.empty() ? "stranger" : name.c_str() )
53       << "] (" 
54       << version.major << "." 
55       << version.minor << "." 
56       << version.release 
57       << ")."
58       << std::ends;
59 
60     return oss.str();
61   }
62   catch ( ... )
63   {
64     return "";
65   }
66 }
67 // }}}2
68 
69 // }}}1
70 

I've chosen to rebuild the changes as follows:

$ cd ~/project/greeting/2.1.5

$ g++ \
  -m64 -std=c++11 -march=core2 -mtune=core2 -fPIC -nodefaultlibs \
  -shared -Wl,-soname=libgreeting.so.2 -o libgreeting.so.2.1.5 \
  lib-greeting-main.cpp \
  lib-greeting-goodwill.cpp

Next I copy the new library libgreeting.so.2.1.5 next to the previous versions, but as this is a new major version I'll have to create libgreeting.so.2 symbolic-link as follows:

$ cd ~/project/myapp/1/lib
$ cp ~/project/greeting/2.1.5/libgreeting.so.2.1.5
$ ln -s libgreeting.so.2.1.5 libgreeting.so.2

I also want that the new backward compatible shared library become the default linking artifact. New applications will benefit from new features and old applications which are relinked against it won't be broken!

$ rm libgreeting.so
$ ln -s libgreeting.so.2 libgreeting.so

For my old application, it suffices to relink it:

$ cd ~/project/myapp/1

$ g++ \
  -m 64 \
  -L./lib -R./lib -lgreeting \
  -o myapp-1 \
  myapp-main.o


$ ldd myapp-1
    libgreeting.so.1 => ./lib/libgreeting.so.2
    libstdc++.so.6   => /usr/lib/64/libstdc++.so.6
    libm.so.2        => /lib/64/libm.so.2
    libgcc_s.so.1    => /usr/lib/64/libgcc_s.so.1
    libc.so.1        => /lib/64/libc.so.1


Running the re-linked application now results in:

$ ./myapp-1

MyApp - Greeting version: 2.1.5

Hello, world! (2
.1.5).

And that's it!
A long post I've considered worthwhile!