Как выглядит программа на ассемблере

Программирование на ассемблере под Win32 воспринимается весьма не однозначно. Считается, что написание приложений слишком сложно для применения ассемблера. Собственно обсуждению того насколько оправдана такая точка зрения и посвящена данная статья. Она не ставит своей целью обучение программированию под Win32 или обучение ассемблеру, я подразумеваю, что читатели имеют определённые знания в этих областях.

В отличие от программирования под DOS , где программы написанные на языках высокого уровня (ЯВУ) были мало похожи на свои аналоги, написанные на ассемблере, приложения под Win32 имеют гораздо больше общего. В первую очередь, это связано с тем, что обращение к сервису операционной системы в Windows осуществляется посредством вызова функций, а не прерываний, что было характерно для DOS . Здесь нет передачи параметров в регистрах при обращении к сервисным функциям и, соответственно, нет и множества результирующих значений возвращаемых в регистрах общего назначения и регистре флагов. Следовательно проще запомнить и использовать протоколы вызова функций системного сервиса. С другой стороны, в Win32 нельзя непосредственно работать с аппаратным уровнем, чем “грешили” программы для DOS . Вообще написание программ под Win32 стало значительно проще и это обусловлено следующими факторами:

NASM. Первая программа. Урок 1

— отсутствие startup кода, характерного для приложений и динамических библиотек написанных под Windows 3.x;

— гибкая система адресации к памяти: возможность обращаться к памяти через любой регистр общего назначения; “отсутствие” сегментных регистров;

— доступность больших объёмов виртуальной памяти;

— развитый сервис операционной системы, обилие функций, облегчающих разработку приложений;

— многообразие и доступность средств создания интерфейса с пользователем (диалоги, меню и т.п.).

Современный ассемблер, к которому относится и TASM 5.0 фирмы Borland International Inc. , в свою очередь, развивал средства, которые ранее были характерны только для ЯВУ. К таким средствам можно отнести макроопределение вызова процедур, возможность введения шаблонов процедур (описание прототипов) и даже объектно-ориентированные расширения. Однако, ассемблер сохранил и такой прекрасный инструмент, как макроопределения вводимые пользователем, полноценного аналога которому нет ни в одном ЯВУ.

Все эти факторы позволяют рассматривать ассемблер, как самостоятельный инструмент для написания приложений под платформы Win32 (Windows NT и Windows 95) . Как иллюстрацию данного положения, рассмотрим простой пример приложения, работающего с диалоговым окном.

Пример 1. Программа работы с диалогом

Файл, содержащий текст приложения, dlg.asm

include «winconst.inc» ; API Win32 consts

include «winptype.inc» ; API Win32 functions prototype

include «winprocs.inc» ; API Win32 function

include «resource.inc» ; resource consts

szAppName db ‘Demo 1’, 0

szHello db ‘Hello, ‘

FASM. Установка FASM. Структура программы на ассемблере. Урок 1

szUser db MAX_USER_NAME dup (0)

Start: call GetModuleHandleA, 0

call DialogBoxParamA, eax, IDD_DIALOG, 0, offset DlgProc, 0

call MessageBoxA, 0, offset szHello,

MB_OK or MB_ICONINFORMATION

bye: call ExitProcess, 0

public stdcall DlgProc

proc DlgProc stdcall

offset szUser, MAX_USER_NAME

call SetFocus, eax

Файл ресурсов dlg.rc

IDD_DIALOG DIALOGEX 0, 0, 187, 95

STYLE DS_MODALFRAME | DS_3DLOOK | WS_POPUP | WS_CAPTION | WS_SYSMENU

FONT 8, «MS Sans Serif»

LTEXT «Type your name»,IDC_STATIC,4,36,52,8

Остальные файлы из данного примера, приведены в приложении 1.

Краткие комментарии к программе

