Компилятор MASM

advertisement
Компилятор MASM
MASM создан специально для написания программ на ассемблере для
Win32. В нём есть макросы и специальные директивы для упрощения
программирования.
Функции.
Основное преимущество MASM это макрос invoke, он позволяет вызывать
API функции по-обычному с проверкой количества и типа параметров. Это
почти тот же call, как в TASM, но этот макрос проверяет количество
параметров и их типы. Вот так вызывается функция:
Invoke <функция>, <параметр1>, <параметр2>, <\параметр3>
Чтобы использовать invoke для вызова процедуры, вы должны определить ее
прототип:
PROTO STDCALL testproc:DWORD, :DWORD, :DWORD
Эта директива объявляет процедуру, названную testproc, которая берет 3
параметра размером DWORD.
Теперь, если вы сделаете это...
invoke testproc, 1, 2, 3, 4
...
masm выдаст вам ошибку потому, что процедура testproc берет 3 параметра, а
не 4. Masm также имеет контроль соответствия типов, т.е. проверяет, имеют
ли параметры правильный тип (размер).
В invoke вы можете использовать ADDR вместо OFFSET. Это сделает адрес в
правильной форме, когда код будет собран.
testproc PROTO STDCALL :DWORD, :DWORD, :DWORD
.code
testproc proc param1:DWORD, param2:DWORD, param3:DWORD
....
ret
testproc endp
Это создает процедуру, названную testproc, с тремя параметрами. Прототип
используется, invoke. Все параметры можно использовать в коде процедуры,
они автоматически извлекутся из стека. Также в процедурах можно
использовать локальные переменные.
testproc proc param1:DWORD, param2:DWORD, param3:DWORD
LOCAL var1:DWORD
LOCAL var2:BYTE
mov ecx, param1
mov var2, cl
mov edx, param2
mov eax, param3
mov var1, eax
add edx, eax
mul eax, ecx
mov ebx, var1
.IF bl==var2
xor eax, eax
.ENDIF
ret
testproc endp
Вы не можете использовать эти переменные вне процедуры. Они сохранены
в стеке и удаляются при возврате из процедуры.
Конструкции сравнения и повтора.
If - Она имеет тот же самый синтаксис, что и в TASM.
Repeat - Эта конструкция выполняет блок, пока условие не истинно:
.REPEAT
; код здесь
.UNTIL eax==1
Эта конструкция повторяет код между repeat и until, пока eax не станет
равным 1.
While - Конструкция while это инверсия конструкции repeat. Она выполняет
блок, пока условие истинно:
.WHILE eax==1
; код здесь
.ENDW
Вы можете использовать директиву .BREAK, чтобы прервать цикл и выйти.
.WHILE edx==1
inc eax
.IF eax==7
.BREAK
.ENDIF
.ENDW
Если eax=7, цикл while будет прерван.
Директива continue осуществляет переход на код проверяющий условие
цикла в конструкциях repeat и while.
Теперь наша первая программа видоизменяется следующим образом:
.486
.model flat, stdcall
option casemap :none
includelib .\masm32\lib\kernel32.lib
includelib .\masm32\lib\user32.lib
MessageBoxA PROTO STDCALL :DWORD, :DWORD, :DWORD, :DWORD
ExitProcess PROTO STDCALL :DWORD
.data
ttl db '11111',0
.code
start:
invoke MessageBoxA,0,offset ttl,offset ttl,0
invoke ExitProcess,0
end start
При компиляции в TASM пути к статическим библиотекам указываются при
компиляции, в MASM пути к статическим библиотекам мы указываем в
тексте программы точно так же как и пути к включаемым файлам с помощью
директивы includelib.
Прототипы каждой из библиотек есть в одноимённых включаемых файлах в
папке include. Теперь не надо писать прототипы функций самому, они уже
есть:
includelib .\masm32\lib\kernel32.lib
includelib .\masm32\lib\user32.lib
include .\masm32\include\kernel32.inc
include .\masm32\include\user32.inc
В этих включаемых файлах определены функции без букв A или W в конце.
Теперь не надо указывать эти буквы в конце.
Директива option нужна для задания некоторых настроек компиляции. Опция
casemap задаёт чувствительность к регистру символов. Мы указали none, тем
самым установили чувствительность к регистру символов. Это надо, для того
чтобы избежать конфликтов включаемых файлов от разных авторов.
Компиляция.
Ассемблирование:
ML [ /опции ] filelist [ /link linkoptions ]
ассемблирование без линковки
/c
В основном вы будете использовать
эту опцию, так как вы будете
использовать внешний линкер
(например link.exe), для компоновки
ваших файлов.
генерировать объектный файл в
COFF формате
/coff
Это генерирует формат файла для
компоновщика microsoft.
имя объектного файла
может использоваться, если вы
/Fo<file> хотите, чтобы выходной файл был с
другим именем, не таким как
исходный файл.
Использует вызовы Pascal, C, или
Stdcall
/G<c|d|z>
выберите тип вызовов для ваших
процедур.
Добавить символьную отладочную
информацию
/Zi
Установите эту опцию, если хотите
использовать отладчик.
Установить include путь
/I<name>
Определяет ваш include путь
Линковка:
LINK [опции] [файлы] [@commandfile]
Отладка
/DEBUG
Это создаст информацию для отладки. Используйте
эту опцию, когда вы хотите использовать отладчик.
Тип отладки: codeview / coff
/DEBUGTYPE:CV|COF Выбирает выходной формат отладочной
информации. Это зависит от вашего отладчика.
F
Softice и visual c++ отладчики оба могут
обрабатывать CV (codeview)
DEF файл
/DEF:имя_файла
Указывает файл определения (.def). Используется с
dll, для экспортируемых функций.
DLL
/DLL
Выходной файл DLL, а не EXE.
Путь к библиотекам
/LIBPATH:path
/I<имя>
/OUT:имя_файла
Указывает путь к файлам библиотек (*.lib).
Устанавливает путь для inc-файлов
Указывает путь для inc-файлов, по умолчанию.
Out:имя_файла
Может изменить имя выходного файла.
Подсистема
/SUBSYSTEM:{...}
Выбирает ОС на которой должна выполнятся
программа:
NATIVE|WINDOWS|CONSOLE|WINDOWSCE|POSI
X
Я установил masm в папку d:\masm\masm32, в папке d:\masm находятся все
тексты наших программ *.asm. А для компиляции я создал bat файл, который
компилирует файл current.asm:
;======[CUT HERE]=====
d:\masm\masm32\bin\ml.exe /c /coff current.asm
d:\masm\masm32\bin\link.exe /subsystem:windows current.obj
pause
;=======[CUT HERE]========
В результате после запуска это файл у нас в папке d:\masm готовая
скомпилированная программа current.exe.
Оконное приложение
.386p
.model flat, stdcall
option casemap:none
includelib .\masm32\lib\kernel32.lib
includelib .\masm32\lib\user32.lib
include .\masm32\include\windows.inc
include .\masm32\include\kernel32.inc
include .\masm32\include\user32.inc
.data
newhwnd
hInst
szTitleName
szClassName
msg
wc
dd 0
dd 00000000h
db 'Window Application',0
db 'ASMCLASS32',0
MONMSGSTRUCT <?> ; структура сообщения
WNDCLASS <?> ; структура класса
.code
start:
Invoke GetModuleHandle,0 ; получаем hInstanse
Mov [hInst], eax
Mov [wc.style], CS_HREDRAW+CS_VREDRAW+CS_GLOBALCLASS
; устанавливаем стиль окна
Mov [wc.lpfnWndProc], offset WndProc ;
Mov [wc.cbClsExtra], 0
Mov [wc.cbWndExtra], 0
Mov eax, [hInst]
Mov [wc.hInstance], eax
Invoke LoadIcon,0,IDI_APPLICATION ; получаем значок приложения по
; умолчанию
Mov [wc.hIcon], eax
Invoke LoadCursorA,0,IDC_ARROW ; получаем курсор по умолчанию
Mov [wc.hCursor], eax
Mov [wc.hbrBackground], COLOR_BACKGROUND+1
Mov dword ptr [wc.lpszMenuName], 0
Mov dword ptr [wc.lpszClassName], offset szClassName ; задаём имя класса
;окна
Invoke RegisterClassA,offset wc ; регистрируем класс окна
Push 0
Push [hInst] ; дескриптор
Push 0
Push 0
Push CW_USEDEFAULT ; высота
Push CW_USEDEFAULT ; ширина
Push CW_USEDEFAULT ; y
Push CW_USEDEFAULT ; x
Push WS_OVERLAPPEDWINDOW ; стиль
Push offset szTitleName ; заголовок окна
Push offset szClassName ; имя класса
Push 0 ; дополнительный стиль
Call CreateWindowEx ; создаём окно
mov [newhwnd], eax ; сохраняем его дескриптор
invoke ShowWindow,[newhwnd],SW_SHOWNORMAL; показываем окно
invoke UpdateWindow, [newhwnd]; обновляем его
msg_loop: ; запускаем цикл обработки сообщений
invoke GetMessage,offset msg, 0,0,0
cmp ax, 0
je end_loop
invoke TranslateMessage, offset msg
invoke DispatchMessage, offset msg
jmp msg_loop
end_loop:
invoke ExitProcess, 0
WndProc proc uses ebx edi esi, hwnd:DWORD, wmsg:DWORD,
wparam:DWORD, lparam:DWORD
Cmp [wmsg], WM_DESTROY
Je wmdestroy
Cmp [wmsg], WM_KEYDOWN
Je wmkeydown
Invoke DefWindowProcA,[hwnd],[wmsg],[wparam],[lparam]
; вызываем стандартный обработчик сообщений
Jmp finish
wmkeydown:
cmp [wparam], VK_ESCAPE
je wmdestroy; если нажата клавиша Escape то выход
jmp finish
wmdestroy:
invoke PostQuitMessage, 0
invoke ExitProcess, 0 ; выход
finish:
ret
WndProc endp
end start
В самой первой строке мы получаем hInstanse т.е дескриптор приложения,
фактически это база образа приложения, т.е. то место в памяти в которого
начинается наша программа. Функция GetModuleHandle нужна для
получения дескриптора библиотеки (фактически хендл библиотеки это и есть
её база образа), параметром этой функции надо передавать имя библиотеки,
но если мы укажем 0, то получим дескриптор нашего приложения. Потом мы
заполняем структуру класса окна, т.е. задаём атрибуты класса окна.
Mov [wc.lpfnWndProc], offset WndProc
Этой строкой мы указываем, какая функция будет обрабатывать сообщения
окна. Функция, которая обрабатывает сообщения окна, называется оконной
функцией. Оконная функция вызывается всякий раз, когда окну было
прислано какое-либо сообщение.
Потом мы получаем иконку окна с помощью функции LoadIcon, в качестве
первого параметра этой функции надо передавать дескриптор приложения,
но мы указали 0 поэтому мы получим стандартный значок окна. Во втором
параметре мы указываем указатель на строку, которая содержит имя иконки,
которую мы хотим получить, но если мы укажем определённую константу,
то сможем получить предопределённую иконку. Точно также мы получаем
курсор, т.е. его дескриптор.
Потом мы вызываем RegisterClass, единственным параметром которой
является указатель на структуру, которая описывает новый класс окна.
Потом мы создаём окно с помощью функции CreateWindowEx.
HWND CreateWindowEx(
DWORD dwExStyle, // расширенный стиль окна
LPCTSTR lpClassName,
// имя класса
LPCTSTR lpWindowName, // заголовок окна
DWORD dwStyle, // стиль окна
int x, // позиция х
int y, // позиция у
int nWidth, // ширина
int nHeight, // высота
HWND hWndParent, // хендл окна родителя
HMENU hMenu,
// хендл меню
HINSTANCE hInstance,
// хендл приложения
LPVOID lpParam
//указатель на данные (обычно они не нужны)
);
После создания окна мы показываем его и обновляем. Потом запускаем
бесконечный цикл обработки системных сообщений.
В оконной функции мы получаем сообщение и обрабатываем его. Если тип
сообщения WM_DESTROY, то мы переходим на соответствующую метку,
после которой выходим из нашей программы. Если была нажата клавиша, то
мы продолжаем обработку и смотрим, нажата ли клавиша escape, если да, то
выходим. Если сообщение, какое либо другое, то вызываем стандартный
обработчик сообщений окна. Этот обработчик превращает наше окно в
настоящее "живое" окно, которое может двигаться, расширятся и
свёртываться. Можно и не вызывать эту функцию, но для того чтобы окно
стало живым нам придётся вручную обрабатывать все сообщения окна, а это
просто нецелесообразно.
Некоторые функции Win32
Несколько функций API для работы с памятью.
В Win32 есть только API функции. ЛЮБОЕ приложение в ЛЮБОМ случае
должно использовать API функции (по крайней мере, для того чтобы оно
могло нормально завершиться). Эти функции находятся в библиотеках
kernel32.dll, user32.dll, gdi32.dll (3 классические библиотеки) и др. Функции
библиотеки kernel32.dll самые главные, они отвечают за работу с памятью,
user32.dll отвечает за окна и интерфейс пользователя, а gdi32.dll за
"рисование" и мультимедиа. Для того, что бы использовать функции этих
библиотек надо сначала их загрузить (kernel32.dll загружать не надо).
Каждое приложение проецируется в своё собственное виртуальное адресное
пространство размером в 4 ГБ, верхние 2 из которых недоступны для него.
Это пространство (вернее нижние 2 ГБ) подготавливается для него: в него
загружаются 2 библиотеки kernel32.dll и ntdll.dll (для Win NT) всегда по
одинаковым адресам, подготавливается стек для главной нити процесса и
ещё много чего. В эту память загружаются код и данные приложения. Для
того, что бы операционная система знала по каким адресам надо загружать
данные и код приложения она смотрит дополнительную информацию о
секциях приложения в "exe" файле. Если она этого не сделает, то она просто
не будет знать, куда спроецировать код и данные, следовательно, все
обращения к памяти будут неверными. (Мы, конечно, помним что под
записью mov eax, [value] понимается помещение в регистр eax содержимого
некого адреса памяти, которую подразумевает метка value, эта метка
подразумевает некий фиксированный адрес, все адреса рассчитываются при
компиляции программы и вам об этом задумываться не надо).
Каждое приложение в своей работе вызывает API функции, под вызовом
функции подразумевается передача управления некоторому адресу, который
находится в диапазоне памяти, в которую загружена библиотека, которая
содержит данную функцию. Рассмотрим пример.
Call VirtualAalloc
Как мы уже знаем слово VirtualAlloc будет заменено на некоторый адрес.
Этот адрес будет соответствовать адресу точки входа функции VirtualAlloc.
Потом эта функция передаёт управление функциям Native API которые
находятся в библиотеке ntdll.dll. Функции Native API подготавливают
процесс к переходу в режим ядра и командой sysenter переводят процесс в
режим ядра и т.д.
Несколько API функций для работы с памятью.
Функция VirtualAlloc позволяет зарезервировать некоторый участок памяти
заданного размера, при этом эта память полностью обнуляется.
Вот описание этой функции из MS SDK в оригинале
LPVOID VirtualAlloc(
LPVOID lpAddress, // address of region to reserve or commit
DWORD dwSize,
// size of region
DWORD flAllocationType, // type of allocation
DWORD flProtect // type of access protection
);
 lpAddress - начальный адрес региона
 dwSize - размер региона
 flAllocationType - тип резервирования может быть указана одна из 3х
