Что такое TCHAR, WCHAR, LPSTR, LPWSTR,LPCTSTR (итд)

  • Tutorial


Многие C++ программисты, пишущие под Windows часто путаются над этими странными идентификаторами как TCHAR, LPCTSTR. В этой статье я попытаюсь наилучшим способом расставить все точки над И. И рассеять туман сомнений.

В свое время я потратил много времени копаясь в исходниках и не понимал что значат эти загадочные TCHAR, WCHAR, LPSTR, LPWSTR,LPCTSTR.
Недавно нашел очень грамотную статью и представляю ее качественный перевод.
Статья рекомендуется тем кто бессонными ночами копошиться в кодах С++.

Вам интересно ??
Прошу под кат!!!


В общем, символ строки может быть представлен в виде 1-го байта и 2-х байтов.
Обычно одно-байтовый символ это символ кодировки ANSI- в этой кодировке представлены все английские символы. А 2-х байтовый символ это кодировка UNICODE, в которой могут быть представлены все остальные языки в мире.

Компилятор Visual C++ поддерживает char и wchar_t как встроенные типы данных для кодировок ANSI и UNICODE.Хотя есть более конкретное определение Юникода, но для понимания, ОС Windows использует именно 2-х байтовую кодировку для много языковой поддержки приложений.

Для представления 2-х байтовой кодировки Юникод Microsoft Windows использует UTF16-кодирование.
Microsoft стала одна из первых компаний которая начала внедрять поддержку Юникода в своих операционных системах (семейство Windows NT).


Что делать если вы хотите чтобы ваш С/С++ код был независимым от кодировок и использование разных режимов кодирования?

СОВЕТ. Используйте общие типы данных и имена для представления символов и строк.

Например, вместо того чтобы менять следующий код:
char cResponse; // 'Y' or 'N'
char sUsername[64];
// str* functions  (с типом char работают функции который начинаются с префикса str*)


На этот!!!
wchar_t cResponse; // 'Y' or 'N'
wchar_t sUsername[64];
// wcs* functions   (с типом wchar_t работают функции который начинаются с префикса wcs*)


В целях поддержки многоязычных приложений (например, Unicode), вы можете писать код в более общей манере.
#include<TCHAR.H> // Implicit or explicit include
TCHAR cResponse; // 'Y' or 'N'
TCHAR sUsername[64];
// _tcs* functions  (с типом TCHAR работают функции который начинаются с префикса _tcs*)


В настройках проекта на вкладке GENERAL есть параметр CHARACTER SET который указывает в какой кодировке будет компилироваться программа:



Если указан параметр «Use Unicode Character set», тип TCHAR будет транслироваться в тип wchar_t. Если указан параметр «Use Multi-byte character set» то тогда TCHAR будет транслироваться в тип char. Вы можете свободно использовать типы char и wchar_t, и настройки проекта никоим образом не повлияют на использование этих ключевых слов.

TCHAR определен так:
#ifdef _UNICODE
typedef wchar_t TCHAR;
#else
typedef char TCHAR;
#endif


Макрос _UNICODE будет включен если вы укажите «Use Unicode Character set» и тогда тип TCHAR будет определен как wchar_t. Когда же вы укажите «Use Multi-byte character set» TCHAR будет определен как char.

Помимо этого, для того что была поддержка нескольких наборов символов используя общий базовый код, и возможно поддержки много языковых приложений, используйте Специфические функции (то есть макросы).
Вместо того чтобы использовать strcpy, strlen, strcat (в том числе защищенные варианты функции с префиксом _s), или wcscpy, wcslen, wcscat (включая защищенные варианты), вам лучше использовать функции _tcscpy, _tcslen, _tcscat.

Как вы знаете функция strlen описана так:
size_t strlen(const char*);


И функция wcslen описана так:
size_t wcslen(const wchar_t* );


Вам лучше использовать _tcslen, который логически описан так:
size_t _tcslen(const TCHAR* );


WC это Wide Character (Большой символ). Поэтому, wcs функции будут для wide-character-string (то есть для большой-символьной-строки).Таким образом _tcs будет означать _T символьная строка. И как вы знаете строки с префиксом _T могут быть типа char или wchar_t.

Но в реальности _tcslen (и другие функции с префиксом _tcs) вовсе не функции, это макросы. Они просто описаны как:
#ifdef _UNICODE
#define _tcslen wcslen 
#else
#define _tcslen strlen
#endif


Вы можете просмотреть заголовочный файл TCHAR.H и поискать там еще Макро описания похожее на вышеупомянутое.

Таким образом TCHAR оказывается вовсе не типом, а надстройкой над типами char и wchar_t. Позволяя тем самым выбирать мульти язычное приложение у нас будет или же все таки, одно язычное.

Вы спросите почему они описаны как макросы, а не как полноценная функция ??
Причина проста: Библиотека или DLL могут экспортировать простую функцию с тем же именем и прототипом (Исключая концепцию перегрузки в С++).
Для примера если вы экспортируете функцию:
void _TPrintChar(char);


Как должен вызывать ее клиент ?? Как:
void _TPrintChar(wchar_t);


_TPrintChar магическим образом может быть преобразована в функцию принимающая двух байтовый символ в качестве аргумента.

Для этого мы сделаем две различные функции:
 void PrintCharA(char); // A = ANSI ( для однобайтового)
void PrintCharW(wchar_t); // W = Wide character (для двухбайтового)


И простой макрос скроет разницу между ними:
#ifdef _UNICODE
void _TPrintChar(wchar_t); 
#else 
void _TPrintChar(char);
#endif


Клиент просто вызовет функцию как
TCHAR cChar;
_TPrintChar(cChar);


Заметьте, что TCHAR и _TPrintChar теперь будут сопоставимы с UNICODE или ANSI, а переменная cChar и параметр функции будет сопоставим с типом данных char или wchar_t.