Сразу после метки Start , программа обращается к функции API Win32 GetModuleHandle для получения handle данного модуля (данный параметр чаще именуют как handle of instance) . Получив handle , мы вызываем диалог, созданный либо вручную, либо с помощью какой-либо программы построителя ресурсов. Далее программа проверяет результат работы диалогового окна. Если пользователь вышел из диалога посредством нажатия клавиши OK , то приложение запускает MessageBox с текстом приветствия.

Диалоговая процедура обрабатывает следующие сообщения. При инициализации диалога ( WM_INITDIALOG ) она просит Windows установить фокус на поле ввода имени пользователя. Сообщение WM_COMMAND обрабатывается в таком порядке: делается проверка на код нажатия клавиши . Если была нажата клавиша OK , то пользовательский ввод копируется в переменную szValue , если же была нажата клавиша Cancel , то копирования не производится. Но и в том и другом случае вызывается функция окончания диалога: EndDialog . Остальные сообщения в группе WM_COMMAND просто игнорируются, предоставляя Windows действовать по умолчанию.

Вы можете сравнить приведённую программу с аналогичной программой, написанной на ЯВУ, разница в написании будет незначительна. Очевидно те, кто писал приложения на ассемблере под Windows 3.x, отметят тот факт, что исчезла необходимость в сложном и громоздком startup коде. Теперь приложение выглядит более просто и естественно.

Пример 2. Динамическая библиотека

Написание динамических библиотек под Win32 также значительно упростилось, по сравнению с тем, как это делалось под Windows 3.x . Исчезла необходимость вставлять startup код, а использование четырёх событий инициализации/деинициализации на уровне процессов и потоков, кажется логичным.

Рассмотрим простой пример динамической библиотеки, в которой всего одна функция, преобразования целого числа в строку в шестнадцатеричной системе счисления.

extrn GetVersion: proc

proc libEntry stdcall

public stdcall Hex2Str

proc Hex2Str stdcall

mov [byte ebx + 8],0

Остальные файлы, которые необходимы для данного примера, можно найти в приложении 2.

Краткие комментарии к динамической библиотеке

Процедура libEntry является точкой входа в динамическую библиотеку, её не надо объявлять как экспортируемую, загрузчик сам определяет её местонахождение. LibEntry может вызываться в четырёх случаях:

— при проецировании библиотеки в адресное пространство процесса ( DLL_PROCESS_ATTACH );

— при первом вызове библиотеки из потока (DLL_THREAD_ATTACH), например, с помощью функции LoadLibrary ;

— при выгрузке библиотеки потоком ( DLL_THREAD_DETACH );

— при выгрузке библиотеки из адресного пространства процесса ( DLL_PROCESS_DETACH ).

В нашем примере обрабатывается только первое из событий DLL_PROCESS_ATTACH . При обработке данного события библиотека запрашивает версию OS сохраняет её, а также свой handle of instance .

Библиотека содержит только одну экспортируемую функцию, которая собственно не требует пояснений. Вы, пожалуй, можете обратить внимание на то, как производится запись преобразованных значений. Интересна система адресации посредством двух регистров общего назначения: ebx + ecx, она позволяет нам использовать регистр ecx одновременно и как счётчик и как составную часть адреса.

Пример 3. Оконное приложение

lpfnWndProc dd 0

cbClsExtra dd 0

cbWndExtra dd 0

hbrBackground dd 0

lpszMenuName dd 0

lpszClassName dd 0

WS_EX_OVERLAPPEDWINDOW = WS_EX_WINDOWEDGE OR WS_EX_CLIENTEDGE

WS_OVERLAPPEDWINDOW = WS_OVERLAPPED OR

PROCTYPE ptGetModuleHandle stdcall

PROCTYPE ptLoadIcon stdcall

PROCTYPE ptLoadCursor stdcall

PROCTYPE ptLoadMenu stdcall

PROCTYPE ptRegisterClassEx stdcall

PROCTYPE ptCreateWindowEx stdcall

PROCTYPE ptShowWindow stdcall

PROCTYPE ptUpdateWindow stdcall

PROCTYPE ptGetMessage stdcall

PROCTYPE ptTranslateMessage stdcall

