Friday, October 15, 2010

Boost C++ Threads - MinGW & Mac OS X Universal Binaries

I ran across a cool set of C++ frameworks called Boost. They provide loads of stuff that you just take for granted in other languages like RegEx and thread support, but is not part of the C++ standard (at least not yet - apparently large parts of the Boost libraries are on track for adding to the official standard). My current interest in Boost is for its more advanced threading (I had a working implementation using pthreads but those have an inelegant interface which makes the code harder to maintain). Although much of Boost doesn't need to be compiled into a separate libraries (their magic uses C++ templates that are defined in header files), the threads implementation does need to be compiled to a binary. This is where I ran into some trouble (and some hair pulling).
Turns out there are unique problems with the Mac and Windows build, so I will detail them separately. I hope that some of this will help someone else (or help me in the future, if I forget :)
Mac Challenges:
To get Boost there are two main options. Download pre-compiled binaries or compile them yourself. The pre-compiled route is really only available for Windows, so for the Mac I had to compile it myself. This wasn't too hard. The documentation has nice step by step directions that will, if followed correctly, produce library files (two for each library - one ending in .a and one ending in .dylib -.a is a static library that will compile into your code, where-as the dylib is meant to a stand-alone file that is linked at runtime (although this can be hidden by including it in the final app package). I needed the static variant for my project). The directions tell you to change your search paths and libraries paths to include the headers for boost and the new libraries that you just created. From there it should just work. But it didn't for me. Instead I got cryptic linker errors (linker errors are always cryptic) that I eventually figured out meant that the libraries I built didn't include a PowerPC variant. Since this particular project was setup to run on PowerPC and Intel macs, it won't compile. Now Boost will compile for PowerPC the problem is that the compile scripts are automatically only configured for the system that you are currently running. So I suppose I could have dug up an old Mac (PowerPC), installed the developer tools on it, re-installed boost which would have produced the PowerPC binary I was looking for, that didn't occur to me (even it did I would have avoided it at all costs - what a mess). I knew that my machine could compile to both architectures, in fact it does it all the time. So it was just a matter of setting the scripts up correctly. Unfortunately, there were no easy to use instructions for this. Instead I was left trying to piece together different blog posts that were tangentially related. One I came across mentioned using MacPorts to get the Boost libraries. I was heartened to see that MacPorts had a "Universal" variant (Universal is the term used by Apple to indicate multiple architectures in one library.) It usually means PowerPC and Intel, exactly what I needed. MacPorts unfortunately had its own learning curve, it took hours too build Boost (mostly because I couldn't specify that I just needed threads), and turned out their "Universal" option meant includes 32bit and 64bit Intel. Although technically correct use of the term "Universal" in Apple parlance, it isn't what I needed. Further including the MacPort binaries caused conflicts with other libraries that are provided by the OS and were building just fine. In the end I had to purge my project of all external references to the MacPorts build (probably going to do an uninstall of MacPorts later). Back to the drawing board. I finally ran across an excellent script online that was doing mostly the same thing that I wanted. It was designed to build 4 binary variants of Boost (32 & 64 bit variants of the Intel and PowerPC libraries) and then stitch them together using "lipo" (an Apple developed command line tool designed for creating "universal" aka "fat" binaries - hence the name lipo). The script did the first part beautifully but didn't quite work for the second part (the file names had changed slightly from boost version 1.4 and 1.44 and it caused it to choke). No matter "lipo" is pretty easy to use manually (and in my case preferrable, because I didn't want all four parts). The command I used was.
lipo -create build/libPPC32/libboost_thread-xgcc40-mt.a build/libIntel32/libboost_thread-xgcc40-mt.a -output build/lib/libboost_thread.a
. You can use man lipo to get an idea of how to use it. After you are done you can use lipo -info to find out if it got all the right architectures (this is how I know why the MacPorts universal wasn't working). I then just dragged the newly created "Universal" library into Xcode and it worked!!! Now I only had to get the windows version working.

Windows:
My windows compiler is MinGW. I use it because it gets the job done and its free. Its a nice little compiler but not terribly easy to use (I guess I can't complain, it's free after-all). The cool thing about MinGW is that Boost comes included. No compilation necessary. Unfortunately every time I tried to use Boost I would get very cryptic linker errors (those linker errors again :) like the following:
undefined reference to '_imp___ZN5boost6thread20hardware_concurrencyEv'

Everything I tried to fix this failed (and I tried a lot of stuff, and was at it for hours). Finally I had to ask someone for help (I don't like asking for help until I've exhausted my resources - I guess I don't want to waste other people's time, and I don't want to look stupid - ah Vanity). Truth is, there are very few people I can ask for stuff like this, and expect to get an answer. I found a great website of a "Stephan T. Lavavej' who looked like he just might be able to help. He had built his own distribution of MinGW which included the latest version of Boost (just the sort of guy that would at least know what I was talking about). Indeed! He was most helpful. He sent me the following:
Due to changes in Boost 1.44.0 and mingw-runtime 3.18, you'll need to pass
-DBOOST_THREAD_USE_LIB to g++ and define

namespace boost {
void tss_cleanup_implemented() { }
}

in one of your source files.

Do I really understand it? Heck No! But adding both of these unmangled the namespace. Now I was getting much more manageable linker errors like this:
undefined reference to 'boost::thread::hardware_concurrency()'

It was now just a simple matter of adding the right library to my make file to resolve these (I had done this earlier as well but it had no effect until the name mangling was resolved). I added:
-lboost_thread

the library itself is called libboost_thread.a but you drop the "lib" and the ".a" . The -l is a flag to the compiler to link against that library.
Thanks again to Stephen Lavavej of nuwen.net for his insightful email and helpful website and to Manfred Schwind of mani.de for his excellent script for building PPC versions of Boost. I would also like to thank anyone that has ever asked a question about Boost or responded to one, It feels like I read just about every one ;) and they all helped to improve my understanding and push me a little further along.
Now that I've got it compiling I just need to build something useful with it. I guess that is what next week is for :).

No comments: