LPSTR, LPWSTR, TCHAR, char*, .
TCHAR — это изобретение Microsoft и куча проблем с ним связано. Если у вас проект на Unicode, то это TCHAR = wchar_t. Если ASCII то TCHAR = char.
#ifdef _UNICODE typedef wchar_t TCHAR; #else typedef char TCHAR; #endif http://www.pvsm.ru/unicode/23896
Поскольку TCHAR в исходниках примеров драйверов повсюду надо понимать как с ним далее работать. Как следствие надо использовать Windows Safe String Function для разных преобразований:
присвоить значение TCHAR строке :
TCHAR str[100]; StringCchCopy(str, ARRAYSIZE(str), L"127.0.0.1"); /* STRSAFEAPI StringCchCopyA( STRSAFE_LPSTR pszDest, size_t cchDest, STRSAFE_LPCSTR pszSrc ); */
Если надо int в TCHAR преобразовать (int -> TCHAR):
TCHAR str[100]; StringCchPrintf(str, ARRAYSIZE(str), TEXT("%d"), (int)number);
присваивание значений строкам
"строка ANSI"; // ANSI L"строка Unicode"; // Unicode TEXT("строка ANSI|Unucode по ситуации как и с TCHAR") _T = TEXT
LPCSTR понимается так.
• LP — Long Pointer (длинный указатель)
• C – Constant (константа)
• STR – String (строка)
По сути LPCSTR это (Длинный) указатель на строку.
LPSTR общеприменяемый префикс с названии sz
typedef char* LPSTR;
LPCWSTR,LPWSTR описан так:
typedef const WCHAR* LPCWSTR; typedef WCHAR* LPCWSTR;
LPCTSTR
LP — Long Pointer (Длинный указатель)
C — Constant (Константа)
T = TCHAR
STR = String (Строка)
Что такое STRSAFE? Смотрим strsafe.h :
typedef __nullterminated char* STRSAFE_LPSTR; typedef __nullterminated const char* STRSAFE_LPCSTR; typedef __nullterminated wchar_t* STRSAFE_LPWSTR; typedef __nullterminated const wchar_t* STRSAFE_LPCWSTR; typedef __nullterminated const wchar_t UNALIGNED* STRSAFE_LPCUWSTR;
Там же в strsafe.h :
#ifdef UNICODE #define StringCchCopy StringCchCopyW #else #define StringCchCopy StringCchCopyA #endif // !UNICODE #ifdef UNICODE #define StringCchPrintf StringCchPrintfW #else #define StringCchPrintf StringCchPrintfA #endif // !UNICODE
Скопировать TCHAR* в char* (универсальный вариант)
TCHAR ourIpAdress[100]; char *ipAddres = new char[256]; int cbMultiByte = WideCharToMultiByte(CP_ACP,0,ourIpAdress,sizeof(ourIpAdress),ipAddres,sizeof(ipAddres),NULL,NULL);
wcstombs(ipAddres, ourIpAdress, sizeof(ourIpAdress)); к сожалению только если UniCode исползуется
Что такое TCHAR, WCHAR, LPSTR, LPWSTR,LPCTSTR (итд)