Макросы дают нам обойти эти сложности, и позволяют нам использовать ANSI или UNICODE функции для наших символов и строк. Множество функций Windows описаны именно таким образом, и для программиста есть только одна функция (то есть макрос) и это хорошо.

Приведу пример с SetWindowText:
// WinUser.H
#ifdef UNICODE
#define SetWindowText  SetWindowTextW
#else
#define SetWindowText  SetWindowTextA
#endif // !UNICODE


Есть только несколько функций у которых нету таких макросов, и они только с суффиксом W или A. Пример тому функция ReadDirectoryChangesW, которая не имеет эквивалента в кодировки ANSI.



Как вы знаете, мы используем двойные кавычки для представления строк. Строка представленная в этой манере это ANSI-строка, на каждый символ используется 1 байт. Приведу пример:
“Это ANSI строка. Каждый символ занимает 1 байт.”


Указанная верху строка не является строкой UNICODE, и не подходит для много языковой поддержки. Для того чтобы получить UNICODE строку вам надо использовать префикс L.
Приведу пример:
L”Это Unicode строка. Каждый символ которого занимает 2 байта, включая пробелы. ”


Поставьте спереди L и вы получите UNICODE строку. Все символы (Я повторяю все символы ) занимают 2 байта, включая Английские буквы, пробелы, цифры и символ null. Объем данных строки Unicode всегда будет кратен 2-м байтам. Строка Unicode длиной 7 символов будет занимать 14 байтов. Если строка Unicode занимает 15 байтов то это не правильная строка, и она не будет работать в любом контексте.

Также, строка будет кратна размеру sizeof(TCHAR) в байтах.

Когда Вам нужно жестко прописанный код, вы можете писать код так:
"строка ANSI"; // ANSI
L"строка Unicode"; // Unicode

_T("Или строка, зависящая от компиляции"); // ANSI или Unicode
// или используйте макрос TEXT, если вам нужна хорошая читаемость кода


Строки без префикса это ANSI строки, с префиксом L строки Unicode, и строки с префиксом _T и TEXT зависимые от компиляции. И опять же _T и TEXT это снова макросы. Они определены так:
// УПРОЩЕННАЯ
#ifdef _UNICODE 
 #define _T(c) L##c
 #define TEXT(c) L##c
#else 
 #define _T(c) c
 #define TEXT(c) c
#endif


Символ ## это ключ(token) вставки оператора, который превратит _T(«Unicode») в L«Unicode», где строка это аргумент для макроса- если конечно _UNICODE определен.
Если _UNICODE не определен то _T(«Unicode») превратит это в «Unicode». Ключ вставки оператора существовал даже в языке С, и это не специфическая вещь связанная с кодировкой строк в VC++.

К сведению, макросы могут применятся не только для строк но и для символов. Например _T('R') превратит это в L'R' ну или в просто 'R'. Тоесть либо в Unicode символ либо в ANSI символ.

Нет и еще раз нет, вы не можете использовать макрос чтобы конвертировать символ или строку в Unicode и не Unicode текст.
Следующий код будет неправильным:
char c = 'C';
char str[16] = "Habrahabr";
_T( c );
_T(str);


Строки _T( c); _T(str); отлично скомпилируются в режиме ANSI, _T(x) превратится в x, и _T( c) вместе с _T(str) превратятся просто в c и str.
Но когда вы будете собирать проект в режиме Unicode код не с компилируется:
error C2065: 'Lc' : undeclared identifier
error C2065: 'Lstr' : undeclared identifier


Я не хотел бы вызывать инсульт вашего интеллекта и объяснять почему это не работает.

Существует несколько функций для конвертирования Мульбайтовых строк в UNICODE, о которых я скоро расскажу.

Есть важное замечание, почти все функции которые принимает строку или символ, приоритетно в Windows API, имеют обобщенное название в MSDN и в других местах.
Функция SetWindowTextA/W будет классифицирована как:
BOOL SetWindowText(HWND, const TCHAR*);


Но как Вы знаете, SetWindowText это просто макрос, и в зависимости от настроек проекта будет рассматриваться как:
BOOL SetWindowTextA(HWND, const char*);
BOOL SetWindowTextW(HWND, const wchar_t*);


Так что не ломайте голову если не сможете получить адрес этой функции:
HMODULE hDLLHandle;
FARPROC pFuncPtr;

hDLLHandle = LoadLibrary(L"user32.dll");

pFuncPtr = GetProcAddress(hDLLHandle, "SetWindowText");
//значение pFuncPtr будет null, потому что фунций с названием SetWindowText даже не существовало 


В библиотеке User32.DLL, имеются 2 функции SetWindowTextA и SetWindowTextW которые экспортируются, то есть тут нет имен с обобщенным названием.

Все функции которые имеют ANSI и UNICODE версию, вообще то имеют только UNICODE реализацию. Это значит, что когда Вы вызываете SetWindowTextA из своего кода, передавая параметр ANSI строку — она конвертирует ANSI в UNICODE вызывает SetWindowTextW.
Реальную работу (установку заголовка/названия/метки окна) делает только Unicode версия!

Возьмем другой пример, который будет получать текст окна, используя GetWindowText.
Вы вызываете GetWindowTextA передавая ему ANSI буфер как целевой буфер.
GetWindowTextA сначала вызовет GetWindowTextW, возможно выделяя память для Unicode строки (т.е массив wchar_t).
Затем он с конвертирует Unicode в ANSI строку для вас.

Эти ANSI в Unicode преобразования не ограничение только GUI функций, а так работает все подмножество Windows API функций, которое принимает строки и имеет два варианта.
Приведу еще пример таких функций:
  • CreateProcess
  • GetUserName
  • OpenDesktop
  • DeleteFile
  • итд


Поэтому очень рекомендуется вызывать напрямую Unicode функции.
В свою очередь, это означает, что вы всегда должны быть нацелены на сборку Unicode версии, а не на сборку ANSI версии, учитывая тот факт, что вы привыкли использовать ANSI строки в течение многих лет.

