Mr. Shickadance
Mr. Shickadance

Reputation: 5473

How do I resolve naming conflicts with headers that use the preprocessor to redefine common function names?

EDIT: I found a similar question, and the answers are basically that windows.h is bad and you must either rename your functions or #undef the macros: Other's library #define naming conflict

However, I believe mine is still different due to the conflicting behavior of LoadLibrary under debug and release builds.


I am programming on Windows using Visual Studio and I ran into a few peculiar issues with preprocessor directives used by windows.h and the headers it includes.

Our project had a function in its own namespace, MyProject::FileManager::CreateFile(). After including windows.h our code failed to compile due to a linker error stating that it could not resolve MyProject::FileManager::CreateFileW (note the W at the end of the function name). This was not a static function, it was a member function of a FileManager object that was being called with file_manager.CreateFile(...).

When highlighting the function in Visual Studio a tooltip displayed the following:

#define CreateFile CreateFileW

We were puzzled but just renamed the function as a workaround. However later we ran into a similar issue with the LoadLibrary function we were trying to use from the Windows API. Compiling in Debug mode, LoadLibrary was defined as LoadLibraryW() which took an LPCWSTR (wide string) as a parameter. When I tried building in Release mode this function was now defined as LoadLibraryA() which takes a normal LPCSTR. This broke our build because the code was written under the assumption that LoadLibrary took an LPCWSTR.

So, my question is, how should a programmer deal with this? Should I just wrap my calls to LoadLibrary with #ifdef's checking for Debug or Release mode? Or is there a more simple solution?

Also, I found an interesting header file on github which appears to have been created for the sole purpose of #undef'ing all these function names:

https://github.com/waTeim/poco/blob/master/include/Poco/UnWindows.h

Upvotes: 0

Views: 1890

Answers (2)

Pete Becker
Pete Becker

Reputation: 76315

The problem here is that windows.h tries to support two different string models: strings consisting of single-byte characters and strings encoded in unicode (defined by microsoft as two-byte characters). Almost all of the Windows API functions have two different versions, one that takes single-byte character strings and one that takes two-byte character strings. You're supposed to write your code with the generic names (such as CreateFile, LoadLibrary, etc.) and let windows.h take care of mapping those names to the actual API functions. For single-byte characters those two are CreateFileA and LoadLibraryA; for two-byte characters they are CreateFileW and LoadLibraryW. And there are a bajillion more, of course. You choose the model at compile time by defining the macro UNICODE in every compilation unit.

Incidentally, the 'A' suffix stands for "ANSI", and the 'W' suffix stands for "wide character".

This is particularly insidious when you write code that tries to isolate the Windows dependencies into a handful of source files. If you write a class that has a member function named CreateFile, it will be seen in source files that don't use windows.h as CreateFile, and in source files that do use windows.h as CreateFileA or CreateFileW. Result: linker errors.

There are a several ways around this problem:

  1. always #include <windows.h> in every header file; that's a compiler performance killer, but it will work. (windows.h was a major motivator for precompiled headers in early C++ compilers targeting windows)

  2. always use the doctored name, either CreateFileA or CreateFileW; that will work, but at the cost of losing the flexibility of being able to change the underlying string model when you're making API calls; whether that matters is up to you.

  3. don't use any of the Windows API names; potentially painful if you use the same naming convention as Windows; not at all painful if you use snake case, i.e., all lower-case with underbars to separate words. For example, create_file. Alternatively, use a local prefix or suffix: MyCreateFile or CreateFileMine.

Upvotes: 2

Peter Ruderman
Peter Ruderman

Reputation: 12485

There are few things I generally do to cope with this:

  • Isolate all Windows system calls in a Windows-specific layer. For example, if I'm working with the file system API, I'll typically have win/filesystem.h and win/filesystem.cpp to wrap all the calls. (This is also a good place to convert Win32 errors into std::system_error exceptions, remove unneeded/obsolete/reserved parameters, and generally make the Windows API more C++ friendly.)
  • Avoid using Windows-specific types. Allowing definitions like DWORD, LPTSTR and BOOL to infiltrate all levels of your code makes dealing with Windows.h that much more difficult. (And porting too.) The only files that should #include <Windows.h> should be your wrapper C++ files.
  • Avoid using the Windows redirection macros yourself. For example, your wrapper layer should call CreateFileW or CreateFileA directly instead of relying on the macro. That way, you don't need to depend on the Unicode/Multi-byte project setting.

Example

win/filsystem.h might contain definitions like this:

namespace win32
{
  class FileHandle
  {
    void* raw_handle_;
  public:
    // The usual set of constructors, destructors, and accessors (usually move-only)
  };

  FileHandle CreateNewFile(std::wstring const& file_name);
  FileHandle OpenExistingFile(std::wstring const& file_name);
  // and so on...
}

Any part of your code can include this file to access the file system API. Since win/filesystem.h does not itself include <Windows.h>, the client code will be uncontaminated by the various Win32 macros.

Upvotes: 4

Related Questions