Ulf Wendel

Connector/C++: 1.1.0 offers run-time dynamic linking of libmysql

From 1.1.0 on, Connector/C++ can optionally use run-time dynamic linking to access the MySQL Client Library (AKA libmysql). If you make use of this new feature, it will not only change the application binary interface (ABI) but also has some impact on your client applications. The new expert setting is not enabled by default. By default, you will not notice any differences.

The next version of MySQL Connector/C++ will be numbered 1.1.0. 1.1.0 marks the successful end of the first year of development. The driver finally offers everything needed by its two "internal customers". Also, feedback from MySQL Workbench and Connector/OpenOffice.org and the bug inflow demonstrated the maturity that has been reached making it a logical step to bump up the version number.

While MySQL Workbench has been happy with the feature set for a while, we implemented a couple of changes in 1.1.0 to also please Connector/OpenOffice.org. The biggest change is on how MySQL Connector/C++ can load the MySQL Client Library to connect to the MySQL Server. As you may know, the MySQL driver for C++ does not implement the MySQL Client-Server Protocol itself. Instead the driver wraps and uses the MySQL Client Library, the C-API, to talk to the MySQL Server. From version 1.0.0 to 1.0.5 the driver has just linked the MySQL Client Library at compile time. But from 1.1.0 on you can either link the MySQL Client Library at compile time, which remains the "safe" and proven default, or you can instruct the driver to use run-time dynamic linking (dlopen() resp. LoadLibrary()) to load the library.

MySQL Connector/C++
wraps C-API of
MySQL Client Library or MySQL Connector/C
implements MySQL Client-Server Protocol to communicate with
MySQL Server

Run-time dynamic linking

We would have never considered run-time dynamic linking if we had not managed to "bork" a system sufficiently to make Connector/OpenOffice.org crash on it. The Connector/OpenOffice.org wraps the MySQL Connector/C++ and the MySQL driver for C++ in turn makes use of the MySQL Client Library. The crash happened because of the usage of the wrong MySQL Client Library. Whatever we tried, the system decided not to load the MySQL Client Libary it should. The system ignored our copy of the MySQL Client Library and picked one found somewhere else on the system. To make a long story short: expert colleguages strongly recommended to use run-time dynamic linking. Run-time dynamic linking allows you to specify the path of the library to load in your binary. And this is what the next version of the OpenOffice.org driver does: it uses an absolut path to uniquely identify the library to load.

When enabling the run-time dynamic linking feature of Connector/C++ one can specify the client library to use on a per-connection basis. Every connection can load a different version of the MySQL Client Library or MySQL Connector/C, if you want. MySQL Connector/C is basically a standalone version of the MySQL Client Library. The latter ships together with the MySQL Server whereas the first is available as a standalone product. The MySQL Client Library and the MySQL driver for C export the same C-API. Therefore you can choose to either use the MySQL Client Library or Connector/C.

To enable run-time dynamic linking, you have to build Connector/C++ yourself and change the default settings. Set the cmake flag MYSQLCLIENT_STATIC_BINDING:BOOL=1. The pre-compiled binaries of Connector/C++ 1.1.0 will use static linking, just as all versions before. Please check the documentation and earlier blog postings on how to build the driver from source.

rm CMakeCache.txt
cmake -DMYSQLCLIENT_STATIC_BINDING:BOOL=1 .
make clean
make 
make install

Everything said in the following refers to run-time dynamic linking of the MySQL Client Library.

Per connection client libraries

Writing C++ client applications that make use of different C-API libraries at a time is perfectly valid although this is nowhere close to the reason why we added run-time dynamic linking. I cannot think of any use cases beyond testing and for demoing the API why one would want to switch the client library on a per-connection basis. However, here you go with the API example.

As you may recall the API of the MySQL Connector/C++ is modeled after JDBC 4.0. Therefore, connections are encapsulated in connection object. Connection objects can be obtained by calling the connect() method of the DriverManager. The connect() method has two different signatures.

  • Connection * connect(const sql::SQLString& hostName, const sql::SQLString& userName, const sql::SQLString& password)
  • Connection * connect(ConnectOptionsMap & options)

If you use the first one and pass values for the connection url, the user name and the password, Connector/C++ will automatically search for the MySQL Client Library. If you use the second signature, which takes only one argument, you can specify pretty much every connection option also available with the C-API, and you can tell the driver which C-API library it shall load.

sql::Driver *driver;
sql::ConnectOptionsMap connection_properties;

