The Sticky Preprocessor-Based TCHAR Model – Part 2: Where’s My Function?!?

In the previous blog post, I briefly introduced the TCHAR model. I did that not because I think that’s a quality model that should be used in modern Windows C++ applications: on the contrary, I dislike it and consider it useless nowadays. The reason why I introduced the TCHAR model is to help you understand what can be a very nasty bug in your C++ Windows projects.

So, suppose that you are building a cross-platform abstraction layer for some C++ application: in particular, you have a function that returns a string containing the path of the directory designated for temporary files, something like this:

// FILE: Utils.h

#pragma once

#include <string>

namespace Utils
{
    std::string GetTempPath();

    // ... Other functions
}

For the Windows implementation, this function is defined in terms of the GetTempPath Win32 API. In order to use that API, inside the corresponding Utils.cpp source, <Windows.h> is included:

// FILE: Utils.cpp

#include <Windows.h>
#include "Utils.h"

// Utils::GetTempPath() implementation ...

Now, suppose that you have another .cpp file, with cross-platform C++ code, that uses Utils::GetTempPath(). Note that, since this is cross-platform C++ code, <Windows.h> is not included in there. Think for example even of something as simple as:

// FILE: Main.cpp

#include <iostream>
#include "Utils.h"  // for Utils::GetTempPath()

int main()
{
    std::cout << Utils::GetTempPath() << '\n';
}

Well, this code won’t build. You’ll get a linker error, something like this:

1>Main.obj : error LNK2019: unresolved external symbol 
"class std::basic_string<char,struct std::char_traits<char>,
class std::allocator<char> > __cdecl Utils::GetTempPath(void)" 
(?GetTempPath@Utils@@YA?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@XZ) 
referenced in function _main

After removing a little bit of “noise” (including some C++ name mangling), basically the error is:

1>Main.obj : error LNK2019: unresolved external symbol “std::string Utils::GetTempPath()” referenced in function main

So, the linker is complaining about the Utils::GetTempPath() function.

Then you may start going crazy, double- and triple-checking the correct spelling of “GetTempPath” inside your Utils.h header, inside Utils.cpp, inside Main.cpp, etc. But there are no typos: GetTempPath is actually spelled correctly in every place.

Then, you try to rebuild the solution inside Visual Studio one more time, but the mysterious linker error shows up again.

What’s going on? Is this a linker bug? Time to file a bug on Connect?

Nope.

It’s just the nasty preprocessor-based TCHAR model that sneaked into our code!

Let’s try to analyze what’s happened in some details.

In this case, there are a couple of translation units to focus our attention on: one is from the Utils.cpp source file, containing the definition (implementation) of Utils::GetTempPath. The other is from the Main.cpp source file, calling the Utils::GetTempPath function (which is expected to be implemented in the former translation unit).

In the Utils.cpp’s translation unit, the <Windows.h> header is included. This header brings with it the preprocessor-based TCHAR model, discussed in the previous blog post. So, a preprocessor macro named “GetTempPath” is defined, and it is expanded to “GetTempPathW” in Unicode builds.

Think of it as an automatic search-and-replace process: before the actual compilation of C++ code begins, the preprocessor examines the source code, and automatically replaces all instances of “GetTempPath” with “GetTempPathW”. The Utils::GetTempPath function name is found and replaced as well, just like the other occurrences of “GetTempPath”. So, to the C++ compiler and linker, the actual function name for this translation unit is Utils::GetTempPathW (not Utils::GetTempPath, as written in source code!).

Now, what’s happening at the Main.cpp’s translation unit? Since here <Windows.h> was not included (directly or indirectly), the TCHAR preprocessor model didn’t kick in. So, this translation unit is genuinely expecting a Utils::GetTempPath function, just as specified in the Utils.h header. But since the Utils.cpp’s translation unit produced a Utils::GetTempPathW function (because of the TCHAR model’s preprocessor #define), the linker can’t find any definition (implementation) of Utils::GetTempPath, hence the aforementioned apparently mysterious linker error.

TCHAR Preprocessor Bug
TCHAR Preprocessor Bug

This can be a time-wasting subtle bug to spot, especially in non-trivial code bases, and especially when you don’t know about the TCHAR preprocessor model.

You should pay attention to functions and methods that have the same name of Win32 APIs, that can be subjected to this subtle TCHAR preprocessor transformation.

To fix that, an option is to #undef the TCHAR-modified definition of the identifier in Utils.h:

//
// Remove TCHAR preprocessor redefinition 
// of GetTempPath
//
#ifdef GetTempPath
#undef GetTempPath
#endif

A simple repro solution can be downloaded here from GitHub.

 

One Reply to “The Sticky Preprocessor-Based TCHAR Model – Part 2: Where’s My Function?!?”

Leave a Reply

Your email address will not be published. Required fields are marked *