констант :
o MEM_COMMIT эта память может быть сброшена в файл
подкачки
MEM_RESERVE этот участок памяти не может быть сброшен в
файл подкачки он будет присутствовать в физической памяти
всегда, пока эту память процесс не ввысвободит.
o MEM_TOP_DOWN не пойму для чего нужна эта константа, но
она есть, она позволяет зарезервировать виртуальную память,
которая будет соответствовать максимально возможному
физическому адресу.
flProtect тип доступа к памяти. Могут быть использованы эти
константы или их комбинации.
o PAGE_READONLY
o PAGE_READWRITE
o PAGE_EXECUTE позволяет передавать управление этому
региону попытка чтения или записи приводит к нарушению прав
доступа.
o PAGE_EXECUTE_READ
o PAGE_EXECUTE_READWRITE
o PAGE_NOACCESS
o

Мне кажется всё и так понятно, кому не понятно читайте MS SDK.
Функция VirtualFree обратна функции VirtualAlloc высвобождает заданный
диапазон памяти.
BOOL VirtualFree(
LPVOID lpAddress, // начальный адрес освобождаемого региона
DWORD dwSize,
// размер региона
DWORD dwFreeType
// type of free operation
);
dwFreeType метод высвобождения:
если укажете MEM_RELEASE то размер указывать не надо, если
MEM_DECOMMIT, то размер надо указывать, если указано
MEM_RELEASE, то эта память высвобождается полностью.
Если функция завершилась нормально, то результат отличен от нуля.
Функция VirtualProtect позволяет изменить тип доступа к зарезервированной
памяти, но не так всё просто надо ещё указать старый тип доступа.
BOOL VirtualProtect(
LPVOID lpAddress, // начальный адрес
DWORD dwSize,
// размер
DWORD flNewProtect,
// новый тип доступа
PDWORD lpflOldProtect
);
// старый тип доступа
Вроде всё понятно.
Функция VirtualLock блокирует участок памяти, при этом доступ к нему
невозможен и одновременно не приводит к ошибке.
BOOL VirtualLock(
LPVOID lpAddress, // начальный адрес
DWORD dwSize
// размер
);
При удачном исходе результат не равен нулю.
Функция VirtualUnlock обратная предыдущей функции.
BOOL VirtualUnlock(
LPVOID lpAddress,
DWORD dwSize
);
При удачном исходе результат не равен нулю.
Все указанные выше функции могут работать только с памятью текущего
процесса.
ASM + x64 + VS.NET 2005 = ERROR ?!
Автор: ACWares
3 января 2008 года
Здравствуйте, уважаемые любители (и профессионалы) низкоуровневого
программирования. В этой статье рассмотрим проблему, которая, так сказать,
образовалась "на ровном месте". Виновник - "всеми любимая" корпорация
Microsoft. Заключается она в нежелании среды Visual Studio .NET
компилировать ассемблерный код в 64-разрядном режиме. Но что делать,
если требуется реализовать блок кода, который должен работать
максимально быстро? Без использования ассемблера (ассемблерных вставок)
здесь никак не обойтись. Конечно, можно попытаться оптимизировать код,
но то, что этот код на высокоуровневом языке не будет "выжимать все соки
из процессора", можно заявить однозначно.
В большинстве случаев программы на чистом ассемблере пишутся довольно
редко, поэтому будем рассматривать ситуацию в ключе "пользовательский
интерфейс - на языке высокого уровня, ядро - на языке ассемблера". Не
будем выдумывать чего-то сложного, а остановимся на традиционной задаче
- сумма двух чисел. Работать будем в среде VS.NET 2005. Необходимо
отметить, что если Вы не хотите иметь дополнительных трудностей с
отладкой программ, то у Вас так же должна быть установлена 64-разрядная
ОС. Например, Windows XP x64-Edition или Windows Server 2003.
Принципиально задача будет выглядеть так: программа, управляющая часть
(интерфейс) которой будет написана на C++, будет использовать функцию
исполнительной части (ядра), написанного на языке ассемблера (MASM) и
реализованного в виде DLL-модуля, для вычисления суммы 2 чисел.
Приступим. Сначала напишем DLL-модуль. Для этого нам понадобится
MASM-компилятор. Находится он в одной из папок VS.NET 2005, а именно:
:\Microsoft Visual Studio 8\VC\bin\amd64. Требуется в переменную окружения
Path (Control Panel a System a Advanced a Environment Variables a System
Variables) добавить этот путь. Там система и найдет файлы, необходимые для
компиляции программы (ML64, LINK и некоторые другие).
Ассемблерный исходник будет выглядеть примерно так (kernel.asm):
.CODE
DllMain PROC
mov rax,1
ret
DllMain ENDP
Sum PROC
mov
rax, rcx
add
rax, rdx
ret
Sum ENDP
END
Как и в любой DLL здесь есть функция DllMain, которая у нас всегда будет
возвращать истину, и функция вычисления суммы (напомню, что компилятор
C++ всегда ждет целочисленный результат выполнения функции в регистреаккумуляторе). Для создания DLL-модуля необходим еще одни файл - файл с
описанием экспортируемых функций - DEF-файл. Его содержимое будет
выглядеть следующим образом (kernel.def):
LIBRARY
EXPORTS
kernel
Sum @1
С исходными кодами закончили. Напишем пакетный файл для компиляции
(Назовем его COMPILE.BAT, хотя это не принципиально):
ml64 kernel.asm /link /OUT:"kernel.dll" /DLL
/entry:DllMain /DEF:kernel.def /SUBSYSTEM:CONSOLE
Данной строкой запускаем компилятор ML64, компилируем kernel.asm. /link запускаем компоновщик со следующими параметрами: /OUT:kernel.dll выходной файл kernel.dll; /DLL - указывает на то, что должен быть создан
DLL-модуль, /entry:DllMain - точка входа - функция DllMain, /DEF - def-файл
kernel.def.
По первой части все. Запускаем файл COMPILE.BAT. Если все было сделано
без ошибок, то появится несколько файлов. Из них нам нужен только один kernel.dll.
Приступаем к созданию управляющего кода. Создадим консольное
приложение Visual C++, назовем его test_x64. Так же необходимо
скопировать файл kernel.dll в директорию проекта. Отредактируем файл
test_x64.cpp, чтобы он принял следующий вид:
#include "stdafx.h"
#include <windows.h>
#include <iostream>
#include <conio.h>
using namespace std;
//Функция Sum будет иметь 2 целочисленных, возвращать она будет целое
число
typedef __int64 (*pSum)(__int64, __int64);
int _tmain(int argc, _TCHAR* argv[])
{
//Переменные для суммы
__int64 a, b, r = 0;
//Адрес DLL-модуля
HMODULE hModule;
//Адрес фунцкии вычисления адреса
pSum Sum;
//Загрузка DLL в адресное пространство процесса
hModule = LoadLibraryA("kernel.dll");
//Получения адреса функции с именем Sum
Sum = (pSum)GetProcAddress(hModule, "Sum");
cout << "A = ";
cin >> a;
cout << "B = ";
cin >> b;
//Вычислени суммы
r = Sum(a, b);
cout << "Result = " << r << endl;
//Выгрузка DLL
FreeLibrary(hModule);
getch();
return 0;
}
Исходный код готов. Остался последний момент. Необходимо сообщить
компилятору, что мы хотим получить 64-раздяное приложение. Делается это
так. Открываем свойства проекта Solution Explorer a test_x64 a Properties.
Вызываем менеджер конфигураций нажатием кнопки <Configuration
Manager:>. В раскрывающемся списке <Action solution platform> выбираем
<New:>. В первом раскрывающемся списке выбираем <x64>. (Если у вас нет
этой записи, то это значит, что при установке VS.NET вы не указали
компонент для компиляции программ под 64 разряда (находится в папке
компонентов, относящихся к Visual C++) - придется установить этот
компонент). Нажимаем OK. В списке проектов напротив test_x64 в поле
Platform должно установиться x64. Нажимаем Close, тем самым вернувшись в
окно свойств проекта. Далее необходимо отредактировать предпроцессорные
директивы: в поле Configuration Properties a C/C++ a Preprocessor a
Preprocessor Definitions изменим _WIN32 на _WIN64. Так же проверьте, что в
Configuration Properties a Linker a Advanced a Target Machine выбран ключ
/MACHINE:X64. Нажмите OK. Все, теперь все должно заработать.
В заключении отметим: статья кратко описывает весь процесс создания
работающей программы, поэтому, если что-то (что вполне возможно) не
получается, то можно скачать готовый пример. Находится он по следующему
адресу: SampleX64.zip. Многие аспекты были рассмотрены очень кратко
(например, установка компонентов для компиляции по 64 разряда), а
объяснение некоторых - вообще опущено: что за регистры rax, rdx, rcx?
Почему при выходе из функций не чистится стек? Откуда функция Sum
берет параметры? Все это сделано для того, чтобы рассмотреть проблему <в
чистом виде>. Т.е. предполагалось, что читатель уже хорошо ориентируется
в программировании на языке ассемблера и знаком с программной
структурой 64-разрядных процессоров. Если же это не так, то автор, по мере
возможности, ответит на любые возникшие (даже косвенно относящиеся к
данным темам) вопросы.
Комментарии пользователей (всего 1)
Dymytriy / 04 апреля 2008, 12:23:18
Зачем реализовывать отдельную DLL, ведь можно просто подключить asm
файл к проекту, и в свойствах файла прописать строку к масму, в результате
файл будет откомпилирован в obj файл...
1. Краткий справочник по функциям WinAPI
Кpаткий спpавочник для пpогpаммистов, котоpым тpебуется
конкpетная инфоpмация по той или иной возможности интеpфейса API.
Функции и пpоцедуpы Windows пеpечисляются и описываются в
алфавитном поpядке.
http://www.codenet.ru/progr/delphi/WinAPI/
Download