try {
  driver = sql::mysql::get_driver_instance();
  
  /* client library to load */
  sql::SQLString clientlib("libmysqlclient_r.so");
  connection_properties[""clientlib"] = clientlib;
  
  /* url, user, password */
  sql::SQLString url("tcp://127.0.0.1:3306");
  connection_properties["hostName"] = url;  
  sql::SQLString user("root");
  connection_properties["userName"] = user;  
  sql::SQLString password("");
  connection_properties["password"] = password;  
  sql::SQLString schema("test");
  connection_properties["schema"] = schema; 
  
  
  std::auto_ptr< sql::Connection > con(driver->connect(connection_properties));
  
} catch (sql::SQLException &e) {

  cout << "# ERR: SQLException in " << __FILE__;
  cout << "(" << __FUNCTION__ << ") on line " << __LINE__ << endl;		
  cout << "# ERR: " << e.what();
  cout << " (MySQL error code: " << e.getErrorCode();
  cout << ", SQLState: " << e.getSQLState() << " )" << endl;
  
}
  

Please check examples/dynamic_load.cpp for a complete code example. The examples are part of the source distribution and are also available when checking out the latested development version from Launchpad.

The client library search order

In the example no absolute path to the client library is specified. This is the typical case. And it is what happens, if you use the other flavour of the connect() method, which leaves it to Connector/C++ to pick a client library. Assuming you have multiple versions of the client library "libmysqlclient_r.so" on your system, which one will be used? The answer is in your operating system documentation. The MySQL driver for C++ uses the operating system call dlopen() on Unix-like systems and LoadLibrary() on Windows. Please refer to your operating system documentation for details.

On most of todays Linux systems the dynamic linker should search the library as follows:

  1. DT_RPATH of the program’s dynamic ELF section
  2. LD_LIBRARY_PATH environment setting
  3. DT_RUNPATH of the program’s dynamic ELF section
  4. the dynamic loader cache file, e.g. /etc/ld.so.cache
  5. the default system library directories configured at compile-time, e.g. /lib and /usr/lib

Why am I telling you? Because you may sooner or later have to deal with a system on which the dynamic linker picks a library which you don’t want to use in your client application, for example, because you need the latest version of the MySQL Client Library and you want to redistribute it together with your client application. At this point you have three options. You can modfiy the system, which is quite intrusive. You use an absolute path to the client library you deliver, which requires that your application gets installed into the very same location on every system. Or you make use of DT_RPATH, LD_LIBRARY_PATH, DT_RUNPATH to tell the dynamic linker to start its search in a directory relative to the installation location of your program, for example ./mylibs.

Enforcing the usage of a specific client library

Reconfiguring a system to make sure that Connector/C++ loads the version of the MySQL Client Library that you want to use in your client application is probably the most troublesome approach to solving the task. You need to instruct your users how to do it, users may require priviledged access to the system and you have to ensure that your changes do not impact other applications. Using an absolute path in the source code may not be preferrable either as it disallows users to install your binary to any other location than the one you have selected. As a last resort, on most Unix systems, you can set the LD_LIBRARY_PATH environment variable. Or you compile a search path into the binary by setting DT_RPATH or DT_RUNPATH.

The Connector/C++ 1.1.0 library (libmysqlcppconn.so) will have no DT_RPATH set by default. But you can instruct CMake to set it using cmake -DMYSQLCPPCONN_DT_RPATH:STRING=/my/rpath . Setting the DT_RPATH is not as "safe" as using an absolute path to load a specific version of the MySQL Client Library because the dynamic loader continues his search, if he cannot find the library in the given place. However, if users install your client application into the location you tell them to use, your application will choose the library you want it to load. And, if the users does not use the default installation location, they can still run your application by setting the LD_LIBRARY_PATH environment variable. A fair compromise. Even better, you can use a DT_RPATH relative to your "root" installation path and there is no issue at all…

nixnutz@ulflinux:~/src/connector-cpp-bzr/trunk> cmake -L
[...]
CMAKE_BUILD_TYPE:STRING=
CMAKE_INSTALL_PREFIX:PATH=/usr/local
MYSQLCLIENT_STATIC_BINDING:BOOL=0
MYSQLCPPCONN_BUILD_EXAMPLES:BOOL=1
MYSQLCPPCONN_DT_RPATH:STRING=./mylibs
MYSQLCPPCONN_DYNLOAD_MYSQL_LIB:FILEPATH=/usr/lib64/libmysqlclient_r.so
MYSQLCPPCONN_GCOV_ENABLE:BOOL=0
MYSQLCPPCONN_ICU_ENABLE:BOOL=0
MYSQLCPPCONN_STLPORT_ENABLE:BOOL=0
MYSQLCPPCONN_TEST_NOT_IMPLEMENTED:BOOL=0
MYSQLCPPCONN_TRACE_ENABLE:BOOL=0
MYSQL_CONFIG_EXECUTABLE:FILEPATH=/usr/bin/mysql_config

nixnutz@ulflinux:~/src/connector-cpp-bzr/trunk> readelf -d driver/libmysqlcppconn.so.3.1.0.6

Dynamic section at offset 0xf3b38 contains 34 entries:
  Tag        Type                         Name/Value
 [...]
 0x000000000000000e (SONAME)             Library soname: [libmysqlcppconn.so.3]
 0x000000000000000f (RPATH)              Library rpath: [./mylibs]
 0x000000000000001d (RUNPATH)            Library runpath: [./mylibs] 

On Windows DT_RPATH is not available. The MSDN Dynamic-Link Library Search Order may be of interest for you. I am afraid that you will have to find your own way through the dynamic loading jungle on various platforms which you plan to support.

Building client applications when using MYSQLCLIENT_STATIC_BINDING:BOOL=0

Guess what: Connector/C++ does not need to link the MySQL Client Library at compile time if you choose to load it at run-time. And it will not do so. In fact, linking the MySQL Client Library at compile time may be harmful. Avoid linking the MySQL Client Library to ensure that your client application process does not hold two copies of the library in memory. We have seen crashes on Linux when doing so. Windows seems to be immune.

Another pitfall is using the GCC flag --static for static linking of your client application. GCC will tell you already during the compilation that this is not a good idea. And GCC really means what it says. If you do not want your client to crash on many systems, use LDFLAGS=-static instead.

LDFLAGS=-static /usr/bin/c++ -o standalone -I/usr/local/include/cppconn/ -L/usr/local/lib/ -g -Wall  examples/standalone_example.cpp -lmysqlcppconn-static  -ldl

Happy hacking and a have a nice weekend!

Comments are closed.