PROCTYPE ptDispatchMessage stdcall

PROCTYPE ptSetMenu stdcall

PROCTYPE ptPostQuitMessage stdcall

PROCTYPE ptDefWindowProc stdcall

PROCTYPE ptSendMessage stdcall

PROCTYPE ptMessageBox stdcall

PROCTYPE ptExitProcess stdcall

extrn GetModuleHandleA :ptGetModuleHandle

extrn LoadIconA :ptLoadIcon

extrn LoadCursorA :ptLoadCursor

Читайте также:
Программе настройки не удается сохранить исходную конфигурацию загрузки для использования

extrn RegisterClassExA :ptRegisterClassEx

extrn LoadMenuA :ptLoadMenu

extrn CreateWindowExA :ptCreateWindowEx

extrn ShowWindow :ptShowWindow

extrn UpdateWindow :ptUpdateWindow

extrn GetMessageA :ptGetMessage

extrn TranslateMessage :ptTranslateMessage

extrn DispatchMessageA :ptDispatchMessage

extrn SetMenu :ptSetMenu

extrn PostQuitMessage :ptPostQuitMessage

extrn DefWindowProcA :ptDefWindowProc

extrn SendMessageA :ptSendMessage

extrn MessageBoxA :ptMessageBox

extrn ExitProcess :ptExitProcess

classTitle db ‘Menu demo’, 0

wndTitle db ‘Demo program’, 0

msg_open_txt db ‘You selected open’, 0

msg_open_tlt db ‘Open box’, 0

msg_save_txt db ‘You selected save’, 0

msg_save_tlt db ‘Save box’, 0

Start: call GetModuleHandleA, 0 ; не обязательно, но желательно

sub esp,SIZE WndClassEx ; отведём место в стеке под структуру

mov [(WndClassEx esp).cbSize],SIZE WndClassEx

mov [(WndClassEx esp).style],CS_HREDRAW or CS_VREDRAW

mov [(WndClassEx esp).lpfnWndProc],offset WndProc

mov [(WndClassEx esp).cbWndExtra],0

mov [(WndClassEx esp).cbClsExtra],0

mov [(WndClassEx esp).hInstance],eax

call LoadIconA, 0, IDI_APPLICATION

mov [(WndClassEx esp).hIcon],eax

call LoadCursorA, 0, IDC_ARROW

mov [(WndClassEx esp).hCursor],eax

mov [(WndClassEx esp).hbrBackground],COLOR_WINDOW

mov [(WndClassEx esp).lpszMenuName],MyMenu

mov [(WndClassEx esp).lpszMenuName],0

mov [(WndClassEx esp).lpszClassName],offset classTitle

mov [(WndClassEx esp).hIconSm],0

call RegisterClassExA, esp ; зарегистрируем класс окна

add esp,SIZE WndClassEx ; восстановим стек

; и создадим окно

call CreateWindowExA, WS_EX_OVERLAPPEDWINDOW, extended window style

offset classTitle, pointer to registered class name

offset wndTitle, pointer to window name

WS_OVERLAPPEDWINDOW, window style

CW_USEDEFAULT, horizontal position of window

CW_USEDEFAULT, vertical position of window

CW_USEDEFAULT, window width

CW_USEDEFAULT, window height

0, handle to parent or owner window

0, handle to menu, or child-window identifier

[hInst], handle to application instance

0 ; pointer to window-creation data

call LoadMenu, hInst, MyMenu

call CreateWindowExA, WS_EX_OVERLAPPEDWINDOW, extended window style

offset classTitle, pointer to registered class name

offset wndTitle, pointer to window name

WS_OVERLAPPEDWINDOW, window style

CW_USEDEFAULT, horizontal position of window

CW_USEDEFAULT, vertical position of window

CW_USEDEFAULT, window width

CW_USEDEFAULT, window height

0, handle to parent or owner window

eax, handle to menu, or child-window identifier

[hInst], handle to application instance

0 ; pointer to window-creation data

call ShowWindow, eax, SW_SHOW ; show window