Да вы можете сохранять и получать ANSI строки, например для записи в файл, или отправки сообщения чата в ваше программе-чата. Функции конвертации существуют для таких нужд.

Замечание: Есть еще одно описание типа: имя ему WCHAR – оно эквивалентно wchar_t.



TCHAR это макрос, для декларирования одного символа. Вы также можете декларировать массив TCHAR. А что если Вы например захотите описать указатель на символы или, константный указатель на символы.
Приведу пример:
// ANSI символы
foo_ansi(char*); 
foo_ansi(const char*); 
/*const*/ char* pString; 

// Unicode/wide-string 
foo_uni(WCHAR*); 
wchar_t* foo_uni(const WCHAR*); 
/*const*/ WCHAR* pString; 

// Независимые 
foo_char(TCHAR*); 
foo_char(const TCHAR*); 
/*const*/ TCHAR* pString;


После чтения фишек с TCHAR, вы наверное предпочтете использовать именно его. Существуют еще хорошие альтернативы для представления строк в вашем коде. Для этого надо просто включить Windows.h в проект.
Примечание: Если ваш проект включает windows.h (косвенным или прямым образом), вы не должны включать в проект TCHAR.H.
Для начала пересмотрим старую функцию, чтобы было легче понять. Пример функцию strlen.
size_t strlen(const char*);

Которая может быть представлена по другому.
size_t strlen(LPCSTR);


Где LPCSTR описан как:
// Simplified
typedef const char* LPCSTR;  


LPCSTR понимается так.
• LP — Long Pointer (длинный указатель)
• C – Constant (константа)
• STR – String (строка)
По сути LPCSTR это (Длинный) указатель на строку.

Давайте изменим strcpy в соответствие с новым стилем имени типов:
LPSTR strcpy(LPSTR szTarget, LPCSTR szSource);


szTarget имеет тип LPSTR, без использования типов языка С. LPSTR определен так:
typedef char* LPSTR;


Заметьте что szSource имеет тип LPCSTR, так как функция strcpy не модифицирует исходный буфер, поэтому выставлен атрибут const. Возвращаемый тип данных не константная строка: LPSTR.

Итак, функции с префиксом str для манипуляции с ANSI строками. Но нам нужна еще для двух байтовых Unicode строк. Для тех же больших символов имеются эквивалентные функции.
Для примера, чтобы посчитать длину символов больших символов(Unicode строки), вы будете использовать wcslen:

size_t nLength;
nLength = wcslen(L"Unicode");


Прототип функции wcslen такой:
size_t wcslen(const wchar_t* szString); // Или WCHAR*


И код выше может быть представлен по другому:
size_t wcslen(LPCWSTR szString);


Где LPCWSTR описан так:
typedef const WCHAR* LPCWSTR;
// const wchar_t*


LPCWSTR можно понять так:
LP — Long Pointer (Длинный указатель)
C — Constant (константа)
WSTR — Wide character String (строка больших символов)

Аналогичным образом, strcpy эквивалент wcscpy, для Unicode строк:
wchar_t* wcscpy(wchar_t* szTarget, const wchar_t* szSource)


Который может быть представлен как:
LPWSTR wcscpy(LPWSTR szTarget, LPWCSTR szSource);


Где szTarget это не константная большая строка (LPWSTR), а szSource константная большая строка.

Существует ряд эквивалентных wcs-функций для str-функций. str-функции будут использоваться для простых ANSI строк, а wcs-функции для Unicode строк.

Хотя Я уже советовал что надо использовать native Unicode функции, а не только ANSI или только синтезированные TCHAR функции. Причина проста — ваше приложение должно быть только Unicode-ным, и вы не должны заботится о том что спортируются ли они для ANSI. Но для полноты картины я и упомянул эти общие отображения (проецирования)!!!

Чтобы вычислить длину строки, вы можете использовать _tcslen функцию (макро).
Который описан так:

size_t _tcslen(const TCHAR* szString); 


Или так:
size_t _tcslen(LPCTSTR szString);


Где имя типа LPCTSTR можно понять так
LP — Long Pointer (Длинный указатель)
C — Constant (Константа)
T = TCHAR
STR = String (Строка)

В зависимости от настроек проекта, LPCTSTR будет проецироваться в LPCSTR (ANSI) или в LPCWSTR (Unicode).

Заметьте: функции strlen, wcslen или _tcslen будут возвращать количество символов в строке, а не количество байтов.

Обобщенная операция копирования строки _tcscpy описана так:
size_t _tcscpy(TCHAR* pTarget, const TCHAR* pSource);


Или в еще более обобщенной манере, как:
size_t _tcscpy(LPTSTR pTarget, LPCTSTR pSource);


Вы можете догадаться что значит LPTSTR ))



Примеры использования.


Во первых приведу пример нерабочего кода:
int main()
{
    TCHAR name[] = "Saturn";
    int nLen; // Or size_t

    lLen = strlen(name);
}


На ANSI сборке, этот код успешно с компилируется потому что TCHAR будет типом char, и переменная name будет массивом char. Вызов strlen для name тоже будет прекрасно работать.

Итак. Давайте с компилируем тоже самое с включенными UNICODE/_UNICODE (в настройках проекта выберите «Use Unicode Character Set»).
Теперь компилятор будет выдавать такого рода ошибки:
error C2440: 'initializing' : cannot convert from 'const char [7]' to 'TCHAR []'
error C2664: 'strlen' : cannot convert parameter 1 from 'TCHAR []' to 'const char *'


И программисты начнут исправлять ошибку таким образом:
TCHAR name[] = (TCHAR*)"Saturn";


И это не усмирит компилятор, потому что конвертирование из TCHAR* в TCHAR[7] невозможно. Такая же ошибка будет возникать когда встроенные ANSI строки передаются Unicode функции:

nLen = wcslen("Saturn");
// error: cannot convert parameter 1 from 'const char [7]' to 'const wchar_t *'
// Ошибка: не могу с конвертировать параметр 1 из 'const char [7]' в 'const wchar_t *'


