Using libraries

When you're developing code, you almost always make use of a library, a collection of code modules that you or someone else has already developed (and hopefully debugged).

Under QNX Neutrino, we have three different ways of using libraries:

Static linking
You can combine your modules with the modules from the library to form a single executable that's entirely self-contained. We call this static linking. The word “static” implies that it's not going to change—all the required modules are already combined into one executable.
Dynamic linking
Rather than build a self-contained executable ahead of time, you can take your modules and link them in such a way that the Process Manager links them to the library modules before your program runs. We call this dynamic linking. The word “dynamic” here means that the association between your program and the library modules that it uses is done at load time, not at link time (as was the case with the static version).
Runtime loading
There's a variation on the theme of dynamic linking called runtime loading. In this case, the program decides while it's actually running that it wishes to load a particular function from a library.

Static and dynamic libraries

To support the two major kinds of linking described above, QNX Neutrino has two kinds of libraries: static and dynamic:

Static libraries
A static library is usually identified by a .a (for “archive”) suffix (e.g., libc.a). The library contains the modules you want to include in your program and is formatted as a collection of ELF object modules that the linker can then extract (as required by your program) and bind with your program at link time.

This “binding” operation literally copies the object module from the library and incorporates it into your “finished” executable. The major advantage of this approach is that when the executable is created, it's entirely self-sufficient—it doesn't require any other object modules to be present on the target system. This advantage is usually outweighed by two principal disadvantages, however:

  • Every executable created in this manner has its own private copy of the library's object modules, resulting in large executable sizes (and possibly slower loading times, depending on the medium).
  • You must relink the executable in order to upgrade the library modules that it's using.
Dynamic libraries
A dynamic library is usually identified by a .so (for “shared object”) suffix (e.g., libc.so).

Like a static library, this kind of library also contains the modules that you want to include in your program, but these modules are not bound to your program at link time. Instead, your program is linked in such a way that the Process Manager causes your program to be bound to the shared objects at load time.

The Process Manager performs this binding by looking at the program to see if it references any shared objects (.so files). If it does, then the Process Manager looks to see if those particular shared objects are already present in memory. If they're not, it loads them into memory. Then the Process Manager patches your program to be able to use the shared objects. Finally, the Process Manager starts your program.

Note that from your program's perspective, it isn't even aware that it's running with a shared object versus being statically linked—that happened before the first line of your program ran!

The main advantage of dynamic linking is that the programs in the system reference only a particular set of objects—they don't contain them. As a result, programs are smaller. This also means that you can upgrade the shared objects without relinking the programs. This is especially handy when you don't have access to the source code for some of the programs.

dlopen()

If you want to “augment” your program with additional code at runtime, you can call dlopen().

This function call tells the system that it should find the shared object referenced by the dlopen() function and create a binding between the program and the shared object. Again, if the shared object isn't present in memory already, the system loads it. The main advantage of this approach is that the program can determine, at runtime, which objects it needs to have access to.

Note that there's no real difference between a library of shared objects that you link against and a library of shared objects that you load at runtime. Both modules are of the exact same format. The only difference is in how they get used.

By convention, therefore, we place libraries that you link against (whether statically or dynamically) into the lib directory, and shared objects that you load at runtime into the lib/dll (for “dynamically loaded libraries”) directory.

Note that this is just a convention—there's nothing stopping you from linking against a shared object in the lib/dll directory or from using the dlopen() function call on a shared object in the lib directory.

Platform-specific library locations

The development tools have been designed to work out of their processor directories (x86, armle-v7, etc.). This means you can use the same toolset for any target platform.

If you have development libraries for a certain platform, then put them into the platform-specific library directory (e.g., /x86/lib), which is where the compiler and tools look.

Note: You can use the -L option to qcc to explicitly provide a library path.