I see that Visual Studio 2008 and later now start off a new solution with the Character Set set to Unicode. My old C++ code deals with only English ASCII text and is full of:
Note: Wow... Apparently, SOMEONE decided that ALMOST all answers deserved a downmod, even when correct... I took upon myself of upmoding them to balance the downmod...
Let's see if I have my own downmod... :-/
Nine hours ago, someone (probably the one who downvoted every answer but Pavel Radzivilovsky's one) downvoted this answer. Of course, without any comment pointing to what's wrong with my answer.
\o/
What are the changes I need to make to migrate this code so that it works in an ecosystem of Visual Studio Unicode and Unicode enabled libraries? (I have no real need for it work with both ASCII and Unicode, it can be pure Unicode.)
Let's imagine you want to do it gradually (because your app is not small).
I had the same problem in my team: I wanted to produce Unicode ready code coexisting with code that was not Unicode ready.
For this, you must use MS' header tchar.h
, and uses its facilities. Using your own examples:
"Hello World"
----> _T("Hello World")
char
type ----> TCHAR
typechar *
pointers to allocated C strings ----> TCHAR *
pointersstd::string
type ---> This is tricky because you must create your own std::tstring
tstring.hpp
headerTo handle the STL with my compiler (at that time, I was working on Visual C++ 2003, so your mileage could vary), I have to provide a tstring.hpp
header, which is both cross platform and enable the user to use tstring, tiostream, etc.. I can't put the complete source here, but I will give an extract that will enable your to produce your own:
namespace std
{
#ifdef _MSC_VER
#ifdef UNICODE
typedef wstring tstring ;
typedef wistream tistream ;
// etc.
#else // Not UNICODE
typedef string tstring ;
typedef istream tistream ;
// etc.
#endif
#endif
} // namespace std
Normally, it is not authorized to pollute the std
namespace, but I guess this is Ok (and it was tested Ok).
This way, you can prefix most STL/C++ iostreams construct with t
and have it Unicode ready (on Windows).
Now, you can switch from ANSI mode to UNICODE mode by defining the UNICODE
and _UNICODE
defines, usually in the project settings (I remember on Visual C++ 2008 that there are entries in the first settings pages exactly for that).
My advice is, as you probably have a "Debug" and a "Release" mode on you Visual C++ project, to create a "Debug Unicode" and "Release Unicode" mode derived from them, where the macros described above are defined.
Thus, you'll be able to produce ANSI and UNICODE binaries.
If you want your app to be cross-platform, ignore this section.
Now, either you can modify all your codebase in one step, or you already converted all your codebase to use the tchar.h
features described above, you can now remove all macros from your code:
_T("Hello World")
----> L"Hello World"
TCHAR
type ----> wchar_t
typeTCHAR *
pointers to allocated C strings ----> wchar_t *
pointersstd::tstring
type ---> std::wstring
type, etc.One common misconception on Windows is to believe on wchar_t character is one Unicode glyph. This is wrong, as some Unicode glyphs are represented by two wchar_t.
So, any code that relies on one char
being one glyph will potentially break if you uses Unicode glyphs not from the BMP.
Is it also possible to do this in a platform independent way? (i.e., by not using Microsoft types.)
Now, this was the tricky part.
Linux (I don't know for other OSes, but it should be easy to infer from either the Linux or the Windows solution) is now Unicode ready, the char
type supposed to contain an UTF-8 value.
This means that your app, once compiled, for example, on my Ubuntu 10.04, is by default Unicode.
Of course, the advice above on UTF-16 and wide chars is even more critical here:
An Unicode glyph can need from 1 to 4 char
characters to be represented. So any code you use that relies on the assumption that every char
is an intependant Unicode character will break.
tchar.h
on Linux!My solution: Write it.
You only need to define the 't' prefixed symbols to map over the normal ones, as shown in this extract:
#ifdef __GNUC__
#ifdef __cplusplus
extern "C" {
#endif
#define _TEOF EOF
#define __T(x) x
// etc.
#define _tmain main
// etc.
#define _tprintf printf
#define _ftprintf fprintf
// etc.
#define _T(x) __T(x)
#define _TEXT(x) __T(x)
#ifdef __cplusplus
}
#endif
#endif // __GNUC__
... and include it on Linux instead of including the tchar.h
from Windows.
tstring
on Linux!Of course, the STL mapping done above for Windows should be completed to handle Linux' case:
namespace std
{
#ifdef _MSC_VER
#ifdef UNICODE
typedef wstring tstring ;
typedef wistream tistream ;
// etc.
#else // Not UNICODE
typedef string tstring ;
typedef istream tistream ;
// etc.
#endif
#elif defined(__GNUC__)
typedef string tstring ;
typedef istream tistream ;
// etc.
#endif
} // namespace std
Now, you can use _T("Hello World")
and std::tstring
on Linux as well as Windows.
And there is.
First, there is the problem of the pollution of the std
namespace with your own t
prefixed symbols, which is supposed to be forbidden. Then, don't forget the addition on macros, which will pollute your code. In the current case, I guess this is Ok.
Two, I supposed you were using MSVC on Windows (thus the macro _MSC_VER
) and GCC on Linux (thus the macro __GNUC__
). Modify the defines if your case is different.
Three, your code must be Unicode neutral, that is, you must no rely on your strings to be either UTF-8 or UTF-16. In fact, your source should be empty of anything but ASCII chars to remain cross-platform compatible.
This means that some features, like searching for the presence of ONE Unicode Glyph must be done by a separate piece of code, which will have all the #define
needed to make it right.
For example, searching the character é
(Unicode Glyph 233) would need you to search for the first character 233 when using UTF-16 wchar_t on Windows, and the first sequence of two characters 195 and 169 on UTF-8 char
. This means you must either use some Unicode library to do it, or write it yourself.
But this is more an issue of Unicode itself than Unicode on Windows or on Linux.
So what?
The "canonical" example I saw described was the EDIT Win32 control which is supposed to be unable to backspace correctly a non-BMP UTF-16 char on Windows (Not that I did not verify the bug, I just don't care enough).
This is a Microsoft issue. Nothing you'll decide in your code will change the fact this bug exist or not in the Win32 API. So using UTF-8 chars on Windows won't correct the bug on the EDIT control. The only thing you can hope to do is to create your own EDIT control (subclass it and handle the BACKSPACE event correctly?) or your own conversion functions.
Don't mix two different problems, that is: a supposed bug in the Windows API and your own code. Nothing in your own code will avoid the bug in the Windows API unless you do NOT use the supposed bugged Windows API.
Yes, it could lead to bugs on some platform that won't happen on others, if you assume too much about characters.
I assumed your primary platform was Windows (or that you wanted to provide a library for both wchar_t
and char
users).
But if this is not the case, if Windows is not your primary platform, then there is the solution of assuming all your char and std::string will contain UTF-8 characters, unless told different. You'll need, then, to wrap APIs to make sure that your char UTF-8 string will not be mistaken for an ANSI (or other codepaged) char string on Windows. For example, the name of the files for the stdio.h
and iostream
libraries will be assumed to be codepaged, as well as the ANSI version of the Win32 API (CreateWindowA, for example).
This is the approach of GTK+ which uses UTF-8 characters, but not, surprisingly, of QT (upon which Linux KDE is built) which uses UTF-16.
Source:
Still, it won't protect you from the "Hey, but Win32 edit controls don't handle my unicode characters!" problem, so you'll still have to subclass that control to have the desired behaviour (if the bug still exists)...
See my answer at std::wstring VS std::string for a complete difference between std::string
and std::wstring
.