К сожалению (или к счастью), эта ошибка может быть неправильно исправлена простым приведением типов языка C.
nLen = wcslen((const wchar_t*)"Saturn");


И вы думаете что повысили уровень своего опыта при работе с указателями. ВЫ не правы -этот код будет давать неправильный результат, и в большинстве вы будете получать Access Violation (нарушение доступа). Приведение типов таким образом это как передача float-переменной когда ожидалось(логически) структура размером 80 байт.

Строка «Saturn» это последовательность 7 байт:
'S' (83) 'a' (97) 't' (116) 'u' (117) 'r' (114) 'n' (110) '\0' (0)


Но когда вы передаете тот же набор байтов в wcslen, он рассматривает каждые 2 байта как один символ. Поэтому первые 2 байта [97,83] будут рассматриваться как один символ имеющий значение 24915(97<<8 | 83). Это Unicode символ ???.. И другие следующие символы рассматриваются как [117,116] и так далее.

Конечно вы не передавали эти Китайские символы, но приведение типов сделало это за Вас!!!
И поэтому очень важно знать что приведение типов не будет работать. Итак для инициализации первой строки вы должны сделать следующее:
TCHAR name[] = _T("Saturn");


Который будет транслировать в 7 или в 14 байт, в зависимости от компиляции.
Вызов wcslen должен быть таким:
wcslen(L"Saturn");


В примере кода программы, приведенные выше, я использовал strlen, что вызывает ошибки при сборке Unicode.
Приведу пример нерабочего решение с приведением типов языка C:
lLen = strlen ((const char*)name);


На сборках Unicode переменная name будет размером 14 байт (7 unicode символов, включая null). Так как строка
«Saturn» содержит только Английские символы, которые можно представить используя ASCII кодирование, Unicode символ 'S' будет представлен как [83, 0]. Следующие ASCII символы будут представлены как нули. Заметьте сейчас символ 'S' представлен как 2-х байтовое значение 83. Конец строки будет представлен как 2 байта имеющее значение 0.

Итак, когда вы передаете такую строку в strlen, первый символ (то есть первый байт) будет правильным ('S' в случае с 'Saturn'). Но следующий символ/байт будет идентифицирован как конец строки. Поэтому, strlen вернет неправильное значение 1.

Как Вы знаете, Unicode строка может содержать не только Английские символы, и результат strlen будет еще более неопределенным.

Короче говоря приведение типов не будет работать.
Вам придется, либо представлять строки в правильной форме, или использовать функции конвертирования ANSI в Unicode, и обратно.




Теперь, Я надеюсь Вы понимаете следующий код:
BOOL SetCurrentDirectory( LPCTSTR lpPathName );
DWORD GetCurrentDirectory(DWORD nBufferLength,LPTSTR lpBuffer);


Продолжая тему. Вы наверное видели некоторые функции/методы которым нужно передавать количество символов, или возвращающие количество символов. Впрочем есть GetCurrentDirectory, в которую надо передавать число символов, а не количество байт.
Пример:
TCHAR sCurrentDir[255]; 
// Передавайте 255 а не 255*2 
GetCurrentDirectory(sCurrentDir, 255);


С другой стороны, если вам нужно выделять память для нужного количества символов, вы должны выделять надлежащее количество байт. В C + +, вы можете просто использовать оператор new:

LPTSTR pBuffer; // TCHAR* 
pBuffer = new TCHAR[128]; // Выделяет 128 или 256 байт, в зависимости от компиляции.


Но если вы используете функции выделения памяти, такие как malloc, LocalAlloc, GlobalAlloc, и т.д., вы должны указывать количество байт!

pBuffer = (TCHAR*) malloc (128 * sizeof(TCHAR) );


Как вы знаете необходимо приведение типа возвращаемого значения. Выражение в аргументе malloc гарантирует, что оно выделяет необходимое количество байт — и выделяет места для нужного количества символов.

P.S.

Оригинал статьи

