Friday 14 September 2012

From Visual C++ to GCC - Linker Order Matters

Back in July 2009 I wrote a post about how I had started using GCC as a cheap alternative to performing some static code analysis as I lamented the lack of low-cost options for hobbyist programmers the month before. I also promised to write up more about my experiences as I had had a variety of problems outside those with the code itself - this, finally, is the first of my follow-up posts.

Using Code::Blocks (with TDM-GCC) the first project I ported was my Core library as it’s the newest and was written with the goal of portability[*] in mind. After applying a liberal sprinkling of #ifdef’s and fixing a few other minor transgressions I got it compiling and somewhat surprisingly it linked out of the box too. Sadly I needed to fix a couple of bugs with my ANSI/Unicode string conversation helper functions but other than that it was pretty easy. My simple XML library went the same way too.

But success was short-lived because the moment I ported my ancient Windows class library (that underpins every app I’ve written) the linker started spewing out reams of unresolved symbols. I wasn’t overly surprised because I had just fixed up various uses of the Visual C++ specific #pragma[#] that allows you to specify a library that the MS linker should include automatically:-

#ifdef _MSC_VER
#pragma comment(lib, "version")
#endif

So I ploughed through the linker errors, and with a little help from “strings” and “grep” managed to work out which libraries from the MinGW toolset I needed to include. The error count swiftly went down and down until I had just a few left. And then I was stumped. There were only a handful of symbols left to resolve, but one of them was a function of my own that resided in the Core library I had just managed to build! The Core unit tests had just been run too so I couldn’t understand why GCC was now having trouble when it had linked fine minutes before.

I spent ages Googling, but being new to the GCC toolchain and Code::Blocks I didn’t really know if this was a problem with my code, GCC, Code::Blocks or just a limitations of Windows development with that configuration. I decided that given my original goal was satisfied - that I could use GCC to at least compile my code and so provide me with extra static analysis - I would just live with it for now. I still hadn’t got into command line builds with Code::Blocks or worked out how to keep the .vcproj and .cbp projects files in sync, so there I left it. For far too long...

A few months ago I reopened this case as I had ported many of my libraries and apps and now really wanted to build everything end-to-end to satisfy myself that I could (notionally) support more than just Visual C++ and stop being quite such a luddite. So I vented my anger via Twitter and one of my fellow ACCU members (either Anthony Williams or Pete Goodliffe) asked if the order of the libraries made a difference. Naturally my response was “What, the order of libraries matters with GCC?”.

Apparently so. The next problem was that by taking a shortcut and using Code::Blocks I had distanced myself from the underlying compiler and so wasn’t sure what, if any, control I had over the ordering. However a quick flick around the Code::Blocks project settings UI and I started noticing buttons and dials I hadn’t spotted before.

CodeBlocks

And then I discovered the magic setting that made all the difference - the policy “Prepend target options to project options”. This normally defaults to “Append…” but open the drop list and you can switch it to “Prepend…”. That sorted the 3rd party symbols, but I still had a couple of my own symbols unresolved. I quickly realised that was because my own libraries were in the wrong order too. A quick play with the arrows that allow you to reorder the “Link libraries” and everything was good.

GUIs are all very good for browsing and tinkering but when it comes to making repetitive changes they suck. Fortunately the Code::Blocks projects files (.cbp) are XML and so it was pretty easy to isolate the relevant sections and make sure they were consistent across all my solutions:-

<Target title=. . .>
    . . .
    <Option projectLinkerOptionsRelation="2" />
    . . .
    <Linker>
        <Add library="..\Lib\NCL\Debug\libNCL.a" />
        <Add library="..\Lib\WCL\Debug\libWCL.a" />
        <Add library="..\Lib\Core\Debug\libCore.a" />
    </Linker>
</Target>
. . .
<Linker>
    <Add library="ole32" />
    <Add library="oleaut32" />
    <Add library="uuid" />
    <Add library="comdlg32" />
    <Add library="version" />
    <Add library="gdi32" />
    <Add library="ntdll" />
    <Add library="advapi32" />
    <Add library="shlwapi" />
</Linker>

Once I had sorted all my project settings everything was building exactly as it should. Now, with the command line below, I can also build all my Code::Blocks projects just like I do with Visual C++ so I should be able to make GCC a more frequent part of my development regime.

C:\> codeblocks.exe --build %f --no-batch-window-close

 

[*] Portability across Windows compilers and STL implementations essentially. Although I have encapsulated use of the Windows API on the vague off-chance that I should decide to embrace other OS ecosystems.

[#] I think this is one of the best extensions Microsoft added to Visual C++ because it makes writing Facades so much easier - you only have to link your application with your Facade library and the additional linker dependencies are automatically taken care of for you.

1 comment:

  1. I often wish for a gcc extension like the MSVC pragma for automatic linking.

    ReplyDelete