call UpdateWindow, [hWnd] ; redraw window

call LoadMenuA, [hInst], MyMenu

call SetMenu, [hWnd], eax

call GetMessageA, offset msg, 0, 0, 0

call TranslateMessage, offset msg

call DispatchMessageA, offset msg

exit: call ExitProcess, 0

public stdcall WndProc

proc WndProc stdcall

call PostQuitMessage, 0

mov edx, offset msg_open_tlt

mov edx, offset msg_save_tlt

Комментарии к программе

Здесь мне хотелось в первую очередь продемонстрировать использование прототипов функций API Win32 . Конечно их (а также описание констант и структур из API Win32 ) следует вынести в отдельные подключаемые файлы, поскольку, скорее всего Вы будете использовать их и в других программах. Описание прототипов функций обеспечивает строгий контроль со стороны компилятора за количеством и типом параметров, передаваемых в функции. Это существенно облегчает жизнь программисту, позволяя избежать ошибок времени исполнения, тем более, что число параметров в некоторых функциях API Win32 весьма значительно.

Существо данной программы заключается в демонстрации вариантов работы с оконным меню. Программу можно откомпилировать в трёх вариантах (версиях), указывая компилятору ключи VER2 или VER3 (по умолчанию используется ключ VER1) . В первом варианте программы меню определяется на уровне класса окна и все окна данного класса будут иметь аналогичное меню. Во втором варианте, меню определяется при создании окна, как параметр функции CreateWindowEx . Класс окна не имеет меню и в данном случае, каждое окно этого класса может иметь своё собственное меню. Наконец, в третьем варианте, меню загружается после создания окна. Данный вариант показывает, как можно связать меню с уже созданным окном.

Директивы условной компиляции позволяют включить все варианты в текст одной и той же программы. Подобная техника удобна не только для демонстрации, но и для отладки. Например, когда Вам требуется включить в программу новый фрагмент кода, то Вы можете применить данную технику, дабы не потерять функционирующий модуль. Ну, и конечно, применение директив условной компиляции – наиболее удобное средство тестирования различных решений (алгоритмов) на одном модуле.

Представляет определённый интерес использование стековых фреймов и заполнение структур в стеке посредством регистра указателя стека (esp) . Именно это продемонстрировано при заполнении структуры WndClassEx . Выделение места в стеке (фрейма) делается простым перемещением esp :

sub esp,SIZE WndClassEx

Теперь мы можем обращаться к выделенной памяти используя всё тот же регистр указатель стека. При создании 16-битных приложений такой возможностью мы не обладали. Данный приём можно использовать внутри любой процедуры или даже произвольном месте программы. Накладные расходы на подобное выделение памяти минимальны, однако, следует учитывать, что размер стека ограничен и размещать большие объёмы данных в стеке вряд ли целесообразно. Для этих целей лучше использовать “кучи” (heap) или виртуальную память ( virtual memory ).

Остальная часть программы достаточно тривиальна и не требует каких-либо пояснений. Возможно более интересным покажется тема использования макроопределений.

Мне достаточно редко приходилось серьёзно заниматься разработкой макроопределений при программировании под DOS . В Win32 ситуация принципиально иная. Здесь грамотно написанные макроопределения способны не только облегчить чтение и восприятие программ, но и реально облегчить жизнь программистов.

Дело в том, что в Win32 фрагменты кода часто повторяются, имея при этом не принципиальные отличия. Наиболее показательна, в этом смысле, оконная и/или диалоговая процедура. И в том и другом случае мы определяем вид сообщения и передаём управление тому участку кода, который отвечает за обработку полученного сообщения.

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

macro MessageVector message1, message2:REST

macro WndMessages VecName, message1, message2:REST

MessageVector message1, message2

Комментарии к макроопределениям

При написании процедуры окна Вы можете использовать макроопределение WndMessages , указав в списке параметров те сообщения, обработку которых намерены осуществить. Тогда процедура окна примет вид:

proc WndProc stdcall

