Азбука 64-разрядного программирования
Для работы с 64-разрядным кодом понадобятся компилятор, линкер и несколько подключаемых библиотек. Все это есть в последних дистрибутивах SDK и DDK. Прежде всего, необходимо рассмотреть макросы и директивы компилятора, предназначенные для 64-разрядного кода:
- _WIN64 - 64-разрядная платформа;
- _WIN32 - 32-разрядная платформа (для совместимости с 32-разрядной платформой);
- _WIN16 - 16-разрядная платформа.
Также компилятор имеет встроенные предопределенные макросы, специфичные для различных архитектур процессоров:
- _M_IA64 - 64-разрядная архитектура Intel;
- _M_IX86 - 32-разрядная архитектура Intel;
- _M_ALPHA_64 - 64-разрядная архитектура Alpha;
- _M_ALPHA32 - 32-разрядная архитектура Alpha;
- _M_ALPHA - архитектура Alpha, либо 32-разрядная, либо 64-разрядная.
Следует с большой осторожностью пользоваться макросами, определяющими архитектуру процессора, так как на процессоре другой архитектуры приложение может не работать вообще. Напротив, макросы, определяющие платформу, надо использовать как можно чаще.
Теперь есть возможность создавать приложения, работающие и на 32-, и на 64-разрядной платформах. Здесь есть, однако, немало подводных камней. Следует быть очень внимательным - например, следующий код был вполне пригоден для приложений, но не для 64-разрядной платформы: #ifdef _WIN32 // Win32 код ... #else // А здесь неясно, какой код следует далее: 64- или 16-разрядный... ... #endif И этот код не является корректным: #ifdef _WIN16 // Win16 код ... #else // То же самое: 32- или 64-разрядный код? ... #endif
Чтобы исправить этот код, нужно после #else добавить макросы _WIN32 или _WIN64. Просто придется привыкнуть к тому, что теперь код бывает трех (а не двух, как раньше) , поэтому использование директивы #else в привычных конструкциях приведет к неоднозначности (хотя 16-разрядных приложений становится все меньше и меньше, поддержка этой платформы в современных компиляторах остается).
Теперь рассмотрим новые типы данных. Их можно условно разделить на три группы (см. Таблицу 1):
- целочисленные типы явного представления (fixed-precision integer types);
- целочисленные типы, представленные указателями (pointer-precision integer types);
- типы специальных указателей (specific-precision pointer types).
Описания этих типов находятся в файле basetsd.h (входящем в состав DDK/SDK) и приведены в Таблице 1.
Теперь рассмотрим особенности новых типов данных.
Каждый целочисленный тип явного представления имеет размер, соответствующий его определению. Это означает, что переменная типа int32 всегда будет занимать 32 разряда, вне зависимости от платформы, на которой запускается и исполняется код. Обратите внимание, что при следующем макросе
... #ifdef _WIN32 // Win32 код ...
под 32-разрядным кодом подразумевается использование 32-разрядных типов данных. Именно для переменных этой платформы служит тип int32. То же касается и других целочисленных типов явного представления, имеющих размер 32 разряда. Второй вид (целочисленные типы, представленные указателями) имеет свою особенность: размер данных каждого такого типа зависит от платформы, на которой исполняется приложение. Именно поэтому многие из этих типов названы интегральными. Они сочетают в себе свойства 32- и 64-разрядных типов. Эти типы можно сравнить с виртуальными функциями и поздним связыванием из объектно-ориентированного программирования, так как в обоих случаях среда исполнения определяет конкретные свойства элементов только во время исполнения приложения, а не компиляции (в этом и заключается позднее связывание). Эти типы данных позволяют создавать приложения, одинаково хорошо работающие на 32- и 64-разрядной платформах. Далее следуют указатели. При работе с ними надо соблюдать осторожность: необходимо учитывать разрядность данных, которые адресуются указателем, и помнить, что 32-разрядные указатели в 64-разрядном коде всегда расширяются операционной системой до нужного размера. Следующие функции Win64 (Helper Functions) отвечают за преобразование одного типа в другой (эти inline-функции определены в Basetsd.h). Смысл многих из них понятен из определения прототипов, к остальным требуются объяснения:
- unsigned long HandleToUlong( const void *h );
- long HandleToLong( const void *h );
- void *LongToHandle( const long h );
- unsigned long PtrToUlong( const void *p );
- unsigned int PtrToUint( const void *p );
- unsigned short PtrToUshort( const void *p );
- long PtrToLong( const void *p );
- int PtrToInt( const void *p );
- short PtrToShort( const void *p );
- void * IntToPtr( const int i ) расширяет знаком (sign-extends) значение типа int;
- void * UIntToPtr( const unsigned int ui) расширяет нулем (zero-extends) значение типа unsigned int;
- void * LongToPtr( const long l ) расширяет знаком (sign-extends) значение типа long;
- void * ULongToPtr( const unsigned long ul) расширяет нулем (zero-extends) значение типа unsigned long.
Как уже отмечалось, лишь некоторые функции Win64 API были изменены. Для того чтобы перевести 32-разрядный код в 64-разрядный, следует использовать новые функции оконного класса (Window class). Если в private-данных окна или класса есть указатели, необходимо задействовать следующие новые функции: GetClassLongPtr, GetWindowLongPtr, SetClassLongPtr, SetWindowLongPtr. Эти функции могут работать и на 32- и на 64-разрядной платформе, но для компиляции требуют 64-разрядного компилятора. Также необходимо, чтобы указатели и дескрипторы (handles), входящие в private-данные класса, могли использовать новые 64-разрядные функции. Нужно иметь в виду, что следующие элементы в Winuser.h во время 64-разрядной компиляции не определены: GWL_WNDPROC, GWL_HINSTANCE, GWL_HWDPARENT, GWL_USERDATA. Вместо них в Winuser.h определены следующие новые элементы: GWLP_WNDPROC, GWLP_HINSTANCE, GWLP_HWNDPARENT, GWLP_USERDATA, GWLP_ID. Например, следующий код вызовет ошибку компиляции: SetWindowLong(hWnd, GWL_WNDPROC, (LONG)MyWndProc); Его нужно изменить так: SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)MyWndProc); Обращаю внимание читателей на следующий момент. Прежде чем делать cbWndExtra членом структуры WNDCLASS, необходимо убедиться, что для указателя имеется достаточно памяти. Например, если зарезервировано sizeof(DWORD) байт для адресуемой переменной, следует зарезервировать sizeof(DWORD_PTR) байт. Теперь вернемся к новым 64-разрядным API-функциям. Все они объявлены в Winuser.h, включены в Windows.h и используют User32.lib. 1. GetClassLongPtr. GetClassLongPtr возвращает значение из структуры WNDCLASSEX, которое связано с определенным окном. Если вы возвращаете указатель или дескриптор окна (handle), эта функция в точности повторяет GetClassLong. Но чтобы написать код, работающий под двумя платформами (32- и 64-разрядной), нужно использовать только GetClassLongPtr. Вот ее определение: ULONG_PTR GetClassLongPtr( HWND hWnd, // указатель на окно int nIndex // смещение возвращаемого значения). Было бы нецелесообразно описывать все аргументы, так как они в точности повторяют параметры 32-разрядной функции GetClassLong (и, следовательно, доступны в любой справке по Win32 API). 2.
GetWindowLongPtr. Здесь та же картина, что и в предыдущем случае. Эта функция является заменой функции GetWindowLong и служит лишь для совместимости двух платформ (а значит, и для создания кросс-платформенных приложений). Функция GetWindowLongPtr возвращает дескриптор окна и значение из экстрапамяти окна по указанному смещению. Вот ее определение: LONG_PTR GetWindowLongPtr( HWND hWnd, // указатель на окно int nIndex // смещение возвращаемого значения). 3. SetClassLongPtr. Функция SetClassLong заменяет определенные значения по заданным смещениям в экстрапамяти класса или WNDCLASSEX-структуры на значения того класса, к которому принадлежит данное окно. Во многом эта функция похожа на SetClassLong, но она является межплатформенной. ULONG_PTR SetClassLongPtr( HWND hWnd, // указатель на окно int nIndex, // указатель на значение, которое надо поменять LONG_PTR dwNewLong // новое значение); 4. SetWindowLongPtr. Функция SetWindowLongPtr меняет атрибуты окон. Она также записывает определенные значения (по определенным смещениям) в экстрапамять окна. Эта функция является интегральным (в смысле объединения двух платформ) аналогом SetWindowLong. LONG_PTR SetWindowLongPtr( HWND hWnd, // указатель на окно int nIndex, // смещение, куда записывать значение LONG_PTR dwNewLong // новое значение).