The Pitfall of Trying to Be Too Smart When Using dllexport/dllimport

As a seasoned C++ developer I should’ve been aware of this which makes it a little bit embarrassing. But, since this issue has cost me several hours of searching through the Internet over the course of two or three days, I thought it might be worth sharing. Maybe somebody else is trying to be too smart or just doesn’t know better.

The problem? It is summarized in short in this StackOverflow question that I posted. With this blog post I’ll be a bit more elaborate and show some details.

When we started working on a new project and decided not to use existing code, but start from scratch and build on top of the excellent Qt framework. We knew we had to split the code into several libraries to implement specific functionality and that they would depend on each other. If you build a dynamic library you have to export your symbols or otherwise client code won’t be able to find it at link time. Qt provides predefined macros that hide away the syntax of the different compilers.

And of course, having a great documentation, there’s also a nice article on how to use these macros. I had already experimented with this technique on the Microsoft platform before. Using these macros served the purpose of hiding compiler specific options.

Now here is where I thought to be extra smart. There’s one library that is basically needed by every other library (like QtCore). The idea was to have only one #if defined block that defines the macro of whether to import or export the symbols, like this:

#include

#if defined(MYSHAREDLIB_LIBRARY)
 # define MYSHAREDLIB_EXPORT Q_DECL_EXPORT
 #else
 # define MYSHAREDLIB_EXPORT Q_DECL_IMPORT
 #endif

Every source file that was part of a shared library would include the header file that contains this definition (let’s call it my_project_global.h) and specify the preprocessor value MYSHAREDLIB_LIBRARY on the command line to the compiler.

As a result, every class or function that we wrote (that was to be exported) was then annotated with MYSHAREDLIB_EXPORT, like this:

#include

class MYSHAREDLIB_EXPORT Fish
 {
 public:
 Fish();
 bool tastesGood() const;

...
 };

uint MYSHAREDLIB_EXPORT qHash(const Fish& fish);

This way there’s no need to have code for import/export macros in each and every library. And it worked! Our project compiled with GCC from the MinGW package and as long as this compiler was used we had no problems. GCC defaults to external visibility for all symbols unless it’s specified otherwise.

This is taken from the GCC documentation:

-fvisibility=default|internal|hidden|protected The default if -fvisibility isn’t specified is default, i.e., make every symbol public—this causes the same behavior as previous versions of GCC

Qt’s macros expand to __declspec(dllexport/dllimport) on Windows however, so I’m not sure what GCC makes of that (I’m actually surprised this is supported). Maybe these compiler options only exist for compatibility reasons on Windows and are treated as __attribute__ ((visibility(“default”))) internally – which, as it turns out, is the value for both the Q_DECL_EXPORT and Q_DECL_IMPORT macros if not on Windows.

For the curious: More on GCCs visibility.

“Dude, get to the point…” you might say.

Very well. When we switched from GCC to Microsoft’s Visual C++ 2013 compiler, one or two libraries suddenly wouldn’t link because of an “unresolved external” error. Every time, the unresolved method in question was one that is generated by Qt’s meta object compiler (moc).

unresolved external symbol “public: static struct QMetaObject const DHImageConvHandler::staticMetaObject” referenced in function “public: static class QString __cdecl DHImageConvHandler::tr(char const *,char const *,int)”

I checked the generated DLLs of the library that should export these symbols with Dependency Walker and they were there – as expected. Just be 100% sure, I also looked at the import library file (*.lib) with dumpbin. No surprises there either. So what the hell was going on? Changing the project setting from dynamic library to static library in Visual Studio solved the problem – temporarily. Our continuous integration system is based on qmake and the Qt *.pro project files. Changing the project settings there had a whole different surprise in store which broke the builds, so that wasn’t an option.

Turns out, I’m just an idiot.

Remember that every library uses the same MYSHAREDLIB_EXPORT macro and the associated preprocessor definition MYSHAREDLIB_LIBRARY? What do you think happens if two (or potentially more) libraries specify MYSHAREDLIB_LIBRARY? The MYSHAREDLIB_EXPORT macro expands into Q_DECL_EXPORT which then expands into __declspec(dllexport) not only for the classes that shall be exported, but also for the ones that shall be imported. Nothing gets imported and everything gets exported. No wonder the linker didn’t find what he was looking for.

The solution to this is exactly what I was trying to over-smartly avoid. Every library needs its own set of preprocessor value and import/export macro so there are no conflicts when using code from another library.

Seems like the old adage is on point: “Once you do it right, it works”.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.