WndMessages WndVector, WM_CREATE, WM_SIZE, WM_PAINT, WM_CLOSE, WM_DESTROY

; здесь обрабатываем сообщение WM_CREATE

; здесь обрабатываем сообщение WM_SIZE

; здесь обрабатываем сообщение WM_PAINT

; здесь обрабатываем сообщение WM_CLOSE

; здесь обрабатываем сообщение WM_DESTROY

Обработку каждого сообщения можно завершить тремя способами:

Отметьте, что все перечисленные метки определены в макро WndMessages и Вам не следует определять их заново в теле процедуры.

Сейчас в макроопределении WndMessage можно начинать обработку. Теперь существо обработки, скорее всего, будет понятно без дополнительных пояснений.

Обработка сообщений в Windows не является линейной, а, как правило, представляет собой иерархию. Например, сообщение WM_COMMAND может заключать в себе множество сообщений поступающих от меню и/или других управляющих элементов. Следовательно, данную методику можно с успехом применить и для других уровней каскада и даже несколько упростить её.

Читайте также:
Как установить программу сканирования на телефоне

Действительно, не в наших силах исправить код сообщений, поступающих в процедуру окна или диалога, но выбор последовательности констант, назначаемых пунктам меню или управляющим элементам ( controls ) остаётся за нами. В этом случае нет нужды в дополнительном поле, которое сохраняет код сообщения. Тогда каждый элемент вектора будет содержать только адрес обработчика, а найти нужный элемент весьма просто. Из полученной константы, пришедшей в сообщении, вычитается идентификатор первого пункта меню или первого управляющего элемента, это и будет номер нужного элемента вектора. Остаётся только сделать переход на обработчик.

Вообще тема макроопределений весьма поучительна и обширна. Мне редко доводится видеть грамотное использование макросов и это досадно, поскольку с их помощью можно сделать работу в ассемблере значительно проще и приятнее.

Для того, чтобы писать полноценные приложения под Win32 требуется не так много:

— собственно компилятор и компоновщик ( я использую связку TASM32 и TLINK32 из пакета TASM 5.0) . Перед использованием рекомендую “наложить” patch , на данный пакет. Patch можно взять на site www.borland.com или на нашем ftp сервере ftp.uralmet.ru.

— редактор и компилятор ресурсов (я использую Developer Studio и brcc32.exe );

— выполнить перетрансляцию header файлов с описаниями процедур, структур и констант API Win32 из нотации принятой в языке Си, в нотацию выбранного режима ассемблера: Ideal или MASM.

В результате у Вас появится возможность писать лёгкие и изящные приложения под Win32 , с помощью которых Вы сможете создавать и визуальные формы, и работать с базами данных, и обслуживать коммуникации, и работать multimedia инструментами. Как и при написании программ под DOS, у Вас сохраняется возможность наиболее полного использования ресурсов процессора, но при этом сложность написания приложений значительно снижается за счёт более мощного сервиса операционной системы, использования более удобной системы адресации и весьма простого оформления программ.

Приложение 1. Файлы, необходимые для первого примера

Файл констант ресурсов resource.inc

IDD_DIALOG = 65 ; 101

IDR_NAME = 3E8 ; 1000

Файл заголовков resource.h

Источник: www.helloworld.ru

7.2. Первая простейшая программа под Windows на ассемблере

А теперь посмотрим, как написать простейшее приложение под Windows и на его примере рассмотрим все принципы вызовов API-функций на ассемблере. Все, что будет делать наша программа это выводить окно с сообщением, показанное на рис. 7.1. Рис. 7.1.