Всех с НГ!!!
Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама
Комментарии 84
  • –21
    Спасибо огромное за статью. Но увы для меня вы опоздали на 3 кода. Сейчас я уже не пишу на с++. К счастью или к сожалению, я не сталкиваюсь больше с такими заморочками. Но кто знает, где еще могут пригодится знания с++. Так что еще раз спасибо. Кстати. Кто мне может объяснить, зачем нужны такие свистопляски? Почему не унифицировали типы, связанные со строками?
    • +25
      Вот нафига приходить в хаб по С++ с комментарием «я уже 3 года не пишу на С++ и не сталкиваюсь с такими заморочками»?
      А на счёт унификации — это, конечно, огромный провтык С++ (еще со времен языка С) — общего удобного строкового типа в этих языках нет. Массивы char — неудобны, строки stl чуть получше, но по удобству до строк из Qt или там ATL не дотягивают, а удобные строки из вышеуказанных (или других) библиотек, к сожалению, ни разу не стандартны. Вот поэтому и свистопляски с типами.
      • 0
        Уважаемый. Пришел я сюда потому, что мне данная тема до сих пор интересна, пусть я и не использую с++. За объяснение спасибо:-)
      • +8
        >> Кто мне может объяснить, зачем нужны такие свистопляски

        Дополню. Win API складывалось, когда в ходу были 486-е/Пентиумы. Типичной конфигурацией машины с Win95 был Pentium-100 / 16 Mb RAM.

        Для полноценной обработки unicode нужны многомегабайтные таблицы (to upper case, to lower case, сортировка, например чтобы «Fü» шло после «Fu», а не по unicode-значению, функции классификатора символа (буква/цифра/пробел/знак пунктуации). Современная библиотека International Components for Unicode весит около 20Мб в DLL. Не говоря уже о шрифтах, содержащих все unicode-знаки (от 16 Мб и выше). В-общем рановато было в домашнюю Win95 делать unicode, но в корпоративную WinNT сделали.

        Затем был переход с ANSI-программ на UNICODE. Мне это сильно напоминает переход с x86 на x64: заявлено, что «если всё написано правильно, достаточно простого #define», но на самом деле работа по портированию была сложна. Да и мало кто сейчас пишет int _tmain(int argc, _TCHAR* argv[]) вместо привычного int main(int argc, char* argv[]), как и не все программы создаются с возможностью собрать в x64.

        Чтобы два раза не вставать, напомню про ещё одно legacy: LP = long pointer — наследие win3.11 и 16-битного компилятора, когда указатели были короткие (16 бит) и длинные (far, 32-bit) :)

        Если кому-то статья нужна для практических целей, нужно обязательно прочитать о различии символов препроцессора UNICODE и _UNICODE.
        • 0
          насчет LP согласен, просто так не выпилишь историю… Кому нужны проблемы с портированием ??

          Да в любом случае UNICODE и _UNICODE это препроцесорная тема, в каждой платформе по разному ))).
          • +3
            >а и мало кто сейчас пишет int _tmain(int argc, _TCHAR* argv[]) вместо привычного int main(int argc, char* argv[]), как и не все программы создаются с возможностью собрать в x64.

            И правильно делают. Это стандарт, если в винде не смогли в рамках стандарта осилить поддержку юникода, то это исключительно их половые трудности. Не нужно ими захламлять стандарт. Код должен быть переносимым, это один из критериев хорошего кода.
          • +4
            Эээхххх… Жестокие вы, друзья. Впредь буду аккуратнее с комментариями.
          • 0
            потому что очень много Операционных систем, и компиляторов, freepascal например не юникодный, а в нем используется библиотека LCL. И не все поддерживают идеологию Microsoft для работы с TCHAR.
            • +15
              >А 2-х байтовый символ это кодировка UNICODE
              Это, строго говоря, неверно.

              Вообще, распространёнными заблуждениями являются 2 мифа: «любой Юникодный символ можно хранить в двухбайтовом wchar_t/CHAR16/etc» и (следствие) «к любому символу внутри Юникодной строки можно обратиться по индексу».
              • +12
                На дворе 2002 год? Мне казалось, что вот-вот наступит 2013, а оно вона как оказывается.
                • –4
                  Супер статья, автору огромное спасибо!
                  Всех с наступающим!
                  • +2
                    Еще у Рихтера лет 6 назад читал об этом. Наверно лучшее описание Win API именно у него.
                    • +15
                      Всех с наступающим! Как большой специалист по С++, с позволения автора, немного дополню:

                      В общем, символ строки может быть представлен в виде 1-го байта и 2-х байтов.


                      От 1-го до 4-х, конечно. UCS-4 и китайские иероглифы пока никто не отменил :)

                      Обычно одно-байтовый символ это символ кодировки ANSI-


                      Нет такой кодировки «ANSI». Вы, скорее всего, имеете в виду кодировку «latin-1». По поводу кодировок рекомендую всем легендарный труд Джоэла: www.joelonsoftware.com/articles/Unicode.html

                      А 2-х байтовый символ это кодировка UNICODE


                      Опять же, нет такой кодировки как UNICODE, см. выше. Авторы Microsoft утверждают, что у них кодировка UTF-16, но до недавнего времени попытка использовать 4-х байтовые символы из этого набора приводила к access violation. Так что по факту у них все же UCS-2 :).

                      Для представления 2-х байтовой кодировки Юникод Microsoft Windows использует UTF16-кодирование.


                      UTF-16 — кодировка с плавающим количеством байт на символ, от 2 до 4, в зависимости от символа. См. выше.

                      Строка Unicode длиной 7 символов будет занимать 14 байтов. Если строка Unicode занимает 15 байтов то это не правильная строка, и она не будет работать в любом контексте.
                      Также, строка будет кратна размеру sizeof(TCHAR) в байтах.


                      Может быть sizeof(WCHAR)? :)
                      • +2
                        с такой формулировкой («будет кратна»), она будет кратна и sizeof(char), и sizeof(TCHAR), и sizeof(wchar_t) =)
                        • +3
                          Я так понимаю, все вопросы к тов. Ajay Vijayvargiya (Индия), с 96 года все не может разобраться с кодировками, но уже гордо именуется «Senior».
                          • +3
                            Вообще в связи с тем что профессия «программист» становится все попсовее, то участились резко случаи появления статей которые больше вредят, нежели помогают.
                            • +3
                              Хех, автор оригинальной статьи еще гордо заявляет, что весь гуй пишет на MFC o___O
                              Индусы такие индусы, и советы у них индуские…
                            • –4
                              Вы путаете кодировку UTF-16, в которой каждый символ 2 байта, с UTF-8, где символ занимает от 1 до 4х байт. И в windows под UNICODE понимается UTF-16.
                              • +5
                                В UTF-16 символ занимает либо 2, либо 4 байта. Инфа 146% :). А строго 2 байта — это UCS-2 O_O.
                                И вас с наступающим :).
                                • –2
                                  В API Win32, распространённом в современных версиях операционной системы Microsoft Windows, имеется два способа представления текста: в форме традиционных 8-битных кодовых страниц и в виде UTF-16.
                                  В файловых системах NTFS, а также FAT с поддержкой длинных имён, имена файлов записываются в UTF-16LE.

                                  ПРУФ ЛИНК
                                  • +2
                                    И вас с наступающим. А как это коррелирует с тем, что я утверждаю что в UTF-16 либо 2 либо 4 байта на символ, а RubtsovAV — что 2 байта?
                                    • 0
                                      Let us HOLLY WAR begin. Извините не думал что будет такой холивар… Прошло 2 часа после НГ, и комменты посыпались…
                                      • +2
                                        Остролистная война — это круто
                                      • +3
                                        Все же вы правы. До сего момента не знал про 4байта, спасибо.
                                  • +2
                                    ORLY?

                                    Вы путаете кодировку UTF-16, в которой каждый символ 2 байта, с UTF-8, где символ занимает от 1 до 4х байт.

                                    www.unicode.org/faq/utf_bom.html

                                    И в windows под UNICODE понимается UTF-16

                                    Во первый unicode это unicode, тут нечего «подразумевать». Во вторых, windows до какой-то версии использовали в качестве кодировки UCS-2, поддержку UTF-16 добавили позже.
                                    • –3
                                      Про 4 байта, все верно — я не знал, про такую особенность.
                                      Слово Unicode контекстно зависимое, и, на сколько я знаю это сокращение слово сочетания «Universal Code», что переводится как «универсальное кодирование». Линуксоид услышав юникод первым делом поймет как UTF8, а поклонник мелкомягких как UTF16. Если же взять во внимание, что в статье много раз говориться про windows, то не должно остаться сомнений про какой юникод речь.
                                      С новым годом!
                                • +18
                                  TCHAR, LP©TSTR — не нужно. Их нужно закопать, глубоко и надолго. Поддержка вин98 нынче никому не нужна, поэтому включаем режим UNICODE всегда.
                                  И предпочтительно использовать стандартные типы char*, std:string или wchar_t*,std:wstring.

                                  А вообще, С++ без фреймворков не юзабелен. Используя стандартные библиотеки С++, вы даже не сможете открыть файл с юникодным именем под виндой.
                                  • –3
                                    вы даже не сможете открыть файл с юникодным именем под виндой.


                                    Я тоже думал нельзя. Оказывается можно
                                    //Юникод включен
                                    TCHAR* FileName;
                                    
                                    USES_CONVERSION;
                                    char* FNAME = T2A( FileName);
                                    ifstream TargetFile(FNAME,ios::in | ios::binary);
                                    
                                    • 0
                                      Речь шла о стандартных библиотеках С++.
                                      В каком хидере объявлен T2A?
                                      • –3
                                        basic_filebuf<Elem, Tr> *open(
                                            const char *_Filename,
                                            ios_base::openmode _Mode,
                                            int _Prot = (int)ios_base::_Openprot
                                        );
                                        basic_filebuf<Elem, Tr> *open(
                                            const char *_Filename,
                                            ios_base::openmode _Mode
                                        );
                                        basic_filebuf<Elem, Tr> *open(
                                            const wchar_t *_Filename,
                                            ios_base::openmode _Mode,
                                            int _Prot = (int)ios_base::_Openprot
                                        );
                                        basic_filebuf<Elem, Tr> *open(
                                            const wchar_t *_Filename,
                                            ios_base::openmode _Mode
                                        );
                                        
                                        


                                        Header: "<«fstream»>"
                                        Namespace: std
                                        • +2
                                          1. Так в каком хидере объявлен T2A? (правильный ответ — atlconv.h)
                                          2. процитированы нестандартные хидеры, стандарт с++ читайте
                                          www.cplusplus.com/reference/fstream/ofstream/open/

                                          Если речь о венде, лучше сразу CreateFileW, т.к. у меня негативный опыт от использования unicode в c/c++ stdlib.
                                          Например, спецификатор %S в форматке печатает unicode-символы, если они стандартные ASCII и не печатает русские и т.п. Не удивлюсь, если вышенаписанный пример будетт создавать файл только c ascii-знаками в названии
                                          • –2
                                            // char TYPEDEFS
                                            typedef basic_ifstream<char, char_traits > ifstream;
                                            // wchat_t TYPEDEFS
                                            typedef basic_ifstream<wchar_t, char_traits<wchar_t> > wifstream;

                                            ПРУФ ЛИНК

                                            1.Забавно другое, когда утверждали стандарт С++, то они куда нибудь выкладывали его например на DROPBOX ??
                                            2.Где лежат принятые стандарты по С++ по хронологии ???
                                            • +2
                                              Последний draft стандарта здесь:
                                              www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3242.pdf

                                              На странице 1060 мы видим, что функция open, а также explicit-конструктор basic_ofstream принимает либо string&, либо char* в качестве имени файла.

                                              Остальное — выдумки Microsoft ))
                                              • –1
                                                Тогда вопрос как такие умные люди сидят там ???
                                                Если даже для стандарта С++ не могу реализовать открытие файлов с Юникод именами ??

                                                Получается есть С++ стандарт и Есть С++ стандарт от каждой компании которая делает свой компилятор ???

                                                а по поводу wchar_t вы не много загнули он входит в стандарт!!!
                                                • 0
                                                  В Стандарте (в версиях разных лет) присутствуют места с плохим дизайном, либо просто вообще никак не сделанных. «Переносимо открыть файл с именем в std::wstring» — одно из таких.
                                                  • +1
                                                    Наверное они там все линуксоиды.
                                                    В современном *nix принято общаться с файловой системой в кодировке utf-8. Соответственно, всё системные функции принимают имена файлов в char*
                                                  • –2
                                                    Не выдумки wchar_t на 1051 странице!!! Что то Сер вы путаете.

                                                    template <class charT, class traits = char_traits<charT> >
                                                    class basic_ofstream;
                                                    typedef basic_ofstream<char> ofstream;
                                                    typedef basic_ofstream<wchar_t> wofstream;
                                                    
                                                    • +2
                                                      wofstream — «для чтения/записи wchar_t данных». Имя файла в std::wstring задать (по Стандарту не самому последнему) нельзя.
                                                      • +1
                                                        Это кодировка данных внутри файла, если режим i/o текстовый (не бинарный).
                                                        Кодировка имени файла — это совсем другое дело.
                                            • +3
                                              А что такое T2A? Впервые вижу
                                          • +5
                                            >Используя стандартные библиотеки С++, вы даже не сможете открыть файл с юникодным именем под виндой.

                                            Что за сказки?
                                            wchar_t name[] = L"Гав-Гав.txt";
                                            std::fstream file(name); //ну флаги там по вкусу
                                            
                                            • 0
                                              в стандарте такого нет
                                              • +1
                                                уже есть
                                                typedef basic_fstream<wchar_t> wfstream;

                                                страница 982
                                                Working Draft, Standard for Programming
                                                Language C++
                                                • +4
                                                  страница 1062
                                                  image
                                                  • –2
                                                    Приведите компилятор и платформу где не реально будет юникод имена использовать для стандартной библиотеки ??? в частности для ifstream!!!

                                                    Теперь я понимаю почему МС начинает выдумывать Юникод течение под свою платформу. Потому что стандарт это чисто теоретическая тема для обсуждения…
                                                    • –4
                                                      И напрашивается вопрос «А как же великий и могучий С++ ?? со своей стандартной библиотекой».
                                                      Хотя наверное было бы бы еще мусора и хаоса без STL… По правде говоря я немного огорчился…
                                                      • +1
                                                        Приведите компилятор и платформу где не реально будет юникод имена использовать для стандартной библиотеки ??? в частности для ifstream!!!


                                                        Канонический, сверхпопулярный GCC. Вот ссылка хидер-файл в SVN разработчиков:
                                                        gcc.gnu.org/svn/gcc/trunk/libstdc++-v3/include/std/fstream
                                                        • 0
                                                          Ты смотри как выкрутилось всё — начали с наезда на винду, дескать не открыть там файлик юникодный, а закончилось пенянием на то что канонически GCC такого не умеет.
                                                          • –7
                                                            По этому хабр и авторитетный ресурс, тут только специалисты, и могут обсудить очень глубокие темы… Даже теперь не знаю какую статью писать ((
                                                            • +3
                                                              Ты смотри как выкрутилось всё — начали с наезда на винду, дескать не открыть там файлик юникодный, а закончилось пенянием на то что канонически GCC такого не умеет.


                                                              Тут нужно дать уточнение к ответу на вопрос «Приведите компилятор и платформу где не реально будет юникод имена использовать для стандартной библиотеки».

                                                              Не реально использовать юникод при использовании GCC под Windows.
                                                              Потому что стандартная библиотека GCC передаёт операционной системе имена файлов только в char*, а файловые АПИ Windows не умеют использовать unicode в char*

                                                              В Линукс этой проблемы нет, там принято передавать имена файлов юникодом в char* в кодировке uft-8.
                                                              • 0
                                                                И Вы считаете это типа нормальным — функция, получающая char* и не имеющая понятия, что вызывающий её захочет туда положить — ANSI, UTF-8, UTF-16 или что-то еще? Т.е. соглашение не на уровне интерфейсов и типов данных, а просто на словах — «так принято»?
                                                                • +2
                                                                  Вполне логично — stdlib без перекодировок передаёт системе имя файла как null-terminated строку, тащить кодировки в стандарт языка было бы странно. Кто же знал, что винда не справится с utf-8 ))

                                                                  Ведь когда мы выводим символы в cout, в стандарте не указано, в какой кодировке они должны быть. Работа stdlib — передать их в системный stdout без изменений.
                                                                  • +1
                                                                    В общем-то, да. И это хорошо: все эти кодировки и прочая — они на уровне процессов остались, в ядро не тянутся.
                                                • +9
                                                  Пффф, отличный гайд как писать платформо-зависимый непереносимый код. У нас какой год на дворе, что что-то не припомню...?
                                                  • –5
                                                    Кстати по поводу какой год на дворе. Плагины для Тотала пишутся либо АНСИ либо Юникод. Так что статья имхо будет полезна писателям плагинов тотал командера!!! Сам недавно кодил плагин с юникодом…
                                                    • +1
                                                      И зачем связываться с АНСИ?
                                                      • –2
                                                        Затем что юникод версия не работает без Анси версии в плагинах тотал командера
                                                        • +1
                                                          А что им мешает сделать pure unicode версию? Зачем вообще анси в прикладном программировании? Я не говорю про микроконтроллеры и подобную периферию, тотал явно не к ним относится.
                                                          • 0
                                                            для поддержки win98 например
                                                        • 0
                                                          Работает. Достаточно экспортировать набор обязательных ANSI-интерфейсов, чтобы Тотал подцепил плагин, но реализовывать их не нужно, хватит какого-нибудь return 0. Вызываться Тоталом они всё равно не будут.
                                                  • +3
                                                    TCHAR, WCHAR, LPSTR, LPWSTR,LPCTSTR.


                                                    А я всегда думал, что здесь все понятно! LP — знач указатель, W значит Wide TCHAR, для всех кому пофиг, ну и так далее)))) Проще быть, проше)))
                                                    • –4
                                                      Комментарий удален
                                                      • +5
                                                        > Символ ## это ключ(token) вставки оператора
                                                        Пфф… это обозначение операции конкатенации для макросов, не более того.

                                                        > Я не хотел бы вызывать инсульт вашего интеллекта и объяснять почему это не работает.
                                                        Несколько оскорбительно звучит для сишника. Определение макроса есть, вопросов быть не должно.
                                                        • +1
                                                          Достаточно специфический перевод «инсульт вашего интеллекта». Я так понял это прямая калька с «insult your intellect», и «insult» в данном контексте лучше перевести как «оскорблять» или хотя бы «недооценивать ваши умственные способности».
                                                          • 0
                                                            Индусский перевод вышел.
                                                            • +3
                                                              Индусский перевод индусской статьи с индусскими советами как писать индусский код? :)
                                                        • +2
                                                          Автор обещал указать макросы для преобразования, но так и не указал:
                                                          CW2CT, CA2CT и пр. для преобразования из одного вида строк в другие:
                                                          CSourceType2[C]DestinationType[EX], где C — признак константности, а тип назначения/источника определяется следующим образом
                                                          • A — ANSI character string.
                                                          • W — Unicode character string.
                                                          • T — Generic character string (equivalent to W when _UNICODE is defined, equivalent to A otherwise).
                                                          • OLE — OLE character string (equivalent to W).
                                                          • –2
                                                            Эххх жалко Плюсик немогу поставить…
                                                          • 0
                                                            >Многие C++ программисты, пишущие под Windows
                                                            Да, кстати. А под Windows неужели до сих пор пишут на WinAPI да на MFC? Это ж было принято в прошлом веке так делать, сейчас то зачем?
                                                            • –1
                                                              Игры, системные утилиты, драйвера уже не в счет? Или Вы думаете .net/vcl в полной мере может это предоставить?
                                                              • +3
                                                                Есть стандартная библиотека C++, есть кроссплатформенные обертки над этим мраком, вы их разрабатываете или же всё-таки приложения?
                                                                По мне так эта статья может быть интересна только тем, кто таки пытается обернуть всё это «наследие бурной молодости» во что-то приятное и переносимое.
                                                                Но зачем на этом аду в 2013 году писать?
                                                                • –1
                                                                  Упомянутое Вами уже не годится для реализации упомянутого мной, кроме, разумеется, игр. Например, если взять типичный случай для «системного» windows-программиста: разработка usermode-драйвера. Я например не видел «кросслпатформенных оберток». Если есть ссылки, то действительно было бы интересно ознакомиться. Или копнуть глубже: kernel-mode.
                                                                  Конечно, для прикладного софта лучшим решением будет с этим не заморачиваться. Я, например, стараюсь все прикладное реализовывать на C#, но статья ведь не о том, что лучше и для каких целей это «лучше» применять. А статья вполне о конкретных вещах, оне ни к чему не принуждает, больше просто описание. А что и когда лучше использовать тема уже отдельной и весьма спорной статьи.
                                                                  • –2
                                                                    В свое время я изучил пасакль, потом дельфи, ну а потом Visual C++. изучая Winapi итд стало интересно как же апи работает на под винду. Так или иначе если пишешь на WINAPI используй VC++.
                                                                    Зуб даю на отсечение 30 процентов только знает кто такой Джефри Рихтер. Немногие читали его книгу…
                                                                    • +1
                                                                      Игры сейчас как раз стараются кроссплатформенными делать, платформ то много. Прикладуха прекрасно на плюсах пишется без глупого запудривания себе мозгов вот этим кошмаром.
                                                                      Kernel development это отдельная стезя, там уже тяжелее в разы кроссплатформенность делать. Но хотя и там это возможно, видео драйверы имеют общую часть кода и платформозависимые части и спокойно собираются и работают на Windows, Linux и Macos X.
                                                                      А в usermode драйверах лучше таки использовать кроссплатформенные обертки типа libusb и тоже не заморачиваться с этими кишками винды, выглядят они некрасиво.
                                                                  • +2
                                                                    И что ж в «играх» такого, что на WinAPI или MFC писать надо? Там наоборот платформно-зависимый код минимизируют.

                                                                    И как это получается, что «утилиты и драйвера» приходится писать «многим C++ программистам, пишущих под Windows»? Вот прям каждый второй Windows-программист свой рабочий день с этого начинает — очередной драйвер пишет. Или утилиту.
                                                                    • +2
                                                                      Не мешайте человеку быть уверенным в том, что сейчас 2001ый год и только только вышла свежая Windows XP Whistler.
                                                                      • 0
                                                                        Зачем сводить все к абсолюту и видеть только то, что хочется?
                                                                        Я не говорю, что «каждый второй Windows-программист свой рабочий день с этого начинает», но иногда встает необходимость. Я просо говорю, что такое явление еще не исчезло, а значит информация статьи все же кому-то может быть полезна.
                                                                    • 0
                                                                      Ну, например, мы пишем потому что нам сейчас так проще:
                                                                      1. Тонны уже существующего кода, который надо поддерживать.
                                                                      2. Унификация кодовой базы, что уменьшает количество сюрпризов на стыке подходов, а также проще поддерживать.
                                                                    • –2
                                                                      1 спасибо кэп.
                                                                      всё это знал и раньше, другие наверное не знали.
                                                                      не понимаю только, почему статья такая длинная.

                                                                      я вышеуказанными макросами не пользуюсь, вместо этого явно использую либо wchar_t, либо char, где как нужно.
                                                                      то есть если нужно например ссчитать файл с диска, то юзаю wchar_t, если нужно написать тупую программку, вывод которой чисто латиницей, то юзаю char, а если нужно считать сырые данные, то юзаю BYTE
                                                                      также использую UINT, и INT, иногда с 64, вообще не понимаю, почему в си(++) эти обозначения не вошли в стандарт, удобно же.

                                                                      также недолюбливаю макросы за их неинтегрированность в си/си++.
                                                                      в смысле что препроцессор — отдельный язык, не имеющий с си ничего общего.

                                                                      в большинстве случаев без макросов можно обойтись с помошью typedef, inline, __forceinline (в g++ немного по-другому) и прочего
                                                                      • 0
                                                                        Спасибо за статью. Познавательно. Когда-то именно из-за типизации строк в родном Visual-C++ 6 перешел на php.

                                                                        Сейчас собираюсь вернутся к C++ и QT для расширения кругозора, так что спасибо за материал.
                                                                        • 0
                                                                          Интересно каков процент тех людей которые действительно полностью разобрались в каждой мелочи которую используют в своем проекте? Я думаю очень мал)
                                                                          Раз такие статьи пишутся и раз уж она вызывает такое бурное обсуждение. Значит положение практически любого программиста очень шатко. Шатко до степени вероятности появления следующего бага в одном из его проектов.
                                                                          Обычно пишется как? Да просто: «О заработало», «Проверено на большой выборке», «Вывод — программа стабильна».
                                                                          А стабильна — не значит исправна!
                                                                          • 0
                                                                            Автору большой респект за перевод. Работаю на Java. Но к С и C++ питаю интерес (есть даже желание пересесть). Сталкивался с подобными трудностями при экспериментах, но чтоб в одном месте всё было описано и по делу — это редкость. Спасибо!

                                                                            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.