Многие 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 // 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 строку для вас.
- 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
Конечно вы не передавали эти Китайские символы, но приведение типов сделало это за Вас.
И поэтому очень важно знать что приведение типов не будет работать. Итак для инициализации первой строки вы должны сделать следующее:
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 гарантирует, что оно выделяет необходимое количество байт — и выделяет места для нужного количества символов.
В каких случая нужно использова char а в каких wchar_t
И запустил её на Windows 10 полностью английской версии. Всё сработало отлично и папка FolderName как и ожидалось создалась. Затем я подумал что если запустить эту программу на ОС в которой имя пользователя будет на другом языке(не английском) то программа не сработает и нужно будет использовать wchar_t . Но когда я запустил программу на ОС в который имя пользователя было Валерий программа сработала и папка создалась. Отсюда у меня возник вопрос в каких случая нужно использовать wchar_t если и с char всё работает?
Отслеживать
28.8k 12 12 золотых знаков 59 59 серебряных знаков 118 118 бронзовых знаков
Чем отличается tchar от wchar
Для чего ввели «новые» типы в стандарт программирования под Windows?
Почему не рекомендуется ипользовать «нормальные» типы, вроде wchar_t, char*, спецификатор __stdcall вместо всех этих CALLBACK, WINAPI, APIENTRY и т.п.? Неужели так удобнее для всех программировать? Мне например немного проще видеть void* вместо десятков разных псевдонимов вроде HANDLE, ..
Или например, открываешь файл WinNT.h, а там для обычной строки ввели 3 типа *NPSTR, *LPSTR, *PSTR. Зачем?
Re: типы WCHAR, CHAR, LPSTR, LPCZSTR,
| От: | alzt |
| Дата: | 13.10.09 09:03 |
| Оценка: |
Здравствуйте, Аноним, Вы писали:
Поясню на примере/ Есть функция установления имени чего-то.
void Something::setName(const char* newName);//лучше string, но сути не меняет;
В один прекрассный момент решают, что char-а будет недостаточно для всех возможных имён. В начальный момент это могли быть только символы латинского алфавита, потом добавили подчёркивания, цифры, некоторые знаки. А теперь надо добавить ещё и поддержку парочки китайских иероглифов. Т.е. нужна функция
void Something::setName(const wchar_t* newName);
И везде придётся в лучшем случае менять char на wchar (ещё придётся проверять, что никакой код не считает, что newName состоит из char-ов, например использование 1 вместо sizeof).
Если сразу использовать TCHAR, то изменение далосьбы намного легче.
А часто сразу известно, что код должен компилироваться под разные требования — с использованием юникода и без.
Re[2]: типы WCHAR, CHAR, LPSTR, LPCZSTR,
| От: | catBasilio |
| Дата: | 13.10.09 09:15 |
| Оценка: |
Здравствуйте, alzt, Вы писали:
A>Если сразу использовать TCHAR, то изменение далосьбы намного легче.
В исходном посте спрашивалось не про TCHAR
а в чем отличие CHAR от char и WCHAR от wchar_t?
UNIX way — это когда тебе вместо туалетной бумаги дают топор, рубанок и карту близлежащего леса
Re: типы WCHAR, CHAR, LPSTR, LPCZSTR,
| От: | catBasilio | |
| Дата: | 13.10.09 09:21 | |
| Оценка: | 1 (1) | |
Здравствуйте, Аноним, Вы писали:
А>Для чего ввели «новые» типы в стандарт программирования под Windows?
А>Почему не рекомендуется ипользовать «нормальные» типы, вроде wchar_t, char*, спецификатор __stdcall вместо всех этих CALLBACK, WINAPI, APIENTRY и т.п.? Неужели так удобнее для всех программировать? Мне например немного проще видеть void* вместо десятков разных псевдонимов вроде HANDLE, ..
А>Или например, открываешь файл WinNT.h, а там для обычной строки ввели 3 типа *NPSTR, *LPSTR, *PSTR. Зачем?
Про строки не скажу, но HANDLE — это вполне себе определенный тип — описатель и не важно в какой он тип выраждается. Видя в программе HANDLE file можно понять что это именно описатель файла, а видя void* file — мне например ничего понятно не будет.
UNIX way — это когда тебе вместо туалетной бумаги дают топор, рубанок и карту близлежащего леса
Re[3]: типы WCHAR, CHAR, LPSTR, LPCZSTR,
| От: | alzt |
| Дата: | 13.10.09 09:37 |
| Оценка: |
Здравствуйте, catBasilio, Вы писали:
A>>Если сразу использовать TCHAR, то изменение далосьбы намного легче.
B>В исходном посте спрашивалось не про TCHAR
B>а в чем отличие CHAR от char и WCHAR от wchar_t?
Точно, не внимательный я. Ну так ниже ты уже ответил.
Re[4]: типы WCHAR, CHAR, LPSTR, LPCZSTR,
| От: | catBasilio |
| Дата: | 13.10.09 09:42 |
| Оценка: |
Здравствуйте, alzt, Вы писали:
A>Здравствуйте, catBasilio, Вы писали:
A>>>Если сразу использовать TCHAR, то изменение далосьбы намного легче.
B>>В исходном посте спрашивалось не про TCHAR
B>>а в чем отличие CHAR от char и WCHAR от wchar_t?
A>Точно, не внимательный я. Ну так ниже ты уже ответил.
Я про void* отвечал.
а в чем разница между
CHAR buffer[256]; и char buffer1[256];
мне не совсем очевидна.
UNIX way — это когда тебе вместо туалетной бумаги дают топор, рубанок и карту близлежащего леса
Re: типы WCHAR, CHAR, LPSTR, LPCZSTR,
| От: | Кодт | |
| Дата: | 13.10.09 09:47 | |
| Оценка: | +1 | |
Здравствуйте, <Аноним>, Вы писали:
А>Для чего ввели «новые» типы в стандарт программирования под Windows?
Аноним>
Не в «стандарт программирования», а в Windows API.
Для того, чтобы абстрагировать API от любых особенностей любых компиляторов.
Особенно это было важно при переходе с 16 на 32 бита.
А>Почему не рекомендуется ипользовать «нормальные» типы, вроде wchar_t,
wchar_t — самый несчастный тип. Знаете ли вы, что. этот тип предназначен для хранения юникодных символов, но беда в том, что VC и ICC трактуют его как 16-битный (UCS-2/UTF-16), а gcc и Comeau — по умолчанию как 32-битный (USC-4).
В результате, если апишную функцию (или колбек) объявить как (. wchar_t*. ) — то в зависимости от компилятора можно послать-принять мусор.
Тогда как WCHAR, WPARAM, LPARAM и т.п. — типы заведомо правильные (для разных компиляторов они могут определяться по-разному — т.е. typedef unsigned short WCHAR, typedef wchar_t WCHAR, и т.п.)
А> спецификатор __stdcall вместо всех этих CALLBACK, WINAPI, APIENTRY и т.п.?
Для того, чтобы абстрагировать используемые в API конвенции вызова от поддерживаемых компилятором.
Вот завтра для Windows8 какой-нибудь решат, что ну нафиг __stdcall, все дружно пересаживаемся на __cdecl — и что, весь код переписывать?
А> Неужели так удобнее для всех программировать? Мне например немного проще видеть void* вместо десятков разных псевдонимов вроде HANDLE, ..
А>Или например, открываешь файл WinNT.h, а там для обычной строки ввели 3 типа *NPSTR, *LPSTR, *PSTR. Зачем?
Очень удобно. Особенно в отношении HANDLE. Он, кстати, типизированный, в отличие от голого void* или int (который так любят в POSIX).
Перекуём баги на фичи!
Re[2]: GCC sizeof(wchar_t)
| От: | d.4 |
| Дата: | 13.10.09 10:47 |
| Оценка: |
Здравствуйте, Кодт, Вы писали:
К>wchar_t — самый несчастный тип. Знаете ли вы, что. этот тип предназначен для хранения юникодных символов, но беда в том, что VC и ICC трактуют его как 16-битный (UCS-2/UTF-16), а gcc и Comeau — по умолчанию как 32-битный (USC-4).
К>В результате, если апишную функцию (или колбек) объявить как (. wchar_t*. ) — то в зависимости от компилятора можно послать-принять мусор.
Относительно gcc здесь Вы не правы. По крайней мере относительно его реализации для Windows.
При компиляции с использованием mingw32-g++.exe размер wchar_t по умолчанию равен 2 байтам. И это вполне обоснованно. Почему?
Если речь идет о компиляции программ для Windows, то иметь дело придется не только с заголовочными файлами PSDK, но и с пользовательским кодом.
Предположим, PSDK можно было бы подправить, используя все эти «волшебные макросы» вроде WCHAR, LPCWSTR, BSTR, OLECHAR и десятки других. В реальности это сведется не к нескольким банальным заменам, но к месяцу напряженного и бессмысленного ковыряния.
Для наглядности, вот фрагмент файла strmif.h из довольно свежего Microsft SDK v7.0A.
typedef struct tagVMRMONITORINFO < VMRGUID guid; RECT rcMonitor; HMONITOR hMon; DWORD dwFlags; wchar_t szDevice[ 32 ]; wchar_t szDescription[ 256 ]; LARGE_INTEGER liDriverVersion; DWORD dwVendorId; DWORD dwDeviceId; DWORD dwSubSysId; DWORD dwRevision; > VMRMONITORINFO;
Забыли здесь про WCHAR, досадно, да? К слову, я нашел 50 файлов в этом SDK с упоминаниями wchar_t.
И если бы wchar_t был 32-битный, то гарантировано ничего бы не заработало.
А представьте сколько пользовательского кода написано с использованием wchar_t, «торчащим наружу»! Достаточно экспортировать в DLL функцию, принимающую строку вида const wchar_t*.
Так что для платформы Windows (а вместе с ней и Wine) sizeof(wchar_t) === 2. И только так.
Кроме того, я не нашел опции, которая бы заставила сделать размер wchar_t принудительно равным 4. Есть только опция -fshort-wchar, которая укорачивает размер wchar_t до 2.
Override the underlying type for wchar_t to be short unsigned int instead of the default for the target. This option is useful for building programs to run under WINE.
А вот обратный ключ, который поручил бы компилятору mingw32-g++.exe генерировать 32-битные wchar_t, отсутствует.
Re: типы WCHAR, CHAR, LPSTR, LPCZSTR,
| От: | d.4 |
| Дата: | 13.10.09 11:29 |
| Оценка: |
Здравствуйте, Вы писали:
А>Для чего ввели «новые» типы в стандарт программирования под Windows?
А>Почему не рекомендуется ипользовать «нормальные» типы, вроде wchar_t, char*, спецификатор __stdcall вместо всех этих CALLBACK, WINAPI, APIENTRY и т.п.? Неужели так удобнее для всех программировать? Мне например немного проще видеть void* вместо десятков разных псевдонимов вроде HANDLE, ..
А>Или например, открываешь файл WinNT.h, а там для обычной строки ввели 3 типа *NPSTR, *LPSTR, *PSTR. Зачем?
А>Спасибо
Platform SDK — это образцовый бардак с многолетней историей. Каждый, кто создает там новый заголовочный файл, вероятно, не имеет никакого желания ознакомиться с предыдущими наработками. Либо мнит себя неизмеримо умнее своих предшественников. Мы видим труд, находки, велосипеды и сумасбродные идеи сотен разработчиков разных поколений!
PSDK — это несомненно масштабный (по количеству пользователей) проект, представляющий повсеместно используемое API. Как следствие, практически невозможно отказаться от уже имеющихся определений макросов или typedef’ов. Новые ввести — пожалуйста.
Директива #include до сих пор заставляет компилятор «задуматься» на секунды, переваривая тонны не очень осмысленного кода.
Одних разновидностей GUID можно найти в PSDK несметные количества! Особенно нагружают хидеры пустые макросы типа __in, __out, __inout_ecount(), необходимые либо для каких-то внутренних инструментов MS, либо просто, чтобы смотреть было приятно.
Отдельные типы, разумеется имеют смысл. К ним можно отнести T-семейство, зависящие от UNICODE (LPCTSTR, TCHAR и т.п.), типизированные хэндлы, INT_PTR и UINT_PTR, 64-битные LONGLONG и ULONGLONG, а так же BSTR, намекающий, что перед первым символом строки наличествует область заголовка.
Есть устоявшиеся typedef’ы для строк, такие как LPSTR, LPWSTR, LPCSTR и LPCTSTR. Запутаться в них, конечно легко по-первости, но используются они всё-таки широко и по крайней мере понятны другим программистам.
При использовании COM-интерфейсов принято использовать STDMETHOD, HRESULT, SUCCEEDED, FAILED, BSTR.
Передача строковых аргументов в виде const wchar_t* не будет преступлением, т.к. подобное встречается и в PSDK.
Теперь самое главное:
Основной принцип — бинарная совместимость. Все эти макросы и typedef’ы едва ли являются каким-либо «заделом на будущее».
В любой последующей версии Windows на базе x86, x64, Itanium размер wchar_t будет равен двум байтам, IUnknown будет иметь свои три метода с неизменными параметрами и __stdcall-конвенцией вызова. То же касается API-функций. Что-то может стать неактуальным (deprecated), но бинарная совместимость останется.
Иначе мы не сможем запускать старые программы на новых Windows.
Отсюда, кстати, следует, что любой интерфейс WinAPI мы можем использовать без включения заголовочных файлов. Например,
__declspec(import) int __stdcall MessageBoxW(void* hWnd, const wchar_t* strText, const wchar_t* strCaption, unsigned type);
Вывод предлагаю сделать самостоятельно
Re[2]: типы WCHAR, CHAR, LPSTR, LPCZSTR,
| От: | Pavel Dvorkin |
| Дата: | 13.10.09 11:30 |
| Оценка: |
Здравствуйте, catBasilio, Вы писали:
B>Про строки не скажу, но HANDLE — это вполне себе определенный тип — описатель и не важно в какой он тип выраждается. Видя в программе HANDLE file можно понять что это именно описатель файла, а видя void* file — мне например ничего понятно не будет.
Там немного похитрее сделано. Например, вот такое
HBrush hBrush = . ;
HPEN hPen = hBrush;
не будет компилироваться. Концептуально оба хендлы, а хендлы есть unsigned int и присваивание должно проходить. А дело в том. что они реализованы как указатели на разные структуры
#define DECLARE_HANDLE(name) struct name##__ < int unused; >; typedef struct name##__ *name
With best regards
Pavel Dvorkin
Re[2]: типы WCHAR, CHAR, LPSTR, LPCZSTR,
| От: | Pavel Dvorkin | |
| Дата: | 13.10.09 11:44 | |
| Оценка: | +1 | |
Здравствуйте, d.4, Вы писали:
d.4>Основной принцип — бинарная совместимость. Все эти макросы и typedef’ы едва ли являются каким-либо «заделом на будущее».
С помощью этих макросов и typedef удалось относительно безболезненно перейти от Win16 к Win32 , а потом к Win64
d.4>В любой последующей версии Windows на базе x86, x64, Itanium размер wchar_t будет равен двум байтам, IUnknown будет иметь свои три метода с неизменными параметрами и __stdcall-конвенцией вызова.
В x64 нет __stdcall.
#elif (_MSC_VER >= 800) || defined(_STDCALL_SUPPORTED)
#define CALLBACK __stdcall
#define WINAPI __stdcall
#define WINAPIV __cdecl
#define APIENTRY WINAPI
#define APIPRIVATE __stdcall
#define PASCAL __stdcall
#else
#define CALLBACK
#define WINAPI
#define WINAPIV
#define APIENTRY WINAPI
#define APIPRIVATE
#define PASCAL pascal
#endif
>То же касается API-функций. Что-то может стать неактуальным (deprecated), но бинарная совместимость останется.
d.4>Иначе мы не сможем запускать старые программы на новых Windows.
Это верно только для указанной модели (Win16, 32, 64)
d.4>Отсюда, кстати, следует, что любой интерфейс WinAPI мы можем использовать без включения заголовочных файлов.
Это верно и для любого заголовочного файла вообще, но только вряд ли стоит так делать
With best regards
Pavel Dvorkin
Re[3]: типы WCHAR, CHAR, LPSTR, LPCZSTR,
| От: | d.4 |
| Дата: | 13.10.09 12:49 |
| Оценка: |
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>С помощью этих макросов и typedef удалось относительно безболезненно перейти от Win16 к Win32 , а потом к Win64
Насчет безболезненности можно спорить.
Хорошим примером будет пара востребованных функций GetWindowLong/SetWindowLong.
В Win16 использовались GetWindowWord/SetWindowWord, c выходом Win32 пришлось использовать новые. Вообще тяжелый переход был с учетом обширной переделки архитектуры. WPARAM, LPARAM и LRESULT — пожалуй, действительно помогли.
В Win64 нам предлагают GetWindowLongPtr/SetWindowLongPtr, попутно введя в пользование типы INT_PTR, LONG_PTR и т.п. Platform SDK все-таки снова перелопатили
PD>В x64 нет __stdcall.
__stdcall я бы отнес к языковым средствам. 64-битные компиляторы, начиная с MSVC2005 и далее понимают ключевое слово __stdcall и оно дает ожидаемый результат с 64-битным WinAPI, так же, как и __cdecl.
В целом, мое утверждение относится к бинарной совместимости на уровне платформ.
Отдельно x86, отдельно x64. Иначе и быть не может — код разный. Про win16 упоминать не буду, мир его праху
MS обеспечил возможность писать код, компилируемый как под x86, так и под x64 и Itanium. Здесь некоторые их макросы пригодились, хотя многие сторонние библиотеки также пришлось модернизировать.
Пример ниже, по-моему, относится к другому случаю.
Дословно: если компилятор не очень древний (как минимум, Microsoft Visual C++ 1.0, имеющий номер _MSC_VER == 800 — табличка версий здесь) или это какой-то другой компилер, клятвенно обещающий поддерживать __stdcall, то.
PD>#elif (_MSC_VER >= 800) || defined(_STDCALL_SUPPORTED)
PD>#define CALLBACK __stdcall
PD>#define WINAPI __stdcall
PD>#define WINAPIV __cdecl
PD>#define APIENTRY WINAPI
PD>#define APIPRIVATE __stdcall
PD>#define PASCAL __stdcall
PD>#else
PD>#define CALLBACK
PD>#define WINAPI
PD>#define WINAPIV
PD>#define APIENTRY WINAPI
PD>#define APIPRIVATE
PD>#define PASCAL pascal
PD>#endif
Еще один, кстати, пример неактуального и бессмысленного кода. Хоть в музей выставляй
Сдается мне, блок #else автором никогда не тестировался. Типа, если __stdcall не поддерживается, то пусть хоть как-то компилируется
d.4>>Отсюда, кстати, следует, что любой интерфейс WinAPI мы можем использовать без включения заголовочных файлов.
PD>Это верно и для любого заголовочного файла вообще, но только вряд ли стоит так делать
Разумеется, MessageBox лучше вызывать по-человечески. А вот если какую-то функцию загрузить надо динамически (LoadLibrary), то прототип можно и в упрощенном виде записать. Главное — внимательно
Забавный факт, какой Windows-компилятор ни возьми, у каждого свой WinAPI! Самый приличный мне попадался для MinGW. Borland’овские поделки — из рук вон.
Digital Mars С++ compiler имеет в заголовках нечто, позволяющее собирать и Win16, и Win32-приложения, но явно не обновлялось годами.
Re[4]: типы WCHAR, CHAR, LPSTR, LPCZSTR,
| От: | Pavel Dvorkin |
| Дата: | 14.10.09 02:26 |
| Оценка: |
Здравствуйте, d.4, Вы писали:
d.4>Хорошим примером будет пара востребованных функций GetWindowLong/SetWindowLong.
d.4>В Win16 использовались GetWindowWord/SetWindowWord, c выходом Win32 пришлось использовать новые.
GetWindowLong была и в Win16. Просто там некоторые свойства окна были типа WORD, а в Win32 они стали LONG. Поэтому GetWindowWord стала ненужной.
d.4>В Win64 нам предлагают GetWindowLongPtr/SetWindowLongPtr, попутно введя в пользование типы INT_PTR, LONG_PTR и т.п. Platform SDK все-таки снова перелопатили
GetWindowLongPtr есть и в Win32. Ввели ее, точно, для перехода.
d.4>Пример ниже, по-моему, относится к другому случаю.
d.4>Дословно: если компилятор не очень древний (как минимум, Microsoft Visual C++ 1.0, имеющий номер _MSC_VER == 800 — табличка версий здесь) или это какой-то другой компилер, клятвенно обещающий поддерживать __stdcall, то.
PD>>#elif (_MSC_VER >= 800) || defined(_STDCALL_SUPPORTED)
PD>>#define CALLBACK __stdcall
PD>>#define WINAPI __stdcall
PD>>#define WINAPIV __cdecl
PD>>#define APIENTRY WINAPI
PD>>#define APIPRIVATE __stdcall
PD>>#define PASCAL __stdcall
PD>>#else
PD>>#define CALLBACK
PD>>#define WINAPI
PD>>#define WINAPIV
PD>>#define APIENTRY WINAPI
PD>>#define APIPRIVATE
PD>>#define PASCAL pascal
PD>>#endif
d.4>Еще один, кстати, пример неактуального и бессмысленного кода. Хоть в музей выставляй
d.4>Сдается мне, блок #else автором никогда не тестировался. Типа, если __stdcall не поддерживается, то пусть хоть как-то компилируется
Почему ? Блок #else означает — везде , где встретишь WINAPI, замени на пустое место.