Вид окна с сообщением, отображаемого программой Для вывода таких сообщений используется API-функция MessageBox, ниже приведено описание, взятое из MSDN в переводе на русский: int MessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType); Параметры функции:  hWnd — дескриптор родительского окна. Если родительского окна нет, то используется нулевое значение;  lpText — текст, отображаемый внутри окна;  lpCaption — текст в заголовке окна;  uType — тип окна сообщений, который позволяет задать, какие кнопки и иконки будут отображаться в окне. Этот параметр может быть комбинацией флагов из следующих групп флагов: MB_ABORTRETRYIGNORE — окно сообщений будет содержать три кнопки: «Abort», «Retry», и «Ignore»; MB_OK — окно сообщений будет содержать только одну кнопку: «OK»; MB_OKCANCEL — окно сообщений будет содержать две кнопки: «OK» и «Cancel»; MB_YESNO — окно сообщений будет содержать две кнопки: «Yes» и «No»; MB_ICONEXCLAMATION — в окне сообщений появится иконка с восклицательным знаком; MB_ICONHAND — в окне сообщений появится иконка с изображением знака «Stop»;

http://www.sklyaroff.ru 150

MB_ICONQUESTION — в окне сообщений появится иконка с изображением вопросительного знака; MB_ICONASTERISK — в окне сообщений появится иконка с изображением буквы «i»; MB_DEFBUTTON1 — фокус находится на первой кнопке; MB_DEFBUTTON2 — фокус находится на второй кнопке; MB_DEFBUTTON3 — фокус находится на третьей кнопке. Все флаги и вообще любые строковые идентификаторы, которые вы можете встретить в описаниях функций API, на самом деле являются именами определенных числовых значений (кодов).

Вы не можете в программе на ассемблере просто использовать, скажем, флаг MB_OK, т. к. на самом деле требуется указать число, которое заменяет этот флаг. Узнать числовые значения флагов и всех прочих идентификаторов API-функций, всегда можно в так называемых заголовочных файлах (имеют расширение .h). В описании каждой API-функции в MSDN обычно указывается, в каком заголовочном файле следует искать такие числовые значения и сразу предоставляется возможность просмотреть этот заголовочный файл. Для функции MessageBox все значения флагов содержатся в заголовочном файле winuser.h. Вот нужный нам отрывок из winuser.h:

#define MB_OK 0x00000000L
#define MB_OKCANCEL 0x00000001L
#define MB_ABORTRETRYIGNORE 0x00000002L
#define MB_YESNOCANCEL 0x00000003L
#define MB_YESNO 0x00000004L
#define MB_RETRYCANCEL 0x00000005L

Оператор языка Си #define является аналогом ассемблерной директивы эквивалентности EQU, которая присваивает имени значение операнда (см. разд. 2.4.2). Как видим, нужному нам флагу MB_OKCANCEL присвоено значение 0x00000001L. Буква L на конце числа означает в языке Си длинное целое (long).

В ассемблере эта буква не используется, поэтому значение просто будет выглядеть как 1. Мы можем в программе на ассемблере просто использовать это значение или с помощью директивы эквивалентности определить флаг: MB_OKCANCEL EQU 1 К счастью нам не придется это делать самим постоянно, т. к. в пакет MASM32 входит специальный файл WINDOWS.INC (расположен в MASM32include) в котором определены директивы эквивалентности для всевозможных идентификаторов и флагов, которые используют API-функции, в том числе и MB_OKCANCEL. Откройте в любом текстовом редакторе этот файл и посмотрите его содержимое.

Можно (и нужно) подключать файл WINDOWS.INC в своих программах и использовать любые идентификаторы из него, как это сделать мы узнаем ниже. Прежде чем мы начнем составлять программу на ассемблере, вам нужно узнать еще одну важную тонкость, связанную с API-функциями. Все API-функции работающие со строками (как, например, MessageBox) существуют в двух версиях:  ANSI-версия, для работы со строками в кодировке ANSI (один байт на символ). В этом случае к имени функции добавляется суффикс A. Например, для функции MessageBox имя ANSI-версии будет MessageBoxA.  Unicode-версия, для работы со строками в кодировке Unicode (два байта на символ). К имени функции добавляется суффикс W. Например, для функции MessageBox имя Unicode-версии будет MessageBoxW.

API-функции без суффиксов (например MessageBox), на самом деле является лишь обертками для этих двух функций. Компилятор высокоуровневых языков сам решает,

