The woes of TCHAR in WinAPI programming.



Someone asked me why their code wasn't working as intended. After taking a look, I became confused too. Below is the original code they sent me.
#define INFO_BUFFER_SIZE 32767
TCHAR  infoBuf[INFO_BUFFER_SIZE];
DWORD  bufCharCount = INFO_BUFFER_SIZE;

GetComputerName(infoBuf, &bufCharCount);
cout << infoBuf << endl;

Looks fine? Without context, yes. So let's add context.


Note they seem to have used using namespace std;
When this code was compiled and ran, the output was not ideal. They described it as the "memory address of a buffer". I do not know what he saw, but in my own tests, I believe it was similar to something like "010FFC19" (different every run time). The ideal output was to get the current computer name. And so the quest began.

According to MSDN, the function GetComputerName takes a buffer of type LPSTR and a size of type LPDWORD. The internet seems to agree the size for the buffer should be MAX_COMPUTERNAME_LENGTH + 1. It is already defined through "Windows.h" and we +1 to account for the null character. Rather interestingly, INFO_BUFFER_SIZE is set to 32767, which makes zero sense considering the maxiumum computer name length is ~15.
For the buffer, many examples online showed you can just use type char. However using char this happened....

argument of type "char* is incompatible with parameter of type "LPWSTR"

The solution? Change the project settings to "Use Multi-Byte Character Set". But why? Let's look at TCHAR.

If the project is set to Unicode, TCHAR expands to WCHAR. If the project is set to Multi-Byte, TCHAR expands to char.
This is because of how text data is stored in memory. For Multi-Byte projects, programs are compiled with the expectation that all characters to be used in strings can be encoded with 8 bits, which is 1 byte. It is usually expected for C and C++ compilers to treat this as a char. Anything that needs more than 1 byte to be encoded correctly should be stored in a WCHAR (a "wide" char), which on Windows platforms is 16 bits, or 2 bytes. Hence the name Multi-Byte.

However for Unicode projects, programs are compiled with the expectation that strings will always be "wide". On Windows this is because of some history with Microsoft expecting that Unicode will be encoded in UTF-16, which uses 16 bits (however as you may know these days, Unicode is mostly encoded with UTF-8).

So the main issue here is that TCHAR will expand to something different depending on the project setting, which causes the rest of our program to fail. Luckily for us, the GetComputerName function will also change depending on the project setting, so all we need to do is incorporate Unicode into our program!

Below is a sample program that correctly behaves.
#include <iostream>
#include "Windows.h"

int main()
{
	TCHAR infoBuf[MAX_COMPUTERNAME_LENGTH + 1];

	DWORD size = MAX_COMPUTERNAME_LENGTH + 1;

	GetComputerName(infoBuf, &size);
    
    	std::wcout << infoBuf << '\n';

	return 0;
}

Since std::wcout will work for both narrow and wide characters, we can use it for both Multi-Byte and Unicode projects! If there is a restriction on usage of std::wcout, you can assemble a std::string from the wide string. As computer names should only contain ASCII characters, there should be no data loss arising from the narrowing conversion.