Типы данных Windows... почему так избыточно/undescriptive?
Может кто-нибудь, пожалуйста, точно, почему были определены следующие typedef
s/#define
? Какое значение у них есть, по сравнению с оригиналами?
typedef char CHAR;
#define CONST const
typedef float FLOAT;
typedef unsigned __int64 DWORD64; //A 64-bit "double"-word?!
typedef ULONGLONG DWORDLONG; //What the difference?
typedef ULONG_PTR DWORD_PTR; //What the difference?
typedef long LONG_PTR; //Wasn't INT_PTR enough?
typedef signed int LONG32; //Why not "signed long"?
typedef unsigned int UINT; //Wait.. UINT is "int", "LONG" is also int?
typedef unsigned long ULONG; //ULONG is "long", but LONG32 is "int"? what?
typedef void *PVOID; //Why not just say void*?
typedef void *LPVOID; //What?!
typedef ULONG_PTR SIZE_T; //Why not just size_t?
И, лучше всего:
#define VOID void //Assuming this is useful (?), why not typedef?
Каковы причины этого? Это какая-то абстракция, которую я не понимаю?
Edit
Для тех людей, которые упоминают кросс-совместимость компилятора:
Мой вопрос не в том, почему они не использовали unsigned long long
вместо, скажем, DWORD64
. Мой вопрос в том, почему кто-нибудь использовал DWORD64
вместо ULONG64
(или наоборот)? Разве обе эти typedef
ed не должны быть 64 бита?
Или, как еще один пример: даже в "гипотетическом" компиляторе, который должен был обмануть нас во всех отношениях, какая разница между ULONG_PTR
и UINT_PTR
и DWORD_PTR
? Разве эти абстрактные типы данных не означают одно и то же - SIZE_T
?
Однако я спрашиваю, почему они использовали ULONGLONG
вместо long long
- существует ли какая-либо потенциальная разница в значении, не покрытая ни long long
, ни DWORDLONG
?
Ответы
Ответ 1
Большинство этих избыточных имен существуют в основном по двум причинам:
- они являются историческими типами, сохраненными для обратной совместимости.
- это разные имена для того же типа, которые возникли у разных групп разработчиков (для команд может быть удивительно сложно оставаться последовательным в таком огромном проекте, как Windows)
typedef char CHAR;
Подпись char
может варьироваться между платформами и компиляторами, поэтому одна причина. Оригинальные разработчики, возможно, также сохранили это открытое для будущих изменений в кодировке символов, но, конечно, это уже не актуально, поскольку мы используем TCHAR
для этой цели.
typedef unsigned __int64 DWORD64; //A 64-bit "double"-word?!
Во время перехода на 64-разрядные они, вероятно, обнаружили, что некоторые из их аргументов DWORD
действительно должны быть длиной 64 бит, и они, вероятно, переименовали его DWORD64
, чтобы существующие пользователи этих API не были смущены.
typedef void *PVOID; //Why not just say void*?
typedef void *LPVOID; //What?!
Это относится к 16-битным дням, когда были обычные "близкие" указатели, которые были 16-битными и "дальними" указателями, которые были 32-битными. Префикс L
для типов означает "длинный" или "далекий", что сейчас бессмысленно, но в те дни они, вероятно, были определены следующим образом:
typedef void near *PVOID;
typedef void far *LPVOID;
Обновление: Что касается FLOAT
, UINT
и ULONG
, это просто примеры того, что "больше абстракции хорошо" с учетом будущих изменений. Имейте в виду, что Windows также работает на платформах, отличных от x86, - вы могли бы подумать о архитектуре, где числа с плавающей запятой были представлены в нестандартном формате, а функции API были оптимизированы для использования этого представления. Это может привести к конфликту с типом данных C FLOAT
.
Ответ 2
Когда файлы заголовков Windows API были впервые созданы 25 лет назад, int
составлял 16 бит, а long
- 32 бита. Файлы заголовков со временем изменились, чтобы отразить изменения в компиляторах и в оборудовании.
Кроме того, Microsoft С++ не является единственным компилятором С++, который работает с файлами заголовков Windows. Когда Microsoft добавила ключевое слово size_t
, не все компиляторы его поддерживали. Но они могли бы легко создать макрос, size_t
, чтобы выразить его.
Кроме того, существуют (или были) автоматизированные инструменты, которые преобразуют файлы заголовков API из C/С++ в другие языки. Многие из этих инструментов были первоначально написаны для работы с текущими (в то время) определениями заголовков. Если бы Microsoft просто изменила файлы заголовков, чтобы оптимизировать их, как вы предлагаете, многие из этих инструментов перестанут работать.
В основном, файлы заголовков сопоставляют типы Windows с наименее общим знаменателем, так что с ними могут работать несколько инструментов. Кажется, что это время от времени, и я подозреваю, что если Microsoft захочет выбросить какое-либо подобие обратной совместимости, они могут уменьшить значительную часть беспорядка. Но при этом очень много инструментов (не говоря уже о большой документации).
Итак, да, файлы заголовков Windows иногда беспорядочны. Это цена, которую мы платим за эволюцию, обратную совместимость и способность работать с несколькими языками.
Дополнительная информация:
Я соглашусь, что на первый взгляд все эти определения кажутся сумасшедшими. Но поскольку тот, кто видел файлы заголовков Windows, со временем развивается, я понимаю, как они появились. Большинство этих определений имели смысл, когда они были введены, даже если теперь они выглядят сумасшедшими. Что касается конкретного случая ULONGLONG
и DWORD64
, я полагаю, что они были добавлены для согласованности, поскольку старые файлы заголовков имели ULONG
и DWORD
, поэтому программисты ожидали другие два. Что касается того, почему ULONG
и DWORD
были определены как одно и то же, я могу представить несколько возможностей, два из которых:
- Одна команда API использовала
ULONG
, а другая использовала DWORD
, и когда заголовочные файлы были объединены, они просто сохраняли оба, а не разбивали код, преобразовывая их в один или другой.
- Некоторые программисты более удобны в отношении
ULONG
, чем DWORD
. ULONG
подразумевает целочисленный тип, в котором вы можете выполнять математику, тогда как DWORD
просто означает общее 32-битное значение какого-либо типа, обычно это то, что является ключом, дескриптором или другим значением, которое вы не хотели бы изменять.
Ваш первоначальный вопрос заключался в том, были ли какие-то рассуждения за сумасшедшими определениями, или если есть абстракция, которую вы не пропустили. Простой ответ заключается в том, что определения эволюционировали, причем изменения имели смысл в то время. Нет особой абстракции, хотя намерение заключается в том, что если вы напишете свой код для использования типов, определенных в заголовках, то вы сможете с легкостью перенести свой код с 32-битного на 64-битный. То есть, DWORD
будет одинаковым в обеих средах. Но если вы используете DWORD
для возвращаемого значения, когда API говорит, что возвращаемое значение HANDLE
, у вас возникнут проблемы.
Ответ 3
Одна из причин - поддерживать некоторую мобильность между компиляторами C.
В частности, DWORD64, теоретически вам просто нужно изменить определение DWORD64, чтобы получить компиляцию кода на других компиляторах.