http://www.sklyaroff.ru 151

какую из двух версий API-функции нужно использовать, однако программист на ассемблере должен точно указывать нужную версию функции ANSI или Unicode в своих программах. ANSI-версию стоит выбирать когда в функцию передаются строки только из 256 символов кодировки ASCII, а Unicode-версию, когда в функцию требуется передавать строки из расширенного набора (65536 символов) кодировки Unicode.

Читайте также:
Программа сделать меню для ресторана

Строки для API-функций ANSI-версий (MessageBoxA) задаются также, как мы это делали в DOS-программах, к примеру: str DB «Ivan Sklyaroff», 0 А для Unicode-версий (MessageBoxW) строки необходимо определять следующим образом: str DW ‘I’, ‘v’, ‘a’, ‘n’, ‘ ‘, ‘S’, ‘k’, ‘l’, ‘y’, ‘a’, ‘r’, ‘o’, ‘f’, ‘f’, 0 Разумеется, можно просто указывать коды непечатаемых символов, например, в следующей строке заданы четыре символа в кодировке Unicode в шестнадцатеричном виде (значок «евро» – код 020ACh, математическая «бесконечность» – код 0221Eh, «больше или равно» – код 02265h, буква арабского языка – код 0642h): str DW 020ACh, 0221Eh, 02265h, 0642h, 0 Мы далее будем использовать строки, а, следовательно, и функции только ANSIверсий. Обратите также внимание, что в Windows строки заканчиваются нулем, а не символом $ как в DOS-программах.

Это правило вытекает из языка Си, так как в нем строки должны заканчиваться нулевым символом. Однако помните о том, что часто один из параметров API-функции задает длину строки (в документации это часто называется буфером), определяемой другим параметром, из-за этого возвращаемая из функции строка может не иметь нуля на конце.

При использовании в дальнейшем строки без нуля на конце, в других функциях могут возникнуть ошибки. И еще одна важная деталь. В начале программы на ассемблере, также как и в программах на Си, нужно задавать так называемые прототипы всех вызываемых функций. Прототип это просто краткое описание функции с указанием точного числа параметров и их типов.

Вызываемая функция должна соответствовать своему прототипу, иначе линкер выдаст ошибку. В языке программирования Си использование прототипов обычная практика, они помогают писать безошибочные программы. Прототип создается с помощью директивы PROTO. Например, для функции MessageBoxA прототип будет выглядеть следующим образом: MessageBoxA PROTO :DWORD,:DWORD,:DWORD,:DWORD Это означает, что в функцию MessageBoxA должны быть переданы четыре 32разрядных параметра. В прототипах всех API-функций для указания типа параметров можно использовать DWORD, т. к. в API-функциях все параметры только 32-разрядные (мы говорили об этом выше). Теперь мы можем написать предварительный вызов функции MessageBoxA на ассемблере: MB_OKCANCEL EQU 1

http://www.sklyaroff.ru 152
.model flat,stdcall
MessageBoxA PROTO :DWORD,:DWORD,:DWORD,:DWORD
.data
hello_mess db «Первая программа под Windows на ассемблере», 0
hello_title db «Hello, World!», 0

.code start: push MB_OKCANCEL push offset hello_title push offset hello_mess push 0 call MessageBoxA end start Мы верно составили вызов API-функции, однако как мы говорили в начале этого дня, функции API находятся в dll-библиотеках, и пока не присоединить нужные библиотеки к программе никакие даже правильно составленные вызовы APIфункций работать не будут. Но как присоединить динамические библиотеки к программе на ассемблере?

Напрямую это сделать нельзя и нужно использовать так называемые библиотеки импорта, которые обычно имеют тоже имя, что и соответствующие dll-файлы, но с расширением .lib. В MSDN Library в описании функции MessageBox можно увидеть следующую информацию (табл. 7.1). Как видите, здесь указано, что функция содержится в файле user32.dll, а библиотекой импорта является файл User32.lib. Таблица 7.1. Информация из MSDN Library к описанию функции MessageBox

Minimum DLL Version user32.dll
Header Declared in Winuser.h, include Windows.h
Import library User32.lib
Minimum operating systems Windows 95, Windows NT 3.1
Unicode Implemented as ANSI and Unicode versions.

Файлы библиотек импорта входят в дистрибутивы любых средств разработки для Windows, например их можно взять из Visual Studio. Однако пакет MASM32 облегчает нам задачу тем, что в нем уже содержатся все необходимые библиотеки импорта в разделе MASM32lib.

Библиотеки импорта подключается в программе на ассемблере с помощью директивы INCLUDELIB, следующим образом: includelib masm32libuser32.lib Кроме того, в пакет MASM32 входят INC-файлы (расположены в разделе MASM32include) в которых содержатся все прототипы API-функций. Эти INCфайлы имеют имена такие же, как имена соответствующих lib-файлов. Поэтому, подключив к программе INC-файл, вам не придется самостоятельно определять прототипы функций. INC-файлы подключаются с помощью директивы INCLUDE, следующим образом: include masm32includeuser32.inc Посмотрите содержимое файла user32.inc, вы найдете в нем прототип MessageBoxA. Кроме того, как мы уже говорили, в разделе MASM32include существует файл WINDOWS.INC, в котором определены директивы эквивалентности для

http://www.sklyaroff.ru 153

всевозможных идентификаторов и флагов, которые используют API-функции, как, например MB_OKCANCEL. Подключается файл WINDOWS.INC также как и любой INC-файл: include masm32includewindows.inc Файл WINDOWS.INC является большим подспорьем для программистов, т. к. в больших программах число всевозможных флагов, идентификаторов и структур, необходимых функциям API достигает многие десятки.

Подключив этот файл к своей программе, вам не придется больше определять их самостоятельно. Мы далее будем включать этот файл во все наши программы на ассемблере. В листинге 7.1 показана готовая программа, которая выводи окно сообщения. Вы видите, мы задействовали еще одну API-функцию ExitProcess для выхода из программы.

Описание функции: VOID WINAPI ExitProcess(UINT uExitCode ); Она расположена в kernel32.dll, поэтому мы добавляем в программу включения файлов: include masm32includekernel32.inc includelib masm32libkernel32.lib В качестве параметра uExitCode мы должны передать нулевое значение, чтобы завершить текущий процесс. Данная функция не возвращает никакого значения. Функцией ExitProcess мы будем завершать все наши программы на ассемблере под Windows.

Компиляция программы осуществляется следующей строкой: ml /c /coff /Cp hello.asm Назначение параметров: /c компиляция без компоновки. /coff создать .obj файл в формате COFF (Common Object File Format). Применение этого параметра обязательно. /Cр сохранять регистр имен, заданных пользователем.

Вы можете вставить строку «option casemaр:none» в начале исходного кода вашей программы, сразу после директивы .model, чтобы добиться того же эффекта. После успешной компиляции hello.asm , вы получите hello.obj . Это объектный файл, который содержит инструкции и данные в двоичной форме.

Отсутствуют только необходимая корректировка адресов, которая проводится линкером. link.exe /SUBSYSTEM:WINDOWS /LIBPATH:c:masm32lib hello.obj /SUBSYSTEM:WINDOWS информирует линкер о том, что приложение является оконным приложением. Для консольных приложений необходимо использовать параметр /SUBSYSTEM:CONSOLE. /LIBPATH: указывает линкеру местоположение библиотек импорта. Если вы используете MASM32, они будут в MASM32lib. После окончания линковки вы получите файл hello.exe. Листинг 7.1. Простейшая программа под Windows ( hello.asm ) .386P .model flat,stdcall include masm32includewindows.inc include masm32includeuser32.inc include masm32includekernel32.inc

includelib masm32libuser32.lib ; Подключаем библиотеки
includelib masm32libkernel32.lib

Источник: studfile.net

Рейтинг
( Пока оценок нет )
Загрузка ...
EFT-Soft.ru