Форум программистов, компьютерный форум, киберфорум
Наши страницы
Assembler, MASM, TASM
Войти
Регистрация
Восстановить пароль
 
 
Рейтинг 4.60/380: Рейтинг темы: голосов - 380, средняя оценка - 4.60
Mikl___
Автор FAQ
11795 / 6098 / 556
Регистрация: 11.11.2010
Сообщений: 11,091
1

Сам себе Iczelion

02.01.2013, 15:15. Просмотров 69159. Ответов 116
Метки нет (Все метки)

Содержание

21
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
02.01.2013, 15:15
Ответы с готовыми решениями:

Обсуждение темы "Сам себе Iczelion"
Win32 API. Урок 1. Основы Этот Урок предполагает, что читатель знает, как...

создать програму которая содержит в себе команды обработки строк языка асемблер
Создать програму которая содержит в себе команды обработки строк языка...

Регистры eax & edx сами по себе принимают непонятные значения
Задача программы сортировка массива состоящего из 5ти двойных слов. Ассемблер...

ПК сам по себе перезагружается
Добрый вечер,у меня такая же проблема,сам себе перезагружается,без синего...

Запрос сам в себе
Ребята, вот например есть таблица Name Time a 12-04-2011...

116
Mikl___
Автор FAQ
11795 / 6098 / 556
Регистрация: 11.11.2010
Сообщений: 11,091
02.01.2013, 21:50  [ТС] 2
Бредисловие

Кликните здесь для просмотра всего текста
Режисер ― Не пора ли, друзья мои,
нам замахнутся на Уильяма,
понимаете ли, нашего Шекспира?
Голос из зала ― И замахнемся!

Из кинофильма «Берегись автомобиля»)
Не сочтите за кощунство. Но попробую переписать уроки Iczelion'а с позиции уменьшения размера. Писалось под WinXP и Seven на масме, а позже было переведено на FASM, NASM и GoAsm.
В онлайновом учебнике по программированию на ассемблере в win32 (перевод UniSoft) есть такие строки: «Я провел эксперимент, написал программу (обычное окно с одной кнопкой в центре, которая закрывает его) на разных языках высокого уровня и после компиляции получил вот такие размеры этой самой программы:
Кликните здесь для просмотра всего текста
программаразмер
C++ Builder 422 kb
Delphi 5291 kb
Delphi 5 + библиотека KOL26 kb
Ассемблер MASMоколо 3 kb
Большинство EXE-файлов, созданных в уроках Iczelion'а, имеют размер около 4к, я уменьшаю их размер до 1к и менее, наименьший размер, к которому можно/нужно стремиться 97 байт, но это PE-заголовок файла перемешанный с кодом, это уже «ручная выделка»... я пока о стандартных методах уменьшения ЕХЕ файлов, работающих под Win XP и seven.
Русский перевод "Уроков Iczelion'a" взят на сайте WASM.RU, там же в разные годы публиковались и комментарии к "Урокам..."
  • © Iczelion (автор туториалов). См. http://win32assembly.programminghorizon.com/index.html
  • © Aquila (перевод на русский). См. http://www.wasm.ru
  • © SheSan (перевод на русский). Раздел ODBC Tutorial.
  • © WD-40 (перевод на русский).Win32 API. Урок 23, 25.
  • © N/A (перевод на русский).Win32 API. Урок 33.


По версии журнала "Хакер" #45 2002 Iczelion выглядит вот так:
Отрывки из статьи TanaT (TanaT@hotmail.ru) «Ассемблер: свежий взгляд. Что думают об этом профессионалы?»

... мы обратились за советом к мастеру, которого все знают под ником Iczelion...

Для программирования под Windows нет ничего лучшего, чем туториалы, написанные Iczelion'ом. Он потратил целых два года на то, чтобы любой (даже начинающий!) кодер смог почти сразу въехать в программирование на ассемблере под Винду. Его основной девиз: «Кодить под Винду на асме не сложнее, чем на С». Вся документация Iczelion'a разбита на уроки, изучать которые надо, естественно, по порядку...

Личное дело: Iczelion. Родился в 1966. Живет в Таиланде. Работает на правительство. Ассемблер ― первый язык, который он выучил после окончания университета. В книге «Руководство для IBM/PC и PS/2» Питера Нортона в разделе о языках программирования, Iczelion нашел строки: «Только язык ассемблера позволяет использовать мощность вашего ПК в полном объеме...». Эти слова заложили начало длинного пути, в результате которого появился блестящий эксперт по низкоуровневому программированию.

Домашняя страничка Iczelion'а ― http://win32asm.cjb.net.

TanaT ― Некоторые люди считают, что ассемблер мертв. Что скажешь?
Iczelion ― Для некоторых он действительно мертв: они не хотят его использовать, а он ничем не может быть им полезен. Но есть люди, для которых ассемблер является средством существования. Например, VX-кодеры. Для многих людей этот язык просто близок собственному мыслительному процессу.
TanaT ― Нужны ли сегодня программисты на ассемблере?
Iczelion ― Да. Но не в таких количествах, как, скажем, ASP или С++ кодеры. Но сегодня человек должен быть очень хорошо подготовлен, чтобы его приняли на работу.
TanaT ― Если человек знает ассемблер, он может найти себе хорошую работу?
Iczelion ― Я бы на это не рассчитывал. Ассемблер это всего лишь один из языков программирования. У него есть свои сильные и слабые стороны. Что касается работы, то это сильно зависит от человека и самой организации, которой требуются асм-кодеры.
TanaT ― Спасибо, этот вопрос разъяснили. Что бы ты посоветовал нашим читателям, уже знающим ассемблер или только собирающимся его изучать?
Iczelion ― Если вы хотите изучать ассемблер, сперва спросите себя, зачем он вам. Не учите его лишь потому, что он вам кажется «элитным». Ни один из языков программирования не является лучшим для написания абсолютно любых приложений. Сам я пишу на ассемблере далеко не все. Чаще всего я использую VBA или VBScript, если они позволяют решить задачу без потери производительности. А на ассемблере я пишу потому, что люблю этот язык и он мне кажется «естественным». Программирование на ассемблере требует большой концентрации, так как каждая инструкция делает очень мало: только если несколько их собрать воедино, получится какой-нибудь результат. Если вы невнимательны, подумайте еще раз. Программирование на ассемблере требует больших затрат времени, даже если вы используете структурное программирование. То есть программы на ассемблере это чаще всего маленькие утилиты. Я не хочу совсем отпугнуть новичков, просто излагаю некоторые факты самого языка, чтобы вы могли принять объективное решение...


© Mikl___ 2013

Win32 API. Урок 1. Основы
Этот Урок предполагает, что читатель знает, как использовать MASM. Если вы не знакомы с MASM, скачайте c masm32.com и прочитайте текст, входящий в состав пакета, прежде чем продолжать чтение этого введения. Хорошо. Теперь вы готовы. Давайте приступим.
ТЕОРИЯ ― МАТЬ СКЛЕРОЗА
Win32 программы выполняются в защищенном режиме, который доступен начиная с 80286. Hо 80286 теперь история. Поэтому мы предполагаем, что имеем дело только с 80386 и его потомками. Windows запускает каждую Win32 программу в отдельном виртуальном пространстве. Это означает, что каждая Win32 программа будет иметь 4-х гигабайтовое адресное пространство.
Hо это вовсе не означает, что каждая программа имеет 4 гигабайта физической памяти, а только то, что программа может обращаться по любому адресу в этих пределах. Windows сделает все необходимое, чтобы сделать память, к которой программа обращается "существующей". Конечно, программа должна придерживаться правил, установленных Windows, или это вызовет General protection Fault.

Каждая программа одна в своем адресном пространстве, в то время как в Win16 дело обстоит не так. Все Win16 программы могут "видеть" друг друга, что невозможно в Win32. Этот особенность помогает снизить шанс того, что одна программа запишет что-нибудь поверх данных или кода другой программы.

Модель памяти также коренным образом отличается от существующих в старом мире 16-битных программ. Под Win32, мы больше не должны беспокоиться о моделях памяти или сегментах! Теперь только одна модель память: Плоская модель памяти. Теперь нет больше 64K сегментов. Память теперь это большое последовательное 4-х гигабайтовое пространство. Это также означает, что вы не должны "играть" с сегментными регистрами. Вы можете использовать любой сегментный регистр для адресации к любой точке памяти. Это ОГРОМНОЕ подспорье для программистов. Это то, что делает программирование на ассемблере под Win32 таким же простым, как на C.

Когда вы программируете под Win32, вы должны помнить несколько важных правил.
Одно из таких правил то, что Windows использует esi, edi, ebp и ebx внутренне и не ожидает, что значение в этих регистрах меняются. Так что помните это правило: если вы используете какой-либо из этих четырех регистров в вызываемой функции, не забудьте восстановить их перед возвращением управления Windows.
Вызываемая (callback) функция - это функция, которая вызывается Windows.
Очевидный пример - процедура окна. Это не значит, что вы не можете использовать эти четыре регистра. Просто не забудьте восстановить их значения перед передачей управления Windows.
ПРАКТИКА ― МАТЬ ШИЗОФРЕНИИ
Вот каркасная программа. Если что-то из кода вы не понимаете, не паникуйте. В дальнейшем я все объясню.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.386
.MODEL Flat, STDCALL
.DATA
<Ваша инициализируемые данные>
......
.DATA?
<Ваши не инициализируемые данные>
......
.CONST
<Ваши константы>
......
.CODE
<метка>:
<Ваш код>
......
end <метка>
Вот и все! Давайте проанализируем этот "каркас".
Assembler
1
.386
Это ассемблерная директива, говорящая ассемблеру использовать набор операций для процессора 80386. Вы также можете использовать .486, .586, .686 но самый безопасный выбор ― это указывать .386. Также есть два практически идентичных выбора для каждого варианта CPU. .386/.386p, .486/.486p. Эти "p"-версии необходимы только тогда, когда ваша программа использует привилегированные инструкции, то есть инструкции, зарезервированные процессором/операционной системой для работы в защищенном режиме. Они могут быть использованы только в защищенном коде, например, sys-драйверами. Как правило, ваши программы будут работать в непривилегированном режиме, так что лучше использовать не-"p" версии.
Assembler
1
.MODEL FLAT, STDCALL
.MODEL ― ассемблерная директива, определяющая модель памяти вашей программы. Под Win32 есть только одна ― плоская модель.
STDCALL говорит MASM'у о порядке передачи параметров, слева направо или справа налево, а также о том, кто уравнивает стек, после того как функция вызвана.
Под Win16 существует два типа передачи параметров, C и PASCAL. По C-договоренности, параметры передаются справа налево, то есть самый правый параметр кладется в стек первым. Вызывающий должен уравнять стек после вызова. Например, при вызове функции с именем foo(int first_param, int second_param, int third_param), используя C-передачу параметров, ассемблерный код будет выглядеть так:
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
push [third_param] ; Положить в стек третий параметр
push [second_param] ; Следом - второй
push [first_param] ; И, наконец, первый
call foo
add esp, 12 ; Вызывающий уравнивает стек

PASCAL-передача параметров ― это C-передача наоборот. Согласно ей, параметры передаются слева направо и вызываемый параметр должен уравнивать стек.
Win16 использует этот порядок передачи данных, потому что тогда код программы становится меньше. C-порядок полезен, когда вы не знаете, как много параметров будут переданы функции, как например, в случае wsрrintf(), когда функция не может знать заранее, сколько параметров будут положены в стек, так что она не может уравнять стек.
STDCALL - это гибрид C и PASCAL вызовов. Согласно ему, данные передаются справа налево, но вызываемая функция ответственна за очистку стека от переданных ей параметров. Платформа Win32 использует исключительно STDCALL, хотя есть одно исключение -- функция wsprintf(). Вы должны следовать C-порядку вызова в случае wsprintf().
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
.DATA
 
.DATA?
 
.CONST
 
.CODE
Все четыре директивы это то, что называется секциями. Вы помните, что в Win32 нет сегментов? Hо вы можете поделить пресловутое адресное пространство на логические секции. Начало одной секции отмечает конец предыдущей. Есть две группы секций: данных и кода.
.DATA ― Эта секция содержит инициализированные данные вашей программы.
.DATA? ― эта секция содержит неинициализированные данные вашей программы. Иногда вам нужно только "предварительно" выделить некоторое количество памяти, но вы не хотите инициализировать ее. Эта секция для этого и предназначается. Преимущество неинициализированных данных следующее: они не занимают места в исполняемом файле. Например, если вы хотите выделить 10000 байт в вашей .DATA? секции, ваш exe-файл не увеличится на 10kb. Его размер останется таким же. Вы, всего лишь, говорите компилятору, сколько места вам нужно, когда программа загрузится в память.

.CONST ― эта секция содержит объявления констант, используемых программой. Константы не могут быть изменены ей. Это всего лишь "константы".
Вы не обязаны задействовать все три секции. Объявляйте только те, которые хотите использовать.
Есть только одна секция для кода: .CODE, там где содержится весь код.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
<метка>:
.....
end <метка>
где <метка> ― любая произвольная метка, устанавливающая границы кода. Обе метки должны быть идентичны. Весь код должен располагаться между
Assembler
1
<метка>
и
Assembler
1
end <метка>


© Iczelion, пер. Aquila

Win32 API. Урок 2. MessageBox
В этом Уроке мы создадим полнофункциональную Windows программу, которое выводит сообщение — "Win32 assembly is great!".
Скачайте пример здесь.
ТЕОРИЯ, МАТЬ СКЛЕРОЗА
Windows предоставляет огромное количество ресурсов Windows-программам через Windows API (Application Programming Interface). Windows API — это большая коллекция очень полезных функций, располагающихся непосредственно в операционной системе и готовых для использования программами. Эти функции находятся в нескольких динамически подгружаемых библиотеках (DLLs), таких как kernel32.dll, user32.dll и gdi32.dll. Kernel32.dll содержит API-функции, взаимодействующие с памятью и управляющие процессами. User32.dll контролирует пользовательский интерфейс. Gdi32.dll ответственен за графические операции. Кроме этих трех "основных", существуют также другие dll, которые вы можете использовать, при условии, что вы обладаете достаточным количеством информации о нужных API-функциях. Windows программы динамически подсоединяется к этим библиотекам, то есть код API-функций не включается в исполняемый файл. Информация находится в библиотеках импорта. Вы должны слинковать ваши программы с правильными библиотеками импорта, иначе они не смогут найти эти функции. Когда Windows программа загружается в память, Windows читает информацию, сохраненную в программе. Эта информация включает имена функций, которые программа использует и DLL-ей, в которых эти функции располагаются. Когда Windows находит подобную информацию в программе, она вызывает библиотеки и исправляет в программе вызовы этих функций, так что контроль всегда будет передаваться по правильному адресу.
18
Миниатюры
Сам себе Iczelion  
Mikl___
Автор FAQ
11795 / 6098 / 556
Регистрация: 11.11.2010
Сообщений: 11,091
02.01.2013, 22:00  [ТС] 3
Кликните здесь для просмотра всего текста
Этот Урок предполагает, что читатель знает, как использовать MASM. Если вы не знакомы с MASM, скачайте c masm32.com и прочитайте текст, входящий в состав пакета, прежде чем продолжать чтение этого введения. Хорошо. Теперь вы готовы. Давайте приступим.
ТЕОРИЯ ― МАТЬ СКЛЕРОЗА
Win32 программы выполняются в защищенном режиме, который доступен начиная с 80286. Hо 80286 теперь история. Поэтому мы предполагаем, что имеем дело только с 80386 и его потомками. Windows запускает каждую Win32 программу в отдельном виртуальном пространстве. Это означает, что каждая Win32 программа будет иметь 4-х гигабайтовое адресное пространство.
Hо это вовсе не означает, что каждая программа имеет 4 гигабайта физической памяти, а только то, что программа может обращаться по любому адресу в этих пределах. Windows сделает все необходимое, чтобы сделать память, к которой программа обращается "существующей". Конечно, программа должна придерживаться правил, установленных Windows, или это вызовет General protection Fault.

Каждая программа одна в своем адресном пространстве, в то время как в Win16 дело обстоит не так. Все Win16 программы могут "видеть" друг друга, что невозможно в Win32. Этот особенность помогает снизить шанс того, что одна программа запишет что-нибудь поверх данных или кода другой программы.

Модель памяти также коренным образом отличается от существующих в старом мире 16-битных программ. Под Win32, мы больше не должны беспокоиться о моделях памяти или сегментах! Теперь только одна модель память: Плоская модель памяти. Теперь нет больше 64K сегментов. Память теперь это большое последовательное 4-х гигабайтовое пространство. Это также означает, что вы не должны "играть" с сегментными регистрами. Вы можете использовать любой сегментный регистр для адресации к любой точке памяти. Это ОГРОМНОЕ подспорье для программистов. Это то, что делает программирование на ассемблере под Win32 таким же простым, как на C.

Когда вы программируете под Win32, вы должны помнить несколько важных правил.
Одно из таких правил то, что Windows использует esi, edi, ebp и ebx внутренне и не ожидает, что значение в этих регистрах меняются. Так что помните это правило: если вы используете какой-либо из этих четырех регистров в вызываемой функции, не забудьте восстановить их перед возвращением управления Windows.
Вызываемая (callback) функция - это функция, которая вызывается Windows.
Очевидный пример - процедура окна. Это не значит, что вы не можете использовать эти четыре регистра. Просто не забудьте восстановить их значения перед передачей управления Windows.
ПРАКТИКА ― МАТЬ ШИЗОФРЕНИИ
Вот каркасная программа. Если что-то из кода вы не понимаете, не паникуйте. В дальнейшем я все объясню.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.386
.MODEL Flat, STDCALL
.DATA
   <Ваша инициализируемые данные>
   ......
.DATA?
   <Ваши не инициализируемые данные>
   ......
.CONST
   <Ваши константы>
   ......
.CODE
<метка>:
   <Ваш код>
   ......
end <метка>
Вот и все! Давайте проанализируем этот "каркас".
Assembler
1
.386
Это ассемблерная директива, говорящая ассемблеру использовать набор операций для процессора 80386. Вы также можете использовать .486, .586, .686 но самый безопасный выбор ― это указывать .386. Также есть два практически идентичных выбора для каждого варианта CPU. .386/.386p, .486/.486p. Эти "p"-версии необходимы только тогда, когда ваша программа использует привилегированные инструкции, то есть инструкции, зарезервированные процессором/операционной системой для работы в защищенном режиме. Они могут быть использованы только в защищенном коде, например, sys-драйверами. Как правило, ваши программы будут работать в непривилегированном режиме, так что лучше использовать не-"p" версии.
Assembler
1
.MODEL FLAT, STDCALL
.MODEL ― ассемблерная директива, определяющая модель памяти вашей программы. Под Win32 есть только одна ― плоская модель.
STDCALL говорит MASM'у о порядке передачи параметров, слева направо или справа налево, а также о том, кто уравнивает стек, после того как функция вызвана.
Под Win16 существует два типа передачи параметров, C и PASCAL. По C-договоренности, параметры передаются справа налево, то есть самый правый параметр кладется в стек первым. Вызывающий должен уравнять стек после вызова. Например, при вызове функции с именем foo(int first_param, int second_param, int third_param), используя C-передачу параметров, ассемблерный код будет выглядеть так:
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
push [third_param]  ; Положить в стек третий параметр
push [second_param] ; Следом - второй
push [first_param]  ; И, наконец, первый
call foo
add  esp, 12         ; Вызывающий уравнивает стек

PASCAL-передача параметров ― это C-передача наоборот. Согласно ей, параметры передаются слева направо и вызываемый параметр должен уравнивать стек.
Win16 использует этот порядок передачи данных, потому что тогда код программы становится меньше. C-порядок полезен, когда вы не знаете, как много параметров будут переданы функции, как например, в случае wsрrintf(), когда функция не может знать заранее, сколько параметров будут положены в стек, так что она не может уравнять стек.
STDCALL - это гибрид C и PASCAL вызовов. Согласно ему, данные передаются справа налево, но вызываемая функция ответственна за очистку стека от переданных ей параметров. Платформа Win32 использует исключительно STDCALL, хотя есть одно исключение -- функция wsprintf(). Вы должны следовать C-порядку вызова в случае wsprintf().
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
.DATA
 
.DATA?
 
.CONST
 
.CODE
Все четыре директивы это то, что называется секциями. Вы помните, что в Win32 нет сегментов? Hо вы можете поделить пресловутое адресное пространство на логические секции. Начало одной секции отмечает конец предыдущей. Есть две группы секций: данных и кода.
.DATA ― Эта секция содержит инициализированные данные вашей программы.
.DATA? ― эта секция содержит неинициализированные данные вашей программы. Иногда вам нужно только "предварительно" выделить некоторое количество памяти, но вы не хотите инициализировать ее. Эта секция для этого и предназначается. Преимущество неинициализированных данных следующее: они не занимают места в исполняемом файле. Например, если вы хотите выделить 10000 байт в вашей .DATA? секции, ваш exe-файл не увеличится на 10kb. Его размер останется таким же. Вы, всего лишь, говорите компилятору, сколько места вам нужно, когда программа загрузится в память.

.CONST ― эта секция содержит объявления констант, используемых программой. Константы не могут быть изменены ей. Это всего лишь "константы".
Вы не обязаны задействовать все три секции. Объявляйте только те, которые хотите использовать.
Есть только одна секция для кода: .CODE, там где содержится весь код.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
<метка>:
.....
end <метка>
где <метка> ― любая произвольная метка, устанавливающая границы кода. Обе метки должны быть идентичны. Весь код должен располагаться между
Assembler
1
<метка>
и
Assembler
1
end <метка>


© Iczelion, пер. Aquila
20
Вложения
Тип файла: zip tut02.zip (1.6 Кб, 467 просмотров)
Mikl___
Автор FAQ
11795 / 6098 / 556
Регистрация: 11.11.2010
Сообщений: 11,091
03.01.2013, 07:15  [ТС] 4
Win32 API. Урок 2. MessageBox
Кликните здесь для просмотра всего текста
В этом Уроке мы создадим полнофункциональную Windows программу, которое выводит сообщение — "Win32 assembly is great!".
Скачайте пример здесь.
ТЕОРИЯ, МАТЬ СКЛЕРОЗА
Windows предоставляет огромное количество ресурсов Windows-программам через Windows API (Application Programming Interface). Windows API — это большая коллекция очень полезных функций, располагающихся непосредственно в операционной системе и готовых для использования программами. Эти функции находятся в нескольких динамически подгружаемых библиотеках (DLLs), таких как kernel32.dll, user32.dll и gdi32.dll. Kernel32.dll содержит API-функции, взаимодействующие с памятью и управляющие процессами. User32.dll контролирует пользовательский интерфейс. Gdi32.dll ответственен за графические операции. Кроме этих трех "основных", существуют также другие dll, которые вы можете использовать, при условии, что вы обладаете достаточным количеством информации о нужных API-функциях. Windows программы динамически подсоединяется к этим библиотекам, то есть код API-функций не включается в исполняемый файл. Информация находится в библиотеках импорта. Вы должны слинковать ваши программы с правильными библиотеками импорта, иначе они не смогут найти эти функции. Когда Windows программа загружается в память, Windows читает информацию, сохраненную в программе. Эта информация включает имена функций, которые программа использует и DLL-ей, в которых эти функции располагаются. Когда Windows находит подобную информацию в программе, она вызывает библиотеки и исправляет в программе вызовы этих функций, так что контроль всегда будет передаваться по правильному адресу.
Существует две категории API функций: одни работают с ANSI-строками, а другие с Unicode-строками. Имена API-функций использующих ANSI-строки заканчиваются на "A", например, MessageBoxA. В конце имен функций для Unicode находится "W". Windows 95/98 от природы поддерживают ANSI, а Windows NT и производные от нее 2k/XP/Vista поддерживают Unicode. Обычно мы имеем дело с ANSI строками (массивы символов, оканчивающиеся NULL-ом. размер ANSI-символа — 1 байт. В то время как ANSI достаточна для европейских языков, она не поддерживает некоторые восточные языки, в которых есть несколько тысяч уникальных символов. Вот в этих случаях в дело вступает UniCode. размер символа UNICODE — 2 байта, и поэтому может поддерживать 65536 различных символов. Hо по большей части, вы будете использовать include-файл, который может определить и выбрать подходящую для вашей платформы функцию. Просто обращайтесь к именам API-функций без постфикса.
ПРАКТИКА, МАТЬ ШИЗОФРЕНИИ
Я приведу голый скелет программы ниже. Позже мы разберем его.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
.386
.model flat, stdcall
.data
.code
start:
end start
Выполнение начинается с первой инструкции, следующей за меткой, установленной после конца директив. В вышеприведенном каркасе выполнение начинается непосредственно после метки 'start'. Будут последовательно выполняться инструкция за инструкцией, пока не встретится операция передачи управления, такая как: jmр, jne, je, ret и так далее. Эти инструкции перенаправляют поток выполнения другим инструкциям. Когда программа выходит в Windows, ей следует вызвать API-функцию ExitProcess.
Assembler
1
ExitProcess proto uExitCode:DWORD
Строка выше называется прототипом функции. Прототип функции указывает ассемблеру/линкеру атрибуты функции, чтобы он сделал проверку типов данных и количество атрибутов передаваемых функции. Формат прототипа функции следующий:
Assembler
1
ИмяФункции PROTO [ИмяПараметра]:ТипДанных,[ИмяПараметра]:ТипДанных,...
Короче говоря, за именем функции следует ключевое слово PROTO, а затем список переменных с типом данных, разделенных запятыми. В приведенном выше примере с ExitProcess, эта функция была определена как принимающая только один параметр типа DWORD. Прототипы функций очень полезны, когда вы используете высокоуровневый синтаксический вызов — invoke. Вы можете считать об invoke как обычный вызов с проверкой типов данных. Например, если вы напишите:
Assembler
1
call ExitProcess
Линкер уведомит вас, что вы забыли положит в стек двойное слово. Я рекомендую вам использовать invoke вместо простого вызова. Синтаксис invoke следующий:
Assembler
1
invoke выражение [, аргументы]
Выражение может быть именем функции или указателем на функцию. Параметры функции разделены запятыми.
Большинство прототипов для API-функций содержатся в include-файлах. Если вы используете hutch'евский MASM32, они будут находится в директории MASM32/INCLUDE. Файлы подключения имеют расширение .inc и прототипы функций DLL находятся в .inc файле с таким же именем, как и у этой DLL.
Hапример, ExitProcess экспортируется из kernel32.lib, так что прототип ExitProcess находится в kernel32.inc.

Вы также можете создать прототипы для ваших собственных функций. Во всех моих экземплярах я использую hutch'евский windows.inc, который вы можете скачать с http://win32asm.cjb.net
Возвращаясь к ExitProcess: параметр uExitCode - это значение, которое программа вернет Windows после окончания программы. Вы можете вызвать функцию ExitProcess так:
Assembler
1
invoke ExitProcess, 0
Поместив эту строку непосредственно после стартовой метки, вы получите Win32-программу, немедленно выходящую в Windows, но тем не менее полнофункциональную.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
.386
.model flat, stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
.data
.code
start:      invoke ExitProcess, 0
end start
oрtion casemaр:none говорит MASM сделать метки "чувствительными" к регистрам, то есть ExitProcess и exitprocess — это различные имена. Отметьте новую директиву — include. После нее следует имя файла, который вы хотите вставить в то место, где эта директива располагается. В примере выше, когда MASM обрабатывает линию include \masm32\include\windows.inc, он открывает windows.inc, находящийся в директории \MASM32\INCLUDE, и далее анализирует содержимое windows.inc так, как будто вы "вклеили" подключаемый файл. Хатчевский windows.inc содержит в себе определения констант и структур, которые вам могут понадобиться для программирования под Win32. Этот файл не содержит в себе прототипов функций. Windows.inc ни в коем случае не является исчерпывающим и всеобъемлющим. Hutch и я пытаемся заполнить его как можно большим количеством констант и структур, но есть еще довольно, что следовало бы включить. Он постоянно обновляется. Заходите на хатчевскую и мою странички за свежими апдейтами. Из windows.inc, ваша программа будет брать определения констант и структур. Что касается прототипов функций, вы должны подключить другие include-файлы. Они находятся в директории \masm32\include.

В вышеприведенном примере, мы вызываем функцию, экспортированную из kernel32.dll, для чего мы должны подключить прототипы функций из kernel32.dll. Этот файл - kernel32.inc. Если вы открываете его текстовым редактором, вы увидите, что он состоит из прототипов функций из соответствующей dll. Если вы не подключите kernel32.inc, вы все еще можете вызвать ExitProcess, но уже с помощью ассемблерной команды call. Вы не сможете вызвать эту функцию с помощью invoke. Дело вот в чем: для того, чтобы вызвать функцию через invoke, вы должны поместить в исходном коде ее прототип. В примере выше, если вы не подключите kernel32.inc, вы можете определить прототип для ExitProcess где-нибудь до вызова этой функции и это будет работать. Файлы подключения нужны для того, что избавить вас от лишней работы и вам не пришлось набирать все прототипы самим.
Теперь мы встречаем новую директиву — includelib. Она работает не так, как include. Это всего лишь способ сказать ассемблеру какие библиотеки использует ваша программа должна прилинковать. Хотя вы вовсе не обязаны использовать именно этот метод. Вы можете указать имена библиотек импорта к командной строке при запуске линкера, но поверьте мне, это весьма скучно и утомительно, да и командная строка может вместить максимум 128 символов.
Теперь возьмите весь исходный текст примера этого Урока, сохраните его как msgbox.asm и ассемблируйте его так:
Код
ml /c /coff /Cp msgbox.asm
/c говорит MASM'у создать .obj файл в формате COFF. MASM использует вариант COFF (Common Object File Format), использующийся под Unix, как его собственный объектный и исполняемый формат файлов.
/Cр говорит MASM'у сохранять регистр имен, заданных пользователем. Если вы используете hutch'евский MASM32 пакет, вы можете вставить "option casemaр:none" в начале вашего исходника, сразу после директивы .model, чтобы добиться того же эффекта.
После успешной компиляции msgbox.asm, вы получите msgbox.obj. Это объектный файл, от которого один шаг до екзешника. Obj содержит инструкции/данные в двоичной форме. Отсутствуют только необходимая корректировка адресов, которая проводится линкером.
Теперь сделайте следующее:
Код
link /SUBSYSTEM:WINDOWS  /LIBPATH:c:\masm32\lib  msgbox.obj
/SUBSYSTEM:WINDOWS информирует линкер о том, какого вида является будущий исполняемый модуль.
/LIBPATH:<путь к библиотекам импорта> говорит линкеру, где находятся библиотеки импорта. Если вы используете MASM32, они будут в MASM32\lib.

Линкер читает объектный файл и корректирует его, используя адреса, взятые из библиотек импорта. После окончания линковки вы получите файл msgbox.exe. Запустите его. Вы увидите, что она ничего не делает.

Да, мы не поместили в код ничего не интересного. Hо тем не менее полноценная Windows программа. И посмотрите на размер! Hа моем PC — 1.536 байт.
Теперь мы готовы создать окно с сообщением. Прототип функции, которая нам для этого необходима следующая:
Assembler
1
MessageBox PROTO hwnd:DWORD, lpText:DWORD, lpCaption:DWORD, uType:DWORD
hwnd — это хэндл родительского окна. Вы можете считать хэндл числом, представляющим окно, к которому вы обращаетесь. Его значение для вас не важно. Вы только должны знать, что оно представляет окно. Когда вы захотите сделать что-нибудь с окном, вы должны обратиться к нему, используя его хэндл.
lрText — это указатель на текст, который вы хотите отобразить в клиентской части окна сообщения. Указатель ― это адрес чего-либо. Указатель на текстовую строку = адрес этой строки.
lpCaption — это указатель на заголовок окна сообщения.
uType — устанавливает иконку, число и вид кнопок окна.

Давайте изменим msgbox.asm для отображения сообщения.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
.data
MsgBoxCaption  db "Iczelion Tutorial #2",0
MsgBoxText     db "Win32 Assembly is Great!",0
.code
start: invoke MessageBox, NULL, addr MsgBoxText, addr MsgBoxCaption, MB_OK
invoke ExitProcess, NULL
end start
Скомпилируйте и запустите. Вы увидите окошко с сообщением "Win32 Assembly is great!".
Давайте снова взглянем на исходник.
Мы определили две оканчивающиеся NULL'ом строки в секции .data. Помните, что каждая ANSI строка в Windows должна оканчиваться NULL'ом (0 в шестнадцатеричной системе). Мы используем две константы, NULL и MB_OK. Эти константы прописаны в windows.inc, так что вы можете обратиться к ним, указав их имя, а не значение. Это улучшает читабельность кода.
Оператор addr используется для передачи адреса метки (и не только) функции. Он действителен только в контексте директивы invoke. Вы не можете использовать его, чтобы присвоить адрес метки регистру или переменной, например. В данном примере вы можете использовать offset вместо addr. Тем не менее, есть некоторые различия между ними.
1. addr не может быть использован с метками, которые определены впереди, а offset может. Например, если метка определена где-то дальше в коде, чем строка с invoke, addr не будет работать.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
invoke MessageBox,NULL, addr MsgBoxText,addr MsgBoxCaption,MB_OK
......
MsgBoxCaption  db "Iczelion Tutorial #2",0
MsgBoxText       db "Win32 Assembly is Great!",0
MASM доложит об ошибке. Если вы используете offset вместо addr, MASM без проблем скомпилирует указанный отрывок кода.
2. Addr поддерживает локальные переменные, в то время как offset нет.
Локальная переменная — это всего лишь зарезервированное место в стеке. Вы только знаете его адрес во время выполнения программы. Offset интерпретируется во время компиляции ассемблером, поэтому неудивительно, что он не поддерживает локальные переменные. Addr же работает с ними, потому что ассемблер сначала проверяет ― глобальная переменная или локальная. Если она глобальная, он помещает адрес этой переменной в объектный файл. В этом случае оператор работает как offset. Если это локальная переменная, компилятор генерирует следующую последовательность инструкций, перед тем как будет вызвана функция:
Assembler
1
2
lea eax, LocalVar
push eax
Учитывая, что lea может определить адрес метки в "рантайме", все работает прекрасно.
____________________________________________
© Iczelion, пер. Aquila.
7
Mikl___
Автор FAQ
11795 / 6098 / 556
Регистрация: 11.11.2010
Сообщений: 11,091
03.01.2013, 07:39  [ТС] 5
Еще один путь к уменьшению PE-заголовка это уменьшение таблицы разделов, по умолчанию количество разделов должно быть равным 16, нам же требуется только секция импорта. В таблице разделов секция импорта идет сразу после секции экспорта. Хотя мы указали в таблице разделов 2 секции, но загрузчик проверит содержимое секции debug и, если debug_size не равен нулю, тогда при загрузке файла система выдаст сообщение
У нас поле debug_size (смещение +0ACh от сигнатуры "PE") попадает на флаги секции код, там находится число 0E00000020h
20h(секция кода)+20000000h(разрешено исполнение)+40000000h(разрешено чтение)+80000000h(разрешена запись)=0E00000020h
А что если обнулить флаги секции код? Ставим по адресу debug_size 0 и запускаем приложение — О, чудо! Этого не должно было быть, но приложение запускается!
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
.586p
.model tiny
include windows.inc
.code
exebase equ 400000h
main:
;signatures----------------------------
dosHeader dd IMAGE_DOS_SIGNATURE;сигнатура 'MZ'
ntHeader dd IMAGE_NT_SIGNATURE;сигнатура 'PE'
;image_header--------------------------
Machine dw IMAGE_FILE_MACHINE_I386;Процессор Intel386
Count_of_section    dw 1;число секций = 1
TimeStump   dd 0
Symbol_table_offset dd 0
Symbol_table_count  dd 0
Size_of_optional_header dw section_table-optional_header;Размер NT-заголовка обычно E0h, у нас 70h
Characteristics dw IMAGE_FILE_32BIT_MACHINE or \
IMAGE_FILE_RELOCS_STRIPPED or IMAGE_FILE_EXECUTABLE_IMAGE or \
IMAGE_FILE_LINE_NUMS_STRIPPED or IMAGE_FILE_LOCAL_SYMS_STRIPPED;флаги программы 100h+1+2+4+8=10Fh
;-------------------------------------
optional_header:
Magic_optional_header   dw IMAGE_NT_OPTIONAL_HDR32_MAGIC;"магическое" значение 10Bh
Linker_version_major_and_minor dw 0 
Size_of_code    dd end_import-start
Size_of_init_data   dd 0
Size_of_uninit_data dd 0
entry_point dd start
base_of_code    dd start
base_of_data    dd 0
image_base  dd exebase;=400000h
e_lfanew    dd ntHeader-dosHeader;=section alignment=4
file_alignment  dd 4
OS_version_major_minor  dd 4
image_version_major_minor dd 0
subsystem_version_major_minor dd 4
reserved    dd 0
size_of_image   dd end_import
size_of_header  dd start
checksum    dd 0
subsystem_and_DLL_flag  dd IMAGE_SUBSYSTEM_WINDOWS_GUI;=2
Stack_allocation    dd 100000h
Stack_commit    dd 1000h
Heap_allocation dd 100000h
Heap_commit dd 1000h
loader_flag dd 0
number_of_dirs  dd (section_table-export_RVA)/8;=2
export_RVA dd 0
export_size dd 0
import_RVA dd import
import_size dd end_import-import
;------------------------------------------------
section_table dd 'xet.','t';resource_RVA_and_resource_size
virtual_size    dd 0;exeption_RVA 
virtual_address dd start;exeption_size 
Physical_size   dd end_import-start;security_RVA 
Physical_offset dd start;security_size 
Relocations dd 0;fixups_RVA 
Linenumbers dd 0;fixups_size 
Relocations_and_Linenumbers_count dd 0;debug_RVA 
Attributes dd 0;debug_size 
;---------------------------------------------------------------------
start:  push eax;MB_OK 
push offset wTitle+exebase 
push offset Message+exebase 
push eax 
call MessageBox+exebase 
retn 
;----------------------------------------------------------------------
wTitle  db 'Iczelion Tutorial #2',0;заголовок MessageBox'а
Message db 'Win32 Assembly is Great!';выводимое сообщение
;----------------------------------------------------------------------
import  dd 0,0,0;значение смещения от начала секции import до начала массива 
;с адресами импортируемых функций ArrayAddressImpFunc (первая копия)
dd user32_dll;значение смещения от начала секции import
;до начала ACSIIZ-строки с именем dll-библиотеки
dd MessageBox,0,0;значение смещения от начала секции import до начала
;массива с адресами импортируемых функций ArrayAddressImpFunc (вторая копия)
MessageBox dd _MessageBoxA
dw 0 
_MessageBoxA db 0,0,'MessageBoxA',0 ; 
user32_dll  db 'user32' 
end_import:
end main

размер msgbox.exe равен 298 байтам или 11,6% от начального размера в 2560 байт.
Кликните здесь для просмотра всего текста
заголовок код и данные импорт общий размер
180 64 54 298 байт
А теперь, чтобы эстеты не упрекали нас в том, что «для создания такого маленького EXE-файла мы использовали так много текста» — сохраним наш заголовок в файле capito.asm
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
;signatures----------------------------
dosHeader dd IMAGE_DOS_SIGNATURE;сигнатура 'MZ'
ntHeader dd IMAGE_NT_SIGNATURE;сигнатура 'PE'
;image_header--------------------------
Machine dw IMAGE_FILE_MACHINE_I386;Процессор Intel386
Count_of_section    dw 1;число секций = 1
TimeStump   dd 0
Symbol_table_offset dd 0
Symbol_table_count  dd 0
Size_of_optional_header dw section_table-optional_header;Размер NT-заголовка обычно E0h, у нас 70h
Characteristics dw IMAGE_FILE_32BIT_MACHINE or \
IMAGE_FILE_RELOCS_STRIPPED or IMAGE_FILE_EXECUTABLE_IMAGE or \
IMAGE_FILE_LINE_NUMS_STRIPPED or IMAGE_FILE_LOCAL_SYMS_STRIPPED;флаги программы 100h+1+2+4+8=10Fh
;-------------------------------------
optional_header:
Magic_optional_header   dw IMAGE_NT_OPTIONAL_HDR32_MAGIC;"магическое" значение 10Bh
Linker_version_major_and_minor dw 0 
Size_of_code    dd end_import-start
Size_of_init_data   dd 0
Size_of_uninit_data dd 0
entry_point dd start
base_of_code    dd start
base_of_data    dd 0
image_base  dd exebase;400000h
e_lfanew    dd ntHeader-dosHeader;section alignment 4
file_alignment  dd 4
OS_version_major_minor  dd 4
image_version_major_minor dd 0
subsystem_version_major_minor dd 4
reserved    dd 0
size_of_image   dd end_import
size_of_header  dd start
checksum    dd 0
subsystem_and_DLL_flag  dd IMAGE_SUBSYSTEM_WINDOWS_GUI;2
Stack_allocation    dd 100000h
Stack_commit    dd 1000h
Heap_allocation dd 100000h
Heap_commit dd 1000h
loader_flag dd 0
number_of_dirs  dd (section_table-export_RVA)/8
export_RVA dd 0
export_size dd 0
import_RVA dd import
import_size dd end_import-import
;------------------------------------------------
section_table dd 'xet.','t'
virtual_size    dd 0
virtual_address dd start
Physical_size   dd end_import-start
Physical_offset dd start
Relocations dd 0
Linenumbers dd 0
Relocations_and_Linenumbers_count dd 0
Attributes dd 0
Теперь наш ассемблерный текст выглядит так:
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
.586p
.model tiny
include windows.inc
.code
exebase equ 400000h
main:
include capito.asm
;---------------------------------------------------------------------
start:  push eax;MB_OK 
push offset wTitle+exebase 
push offset Message+exebase 
push eax 
call MessageBox+exebase 
retn 
;----------------------------------------------------------------------
wTitle  db 'Iczelion Tutorial #2',0;заголовок MessageBox'а
Message db 'Win32 Assembly is Great!';выводимое сообщение
;----------------------------------------------------------------------
import  dd 0,0,0,user32_dll
dd MessageBox,0,0   
MessageBox dd _MessageBoxA
dw 0 
_MessageBoxA db 0,0,'MessageBoxA',0
user32_dll  db 'user32' 
end_import:
end main
Размер EXE-файла по прежнему 298 байт, а файл capito.asm мы будем использовать во всех наших уроках Iczelion'a
Чтобы «добить» эстетов — создаем «на коленке» собственный макрос INVOKE и помещаем его в capito.asm
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
OPTION NOKEYWORD: <invoke>; OPTION NOKEYWORD позволяет переопределить зарезервироаннное в масме слово INVOKE
invoke MACRO Fn,args:VARARG;после имени макроса invoke идут формальные параметры Fn и args. 
;Причем формальный параметр args представляет из себя строку формальных параметров разделенных запятыми
LOCAL txt,arg;в нашем макросе используются локальные переменные txt и arg 
txt TEXTEQU <>;инициируем переменную txt как пустую строку
IRP arg,<args>;IRP-блок, заканчивается на ближайшем ENDM. Формальный параметр arg будет заменятся 
;на фактические параметры из строки args. Параметры из строки args разделены запятыми
txt CATSTR <arg>, <!,>, txt;в IRP-блоке параметры из args "подклеиваются" к строке txt слева, 
;между параметрами arg ставятся запятые, <!,> -- макрооператор "!" не переносится в окончательный текст, 
;но следующая за ним запятая трактуется не как разделитель параметров, а как обычный ASCII-символ
ENDM;конец IRP-блока
% IRP arg,<txt>;встретив конструкцию % <константное значение> в фактическом параметре, 
;макрогенератор вычисляет указанное выражение и подставляет его значение вместо всей этой конструкции
push arg;параметры arg "пушатся" в правильном порядке
ENDM;конец IRP-блока
call Fn+exebase; вызывается процедура, имя которой было передано, как фактический параметр
ENDM;конец макроса
И теперь наши asm-файл практически не отличается от исходного, а ехе-файл «весит» очень мало
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
; masm dos com #
.586p
.model tiny
;for WinXP - 298 bytes
include windows.inc
.code
exebase equ 400000h
main:
include capito.asm
;----------------------------------
start:  invoke MessageBox,eax,offset Message+exebase,offset wTitle+exebase,eax
retn 
;----------------------------------
wTitle  db 'Iczelion Tutorial #2',0 
Message db 'Win32 Assembly is Great!' 
;-----------------------------------
import  dd 0,0,0,user32_dll,MessageBox,0,0  
MessageBox dd _MessageBoxA
dw 0 
_MessageBoxA    db 0,0,'MessageBoxA',0
user32_dll  db 'user32' 
end_import:
end main



© Mikl___ 2013
Win32 API. Урок 2c. Еще больше уменьшаем MessageBox

Постараемся еще больше сжать нашу программу. Далее приведен текст программы, которая, получив у системного загрузчика адреса WinAPI-функций LoadLibraryA и MessageBox, создает файл tiny97.exe, состоящий только из минимального заголовка PE-файла. Внутри этого заголовка, в тех его полях, которыми можно пренебречь, расположен код программы, выводящей MessageBox, с заголовком и текстом "user32".
Кликните здесь для просмотра всего текста
Приступим к конструированию нашего PE-файла. Примем минимально возможные размеры выравнивания секций ― 4 байта ― и файла ― 4 байт.
Образ нашей программы будет состоять всего из одной секции, в которой будут размещены и данные, и код
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
.686P
.model flat
include windows.inc
includelib user32.lib
includelib kernel32.lib
extern _imp__MessageBoxA@16:dword
extern _imp__WriteFile@20:dword
extern _imp__CreateFileA@28:dword
extern _imp__CloseHandle@4:dword
extern _imp__LoadLibraryA@4:dword
.code
start:  xchg ebx,eax
push MB_ICONINFORMATION OR MB_SYSTEMMODAL;1040h
push offset szInfoCap 
push offset namefile
push ebx
call _imp__MessageBoxA@16
mov eax,_imp__LoadLibraryA@4
sub eax,offset _LoadLibraryA-buffer+ImageBase+size _LoadLibraryA;400023h
mov _LoadLibraryA,eax
mov eax,_imp__MessageBoxA@16
sub eax,offset _MessageBoxA-buffer+ImageBase+size _MessageBoxA;400035h
mov _MessageBoxA,eax
push ebx    ;NULL   
push FILE_ATTRIBUTE_ARCHIVE
push CREATE_ALWAYS
push ebx
push FILE_SHARE_READ or FILE_SHARE_WRITE
push GENERIC_READ or GENERIC_WRITE
push offset namefile
call _imp__CreateFileA@28
push eax    ;hFile для CloseHandle
push ebx ;lpOverlapped
push offset SizeReadWrite   ;lpNumberOfBytesToWrite
push sizeof_image;a4-buffer ;nNumberOfBytesToWrite=97
push offset buffer  ;lpBuffer
push eax    ;hFile для WriteFile
call _imp__WriteFile@20
call _imp__CloseHandle@4
QUIT: retn
ImageBase equ 400000h
buffer  dd 'ZM','EP'
dw 14Ch ;Machine (Intel 386)
dw 0    ;NumberOfSection
EntryPoint: xor ebx,ebx ; ebx = 0
mov edi,offset namedll-buffer+ImageBase
push edi ;push offset user32
jmp short @f
db 0,0  ;   UNUSED
dw a4-optheader ;SizeOfOptionalHeader
dw 103h ;Characteristics (no relocations, executable, 32 bit)
optheader:
dw 10Bh ;Magic PE32
@@:
db 0E8h ;call LoadLibraryA
_LoadLibraryA dd 0
push ebx ;push 0
push edi ;push offset user32
push edi ;push offset user32
push ebx ;push 0
jmp short @f
db 0,0,0
dd EntryPoint-buffer
@@:
db 0E8h ;call MessageBoxA
_MessageBoxA dd 0
retn
dw 0 ;  UNUSED
dd ImageBase    ;ImageBase
dd 4    ;SectionAligment
dd 4 ;FileAligment
namedll db 'user32',0,0 ;   UNUSED
dd 4 ;MinorSubsystemVersion UNUSED
dd 0    ;Win32VersionValue  UNUSED
dd 68h  ;SizeOfimage
dd sizeof_image;64h ;SizeOfHeader
dd 0    ;CheckSum   UNUSED
db 2    ;Subsystem (Win32 GUI)
a4:
;---------------------------------------------------------------------------
sizeof_image=$-buffer
szInfoCap db "Creator tiny MessageBox",0
namefile db 'tiny97.exe',0
SizeReadWrite dd 0
end start

размер tiny97.exe равен 97 байтов или 3,8% от начального размера в 2560 байт.
  1. Получение непосредственных адресов импортируемых функций LoadLibraryA и MessageBox от «материнской» программы позволяет отказаться от использования таблицы импорта в нашей программе и заменить косвенный 6-байтовый вызов функций с кодом 0FF.15.XX.XX.XX.XXh на 5-байтовый код 0EB.XX.XX.XX.XXh (непосредственный CALL)
  2. 97 байт являются теоретическим минимумом для длины PE-файла. По смещению +5Ch от сигнатуры «PE» находится поле Subsystem. Размещение в этом поле нулевого значения или отсутствие этого поля в файле воспринимается системным загрузчиком, как значение IMAGE_SUBSYSTEM_UNKNOWN (=0) и система не станет загружать такой файл. Поэтому по смещению +5Ch от «PE» должно находится ненулевое значение. Так как мы поместим в это поле значение IMAGE_SUBSYSTEM_WINDOWS_GUI=2, то под поле Subsystem достаточно отвести 1 байт. Итого: 4 байта (MZ-заголовок) + 5Ch (размер PE-заголовка до поля «Subsystem») + 1 байт (под собственно поле «Subsystem») = 61h = 97 байт
________________________________________________________
Для написания данной главы использовались работы следующих авторов:
  1. Solar Eclipse. Creating the smallest possible PE executable
  2. Крис Касперски. Сверхбыстрый импорт API-функций.
  3. Ivan2k. Выключалка компа на 97 байт подробности (на англ.) смотреть тут и тут
_______________________
© Mikl___ 2013
12
Изображения
 
Mikl___
Автор FAQ
11795 / 6098 / 556
Регистрация: 11.11.2010
Сообщений: 11,091
03.01.2013, 16:04  [ТС] 6
Win32 API. Урок 2b. MessageBox
Oбщие концепции и требования, предъявляемые к PE-файлам
Кликните здесь для просмотра всего текста
В этом Уроке мы создадим полнофункциональную Windows программу, которое выводит сообщение — "Win32 assembly is great!".
Скачайте пример здесь.
ТЕОРИЯ, МАТЬ СКЛЕРОЗА
Windows предоставляет огромное количество ресурсов Windows-программам через Windows API (Application Programming Interface). Windows API — это большая коллекция очень полезных функций, располагающихся непосредственно в операционной системе и готовых для использования программами. Эти функции находятся в нескольких динамически подгружаемых библиотеках (DLLs), таких как kernel32.dll, user32.dll и gdi32.dll. Kernel32.dll содержит API-функции, взаимодействующие с памятью и управляющие процессами. User32.dll контролирует пользовательский интерфейс. Gdi32.dll ответственен за графические операции. Кроме этих трех "основных", существуют также другие dll, которые вы можете использовать, при условии, что вы обладаете достаточным количеством информации о нужных API-функциях. Windows программы динамически подсоединяется к этим библиотекам, то есть код API-функций не включается в исполняемый файл. Информация находится в библиотеках импорта. Вы должны слинковать ваши программы с правильными библиотеками импорта, иначе они не смогут найти эти функции. Когда Windows программа загружается в память, Windows читает информацию, сохраненную в программе. Эта информация включает имена функций, которые программа использует и DLL-ей, в которых эти функции располагаются. Когда Windows находит подобную информацию в программе, она вызывает библиотеки и исправляет в программе вызовы этих функций, так что контроль всегда будет передаваться по правильному адресу.
Существует две категории API функций: одни работают с ANSI-строками, а другие с Unicode-строками. Имена API-функций использующих ANSI-строки заканчиваются на "A", например, MessageBoxA. В конце имен функций для Unicode находится "W". Windows 95/98 от природы поддерживают ANSI, а Windows NT и производные от нее 2k/XP/Vista поддерживают Unicode. Обычно мы имеем дело с ANSI строками (массивы символов, оканчивающиеся NULL-ом. размер ANSI-символа — 1 байт. В то время как ANSI достаточна для европейских языков, она не поддерживает некоторые восточные языки, в которых есть несколько тысяч уникальных символов. Вот в этих случаях в дело вступает UniCode. размер символа UNICODE — 2 байта, и поэтому может поддерживать 65536 различных символов. Hо по большей части, вы будете использовать include-файл, который может определить и выбрать подходящую для вашей платформы функцию. Просто обращайтесь к именам API-функций без постфикса.
ПРАКТИКА, МАТЬ ШИЗОФРЕНИИ
Я приведу голый скелет программы ниже. Позже мы разберем его.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
.386
.model flat, stdcall
.data
.code
start:
end start
Выполнение начинается с первой инструкции, следующей за меткой, установленной после конца директив. В вышеприведенном каркасе выполнение начинается непосредственно после метки 'start'. Будут последовательно выполняться инструкция за инструкцией, пока не встретится операция передачи управления, такая как: jmр, jne, je, ret и так далее. Эти инструкции перенаправляют поток выполнения другим инструкциям. Когда программа выходит в Windows, ей следует вызвать API-функцию ExitProcess.
Assembler
1
ExitProcess proto uExitCode:DWORD
Строка выше называется прототипом функции. Прототип функции указывает ассемблеру/линкеру атрибуты функции, чтобы он сделал проверку типов данных и количество атрибутов передаваемых функции. Формат прототипа функции следующий:
Assembler
1
ИмяФункции PROTO [ИмяПараметра]:ТипДанных,[ИмяПараметра]:ТипДанных,...
Короче говоря, за именем функции следует ключевое слово PROTO, а затем список переменных с типом данных, разделенных запятыми. В приведенном выше примере с ExitProcess, эта функция была определена как принимающая только один параметр типа DWORD. Прототипы функций очень полезны, когда вы используете высокоуровневый синтаксический вызов — invoke. Вы можете считать об invoke как обычный вызов с проверкой типов данных. Например, если вы напишите:
Assembler
1
call ExitProcess
Линкер уведомит вас, что вы забыли положит в стек двойное слово. Я рекомендую вам использовать invoke вместо простого вызова. Синтаксис invoke следующий:
Assembler
1
invoke выражение [, аргументы]
Выражение может быть именем функции или указателем на функцию. Параметры функции разделены запятыми.
Большинство прототипов для API-функций содержатся в include-файлах. Если вы используете hutch'евский MASM32, они будут находится в директории MASM32/INCLUDE. Файлы подключения имеют расширение .inc и прототипы функций DLL находятся в .inc файле с таким же именем, как и у этой DLL.
Hапример, ExitProcess экспортируется из kernel32.lib, так что прототип ExitProcess находится в kernel32.inc.

Вы также можете создать прототипы для ваших собственных функций. Во всех моих экземплярах я использую hutch'евский windows.inc, который вы можете скачать с http://win32asm.cjb.net
Возвращаясь к ExitProcess: параметр uExitCode - это значение, которое программа вернет Windows после окончания программы. Вы можете вызвать функцию ExitProcess так:
Assembler
1
invoke ExitProcess, 0
Поместив эту строку непосредственно после стартовой метки, вы получите Win32-программу, немедленно выходящую в Windows, но тем не менее полнофункциональную.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
.386
.model flat, stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
.data
.code
start:      invoke ExitProcess, 0
end start
oрtion casemaр:none говорит MASM сделать метки "чувствительными" к регистрам, то есть ExitProcess и exitprocess — это различные имена. Отметьте новую директиву — include. После нее следует имя файла, который вы хотите вставить в то место, где эта директива располагается. В примере выше, когда MASM обрабатывает линию include \masm32\include\windows.inc, он открывает windows.inc, находящийся в директории \MASM32\INCLUDE, и далее анализирует содержимое windows.inc так, как будто вы "вклеили" подключаемый файл. Хатчевский windows.inc содержит в себе определения констант и структур, которые вам могут понадобиться для программирования под Win32. Этот файл не содержит в себе прототипов функций. Windows.inc ни в коем случае не является исчерпывающим и всеобъемлющим. Hutch и я пытаемся заполнить его как можно большим количеством констант и структур, но есть еще довольно, что следовало бы включить. Он постоянно обновляется. Заходите на хатчевскую и мою странички за свежими апдейтами. Из windows.inc, ваша программа будет брать определения констант и структур. Что касается прототипов функций, вы должны подключить другие include-файлы. Они находятся в директории \masm32\include.

В вышеприведенном примере, мы вызываем функцию, экспортированную из kernel32.dll, для чего мы должны подключить прототипы функций из kernel32.dll. Этот файл - kernel32.inc. Если вы открываете его текстовым редактором, вы увидите, что он состоит из прототипов функций из соответствующей dll. Если вы не подключите kernel32.inc, вы все еще можете вызвать ExitProcess, но уже с помощью ассемблерной команды call. Вы не сможете вызвать эту функцию с помощью invoke. Дело вот в чем: для того, чтобы вызвать функцию через invoke, вы должны поместить в исходном коде ее прототип. В примере выше, если вы не подключите kernel32.inc, вы можете определить прототип для ExitProcess где-нибудь до вызова этой функции и это будет работать. Файлы подключения нужны для того, что избавить вас от лишней работы и вам не пришлось набирать все прототипы самим.
Теперь мы встречаем новую директиву — includelib. Она работает не так, как include. Это всего лишь способ сказать ассемблеру какие библиотеки использует ваша программа должна прилинковать. Хотя вы вовсе не обязаны использовать именно этот метод. Вы можете указать имена библиотек импорта к командной строке при запуске линкера, но поверьте мне, это весьма скучно и утомительно, да и командная строка может вместить максимум 128 символов.
Теперь возьмите весь исходный текст примера этого Урока, сохраните его как msgbox.asm и ассемблируйте его так:
Код
ml /c /coff /Cp msgbox.asm
/c говорит MASM'у создать .obj файл в формате COFF. MASM использует вариант COFF (Common Object File Format), использующийся под Unix, как его собственный объектный и исполняемый формат файлов.
/Cр говорит MASM'у сохранять регистр имен, заданных пользователем. Если вы используете hutch'евский MASM32 пакет, вы можете вставить "option casemaр:none" в начале вашего исходника, сразу после директивы .model, чтобы добиться того же эффекта.
После успешной компиляции msgbox.asm, вы получите msgbox.obj. Это объектный файл, от которого один шаг до екзешника. Obj содержит инструкции/данные в двоичной форме. Отсутствуют только необходимая корректировка адресов, которая проводится линкером.
Теперь сделайте следующее:
Код
link /SUBSYSTEM:WINDOWS  /LIBPATH:c:\masm32\lib  msgbox.obj
/SUBSYSTEM:WINDOWS информирует линкер о том, какого вида является будущий исполняемый модуль.
/LIBPATH:<путь к библиотекам импорта> говорит линкеру, где находятся библиотеки импорта. Если вы используете MASM32, они будут в MASM32\lib.

Линкер читает объектный файл и корректирует его, используя адреса, взятые из библиотек импорта. После окончания линковки вы получите файл msgbox.exe. Запустите его. Вы увидите, что она ничего не делает.

Да, мы не поместили в код ничего не интересного. Hо тем не менее полноценная Windows программа. И посмотрите на размер! Hа моем PC — 1.536 байт.
Теперь мы готовы создать окно с сообщением. Прототип функции, которая нам для этого необходима следующая:
Assembler
1
MessageBox PROTO hwnd:DWORD, lpText:DWORD, lpCaption:DWORD, uType:DWORD
hwnd — это хэндл родительского окна. Вы можете считать хэндл числом, представляющим окно, к которому вы обращаетесь. Его значение для вас не важно. Вы только должны знать, что оно представляет окно. Когда вы захотите сделать что-нибудь с окном, вы должны обратиться к нему, используя его хэндл.
lрText — это указатель на текст, который вы хотите отобразить в клиентской части окна сообщения. Указатель ― это адрес чего-либо. Указатель на текстовую строку = адрес этой строки.
lpCaption — это указатель на заголовок окна сообщения.
uType — устанавливает иконку, число и вид кнопок окна.

Давайте изменим msgbox.asm для отображения сообщения.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
.data
MsgBoxCaption  db "Iczelion Tutorial #2",0
MsgBoxText     db "Win32 Assembly is Great!",0
.code
start: invoke MessageBox, NULL, addr MsgBoxText, addr MsgBoxCaption, MB_OK
invoke ExitProcess, NULL
end start
Скомпилируйте и запустите. Вы увидите окошко с сообщением "Win32 Assembly is great!".
Давайте снова взглянем на исходник.
Мы определили две оканчивающиеся NULL'ом строки в секции .data. Помните, что каждая ANSI строка в Windows должна оканчиваться NULL'ом (0 в шестнадцатеричной системе). Мы используем две константы, NULL и MB_OK. Эти константы прописаны в windows.inc, так что вы можете обратиться к ним, указав их имя, а не значение. Это улучшает читабельность кода.
Оператор addr используется для передачи адреса метки (и не только) функции. Он действителен только в контексте директивы invoke. Вы не можете использовать его, чтобы присвоить адрес метки регистру или переменной, например. В данном примере вы можете использовать offset вместо addr. Тем не менее, есть некоторые различия между ними.
1. addr не может быть использован с метками, которые определены впереди, а offset может. Например, если метка определена где-то дальше в коде, чем строка с invoke, addr не будет работать.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
invoke MessageBox,NULL, addr MsgBoxText,addr MsgBoxCaption,MB_OK
......
MsgBoxCaption  db "Iczelion Tutorial #2",0
MsgBoxText       db "Win32 Assembly is Great!",0
MASM доложит об ошибке. Если вы используете offset вместо addr, MASM без проблем скомпилирует указанный отрывок кода.
2. Addr поддерживает локальные переменные, в то время как offset нет.
Локальная переменная — это всего лишь зарезервированное место в стеке. Вы только знаете его адрес во время выполнения программы. Offset интерпретируется во время компиляции ассемблером, поэтому неудивительно, что он не поддерживает локальные переменные. Addr же работает с ними, потому что ассемблер сначала проверяет ― глобальная переменная или локальная. Если она глобальная, он помещает адрес этой переменной в объектный файл. В этом случае оператор работает как offset. Если это локальная переменная, компилятор генерирует следующую последовательность инструкций, перед тем как будет вызвана функция:
Assembler
1
2
lea eax, LocalVar
push eax
Учитывая, что lea может определить адрес метки в "рантайме", все работает прекрасно.
____________________________________________
© Iczelion, пер. Aquila.
14
Миниатюры
Сам себе Iczelion   Сам себе Iczelion   Сам себе Iczelion  

Сам себе Iczelion  
Mikl___
Автор FAQ
11795 / 6098 / 556
Регистрация: 11.11.2010
Сообщений: 11,091
03.01.2013, 17:11  [ТС] 7
Кликните здесь для просмотра всего текста
В этом Уроке мы создадим полнофункциональную Windows программу, которое выводит сообщение — "Win32 assembly is great!".
Скачайте пример здесь.
ТЕОРИЯ, МАТЬ СКЛЕРОЗА
Windows предоставляет огромное количество ресурсов Windows-программам через Windows API (Application Programming Interface). Windows API — это большая коллекция очень полезных функций, располагающихся непосредственно в операционной системе и готовых для использования программами. Эти функции находятся в нескольких динамически подгружаемых библиотеках (DLLs), таких как kernel32.dll, user32.dll и gdi32.dll. Kernel32.dll содержит API-функции, взаимодействующие с памятью и управляющие процессами. User32.dll контролирует пользовательский интерфейс. Gdi32.dll ответственен за графические операции. Кроме этих трех "основных", существуют также другие dll, которые вы можете использовать, при условии, что вы обладаете достаточным количеством информации о нужных API-функциях. Windows программы динамически подсоединяется к этим библиотекам, то есть код API-функций не включается в исполняемый файл. Информация находится в библиотеках импорта. Вы должны слинковать ваши программы с правильными библиотеками импорта, иначе они не смогут найти эти функции. Когда Windows программа загружается в память, Windows читает информацию, сохраненную в программе. Эта информация включает имена функций, которые программа использует и DLL-ей, в которых эти функции располагаются. Когда Windows находит подобную информацию в программе, она вызывает библиотеки и исправляет в программе вызовы этих функций, так что контроль всегда будет передаваться по правильному адресу.
Существует две категории API функций: одни работают с ANSI-строками, а другие с Unicode-строками. Имена API-функций использующих ANSI-строки заканчиваются на "A", например, MessageBoxA. В конце имен функций для Unicode находится "W". Windows 95/98 от природы поддерживают ANSI, а Windows NT и производные от нее 2k/XP/Vista поддерживают Unicode. Обычно мы имеем дело с ANSI строками (массивы символов, оканчивающиеся NULL-ом. размер ANSI-символа — 1 байт. В то время как ANSI достаточна для европейских языков, она не поддерживает некоторые восточные языки, в которых есть несколько тысяч уникальных символов. Вот в этих случаях в дело вступает UniCode. размер символа UNICODE — 2 байта, и поэтому может поддерживать 65536 различных символов. Hо по большей части, вы будете использовать include-файл, который может определить и выбрать подходящую для вашей платформы функцию. Просто обращайтесь к именам API-функций без постфикса.
ПРАКТИКА, МАТЬ ШИЗОФРЕНИИ
Я приведу голый скелет программы ниже. Позже мы разберем его.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
.386
.model flat, stdcall
.data
.code
start:
end start
Выполнение начинается с первой инструкции, следующей за меткой, установленной после конца директив. В вышеприведенном каркасе выполнение начинается непосредственно после метки 'start'. Будут последовательно выполняться инструкция за инструкцией, пока не встретится операция передачи управления, такая как: jmр, jne, je, ret и так далее. Эти инструкции перенаправляют поток выполнения другим инструкциям. Когда программа выходит в Windows, ей следует вызвать API-функцию ExitProcess.
Assembler
1
ExitProcess proto uExitCode:DWORD
Строка выше называется прототипом функции. Прототип функции указывает ассемблеру/линкеру атрибуты функции, чтобы он сделал проверку типов данных и количество атрибутов передаваемых функции. Формат прототипа функции следующий:
Assembler
1
ИмяФункции PROTO [ИмяПараметра]:ТипДанных,[ИмяПараметра]:ТипДанных,...
Короче говоря, за именем функции следует ключевое слово PROTO, а затем список переменных с типом данных, разделенных запятыми. В приведенном выше примере с ExitProcess, эта функция была определена как принимающая только один параметр типа DWORD. Прототипы функций очень полезны, когда вы используете высокоуровневый синтаксический вызов — invoke. Вы можете считать об invoke как обычный вызов с проверкой типов данных. Например, если вы напишите:
Assembler
1
call ExitProcess
Линкер уведомит вас, что вы забыли положит в стек двойное слово. Я рекомендую вам использовать invoke вместо простого вызова. Синтаксис invoke следующий:
Assembler
1
invoke выражение [, аргументы]
Выражение может быть именем функции или указателем на функцию. Параметры функции разделены запятыми.
Большинство прототипов для API-функций содержатся в include-файлах. Если вы используете hutch'евский MASM32, они будут находится в директории MASM32/INCLUDE. Файлы подключения имеют расширение .inc и прототипы функций DLL находятся в .inc файле с таким же именем, как и у этой DLL.
Hапример, ExitProcess экспортируется из kernel32.lib, так что прототип ExitProcess находится в kernel32.inc.

Вы также можете создать прототипы для ваших собственных функций. Во всех моих экземплярах я использую hutch'евский windows.inc, который вы можете скачать с http://win32asm.cjb.net
Возвращаясь к ExitProcess: параметр uExitCode - это значение, которое программа вернет Windows после окончания программы. Вы можете вызвать функцию ExitProcess так:
Assembler
1
invoke ExitProcess, 0
Поместив эту строку непосредственно после стартовой метки, вы получите Win32-программу, немедленно выходящую в Windows, но тем не менее полнофункциональную.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
.386
.model flat, stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
.data
.code
start:      invoke ExitProcess, 0
end start
oрtion casemaр:none говорит MASM сделать метки "чувствительными" к регистрам, то есть ExitProcess и exitprocess — это различные имена. Отметьте новую директиву — include. После нее следует имя файла, который вы хотите вставить в то место, где эта директива располагается. В примере выше, когда MASM обрабатывает линию include \masm32\include\windows.inc, он открывает windows.inc, находящийся в директории \MASM32\INCLUDE, и далее анализирует содержимое windows.inc так, как будто вы "вклеили" подключаемый файл. Хатчевский windows.inc содержит в себе определения констант и структур, которые вам могут понадобиться для программирования под Win32. Этот файл не содержит в себе прототипов функций. Windows.inc ни в коем случае не является исчерпывающим и всеобъемлющим. Hutch и я пытаемся заполнить его как можно большим количеством констант и структур, но есть еще довольно, что следовало бы включить. Он постоянно обновляется. Заходите на хатчевскую и мою странички за свежими апдейтами. Из windows.inc, ваша программа будет брать определения констант и структур. Что касается прототипов функций, вы должны подключить другие include-файлы. Они находятся в директории \masm32\include.

В вышеприведенном примере, мы вызываем функцию, экспортированную из kernel32.dll, для чего мы должны подключить прототипы функций из kernel32.dll. Этот файл - kernel32.inc. Если вы открываете его текстовым редактором, вы увидите, что он состоит из прототипов функций из соответствующей dll. Если вы не подключите kernel32.inc, вы все еще можете вызвать ExitProcess, но уже с помощью ассемблерной команды call. Вы не сможете вызвать эту функцию с помощью invoke. Дело вот в чем: для того, чтобы вызвать функцию через invoke, вы должны поместить в исходном коде ее прототип. В примере выше, если вы не подключите kernel32.inc, вы можете определить прототип для ExitProcess где-нибудь до вызова этой функции и это будет работать. Файлы подключения нужны для того, что избавить вас от лишней работы и вам не пришлось набирать все прототипы самим.
Теперь мы встречаем новую директиву — includelib. Она работает не так, как include. Это всего лишь способ сказать ассемблеру какие библиотеки использует ваша программа должна прилинковать. Хотя вы вовсе не обязаны использовать именно этот метод. Вы можете указать имена библиотек импорта к командной строке при запуске линкера, но поверьте мне, это весьма скучно и утомительно, да и командная строка может вместить максимум 128 символов.
Теперь возьмите весь исходный текст примера этого Урока, сохраните его как msgbox.asm и ассемблируйте его так:
Код
ml /c /coff /Cp msgbox.asm
/c говорит MASM'у создать .obj файл в формате COFF. MASM использует вариант COFF (Common Object File Format), использующийся под Unix, как его собственный объектный и исполняемый формат файлов.
/Cр говорит MASM'у сохранять регистр имен, заданных пользователем. Если вы используете hutch'евский MASM32 пакет, вы можете вставить "option casemaр:none" в начале вашего исходника, сразу после директивы .model, чтобы добиться того же эффекта.
После успешной компиляции msgbox.asm, вы получите msgbox.obj. Это объектный файл, от которого один шаг до екзешника. Obj содержит инструкции/данные в двоичной форме. Отсутствуют только необходимая корректировка адресов, которая проводится линкером.
Теперь сделайте следующее:
Код
link /SUBSYSTEM:WINDOWS  /LIBPATH:c:\masm32\lib  msgbox.obj
/SUBSYSTEM:WINDOWS информирует линкер о том, какого вида является будущий исполняемый модуль.
/LIBPATH:<путь к библиотекам импорта> говорит линкеру, где находятся библиотеки импорта. Если вы используете MASM32, они будут в MASM32\lib.

Линкер читает объектный файл и корректирует его, используя адреса, взятые из библиотек импорта. После окончания линковки вы получите файл msgbox.exe. Запустите его. Вы увидите, что она ничего не делает.

Да, мы не поместили в код ничего не интересного. Hо тем не менее полноценная Windows программа. И посмотрите на размер! Hа моем PC — 1.536 байт.
Теперь мы готовы создать окно с сообщением. Прототип функции, которая нам для этого необходима следующая:
Assembler
1
MessageBox PROTO hwnd:DWORD, lpText:DWORD, lpCaption:DWORD, uType:DWORD
hwnd — это хэндл родительского окна. Вы можете считать хэндл числом, представляющим окно, к которому вы обращаетесь. Его значение для вас не важно. Вы только должны знать, что оно представляет окно. Когда вы захотите сделать что-нибудь с окном, вы должны обратиться к нему, используя его хэндл.
lрText — это указатель на текст, который вы хотите отобразить в клиентской части окна сообщения. Указатель ― это адрес чего-либо. Указатель на текстовую строку = адрес этой строки.
lpCaption — это указатель на заголовок окна сообщения.
uType — устанавливает иконку, число и вид кнопок окна.

Давайте изменим msgbox.asm для отображения сообщения.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
.data
MsgBoxCaption  db "Iczelion Tutorial #2",0
MsgBoxText     db "Win32 Assembly is Great!",0
.code
start: invoke MessageBox, NULL, addr MsgBoxText, addr MsgBoxCaption, MB_OK
invoke ExitProcess, NULL
end start
Скомпилируйте и запустите. Вы увидите окошко с сообщением "Win32 Assembly is great!".
Давайте снова взглянем на исходник.
Мы определили две оканчивающиеся NULL'ом строки в секции .data. Помните, что каждая ANSI строка в Windows должна оканчиваться NULL'ом (0 в шестнадцатеричной системе). Мы используем две константы, NULL и MB_OK. Эти константы прописаны в windows.inc, так что вы можете обратиться к ним, указав их имя, а не значение. Это улучшает читабельность кода.
Оператор addr используется для передачи адреса метки (и не только) функции. Он действителен только в контексте директивы invoke. Вы не можете использовать его, чтобы присвоить адрес метки регистру или переменной, например. В данном примере вы можете использовать offset вместо addr. Тем не менее, есть некоторые различия между ними.
1. addr не может быть использован с метками, которые определены впереди, а offset может. Например, если метка определена где-то дальше в коде, чем строка с invoke, addr не будет работать.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
invoke MessageBox,NULL, addr MsgBoxText,addr MsgBoxCaption,MB_OK
......
MsgBoxCaption  db "Iczelion Tutorial #2",0
MsgBoxText       db "Win32 Assembly is Great!",0
MASM доложит об ошибке. Если вы используете offset вместо addr, MASM без проблем скомпилирует указанный отрывок кода.
2. Addr поддерживает локальные переменные, в то время как offset нет.
Локальная переменная — это всего лишь зарезервированное место в стеке. Вы только знаете его адрес во время выполнения программы. Offset интерпретируется во время компиляции ассемблером, поэтому неудивительно, что он не поддерживает локальные переменные. Addr же работает с ними, потому что ассемблер сначала проверяет ― глобальная переменная или локальная. Если она глобальная, он помещает адрес этой переменной в объектный файл. В этом случае оператор работает как offset. Если это локальная переменная, компилятор генерирует следующую последовательность инструкций, перед тем как будет вызвана функция:
Assembler
1
2
lea eax, LocalVar
push eax
Учитывая, что lea может определить адрес метки в "рантайме", все работает прекрасно.
[center][COLOR="Silver"]____________________________________________
© Iczelion, пер. Aquila.
15
Миниатюры
Сам себе Iczelion   Сам себе Iczelion   Сам себе Iczelion  

Mikl___
Автор FAQ
11795 / 6098 / 556
Регистрация: 11.11.2010
Сообщений: 11,091
04.01.2013, 12:24  [ТС] 8
Win32 API. Урок 2d. А что находится внутри функции MessageBox или как еще можно создать MessageBox?

Кликните здесь для просмотра всего текста
В этом Уроке мы создадим полнофункциональную Windows программу, которое выводит сообщение — "Win32 assembly is great!".
Скачайте пример здесь.
ТЕОРИЯ, МАТЬ СКЛЕРОЗА
Windows предоставляет огромное количество ресурсов Windows-программам через Windows API (Application Programming Interface). Windows API — это большая коллекция очень полезных функций, располагающихся непосредственно в операционной системе и готовых для использования программами. Эти функции находятся в нескольких динамически подгружаемых библиотеках (DLLs), таких как kernel32.dll, user32.dll и gdi32.dll. Kernel32.dll содержит API-функции, взаимодействующие с памятью и управляющие процессами. User32.dll контролирует пользовательский интерфейс. Gdi32.dll ответственен за графические операции. Кроме этих трех "основных", существуют также другие dll, которые вы можете использовать, при условии, что вы обладаете достаточным количеством информации о нужных API-функциях. Windows программы динамически подсоединяется к этим библиотекам, то есть код API-функций не включается в исполняемый файл. Информация находится в библиотеках импорта. Вы должны слинковать ваши программы с правильными библиотеками импорта, иначе они не смогут найти эти функции. Когда Windows программа загружается в память, Windows читает информацию, сохраненную в программе. Эта информация включает имена функций, которые программа использует и DLL-ей, в которых эти функции располагаются. Когда Windows находит подобную информацию в программе, она вызывает библиотеки и исправляет в программе вызовы этих функций, так что контроль всегда будет передаваться по правильному адресу.
Существует две категории API функций: одни работают с ANSI-строками, а другие с Unicode-строками. Имена API-функций использующих ANSI-строки заканчиваются на "A", например, MessageBoxA. В конце имен функций для Unicode находится "W". Windows 95/98 от природы поддерживают ANSI, а Windows NT и производные от нее 2k/XP/Vista поддерживают Unicode. Обычно мы имеем дело с ANSI строками (массивы символов, оканчивающиеся NULL-ом. размер ANSI-символа — 1 байт. В то время как ANSI достаточна для европейских языков, она не поддерживает некоторые восточные языки, в которых есть несколько тысяч уникальных символов. Вот в этих случаях в дело вступает UniCode. размер символа UNICODE — 2 байта, и поэтому может поддерживать 65536 различных символов. Hо по большей части, вы будете использовать include-файл, который может определить и выбрать подходящую для вашей платформы функцию. Просто обращайтесь к именам API-функций без постфикса.
ПРАКТИКА, МАТЬ ШИЗОФРЕНИИ
Я приведу голый скелет программы ниже. Позже мы разберем его.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
.386
.model flat, stdcall
.data
.code
start:
end start
Выполнение начинается с первой инструкции, следующей за меткой, установленной после конца директив. В вышеприведенном каркасе выполнение начинается непосредственно после метки 'start'. Будут последовательно выполняться инструкция за инструкцией, пока не встретится операция передачи управления, такая как: jmр, jne, je, ret и так далее. Эти инструкции перенаправляют поток выполнения другим инструкциям. Когда программа выходит в Windows, ей следует вызвать API-функцию ExitProcess.
Assembler
1
ExitProcess proto uExitCode:DWORD
Строка выше называется прототипом функции. Прототип функции указывает ассемблеру/линкеру атрибуты функции, чтобы он сделал проверку типов данных и количество атрибутов передаваемых функции. Формат прототипа функции следующий:
Assembler
1
ИмяФункции PROTO [ИмяПараметра]:ТипДанных,[ИмяПараметра]:ТипДанных,...
Короче говоря, за именем функции следует ключевое слово PROTO, а затем список переменных с типом данных, разделенных запятыми. В приведенном выше примере с ExitProcess, эта функция была определена как принимающая только один параметр типа DWORD. Прототипы функций очень полезны, когда вы используете высокоуровневый синтаксический вызов — invoke. Вы можете считать об invoke как обычный вызов с проверкой типов данных. Например, если вы напишите:
Assembler
1
call ExitProcess
Линкер уведомит вас, что вы забыли положит в стек двойное слово. Я рекомендую вам использовать invoke вместо простого вызова. Синтаксис invoke следующий:
Assembler
1
invoke выражение [, аргументы]
Выражение может быть именем функции или указателем на функцию. Параметры функции разделены запятыми.
Большинство прототипов для API-функций содержатся в include-файлах. Если вы используете hutch'евский MASM32, они будут находится в директории MASM32/INCLUDE. Файлы подключения имеют расширение .inc и прототипы функций DLL находятся в .inc файле с таким же именем, как и у этой DLL.
Hапример, ExitProcess экспортируется из kernel32.lib, так что прототип ExitProcess находится в kernel32.inc.

Вы также можете создать прототипы для ваших собственных функций. Во всех моих экземплярах я использую hutch'евский windows.inc, который вы можете скачать с http://win32asm.cjb.net
Возвращаясь к ExitProcess: параметр uExitCode - это значение, которое программа вернет Windows после окончания программы. Вы можете вызвать функцию ExitProcess так:
Assembler
1
invoke ExitProcess, 0
Поместив эту строку непосредственно после стартовой метки, вы получите Win32-программу, немедленно выходящую в Windows, но тем не менее полнофункциональную.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
.386
.model flat, stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
.data
.code
start:      invoke ExitProcess, 0
end start
oрtion casemaр:none говорит MASM сделать метки "чувствительными" к регистрам, то есть ExitProcess и exitprocess — это различные имена. Отметьте новую директиву — include. После нее следует имя файла, который вы хотите вставить в то место, где эта директива располагается. В примере выше, когда MASM обрабатывает линию include \masm32\include\windows.inc, он открывает windows.inc, находящийся в директории \MASM32\INCLUDE, и далее анализирует содержимое windows.inc так, как будто вы "вклеили" подключаемый файл. Хатчевский windows.inc содержит в себе определения констант и структур, которые вам могут понадобиться для программирования под Win32. Этот файл не содержит в себе прототипов функций. Windows.inc ни в коем случае не является исчерпывающим и всеобъемлющим. Hutch и я пытаемся заполнить его как можно большим количеством констант и структур, но есть еще довольно, что следовало бы включить. Он постоянно обновляется. Заходите на хатчевскую и мою странички за свежими апдейтами. Из windows.inc, ваша программа будет брать определения констант и структур. Что касается прототипов функций, вы должны подключить другие include-файлы. Они находятся в директории \masm32\include.

В вышеприведенном примере, мы вызываем функцию, экспортированную из kernel32.dll, для чего мы должны подключить прототипы функций из kernel32.dll. Этот файл - kernel32.inc. Если вы открываете его текстовым редактором, вы увидите, что он состоит из прототипов функций из соответствующей dll. Если вы не подключите kernel32.inc, вы все еще можете вызвать ExitProcess, но уже с помощью ассемблерной команды call. Вы не сможете вызвать эту функцию с помощью invoke. Дело вот в чем: для того, чтобы вызвать функцию через invoke, вы должны поместить в исходном коде ее прототип. В примере выше, если вы не подключите kernel32.inc, вы можете определить прототип для ExitProcess где-нибудь до вызова этой функции и это будет работать. Файлы подключения нужны для того, что избавить вас от лишней работы и вам не пришлось набирать все прототипы самим.
Теперь мы встречаем новую директиву — includelib. Она работает не так, как include. Это всего лишь способ сказать ассемблеру какие библиотеки использует ваша программа должна прилинковать. Хотя вы вовсе не обязаны использовать именно этот метод. Вы можете указать имена библиотек импорта к командной строке при запуске линкера, но поверьте мне, это весьма скучно и утомительно, да и командная строка может вместить максимум 128 символов.
Теперь возьмите весь исходный текст примера этого Урока, сохраните его как msgbox.asm и ассемблируйте его так:
Код
ml /c /coff /Cp msgbox.asm
/c говорит MASM'у создать .obj файл в формате COFF. MASM использует вариант COFF (Common Object File Format), использующийся под Unix, как его собственный объектный и исполняемый формат файлов.
/Cр говорит MASM'у сохранять регистр имен, заданных пользователем. Если вы используете hutch'евский MASM32 пакет, вы можете вставить "option casemaр:none" в начале вашего исходника, сразу после директивы .model, чтобы добиться того же эффекта.
После успешной компиляции msgbox.asm, вы получите msgbox.obj. Это объектный файл, от которого один шаг до екзешника. Obj содержит инструкции/данные в двоичной форме. Отсутствуют только необходимая корректировка адресов, которая проводится линкером.
Теперь сделайте следующее:
Код
link /SUBSYSTEM:WINDOWS  /LIBPATH:c:\masm32\lib  msgbox.obj
/SUBSYSTEM:WINDOWS информирует линкер о том, какого вида является будущий исполняемый модуль.
/LIBPATH:<путь к библиотекам импорта> говорит линкеру, где находятся библиотеки импорта. Если вы используете MASM32, они будут в MASM32\lib.

Линкер читает объектный файл и корректирует его, используя адреса, взятые из библиотек импорта. После окончания линковки вы получите файл msgbox.exe. Запустите его. Вы увидите, что она ничего не делает.

Да, мы не поместили в код ничего не интересного. Hо тем не менее полноценная Windows программа. И посмотрите на размер! Hа моем PC — 1.536 байт.
Теперь мы готовы создать окно с сообщением. Прототип функции, которая нам для этого необходима следующая:
Assembler
1
MessageBox PROTO hwnd:DWORD, lpText:DWORD, lpCaption:DWORD, uType:DWORD
hwnd — это хэндл родительского окна. Вы можете считать хэндл числом, представляющим окно, к которому вы обращаетесь. Его значение для вас не важно. Вы только должны знать, что оно представляет окно. Когда вы захотите сделать что-нибудь с окном, вы должны обратиться к нему, используя его хэндл.
lрText — это указатель на текст, который вы хотите отобразить в клиентской части окна сообщения. Указатель ― это адрес чего-либо. Указатель на текстовую строку = адрес этой строки.
lpCaption — это указатель на заголовок окна сообщения.
uType — устанавливает иконку, число и вид кнопок окна.

Давайте изменим msgbox.asm для отображения сообщения.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
.data
MsgBoxCaption  db "Iczelion Tutorial #2",0
MsgBoxText     db "Win32 Assembly is Great!",0
.code
start: invoke MessageBox, NULL, addr MsgBoxText, addr MsgBoxCaption, MB_OK
invoke ExitProcess, NULL
end start
Скомпилируйте и запустите. Вы увидите окошко с сообщением "Win32 Assembly is great!".
Давайте снова взглянем на исходник.
Мы определили две оканчивающиеся NULL'ом строки в секции .data. Помните, что каждая ANSI строка в Windows должна оканчиваться NULL'ом (0 в шестнадцатеричной системе). Мы используем две константы, NULL и MB_OK. Эти константы прописаны в windows.inc, так что вы можете обратиться к ним, указав их имя, а не значение. Это улучшает читабельность кода.
Оператор addr используется для передачи адреса метки (и не только) функции. Он действителен только в контексте директивы invoke. Вы не можете использовать его, чтобы присвоить адрес метки регистру или переменной, например. В данном примере вы можете использовать offset вместо addr. Тем не менее, есть некоторые различия между ними.
1. addr не может быть использован с метками, которые определены впереди, а offset может. Например, если метка определена где-то дальше в коде, чем строка с invoke, addr не будет работать.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
invoke MessageBox,NULL, addr MsgBoxText,addr MsgBoxCaption,MB_OK
......
MsgBoxCaption  db "Iczelion Tutorial #2",0
MsgBoxText       db "Win32 Assembly is Great!",0
MASM доложит об ошибке. Если вы используете offset вместо addr, MASM без проблем скомпилирует указанный отрывок кода.
2. Addr поддерживает локальные переменные, в то время как offset нет.
Локальная переменная — это всего лишь зарезервированное место в стеке. Вы только знаете его адрес во время выполнения программы. Offset интерпретируется во время компиляции ассемблером, поэтому неудивительно, что он не поддерживает локальные переменные. Addr же работает с ними, потому что ассемблер сначала проверяет ― глобальная переменная или локальная. Если она глобальная, он помещает адрес этой переменной в объектный файл. В этом случае оператор работает как offset. Если это локальная переменная, компилятор генерирует следующую последовательность инструкций, перед тем как будет вызвана функция:
Assembler
1
2
lea eax, LocalVar
push eax
Учитывая, что lea может определить адрес метки в "рантайме", все работает прекрасно.
____________________________________________
© Iczelion, пер. Aquila.
10
Thread
programmer
1870 / 331 / 27
Регистрация: 01.06.2011
Сообщений: 2,893
Записей в блоге: 1
04.01.2013, 13:35 9
Кликните здесь для просмотра всего текста
Этот Урок предполагает, что читатель знает, как использовать MASM. Если вы не знакомы с MASM, скачайте c masm32.com и прочитайте текст, входящий в состав пакета, прежде чем продолжать чтение этого введения. Хорошо. Теперь вы готовы. Давайте приступим.
ТЕОРИЯ ― МАТЬ СКЛЕРОЗА
Win32 программы выполняются в защищенном режиме, который доступен начиная с 80286. Hо 80286 теперь история. Поэтому мы предполагаем, что имеем дело только с 80386 и его потомками. Windows запускает каждую Win32 программу в отдельном виртуальном пространстве. Это означает, что каждая Win32 программа будет иметь 4-х гигабайтовое адресное пространство.
Hо это вовсе не означает, что каждая программа имеет 4 гигабайта физической памяти, а только то, что программа может обращаться по любому адресу в этих пределах. Windows сделает все необходимое, чтобы сделать память, к которой программа обращается "существующей". Конечно, программа должна придерживаться правил, установленных Windows, или это вызовет General protection Fault.

Каждая программа одна в своем адресном пространстве, в то время как в Win16 дело обстоит не так. Все Win16 программы могут "видеть" друг друга, что невозможно в Win32. Этот особенность помогает снизить шанс того, что одна программа запишет что-нибудь поверх данных или кода другой программы.

Модель памяти также коренным образом отличается от существующих в старом мире 16-битных программ. Под Win32, мы больше не должны беспокоиться о моделях памяти или сегментах! Теперь только одна модель память: Плоская модель памяти. Теперь нет больше 64K сегментов. Память теперь это большое последовательное 4-х гигабайтовое пространство. Это также означает, что вы не должны "играть" с сегментными регистрами. Вы можете использовать любой сегментный регистр для адресации к любой точке памяти. Это ОГРОМНОЕ подспорье для программистов. Это то, что делает программирование на ассемблере под Win32 таким же простым, как на C.

Когда вы программируете под Win32, вы должны помнить несколько важных правил.
Одно из таких правил то, что Windows использует esi, edi, ebp и ebx внутренне и не ожидает, что значение в этих регистрах меняются. Так что помните это правило: если вы используете какой-либо из этих четырех регистров в вызываемой функции, не забудьте восстановить их перед возвращением управления Windows.
Вызываемая (callback) функция - это функция, которая вызывается Windows.
Очевидный пример - процедура окна. Это не значит, что вы не можете использовать эти четыре регистра. Просто не забудьте восстановить их значения перед передачей управления Windows.
ПРАКТИКА ― МАТЬ ШИЗОФРЕНИИ
Вот каркасная программа. Если что-то из кода вы не понимаете, не паникуйте. В дальнейшем я все объясню.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.386
.MODEL Flat, STDCALL
.DATA
   <Ваша инициализируемые данные>
   ......
.DATA?
   <Ваши не инициализируемые данные>
   ......
.CONST
   <Ваши константы>
   ......
.CODE
<метка>:
   <Ваш код>
   ......
end <метка>
Вот и все! Давайте проанализируем этот "каркас".
Assembler
1
.386
Это ассемблерная директива, говорящая ассемблеру использовать набор операций для процессора 80386. Вы также можете использовать .486, .586, .686 но самый безопасный выбор ― это указывать .386. Также есть два практически идентичных выбора для каждого варианта CPU. .386/.386p, .486/.486p. Эти "p"-версии необходимы только тогда, когда ваша программа использует привилегированные инструкции, то есть инструкции, зарезервированные процессором/операционной системой для работы в защищенном режиме. Они могут быть использованы только в защищенном коде, например, sys-драйверами. Как правило, ваши программы будут работать в непривилегированном режиме, так что лучше использовать не-"p" версии.
Assembler
1
.MODEL FLAT, STDCALL
.MODEL ― ассемблерная директива, определяющая модель памяти вашей программы. Под Win32 есть только одна ― плоская модель.
STDCALL говорит MASM'у о порядке передачи параметров, слева направо или справа налево, а также о том, кто уравнивает стек, после того как функция вызвана.
Под Win16 существует два типа передачи параметров, C и PASCAL. По C-договоренности, параметры передаются справа налево, то есть самый правый параметр кладется в стек первым. Вызывающий должен уравнять стек после вызова. Например, при вызове функции с именем foo(int first_param, int second_param, int third_param), используя C-передачу параметров, ассемблерный код будет выглядеть так:
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
push [third_param]  ; Положить в стек третий параметр
push [second_param] ; Следом - второй
push [first_param]  ; И, наконец, первый
call foo
add  esp, 12         ; Вызывающий уравнивает стек

PASCAL-передача параметров ― это C-передача наоборот. Согласно ей, параметры передаются слева направо и вызываемый параметр должен уравнивать стек.
Win16 использует этот порядок передачи данных, потому что тогда код программы становится меньше. C-порядок полезен, когда вы не знаете, как много параметров будут переданы функции, как например, в случае wsрrintf(), когда функция не может знать заранее, сколько параметров будут положены в стек, так что она не может уравнять стек.
STDCALL - это гибрид C и PASCAL вызовов. Согласно ему, данные передаются справа налево, но вызываемая функция ответственна за очистку стека от переданных ей параметров. Платформа Win32 использует исключительно STDCALL, хотя есть одно исключение -- функция wsprintf(). Вы должны следовать C-порядку вызова в случае wsprintf().
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
.DATA
 
.DATA?
 
.CONST
 
.CODE
Все четыре директивы это то, что называется секциями. Вы помните, что в Win32 нет сегментов? Hо вы можете поделить пресловутое адресное пространство на логические секции. Начало одной секции отмечает конец предыдущей. Есть две группы секций: данных и кода.
.DATA ― Эта секция содержит инициализированные данные вашей программы.
.DATA? ― эта секция содержит неинициализированные данные вашей программы. Иногда вам нужно только "предварительно" выделить некоторое количество памяти, но вы не хотите инициализировать ее. Эта секция для этого и предназначается. Преимущество неинициализированных данных следующее: они не занимают места в исполняемом файле. Например, если вы хотите выделить 10000 байт в вашей .DATA? секции, ваш exe-файл не увеличится на 10kb. Его размер останется таким же. Вы, всего лишь, говорите компилятору, сколько места вам нужно, когда программа загрузится в память.

.CONST ― эта секция содержит объявления констант, используемых программой. Константы не могут быть изменены ей. Это всего лишь "константы".
Вы не обязаны задействовать все три секции. Объявляйте только те, которые хотите использовать.
Есть только одна секция для кода: .CODE, там где содержится весь код.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
<метка>:
.....
end <метка>
где <метка> ― любая произвольная метка, устанавливающая границы кода. Обе метки должны быть идентичны. Весь код должен располагаться между
Assembler
1
<метка>
и
Assembler
1
end <метка>


© Iczelion, пер. Aquila
1
Mikl___
Автор FAQ
11795 / 6098 / 556
Регистрация: 11.11.2010
Сообщений: 11,091
04.01.2013, 14:09  [ТС] 10
Win32 API. Урок 3. простое окно
Кликните здесь для просмотра всего текста
В этом Уроке мы создадим полнофункциональную Windows программу, которое выводит сообщение — "Win32 assembly is great!".
Скачайте пример здесь.
ТЕОРИЯ, МАТЬ СКЛЕРОЗА
Windows предоставляет огромное количество ресурсов Windows-программам через Windows API (Application Programming Interface). Windows API — это большая коллекция очень полезных функций, располагающихся непосредственно в операционной системе и готовых для использования программами. Эти функции находятся в нескольких динамически подгружаемых библиотеках (DLLs), таких как kernel32.dll, user32.dll и gdi32.dll. Kernel32.dll содержит API-функции, взаимодействующие с памятью и управляющие процессами. User32.dll контролирует пользовательский интерфейс. Gdi32.dll ответственен за графические операции. Кроме этих трех "основных", существуют также другие dll, которые вы можете использовать, при условии, что вы обладаете достаточным количеством информации о нужных API-функциях. Windows программы динамически подсоединяется к этим библиотекам, то есть код API-функций не включается в исполняемый файл. Информация находится в библиотеках импорта. Вы должны слинковать ваши программы с правильными библиотеками импорта, иначе они не смогут найти эти функции. Когда Windows программа загружается в память, Windows читает информацию, сохраненную в программе. Эта информация включает имена функций, которые программа использует и DLL-ей, в которых эти функции располагаются. Когда Windows находит подобную информацию в программе, она вызывает библиотеки и исправляет в программе вызовы этих функций, так что контроль всегда будет передаваться по правильному адресу.
Существует две категории API функций: одни работают с ANSI-строками, а другие с Unicode-строками. Имена API-функций использующих ANSI-строки заканчиваются на "A", например, MessageBoxA. В конце имен функций для Unicode находится "W". Windows 95/98 от природы поддерживают ANSI, а Windows NT и производные от нее 2k/XP/Vista поддерживают Unicode. Обычно мы имеем дело с ANSI строками (массивы символов, оканчивающиеся NULL-ом. размер ANSI-символа — 1 байт. В то время как ANSI достаточна для европейских языков, она не поддерживает некоторые восточные языки, в которых есть несколько тысяч уникальных символов. Вот в этих случаях в дело вступает UniCode. размер символа UNICODE — 2 байта, и поэтому может поддерживать 65536 различных символов. Hо по большей части, вы будете использовать include-файл, который может определить и выбрать подходящую для вашей платформы функцию. Просто обращайтесь к именам API-функций без постфикса.
ПРАКТИКА, МАТЬ ШИЗОФРЕНИИ
Я приведу голый скелет программы ниже. Позже мы разберем его.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
.386
.model flat, stdcall
.data
.code
start:
end start
Выполнение начинается с первой инструкции, следующей за меткой, установленной после конца директив. В вышеприведенном каркасе выполнение начинается непосредственно после метки 'start'. Будут последовательно выполняться инструкция за инструкцией, пока не встретится операция передачи управления, такая как: jmр, jne, je, ret и так далее. Эти инструкции перенаправляют поток выполнения другим инструкциям. Когда программа выходит в Windows, ей следует вызвать API-функцию ExitProcess.
Assembler
1
ExitProcess proto uExitCode:DWORD
Строка выше называется прототипом функции. Прототип функции указывает ассемблеру/линкеру атрибуты функции, чтобы он сделал проверку типов данных и количество атрибутов передаваемых функции. Формат прототипа функции следующий:
Assembler
1
ИмяФункции PROTO [ИмяПараметра]:ТипДанных,[ИмяПараметра]:ТипДанных,...
Короче говоря, за именем функции следует ключевое слово PROTO, а затем список переменных с типом данных, разделенных запятыми. В приведенном выше примере с ExitProcess, эта функция была определена как принимающая только один параметр типа DWORD. Прототипы функций очень полезны, когда вы используете высокоуровневый синтаксический вызов — invoke. Вы можете считать об invoke как обычный вызов с проверкой типов данных. Например, если вы напишите:
Assembler
1
call ExitProcess
Линкер уведомит вас, что вы забыли положит в стек двойное слово. Я рекомендую вам использовать invoke вместо простого вызова. Синтаксис invoke следующий:
Assembler
1
invoke выражение [, аргументы]
Выражение может быть именем функции или указателем на функцию. Параметры функции разделены запятыми.
Большинство прототипов для API-функций содержатся в include-файлах. Если вы используете hutch'евский MASM32, они будут находится в директории MASM32/INCLUDE. Файлы подключения имеют расширение .inc и прототипы функций DLL находятся в .inc файле с таким же именем, как и у этой DLL.
Hапример, ExitProcess экспортируется из kernel32.lib, так что прототип ExitProcess находится в kernel32.inc.

Вы также можете создать прототипы для ваших собственных функций. Во всех моих экземплярах я использую hutch'евский windows.inc, который вы можете скачать с http://win32asm.cjb.net
Возвращаясь к ExitProcess: параметр uExitCode - это значение, которое программа вернет Windows после окончания программы. Вы можете вызвать функцию ExitProcess так:
Assembler
1
invoke ExitProcess, 0
Поместив эту строку непосредственно после стартовой метки, вы получите Win32-программу, немедленно выходящую в Windows, но тем не менее полнофункциональную.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
.386
.model flat, stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
.data
.code
start:      invoke ExitProcess, 0
end start
oрtion casemaр:none говорит MASM сделать метки "чувствительными" к регистрам, то есть ExitProcess и exitprocess — это различные имена. Отметьте новую директиву — include. После нее следует имя файла, который вы хотите вставить в то место, где эта директива располагается. В примере выше, когда MASM обрабатывает линию include \masm32\include\windows.inc, он открывает windows.inc, находящийся в директории \MASM32\INCLUDE, и далее анализирует содержимое windows.inc так, как будто вы "вклеили" подключаемый файл. Хатчевский windows.inc содержит в себе определения констант и структур, которые вам могут понадобиться для программирования под Win32. Этот файл не содержит в себе прототипов функций. Windows.inc ни в коем случае не является исчерпывающим и всеобъемлющим. Hutch и я пытаемся заполнить его как можно большим количеством констант и структур, но есть еще довольно, что следовало бы включить. Он постоянно обновляется. Заходите на хатчевскую и мою странички за свежими апдейтами. Из windows.inc, ваша программа будет брать определения констант и структур. Что касается прототипов функций, вы должны подключить другие include-файлы. Они находятся в директории \masm32\include.

В вышеприведенном примере, мы вызываем функцию, экспортированную из kernel32.dll, для чего мы должны подключить прототипы функций из kernel32.dll. Этот файл - kernel32.inc. Если вы открываете его текстовым редактором, вы увидите, что он состоит из прототипов функций из соответствующей dll. Если вы не подключите kernel32.inc, вы все еще можете вызвать ExitProcess, но уже с помощью ассемблерной команды call. Вы не сможете вызвать эту функцию с помощью invoke. Дело вот в чем: для того, чтобы вызвать функцию через invoke, вы должны поместить в исходном коде ее прототип. В примере выше, если вы не подключите kernel32.inc, вы можете определить прототип для ExitProcess где-нибудь до вызова этой функции и это будет работать. Файлы подключения нужны для того, что избавить вас от лишней работы и вам не пришлось набирать все прототипы самим.
Теперь мы встречаем новую директиву — includelib. Она работает не так, как include. Это всего лишь способ сказать ассемблеру какие библиотеки использует ваша программа должна прилинковать. Хотя вы вовсе не обязаны использовать именно этот метод. Вы можете указать имена библиотек импорта к командной строке при запуске линкера, но поверьте мне, это весьма скучно и утомительно, да и командная строка может вместить максимум 128 символов.
Теперь возьмите весь исходный текст примера этого Урока, сохраните его как msgbox.asm и ассемблируйте его так:
Код
ml /c /coff /Cp msgbox.asm
/c говорит MASM'у создать .obj файл в формате COFF. MASM использует вариант COFF (Common Object File Format), использующийся под Unix, как его собственный объектный и исполняемый формат файлов.
/Cр говорит MASM'у сохранять регистр имен, заданных пользователем. Если вы используете hutch'евский MASM32 пакет, вы можете вставить "option casemaр:none" в начале вашего исходника, сразу после директивы .model, чтобы добиться того же эффекта.
После успешной компиляции msgbox.asm, вы получите msgbox.obj. Это объектный файл, от которого один шаг до екзешника. Obj содержит инструкции/данные в двоичной форме. Отсутствуют только необходимая корректировка адресов, которая проводится линкером.
Теперь сделайте следующее:
Код
link /SUBSYSTEM:WINDOWS  /LIBPATH:c:\masm32\lib  msgbox.obj
/SUBSYSTEM:WINDOWS информирует линкер о том, какого вида является будущий исполняемый модуль.
/LIBPATH:<путь к библиотекам импорта> говорит линкеру, где находятся библиотеки импорта. Если вы используете MASM32, они будут в MASM32\lib.

Линкер читает объектный файл и корректирует его, используя адреса, взятые из библиотек импорта. После окончания линковки вы получите файл msgbox.exe. Запустите его. Вы увидите, что она ничего не делает.

Да, мы не поместили в код ничего не интересного. Hо тем не менее полноценная Windows программа. И посмотрите на размер! Hа моем PC — 1.536 байт.
Теперь мы готовы создать окно с сообщением. Прототип функции, которая нам для этого необходима следующая:
Assembler
1
MessageBox PROTO hwnd:DWORD, lpText:DWORD, lpCaption:DWORD, uType:DWORD
hwnd — это хэндл родительского окна. Вы можете считать хэндл числом, представляющим окно, к которому вы обращаетесь. Его значение для вас не важно. Вы только должны знать, что оно представляет окно. Когда вы захотите сделать что-нибудь с окном, вы должны обратиться к нему, используя его хэндл.
lрText — это указатель на текст, который вы хотите отобразить в клиентской части окна сообщения. Указатель ― это адрес чего-либо. Указатель на текстовую строку = адрес этой строки.
lpCaption — это указатель на заголовок окна сообщения.
uType — устанавливает иконку, число и вид кнопок окна.

Давайте изменим msgbox.asm для отображения сообщения.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
.data
MsgBoxCaption  db "Iczelion Tutorial #2",0
MsgBoxText     db "Win32 Assembly is Great!",0
.code
start: invoke MessageBox, NULL, addr MsgBoxText, addr MsgBoxCaption, MB_OK
invoke ExitProcess, NULL
end start
Скомпилируйте и запустите. Вы увидите окошко с сообщением "Win32 Assembly is great!".
Давайте снова взглянем на исходник.
Мы определили две оканчивающиеся NULL'ом строки в секции .data. Помните, что каждая ANSI строка в Windows должна оканчиваться NULL'ом (0 в шестнадцатеричной системе). Мы используем две константы, NULL и MB_OK. Эти константы прописаны в windows.inc, так что вы можете обратиться к ним, указав их имя, а не значение. Это улучшает читабельность кода.
Оператор addr используется для передачи адреса метки (и не только) функции. Он действителен только в контексте директивы invoke. Вы не можете использовать его, чтобы присвоить адрес метки регистру или переменной, например. В данном примере вы можете использовать offset вместо addr. Тем не менее, есть некоторые различия между ними.
1. addr не может быть использован с метками, которые определены впереди, а offset может. Например, если метка определена где-то дальше в коде, чем строка с invoke, addr не будет работать.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
invoke MessageBox,NULL, addr MsgBoxText,addr MsgBoxCaption,MB_OK
......
MsgBoxCaption  db "Iczelion Tutorial #2",0
MsgBoxText       db "Win32 Assembly is Great!",0
MASM доложит об ошибке. Если вы используете offset вместо addr, MASM без проблем скомпилирует указанный отрывок кода.
2. Addr поддерживает локальные переменные, в то время как offset нет.
Локальная переменная — это всего лишь зарезервированное место в стеке. Вы только знаете его адрес во время выполнения программы. Offset интерпретируется во время компиляции ассемблером, поэтому неудивительно, что он не поддерживает локальные переменные. Addr же работает с ними, потому что ассемблер сначала проверяет ― глобальная переменная или локальная. Если она глобальная, он помещает адрес этой переменной в объектный файл. В этом случае оператор работает как offset. Если это локальная переменная, компилятор генерирует следующую последовательность инструкций, перед тем как будет вызвана функция:
Assembler
1
2
lea eax, LocalVar
push eax
Учитывая, что lea может определить адрес метки в "рантайме", все работает прекрасно.
____________________________________________
© Iczelion, пер. Aquila.
8
Вложения
Тип файла: zip tut03.zip (2.5 Кб, 194 просмотров)
Mikl___
Автор FAQ
11795 / 6098 / 556
Регистрация: 11.11.2010
Сообщений: 11,091
05.01.2013, 14:59  [ТС] 11
Win32 API. Урок 3a. Наш вариант простого окна
Кликните здесь для просмотра всего текста
В этом Уроке мы создадим полнофункциональную Windows программу, которое выводит сообщение — "Win32 assembly is great!".
Скачайте пример здесь.
ТЕОРИЯ, МАТЬ СКЛЕРОЗА
Windows предоставляет огромное количество ресурсов Windows-программам через Windows API (Application Programming Interface). Windows API — это большая коллекция очень полезных функций, располагающихся непосредственно в операционной системе и готовых для использования программами. Эти функции находятся в нескольких динамически подгружаемых библиотеках (DLLs), таких как kernel32.dll, user32.dll и gdi32.dll. Kernel32.dll содержит API-функции, взаимодействующие с памятью и управляющие процессами. User32.dll контролирует пользовательский интерфейс. Gdi32.dll ответственен за графические операции. Кроме этих трех "основных", существуют также другие dll, которые вы можете использовать, при условии, что вы обладаете достаточным количеством информации о нужных API-функциях. Windows программы динамически подсоединяется к этим библиотекам, то есть код API-функций не включается в исполняемый файл. Информация находится в библиотеках импорта. Вы должны слинковать ваши программы с правильными библиотеками импорта, иначе они не смогут найти эти функции. Когда Windows программа загружается в память, Windows читает информацию, сохраненную в программе. Эта информация включает имена функций, которые программа использует и DLL-ей, в которых эти функции располагаются. Когда Windows находит подобную информацию в программе, она вызывает библиотеки и исправляет в программе вызовы этих функций, так что контроль всегда будет передаваться по правильному адресу.
Существует две категории API функций: одни работают с ANSI-строками, а другие с Unicode-строками. Имена API-функций использующих ANSI-строки заканчиваются на "A", например, MessageBoxA. В конце имен функций для Unicode находится "W". Windows 95/98 от природы поддерживают ANSI, а Windows NT и производные от нее 2k/XP/Vista поддерживают Unicode. Обычно мы имеем дело с ANSI строками (массивы символов, оканчивающиеся NULL-ом. размер ANSI-символа — 1 байт. В то время как ANSI достаточна для европейских языков, она не поддерживает некоторые восточные языки, в которых есть несколько тысяч уникальных символов. Вот в этих случаях в дело вступает UniCode. размер символа UNICODE — 2 байта, и поэтому может поддерживать 65536 различных символов. Hо по большей части, вы будете использовать include-файл, который может определить и выбрать подходящую для вашей платформы функцию. Просто обращайтесь к именам API-функций без постфикса.
ПРАКТИКА, МАТЬ ШИЗОФРЕНИИ
Я приведу голый скелет программы ниже. Позже мы разберем его.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
.386
.model flat, stdcall
.data
.code
start:
end start
Выполнение начинается с первой инструкции, следующей за меткой, установленной после конца директив. В вышеприведенном каркасе выполнение начинается непосредственно после метки 'start'. Будут последовательно выполняться инструкция за инструкцией, пока не встретится операция передачи управления, такая как: jmр, jne, je, ret и так далее. Эти инструкции перенаправляют поток выполнения другим инструкциям. Когда программа выходит в Windows, ей следует вызвать API-функцию ExitProcess.
Assembler
1
ExitProcess proto uExitCode:DWORD
Строка выше называется прототипом функции. Прототип функции указывает ассемблеру/линкеру атрибуты функции, чтобы он сделал проверку типов данных и количество атрибутов передаваемых функции. Формат прототипа функции следующий:
Assembler
1
ИмяФункции PROTO [ИмяПараметра]:ТипДанных,[ИмяПараметра]:ТипДанных,...
Короче говоря, за именем функции следует ключевое слово PROTO, а затем список переменных с типом данных, разделенных запятыми. В приведенном выше примере с ExitProcess, эта функция была определена как принимающая только один параметр типа DWORD. Прототипы функций очень полезны, когда вы используете высокоуровневый синтаксический вызов — invoke. Вы можете считать об invoke как обычный вызов с проверкой типов данных. Например, если вы напишите:
Assembler
1
call ExitProcess
Линкер уведомит вас, что вы забыли положит в стек двойное слово. Я рекомендую вам использовать invoke вместо простого вызова. Синтаксис invoke следующий:
Assembler
1
invoke выражение [, аргументы]
Выражение может быть именем функции или указателем на функцию. Параметры функции разделены запятыми.
Большинство прототипов для API-функций содержатся в include-файлах. Если вы используете hutch'евский MASM32, они будут находится в директории MASM32/INCLUDE. Файлы подключения имеют расширение .inc и прототипы функций DLL находятся в .inc файле с таким же именем, как и у этой DLL.
Hапример, ExitProcess экспортируется из kernel32.lib, так что прототип ExitProcess находится в kernel32.inc.

Вы также можете создать прототипы для ваших собственных функций. Во всех моих экземплярах я использую hutch'евский windows.inc, который вы можете скачать с http://win32asm.cjb.net
Возвращаясь к ExitProcess: параметр uExitCode - это значение, которое программа вернет Windows после окончания программы. Вы можете вызвать функцию ExitProcess так:
Assembler
1
invoke ExitProcess, 0
Поместив эту строку непосредственно после стартовой метки, вы получите Win32-программу, немедленно выходящую в Windows, но тем не менее полнофункциональную.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
.386
.model flat, stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
.data
.code
start:      invoke ExitProcess, 0
end start
oрtion casemaр:none говорит MASM сделать метки "чувствительными" к регистрам, то есть ExitProcess и exitprocess — это различные имена. Отметьте новую директиву — include. После нее следует имя файла, который вы хотите вставить в то место, где эта директива располагается. В примере выше, когда MASM обрабатывает линию include \masm32\include\windows.inc, он открывает windows.inc, находящийся в директории \MASM32\INCLUDE, и далее анализирует содержимое windows.inc так, как будто вы "вклеили" подключаемый файл. Хатчевский windows.inc содержит в себе определения констант и структур, которые вам могут понадобиться для программирования под Win32. Этот файл не содержит в себе прототипов функций. Windows.inc ни в коем случае не является исчерпывающим и всеобъемлющим. Hutch и я пытаемся заполнить его как можно большим количеством констант и структур, но есть еще довольно, что следовало бы включить. Он постоянно обновляется. Заходите на хатчевскую и мою странички за свежими апдейтами. Из windows.inc, ваша программа будет брать определения констант и структур. Что касается прототипов функций, вы должны подключить другие include-файлы. Они находятся в директории \masm32\include.

В вышеприведенном примере, мы вызываем функцию, экспортированную из kernel32.dll, для чего мы должны подключить прототипы функций из kernel32.dll. Этот файл - kernel32.inc. Если вы открываете его текстовым редактором, вы увидите, что он состоит из прототипов функций из соответствующей dll. Если вы не подключите kernel32.inc, вы все еще можете вызвать ExitProcess, но уже с помощью ассемблерной команды call. Вы не сможете вызвать эту функцию с помощью invoke. Дело вот в чем: для того, чтобы вызвать функцию через invoke, вы должны поместить в исходном коде ее прототип. В примере выше, если вы не подключите kernel32.inc, вы можете определить прототип для ExitProcess где-нибудь до вызова этой функции и это будет работать. Файлы подключения нужны для того, что избавить вас от лишней работы и вам не пришлось набирать все прототипы самим.
Теперь мы встречаем новую директиву — includelib. Она работает не так, как include. Это всего лишь способ сказать ассемблеру какие библиотеки использует ваша программа должна прилинковать. Хотя вы вовсе не обязаны использовать именно этот метод. Вы можете указать имена библиотек импорта к командной строке при запуске линкера, но поверьте мне, это весьма скучно и утомительно, да и командная строка может вместить максимум 128 символов.
Теперь возьмите весь исходный текст примера этого Урока, сохраните его как msgbox.asm и ассемблируйте его так:
Код
ml /c /coff /Cp msgbox.asm
/c говорит MASM'у создать .obj файл в формате COFF. MASM использует вариант COFF (Common Object File Format), использующийся под Unix, как его собственный объектный и исполняемый формат файлов.
/Cр говорит MASM'у сохранять регистр имен, заданных пользователем. Если вы используете hutch'евский MASM32 пакет, вы можете вставить "option casemaр:none" в начале вашего исходника, сразу после директивы .model, чтобы добиться того же эффекта.
После успешной компиляции msgbox.asm, вы получите msgbox.obj. Это объектный файл, от которого один шаг до екзешника. Obj содержит инструкции/данные в двоичной форме. Отсутствуют только необходимая корректировка адресов, которая проводится линкером.
Теперь сделайте следующее:
Код
link /SUBSYSTEM:WINDOWS  /LIBPATH:c:\masm32\lib  msgbox.obj
/SUBSYSTEM:WINDOWS информирует линкер о том, какого вида является будущий исполняемый модуль.
/LIBPATH:<путь к библиотекам импорта> говорит линкеру, где находятся библиотеки импорта. Если вы используете MASM32, они будут в MASM32\lib.

Линкер читает объектный файл и корректирует его, используя адреса, взятые из библиотек импорта. После окончания линковки вы получите файл msgbox.exe. Запустите его. Вы увидите, что она ничего не делает.

Да, мы не поместили в код ничего не интересного. Hо тем не менее полноценная Windows программа. И посмотрите на размер! Hа моем PC — 1.536 байт.
Теперь мы готовы создать окно с сообщением. Прототип функции, которая нам для этого необходима следующая:
Assembler
1
MessageBox PROTO hwnd:DWORD, lpText:DWORD, lpCaption:DWORD, uType:DWORD
hwnd — это хэндл родительского окна. Вы можете считать хэндл числом, представляющим окно, к которому вы обращаетесь. Его значение для вас не важно. Вы только должны знать, что оно представляет окно. Когда вы захотите сделать что-нибудь с окном, вы должны обратиться к нему, используя его хэндл.
lрText — это указатель на текст, который вы хотите отобразить в клиентской части окна сообщения. Указатель ― это адрес чего-либо. Указатель на текстовую строку = адрес этой строки.
lpCaption — это указатель на заголовок окна сообщения.
uType — устанавливает иконку, число и вид кнопок окна.

Давайте изменим msgbox.asm для отображения сообщения.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
.data
MsgBoxCaption  db "Iczelion Tutorial #2",0
MsgBoxText     db "Win32 Assembly is Great!",0
.code
start: invoke MessageBox, NULL, addr MsgBoxText, addr MsgBoxCaption, MB_OK
invoke ExitProcess, NULL
end start
Скомпилируйте и запустите. Вы увидите окошко с сообщением "Win32 Assembly is great!".
Давайте снова взглянем на исходник.
Мы определили две оканчивающиеся NULL'ом строки в секции .data. Помните, что каждая ANSI строка в Windows должна оканчиваться NULL'ом (0 в шестнадцатеричной системе). Мы используем две константы, NULL и MB_OK. Эти константы прописаны в windows.inc, так что вы можете обратиться к ним, указав их имя, а не значение. Это улучшает читабельность кода.
Оператор addr используется для передачи адреса метки (и не только) функции. Он действителен только в контексте директивы invoke. Вы не можете использовать его, чтобы присвоить адрес метки регистру или переменной, например. В данном примере вы можете использовать offset вместо addr. Тем не менее, есть некоторые различия между ними.
1. addr не может быть использован с метками, которые определены впереди, а offset может. Например, если метка определена где-то дальше в коде, чем строка с invoke, addr не будет работать.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
invoke MessageBox,NULL, addr MsgBoxText,addr MsgBoxCaption,MB_OK
......
MsgBoxCaption  db "Iczelion Tutorial #2",0
MsgBoxText       db "Win32 Assembly is Great!",0
MASM доложит об ошибке. Если вы используете offset вместо addr, MASM без проблем скомпилирует указанный отрывок кода.
2. Addr поддерживает локальные переменные, в то время как offset нет.
Локальная переменная — это всего лишь зарезервированное место в стеке. Вы только знаете его адрес во время выполнения программы. Offset интерпретируется во время компиляции ассемблером, поэтому неудивительно, что он не поддерживает локальные переменные. Addr же работает с ними, потому что ассемблер сначала проверяет ― глобальная переменная или локальная. Если она глобальная, он помещает адрес этой переменной в объектный файл. В этом случае оператор работает как offset. Если это локальная переменная, компилятор генерирует следующую последовательность инструкций, перед тем как будет вызвана функция:
Assembler
1
2
lea eax, LocalVar
push eax
Учитывая, что lea может определить адрес метки в "рантайме", все работает прекрасно.
____________________________________________
© Iczelion, пер. Aquila.
9
Изображения
  
Вложения
Тип файла: zip tut03c.zip (17.8 Кб, 153 просмотров)
Mikl___
Автор FAQ
11795 / 6098 / 556
Регистрация: 11.11.2010
Сообщений: 11,091
05.01.2013, 16:29  [ТС] 12
Win32 API. Урок 4. Отрисовка текста
Кликните здесь для просмотра всего текста
В этом Уроке мы создадим полнофункциональную Windows программу, которое выводит сообщение — "Win32 assembly is great!".
Скачайте пример здесь.
ТЕОРИЯ, МАТЬ СКЛЕРОЗА
Windows предоставляет огромное количество ресурсов Windows-программам через Windows API (Application Programming Interface). Windows API — это большая коллекция очень полезных функций, располагающихся непосредственно в операционной системе и готовых для использования программами. Эти функции находятся в нескольких динамически подгружаемых библиотеках (DLLs), таких как kernel32.dll, user32.dll и gdi32.dll. Kernel32.dll содержит API-функции, взаимодействующие с памятью и управляющие процессами. User32.dll контролирует пользовательский интерфейс. Gdi32.dll ответственен за графические операции. Кроме этих трех "основных", существуют также другие dll, которые вы можете использовать, при условии, что вы обладаете достаточным количеством информации о нужных API-функциях. Windows программы динамически подсоединяется к этим библиотекам, то есть код API-функций не включается в исполняемый файл. Информация находится в библиотеках импорта. Вы должны слинковать ваши программы с правильными библиотеками импорта, иначе они не смогут найти эти функции. Когда Windows программа загружается в память, Windows читает информацию, сохраненную в программе. Эта информация включает имена функций, которые программа использует и DLL-ей, в которых эти функции располагаются. Когда Windows находит подобную информацию в программе, она вызывает библиотеки и исправляет в программе вызовы этих функций, так что контроль всегда будет передаваться по правильному адресу.
Существует две категории API функций: одни работают с ANSI-строками, а другие с Unicode-строками. Имена API-функций использующих ANSI-строки заканчиваются на "A", например, MessageBoxA. В конце имен функций для Unicode находится "W". Windows 95/98 от природы поддерживают ANSI, а Windows NT и производные от нее 2k/XP/Vista поддерживают Unicode. Обычно мы имеем дело с ANSI строками (массивы символов, оканчивающиеся NULL-ом. размер ANSI-символа — 1 байт. В то время как ANSI достаточна для европейских языков, она не поддерживает некоторые восточные языки, в которых есть несколько тысяч уникальных символов. Вот в этих случаях в дело вступает UniCode. размер символа UNICODE — 2 байта, и поэтому может поддерживать 65536 различных символов. Hо по большей части, вы будете использовать include-файл, который может определить и выбрать подходящую для вашей платформы функцию. Просто обращайтесь к именам API-функций без постфикса.
ПРАКТИКА, МАТЬ ШИЗОФРЕНИИ
Я приведу голый скелет программы ниже. Позже мы разберем его.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
.386
.model flat, stdcall
.data
.code
start:
end start
Выполнение начинается с первой инструкции, следующей за меткой, установленной после конца директив. В вышеприведенном каркасе выполнение начинается непосредственно после метки 'start'. Будут последовательно выполняться инструкция за инструкцией, пока не встретится операция передачи управления, такая как: jmр, jne, je, ret и так далее. Эти инструкции перенаправляют поток выполнения другим инструкциям. Когда программа выходит в Windows, ей следует вызвать API-функцию ExitProcess.
Assembler
1
ExitProcess proto uExitCode:DWORD
Строка выше называется прототипом функции. Прототип функции указывает ассемблеру/линкеру атрибуты функции, чтобы он сделал проверку типов данных и количество атрибутов передаваемых функции. Формат прототипа функции следующий:
Assembler
1
ИмяФункции PROTO [ИмяПараметра]:ТипДанных,[ИмяПараметра]:ТипДанных,...
Короче говоря, за именем функции следует ключевое слово PROTO, а затем список переменных с типом данных, разделенных запятыми. В приведенном выше примере с ExitProcess, эта функция была определена как принимающая только один параметр типа DWORD. Прототипы функций очень полезны, когда вы используете высокоуровневый синтаксический вызов — invoke. Вы можете считать об invoke как обычный вызов с проверкой типов данных. Например, если вы напишите:
Assembler
1
call ExitProcess
Линкер уведомит вас, что вы забыли положит в стек двойное слово. Я рекомендую вам использовать invoke вместо простого вызова. Синтаксис invoke следующий:
Assembler
1
invoke выражение [, аргументы]
Выражение может быть именем функции или указателем на функцию. Параметры функции разделены запятыми.
Большинство прототипов для API-функций содержатся в include-файлах. Если вы используете hutch'евский MASM32, они будут находится в директории MASM32/INCLUDE. Файлы подключения имеют расширение .inc и прототипы функций DLL находятся в .inc файле с таким же именем, как и у этой DLL.
Hапример, ExitProcess экспортируется из kernel32.lib, так что прототип ExitProcess находится в kernel32.inc.

Вы также можете создать прототипы для ваших собственных функций. Во всех моих экземплярах я использую hutch'евский windows.inc, который вы можете скачать с http://win32asm.cjb.net
Возвращаясь к ExitProcess: параметр uExitCode - это значение, которое программа вернет Windows после окончания программы. Вы можете вызвать функцию ExitProcess так:
Assembler
1
invoke ExitProcess, 0
Поместив эту строку непосредственно после стартовой метки, вы получите Win32-программу, немедленно выходящую в Windows, но тем не менее полнофункциональную.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
.386
.model flat, stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
.data
.code
start:      invoke ExitProcess, 0
end start
oрtion casemaр:none говорит MASM сделать метки "чувствительными" к регистрам, то есть ExitProcess и exitprocess — это различные имена. Отметьте новую директиву — include. После нее следует имя файла, который вы хотите вставить в то место, где эта директива располагается. В примере выше, когда MASM обрабатывает линию include \masm32\include\windows.inc, он открывает windows.inc, находящийся в директории \MASM32\INCLUDE, и далее анализирует содержимое windows.inc так, как будто вы "вклеили" подключаемый файл. Хатчевский windows.inc содержит в себе определения констант и структур, которые вам могут понадобиться для программирования под Win32. Этот файл не содержит в себе прототипов функций. Windows.inc ни в коем случае не является исчерпывающим и всеобъемлющим. Hutch и я пытаемся заполнить его как можно большим количеством констант и структур, но есть еще довольно, что следовало бы включить. Он постоянно обновляется. Заходите на хатчевскую и мою странички за свежими апдейтами. Из windows.inc, ваша программа будет брать определения констант и структур. Что касается прототипов функций, вы должны подключить другие include-файлы. Они находятся в директории \masm32\include.

В вышеприведенном примере, мы вызываем функцию, экспортированную из kernel32.dll, для чего мы должны подключить прототипы функций из kernel32.dll. Этот файл - kernel32.inc. Если вы открываете его текстовым редактором, вы увидите, что он состоит из прототипов функций из соответствующей dll. Если вы не подключите kernel32.inc, вы все еще можете вызвать ExitProcess, но уже с помощью ассемблерной команды call. Вы не сможете вызвать эту функцию с помощью invoke. Дело вот в чем: для того, чтобы вызвать функцию через invoke, вы должны поместить в исходном коде ее прототип. В примере выше, если вы не подключите kernel32.inc, вы можете определить прототип для ExitProcess где-нибудь до вызова этой функции и это будет работать. Файлы подключения нужны для того, что избавить вас от лишней работы и вам не пришлось набирать все прототипы самим.
Теперь мы встречаем новую директиву — includelib. Она работает не так, как include. Это всего лишь способ сказать ассемблеру какие библиотеки использует ваша программа должна прилинковать. Хотя вы вовсе не обязаны использовать именно этот метод. Вы можете указать имена библиотек импорта к командной строке при запуске линкера, но поверьте мне, это весьма скучно и утомительно, да и командная строка может вместить максимум 128 символов.
Теперь возьмите весь исходный текст примера этого Урока, сохраните его как msgbox.asm и ассемблируйте его так:
Код
ml /c /coff /Cp msgbox.asm
/c говорит MASM'у создать .obj файл в формате COFF. MASM использует вариант COFF (Common Object File Format), использующийся под Unix, как его собственный объектный и исполняемый формат файлов.
/Cр говорит MASM'у сохранять регистр имен, заданных пользователем. Если вы используете hutch'евский MASM32 пакет, вы можете вставить "option casemaр:none" в начале вашего исходника, сразу после директивы .model, чтобы добиться того же эффекта.
После успешной компиляции msgbox.asm, вы получите msgbox.obj. Это объектный файл, от которого один шаг до екзешника. Obj содержит инструкции/данные в двоичной форме. Отсутствуют только необходимая корректировка адресов, которая проводится линкером.
Теперь сделайте следующее:
Код
link /SUBSYSTEM:WINDOWS  /LIBPATH:c:\masm32\lib  msgbox.obj
/SUBSYSTEM:WINDOWS информирует линкер о том, какого вида является будущий исполняемый модуль.
/LIBPATH:<путь к библиотекам импорта> говорит линкеру, где находятся библиотеки импорта. Если вы используете MASM32, они будут в MASM32\lib.

Линкер читает объектный файл и корректирует его, используя адреса, взятые из библиотек импорта. После окончания линковки вы получите файл msgbox.exe. Запустите его. Вы увидите, что она ничего не делает.

Да, мы не поместили в код ничего не интересного. Hо тем не менее полноценная Windows программа. И посмотрите на размер! Hа моем PC — 1.536 байт.
Теперь мы готовы создать окно с сообщением. Прототип функции, которая нам для этого необходима следующая:
Assembler
1
MessageBox PROTO hwnd:DWORD, lpText:DWORD, lpCaption:DWORD, uType:DWORD
hwnd — это хэндл родительского окна. Вы можете считать хэндл числом, представляющим окно, к которому вы обращаетесь. Его значение для вас не важно. Вы только должны знать, что оно представляет окно. Когда вы захотите сделать что-нибудь с окном, вы должны обратиться к нему, используя его хэндл.
lрText — это указатель на текст, который вы хотите отобразить в клиентской части окна сообщения. Указатель ― это адрес чего-либо. Указатель на текстовую строку = адрес этой строки.
lpCaption — это указатель на заголовок окна сообщения.
uType — устанавливает иконку, число и вид кнопок окна.

Давайте изменим msgbox.asm для отображения сообщения.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
.data
MsgBoxCaption  db "Iczelion Tutorial #2",0
MsgBoxText     db "Win32 Assembly is Great!",0
.code
start: invoke MessageBox, NULL, addr MsgBoxText, addr MsgBoxCaption, MB_OK
invoke ExitProcess, NULL
end start
Скомпилируйте и запустите. Вы увидите окошко с сообщением "Win32 Assembly is great!".
Давайте снова взглянем на исходник.
Мы определили две оканчивающиеся NULL'ом строки в секции .data. Помните, что каждая ANSI строка в Windows должна оканчиваться NULL'ом (0 в шестнадцатеричной системе). Мы используем две константы, NULL и MB_OK. Эти константы прописаны в windows.inc, так что вы можете обратиться к ним, указав их имя, а не значение. Это улучшает читабельность кода.
Оператор addr используется для передачи адреса метки (и не только) функции. Он действителен только в контексте директивы invoke. Вы не можете использовать его, чтобы присвоить адрес метки регистру или переменной, например. В данном примере вы можете использовать offset вместо addr. Тем не менее, есть некоторые различия между ними.
1. addr не может быть использован с метками, которые определены впереди, а offset может. Например, если метка определена где-то дальше в коде, чем строка с invoke, addr не будет работать.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
invoke MessageBox,NULL, addr MsgBoxText,addr MsgBoxCaption,MB_OK
......
MsgBoxCaption  db "Iczelion Tutorial #2",0
MsgBoxText       db "Win32 Assembly is Great!",0
MASM доложит об ошибке. Если вы используете offset вместо addr, MASM без проблем скомпилирует указанный отрывок кода.
2. Addr поддерживает локальные переменные, в то время как offset нет.
Локальная переменная — это всего лишь зарезервированное место в стеке. Вы только знаете его адрес во время выполнения программы. Offset интерпретируется во время компиляции ассемблером, поэтому неудивительно, что он не поддерживает локальные переменные. Addr же работает с ними, потому что ассемблер сначала проверяет ― глобальная переменная или локальная. Если она глобальная, он помещает адрес этой переменной в объектный файл. В этом случае оператор работает как offset. Если это локальная переменная, компилятор генерирует следующую последовательность инструкций, перед тем как будет вызвана функция:
Assembler
1
2
lea eax, LocalVar
push eax
Учитывая, что lea может определить адрес метки в "рантайме", все работает прекрасно.
____________________________________________
© Iczelion, пер. Aquila.
7
Вложения
Тип файла: zip tut04.zip (2.8 Кб, 105 просмотров)
Mikl___
Автор FAQ
11795 / 6098 / 556
Регистрация: 11.11.2010
Сообщений: 11,091
05.01.2013, 18:21  [ТС] 13
Win32 API. Урок 4a. Отрисовка текста

Кликните здесь для просмотра всего текста
В этом Уроке мы создадим полнофункциональную Windows программу, которое выводит сообщение — "Win32 assembly is great!".
Скачайте пример здесь.
ТЕОРИЯ, МАТЬ СКЛЕРОЗА
Windows предоставляет огромное количество ресурсов Windows-программам через Windows API (Application Programming Interface). Windows API — это большая коллекция очень полезных функций, располагающихся непосредственно в операционной системе и готовых для использования программами. Эти функции находятся в нескольких динамически подгружаемых библиотеках (DLLs), таких как kernel32.dll, user32.dll и gdi32.dll. Kernel32.dll содержит API-функции, взаимодействующие с памятью и управляющие процессами. User32.dll контролирует пользовательский интерфейс. Gdi32.dll ответственен за графические операции. Кроме этих трех "основных", существуют также другие dll, которые вы можете использовать, при условии, что вы обладаете достаточным количеством информации о нужных API-функциях. Windows программы динамически подсоединяется к этим библиотекам, то есть код API-функций не включается в исполняемый файл. Информация находится в библиотеках импорта. Вы должны слинковать ваши программы с правильными библиотеками импорта, иначе они не смогут найти эти функции. Когда Windows программа загружается в память, Windows читает информацию, сохраненную в программе. Эта информация включает имена функций, которые программа использует и DLL-ей, в которых эти функции располагаются. Когда Windows находит подобную информацию в программе, она вызывает библиотеки и исправляет в программе вызовы этих функций, так что контроль всегда будет передаваться по правильному адресу.
Существует две категории API функций: одни работают с ANSI-строками, а другие с Unicode-строками. Имена API-функций использующих ANSI-строки заканчиваются на "A", например, MessageBoxA. В конце имен функций для Unicode находится "W". Windows 95/98 от природы поддерживают ANSI, а Windows NT и производные от нее 2k/XP/Vista поддерживают Unicode. Обычно мы имеем дело с ANSI строками (массивы символов, оканчивающиеся NULL-ом. размер ANSI-символа — 1 байт. В то время как ANSI достаточна для европейских языков, она не поддерживает некоторые восточные языки, в которых есть несколько тысяч уникальных символов. Вот в этих случаях в дело вступает UniCode. размер символа UNICODE — 2 байта, и поэтому может поддерживать 65536 различных символов. Hо по большей части, вы будете использовать include-файл, который может определить и выбрать подходящую для вашей платформы функцию. Просто обращайтесь к именам API-функций без постфикса.
ПРАКТИКА, МАТЬ ШИЗОФРЕНИИ
Я приведу голый скелет программы ниже. Позже мы разберем его.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
.386
.model flat, stdcall
.data
.code
start:
end start
Выполнение начинается с первой инструкции, следующей за меткой, установленной после конца директив. В вышеприведенном каркасе выполнение начинается непосредственно после метки 'start'. Будут последовательно выполняться инструкция за инструкцией, пока не встретится операция передачи управления, такая как: jmр, jne, je, ret и так далее. Эти инструкции перенаправляют поток выполнения другим инструкциям. Когда программа выходит в Windows, ей следует вызвать API-функцию ExitProcess.
Assembler
1
ExitProcess proto uExitCode:DWORD
Строка выше называется прототипом функции. Прототип функции указывает ассемблеру/линкеру атрибуты функции, чтобы он сделал проверку типов данных и количество атрибутов передаваемых функции. Формат прототипа функции следующий:
Assembler
1
ИмяФункции PROTO [ИмяПараметра]:ТипДанных,[ИмяПараметра]:ТипДанных,...
Короче говоря, за именем функции следует ключевое слово PROTO, а затем список переменных с типом данных, разделенных запятыми. В приведенном выше примере с ExitProcess, эта функция была определена как принимающая только один параметр типа DWORD. Прототипы функций очень полезны, когда вы используете высокоуровневый синтаксический вызов — invoke. Вы можете считать об invoke как обычный вызов с проверкой типов данных. Например, если вы напишите:
Assembler
1
call ExitProcess
Линкер уведомит вас, что вы забыли положит в стек двойное слово. Я рекомендую вам использовать invoke вместо простого вызова. Синтаксис invoke следующий:
Assembler
1
invoke выражение [, аргументы]
Выражение может быть именем функции или указателем на функцию. Параметры функции разделены запятыми.
Большинство прототипов для API-функций содержатся в include-файлах. Если вы используете hutch'евский MASM32, они будут находится в директории MASM32/INCLUDE. Файлы подключения имеют расширение .inc и прототипы функций DLL находятся в .inc файле с таким же именем, как и у этой DLL.
Hапример, ExitProcess экспортируется из kernel32.lib, так что прототип ExitProcess находится в kernel32.inc.

Вы также можете создать прототипы для ваших собственных функций. Во всех моих экземплярах я использую hutch'евский windows.inc, который вы можете скачать с http://win32asm.cjb.net
Возвращаясь к ExitProcess: параметр uExitCode - это значение, которое программа вернет Windows после окончания программы. Вы можете вызвать функцию ExitProcess так:
Assembler
1
invoke ExitProcess, 0
Поместив эту строку непосредственно после стартовой метки, вы получите Win32-программу, немедленно выходящую в Windows, но тем не менее полнофункциональную.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
.386
.model flat, stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
.data
.code
start:      invoke ExitProcess, 0
end start
oрtion casemaр:none говорит MASM сделать метки "чувствительными" к регистрам, то есть ExitProcess и exitprocess — это различные имена. Отметьте новую директиву — include. После нее следует имя файла, который вы хотите вставить в то место, где эта директива располагается. В примере выше, когда MASM обрабатывает линию include \masm32\include\windows.inc, он открывает windows.inc, находящийся в директории \MASM32\INCLUDE, и далее анализирует содержимое windows.inc так, как будто вы "вклеили" подключаемый файл. Хатчевский windows.inc содержит в себе определения констант и структур, которые вам могут понадобиться для программирования под Win32. Этот файл не содержит в себе прототипов функций. Windows.inc ни в коем случае не является исчерпывающим и всеобъемлющим. Hutch и я пытаемся заполнить его как можно большим количеством констант и структур, но есть еще довольно, что следовало бы включить. Он постоянно обновляется. Заходите на хатчевскую и мою странички за свежими апдейтами. Из windows.inc, ваша программа будет брать определения констант и структур. Что касается прототипов функций, вы должны подключить другие include-файлы. Они находятся в директории \masm32\include.

В вышеприведенном примере, мы вызываем функцию, экспортированную из kernel32.dll, для чего мы должны подключить прототипы функций из kernel32.dll. Этот файл - kernel32.inc. Если вы открываете его текстовым редактором, вы увидите, что он состоит из прототипов функций из соответствующей dll. Если вы не подключите kernel32.inc, вы все еще можете вызвать ExitProcess, но уже с помощью ассемблерной команды call. Вы не сможете вызвать эту функцию с помощью invoke. Дело вот в чем: для того, чтобы вызвать функцию через invoke, вы должны поместить в исходном коде ее прототип. В примере выше, если вы не подключите kernel32.inc, вы можете определить прототип для ExitProcess где-нибудь до вызова этой функции и это будет работать. Файлы подключения нужны для того, что избавить вас от лишней работы и вам не пришлось набирать все прототипы самим.
Теперь мы встречаем новую директиву — includelib. Она работает не так, как include. Это всего лишь способ сказать ассемблеру какие библиотеки использует ваша программа должна прилинковать. Хотя вы вовсе не обязаны использовать именно этот метод. Вы можете указать имена библиотек импорта к командной строке при запуске линкера, но поверьте мне, это весьма скучно и утомительно, да и командная строка может вместить максимум 128 символов.
Теперь возьмите весь исходный текст примера этого Урока, сохраните его как msgbox.asm и ассемблируйте его так:
Код
ml /c /coff /Cp msgbox.asm
/c говорит MASM'у создать .obj файл в формате COFF. MASM использует вариант COFF (Common Object File Format), использующийся под Unix, как его собственный объектный и исполняемый формат файлов.
/Cр говорит MASM'у сохранять регистр имен, заданных пользователем. Если вы используете hutch'евский MASM32 пакет, вы можете вставить "option casemaр:none" в начале вашего исходника, сразу после директивы .model, чтобы добиться того же эффекта.
После успешной компиляции msgbox.asm, вы получите msgbox.obj. Это объектный файл, от которого один шаг до екзешника. Obj содержит инструкции/данные в двоичной форме. Отсутствуют только необходимая корректировка адресов, которая проводится линкером.
Теперь сделайте следующее:
Код
link /SUBSYSTEM:WINDOWS  /LIBPATH:c:\masm32\lib  msgbox.obj
/SUBSYSTEM:WINDOWS информирует линкер о том, какого вида является будущий исполняемый модуль.
/LIBPATH:<путь к библиотекам импорта> говорит линкеру, где находятся библиотеки импорта. Если вы используете MASM32, они будут в MASM32\lib.

Линкер читает объектный файл и корректирует его, используя адреса, взятые из библиотек импорта. После окончания линковки вы получите файл msgbox.exe. Запустите его. Вы увидите, что она ничего не делает.

Да, мы не поместили в код ничего не интересного. Hо тем не менее полноценная Windows программа. И посмотрите на размер! Hа моем PC — 1.536 байт.
Теперь мы готовы создать окно с сообщением. Прототип функции, которая нам для этого необходима следующая:
Assembler
1
MessageBox PROTO hwnd:DWORD, lpText:DWORD, lpCaption:DWORD, uType:DWORD
hwnd — это хэндл родительского окна. Вы можете считать хэндл числом, представляющим окно, к которому вы обращаетесь. Его значение для вас не важно. Вы только должны знать, что оно представляет окно. Когда вы захотите сделать что-нибудь с окном, вы должны обратиться к нему, используя его хэндл.
lрText — это указатель на текст, который вы хотите отобразить в клиентской части окна сообщения. Указатель ― это адрес чего-либо. Указатель на текстовую строку = адрес этой строки.
lpCaption — это указатель на заголовок окна сообщения.
uType — устанавливает иконку, число и вид кнопок окна.

Давайте изменим msgbox.asm для отображения сообщения.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
.data
MsgBoxCaption  db "Iczelion Tutorial #2",0
MsgBoxText     db "Win32 Assembly is Great!",0
.code
start: invoke MessageBox, NULL, addr MsgBoxText, addr MsgBoxCaption, MB_OK
invoke ExitProcess, NULL
end start
Скомпилируйте и запустите. Вы увидите окошко с сообщением "Win32 Assembly is great!".
Давайте снова взглянем на исходник.
Мы определили две оканчивающиеся NULL'ом строки в секции .data. Помните, что каждая ANSI строка в Windows должна оканчиваться NULL'ом (0 в шестнадцатеричной системе). Мы используем две константы, NULL и MB_OK. Эти константы прописаны в windows.inc, так что вы можете обратиться к ним, указав их имя, а не значение. Это улучшает читабельность кода.
Оператор addr используется для передачи адреса метки (и не только) функции. Он действителен только в контексте директивы invoke. Вы не можете использовать его, чтобы присвоить адрес метки регистру или переменной, например. В данном примере вы можете использовать offset вместо addr. Тем не менее, есть некоторые различия между ними.
1. addr не может быть использован с метками, которые определены впереди, а offset может. Например, если метка определена где-то дальше в коде, чем строка с invoke, addr не будет работать.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
invoke MessageBox,NULL, addr MsgBoxText,addr MsgBoxCaption,MB_OK
......
MsgBoxCaption  db "Iczelion Tutorial #2",0
MsgBoxText       db "Win32 Assembly is Great!",0
MASM доложит об ошибке. Если вы используете offset вместо addr, MASM без проблем скомпилирует указанный отрывок кода.
2. Addr поддерживает локальные переменные, в то время как offset нет.
Локальная переменная — это всего лишь зарезервированное место в стеке. Вы только знаете его адрес во время выполнения программы. Offset интерпретируется во время компиляции ассемблером, поэтому неудивительно, что он не поддерживает локальные переменные. Addr же работает с ними, потому что ассемблер сначала проверяет ― глобальная переменная или локальная. Если она глобальная, он помещает адрес этой переменной в объектный файл. В этом случае оператор работает как offset. Если это локальная переменная, компилятор генерирует следующую последовательность инструкций, перед тем как будет вызвана функция:
Assembler
1
2
lea eax, LocalVar
push eax
Учитывая, что lea может определить адрес метки в "рантайме", все работает прекрасно.
____________________________________________
© Iczelion, пер. Aquila.
8
Вложения
Тип файла: zip tut05.zip (3.1 Кб, 91 просмотров)
Mikl___
Автор FAQ
11795 / 6098 / 556
Регистрация: 11.11.2010
Сообщений: 11,091
05.01.2013, 19:23  [ТС] 14
Win32 API. Урок 5a. Вращающийся текст

Кликните здесь для просмотра всего текста
В этом Уроке мы создадим полнофункциональную Windows программу, которое выводит сообщение — "Win32 assembly is great!".
Скачайте пример здесь.
ТЕОРИЯ, МАТЬ СКЛЕРОЗА
Windows предоставляет огромное количество ресурсов Windows-программам через Windows API (Application Programming Interface). Windows API — это большая коллекция очень полезных функций, располагающихся непосредственно в операционной системе и готовых для использования программами. Эти функции находятся в нескольких динамически подгружаемых библиотеках (DLLs), таких как kernel32.dll, user32.dll и gdi32.dll. Kernel32.dll содержит API-функции, взаимодействующие с памятью и управляющие процессами. User32.dll контролирует пользовательский интерфейс. Gdi32.dll ответственен за графические операции. Кроме этих трех "основных", существуют также другие dll, которые вы можете использовать, при условии, что вы обладаете достаточным количеством информации о нужных API-функциях. Windows программы динамически подсоединяется к этим библиотекам, то есть код API-функций не включается в исполняемый файл. Информация находится в библиотеках импорта. Вы должны слинковать ваши программы с правильными библиотеками импорта, иначе они не смогут найти эти функции. Когда Windows программа загружается в память, Windows читает информацию, сохраненную в программе. Эта информация включает имена функций, которые программа использует и DLL-ей, в которых эти функции располагаются. Когда Windows находит подобную информацию в программе, она вызывает библиотеки и исправляет в программе вызовы этих функций, так что контроль всегда будет передаваться по правильному адресу.
Существует две категории API функций: одни работают с ANSI-строками, а другие с Unicode-строками. Имена API-функций использующих ANSI-строки заканчиваются на "A", например, MessageBoxA. В конце имен функций для Unicode находится "W". Windows 95/98 от природы поддерживают ANSI, а Windows NT и производные от нее 2k/XP/Vista поддерживают Unicode. Обычно мы имеем дело с ANSI строками (массивы символов, оканчивающиеся NULL-ом. размер ANSI-символа — 1 байт. В то время как ANSI достаточна для европейских языков, она не поддерживает некоторые восточные языки, в которых есть несколько тысяч уникальных символов. Вот в этих случаях в дело вступает UniCode. размер символа UNICODE — 2 байта, и поэтому может поддерживать 65536 различных символов. Hо по большей части, вы будете использовать include-файл, который может определить и выбрать подходящую для вашей платформы функцию. Просто обращайтесь к именам API-функций без постфикса.
ПРАКТИКА, МАТЬ ШИЗОФРЕНИИ
Я приведу голый скелет программы ниже. Позже мы разберем его.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
.386
.model flat, stdcall
.data
.code
start:
end start
Выполнение начинается с первой инструкции, следующей за меткой, установленной после конца директив. В вышеприведенном каркасе выполнение начинается непосредственно после метки 'start'. Будут последовательно выполняться инструкция за инструкцией, пока не встретится операция передачи управления, такая как: jmр, jne, je, ret и так далее. Эти инструкции перенаправляют поток выполнения другим инструкциям. Когда программа выходит в Windows, ей следует вызвать API-функцию ExitProcess.
Assembler
1
ExitProcess proto uExitCode:DWORD
Строка выше называется прототипом функции. Прототип функции указывает ассемблеру/линкеру атрибуты функции, чтобы он сделал проверку типов данных и количество атрибутов передаваемых функции. Формат прототипа функции следующий:
Assembler
1
ИмяФункции PROTO [ИмяПараметра]:ТипДанных,[ИмяПараметра]:ТипДанных,...
Короче говоря, за именем функции следует ключевое слово PROTO, а затем список переменных с типом данных, разделенных запятыми. В приведенном выше примере с ExitProcess, эта функция была определена как принимающая только один параметр типа DWORD. Прототипы функций очень полезны, когда вы используете высокоуровневый синтаксический вызов — invoke. Вы можете считать об invoke как обычный вызов с проверкой типов данных. Например, если вы напишите:
Assembler
1
call ExitProcess
Линкер уведомит вас, что вы забыли положит в стек двойное слово. Я рекомендую вам использовать invoke вместо простого вызова. Синтаксис invoke следующий:
Assembler
1
invoke выражение [, аргументы]
Выражение может быть именем функции или указателем на функцию. Параметры функции разделены запятыми.
Большинство прототипов для API-функций содержатся в include-файлах. Если вы используете hutch'евский MASM32, они будут находится в директории MASM32/INCLUDE. Файлы подключения имеют расширение .inc и прототипы функций DLL находятся в .inc файле с таким же именем, как и у этой DLL.
Hапример, ExitProcess экспортируется из kernel32.lib, так что прототип ExitProcess находится в kernel32.inc.

Вы также можете создать прототипы для ваших собственных функций. Во всех моих экземплярах я использую hutch'евский windows.inc, который вы можете скачать с http://win32asm.cjb.net
Возвращаясь к ExitProcess: параметр uExitCode - это значение, которое программа вернет Windows после окончания программы. Вы можете вызвать функцию ExitProcess так:
Assembler
1
invoke ExitProcess, 0
Поместив эту строку непосредственно после стартовой метки, вы получите Win32-программу, немедленно выходящую в Windows, но тем не менее полнофункциональную.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
.386
.model flat, stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
.data
.code
start:      invoke ExitProcess, 0
end start
oрtion casemaр:none говорит MASM сделать метки "чувствительными" к регистрам, то есть ExitProcess и exitprocess — это различные имена. Отметьте новую директиву — include. После нее следует имя файла, который вы хотите вставить в то место, где эта директива располагается. В примере выше, когда MASM обрабатывает линию include \masm32\include\windows.inc, он открывает windows.inc, находящийся в директории \MASM32\INCLUDE, и далее анализирует содержимое windows.inc так, как будто вы "вклеили" подключаемый файл. Хатчевский windows.inc содержит в себе определения констант и структур, которые вам могут понадобиться для программирования под Win32. Этот файл не содержит в себе прототипов функций. Windows.inc ни в коем случае не является исчерпывающим и всеобъемлющим. Hutch и я пытаемся заполнить его как можно большим количеством констант и структур, но есть еще довольно, что следовало бы включить. Он постоянно обновляется. Заходите на хатчевскую и мою странички за свежими апдейтами. Из windows.inc, ваша программа будет брать определения констант и структур. Что касается прототипов функций, вы должны подключить другие include-файлы. Они находятся в директории \masm32\include.

В вышеприведенном примере, мы вызываем функцию, экспортированную из kernel32.dll, для чего мы должны подключить прототипы функций из kernel32.dll. Этот файл - kernel32.inc. Если вы открываете его текстовым редактором, вы увидите, что он состоит из прототипов функций из соответствующей dll. Если вы не подключите kernel32.inc, вы все еще можете вызвать ExitProcess, но уже с помощью ассемблерной команды call. Вы не сможете вызвать эту функцию с помощью invoke. Дело вот в чем: для того, чтобы вызвать функцию через invoke, вы должны поместить в исходном коде ее прототип. В примере выше, если вы не подключите kernel32.inc, вы можете определить прототип для ExitProcess где-нибудь до вызова этой функции и это будет работать. Файлы подключения нужны для того, что избавить вас от лишней работы и вам не пришлось набирать все прототипы самим.
Теперь мы встречаем новую директиву — includelib. Она работает не так, как include. Это всего лишь способ сказать ассемблеру какие библиотеки использует ваша программа должна прилинковать. Хотя вы вовсе не обязаны использовать именно этот метод. Вы можете указать имена библиотек импорта к командной строке при запуске линкера, но поверьте мне, это весьма скучно и утомительно, да и командная строка может вместить максимум 128 символов.
Теперь возьмите весь исходный текст примера этого Урока, сохраните его как msgbox.asm и ассемблируйте его так:
Код
ml /c /coff /Cp msgbox.asm
/c говорит MASM'у создать .obj файл в формате COFF. MASM использует вариант COFF (Common Object File Format), использующийся под Unix, как его собственный объектный и исполняемый формат файлов.
/Cр говорит MASM'у сохранять регистр имен, заданных пользователем. Если вы используете hutch'евский MASM32 пакет, вы можете вставить "option casemaр:none" в начале вашего исходника, сразу после директивы .model, чтобы добиться того же эффекта.
После успешной компиляции msgbox.asm, вы получите msgbox.obj. Это объектный файл, от которого один шаг до екзешника. Obj содержит инструкции/данные в двоичной форме. Отсутствуют только необходимая корректировка адресов, которая проводится линкером.
Теперь сделайте следующее:
Код
link /SUBSYSTEM:WINDOWS  /LIBPATH:c:\masm32\lib  msgbox.obj
/SUBSYSTEM:WINDOWS информирует линкер о том, какого вида является будущий исполняемый модуль.
/LIBPATH:<путь к библиотекам импорта> говорит линкеру, где находятся библиотеки импорта. Если вы используете MASM32, они будут в MASM32\lib.

Линкер читает объектный файл и корректирует его, используя адреса, взятые из библиотек импорта. После окончания линковки вы получите файл msgbox.exe. Запустите его. Вы увидите, что она ничего не делает.

Да, мы не поместили в код ничего не интересного. Hо тем не менее полноценная Windows программа. И посмотрите на размер! Hа моем PC — 1.536 байт.
Теперь мы готовы создать окно с сообщением. Прототип функции, которая нам для этого необходима следующая:
Assembler
1
MessageBox PROTO hwnd:DWORD, lpText:DWORD, lpCaption:DWORD, uType:DWORD
hwnd — это хэндл родительского окна. Вы можете считать хэндл числом, представляющим окно, к которому вы обращаетесь. Его значение для вас не важно. Вы только должны знать, что оно представляет окно. Когда вы захотите сделать что-нибудь с окном, вы должны обратиться к нему, используя его хэндл.
lрText — это указатель на текст, который вы хотите отобразить в клиентской части окна сообщения. Указатель ― это адрес чего-либо. Указатель на текстовую строку = адрес этой строки.
lpCaption — это указатель на заголовок окна сообщения.
uType — устанавливает иконку, число и вид кнопок окна.

Давайте изменим msgbox.asm для отображения сообщения.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
.data
MsgBoxCaption  db "Iczelion Tutorial #2",0
MsgBoxText     db "Win32 Assembly is Great!",0
.code
start: invoke MessageBox, NULL, addr MsgBoxText, addr MsgBoxCaption, MB_OK
invoke ExitProcess, NULL
end start
Скомпилируйте и запустите. Вы увидите окошко с сообщением "Win32 Assembly is great!".
Давайте снова взглянем на исходник.
Мы определили две оканчивающиеся NULL'ом строки в секции .data. Помните, что каждая ANSI строка в Windows должна оканчиваться NULL'ом (0 в шестнадцатеричной системе). Мы используем две константы, NULL и MB_OK. Эти константы прописаны в windows.inc, так что вы можете обратиться к ним, указав их имя, а не значение. Это улучшает читабельность кода.
Оператор addr используется для передачи адреса метки (и не только) функции. Он действителен только в контексте директивы invoke. Вы не можете использовать его, чтобы присвоить адрес метки регистру или переменной, например. В данном примере вы можете использовать offset вместо addr. Тем не менее, есть некоторые различия между ними.
1. addr не может быть использован с метками, которые определены впереди, а offset может. Например, если метка определена где-то дальше в коде, чем строка с invoke, addr не будет работать.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
invoke MessageBox,NULL, addr MsgBoxText,addr MsgBoxCaption,MB_OK
......
MsgBoxCaption  db "Iczelion Tutorial #2",0
MsgBoxText       db "Win32 Assembly is Great!",0
MASM доложит об ошибке. Если вы используете offset вместо addr, MASM без проблем скомпилирует указанный отрывок кода.
2. Addr поддерживает локальные переменные, в то время как offset нет.
Локальная переменная — это всего лишь зарезервированное место в стеке. Вы только знаете его адрес во время выполнения программы. Offset интерпретируется во время компиляции ассемблером, поэтому неудивительно, что он не поддерживает локальные переменные. Addr же работает с ними, потому что ассемблер сначала проверяет ― глобальная переменная или локальная. Если она глобальная, он помещает адрес этой переменной в объектный файл. В этом случае оператор работает как offset. Если это локальная переменная, компилятор генерирует следующую последовательность инструкций, перед тем как будет вызвана функция:
Assembler
1
2
lea eax, LocalVar
push eax
Учитывая, что lea может определить адрес метки в "рантайме", все работает прекрасно.
____________________________________________
© Iczelion, пер. Aquila.
10
Миниатюры
Сам себе Iczelion  
Вложения
Тип файла: zip tut05a.zip (4.4 Кб, 112 просмотров)
Mikl___
Автор FAQ
11795 / 6098 / 556
Регистрация: 11.11.2010
Сообщений: 11,091
06.01.2013, 04:42  [ТС] 15
Win32 API. Урок 6. Клавиатура
Кликните здесь для просмотра всего текста
В этом Уроке мы создадим полнофункциональную Windows программу, которое выводит сообщение — "Win32 assembly is great!".
Скачайте пример здесь.
ТЕОРИЯ, МАТЬ СКЛЕРОЗА
Windows предоставляет огромное количество ресурсов Windows-программам через Windows API (Application Programming Interface). Windows API — это большая коллекция очень полезных функций, располагающихся непосредственно в операционной системе и готовых для использования программами. Эти функции находятся в нескольких динамически подгружаемых библиотеках (DLLs), таких как kernel32.dll, user32.dll и gdi32.dll. Kernel32.dll содержит API-функции, взаимодействующие с памятью и управляющие процессами. User32.dll контролирует пользовательский интерфейс. Gdi32.dll ответственен за графические операции. Кроме этих трех "основных", существуют также другие dll, которые вы можете использовать, при условии, что вы обладаете достаточным количеством информации о нужных API-функциях. Windows программы динамически подсоединяется к этим библиотекам, то есть код API-функций не включается в исполняемый файл. Информация находится в библиотеках импорта. Вы должны слинковать ваши программы с правильными библиотеками импорта, иначе они не смогут найти эти функции. Когда Windows программа загружается в память, Windows читает информацию, сохраненную в программе. Эта информация включает имена функций, которые программа использует и DLL-ей, в которых эти функции располагаются. Когда Windows находит подобную информацию в программе, она вызывает библиотеки и исправляет в программе вызовы этих функций, так что контроль всегда будет передаваться по правильному адресу.
Существует две категории API функций: одни работают с ANSI-строками, а другие с Unicode-строками. Имена API-функций использующих ANSI-строки заканчиваются на "A", например, MessageBoxA. В конце имен функций для Unicode находится "W". Windows 95/98 от природы поддерживают ANSI, а Windows NT и производные от нее 2k/XP/Vista поддерживают Unicode. Обычно мы имеем дело с ANSI строками (массивы символов, оканчивающиеся NULL-ом. размер ANSI-символа — 1 байт. В то время как ANSI достаточна для европейских языков, она не поддерживает некоторые восточные языки, в которых есть несколько тысяч уникальных символов. Вот в этих случаях в дело вступает UniCode. размер символа UNICODE — 2 байта, и поэтому может поддерживать 65536 различных символов. Hо по большей части, вы будете использовать include-файл, который может определить и выбрать подходящую для вашей платформы функцию. Просто обращайтесь к именам API-функций без постфикса.
ПРАКТИКА, МАТЬ ШИЗОФРЕНИИ
Я приведу голый скелет программы ниже. Позже мы разберем его.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
.386
.model flat, stdcall
.data
.code
start:
end start
Выполнение начинается с первой инструкции, следующей за меткой, установленной после конца директив. В вышеприведенном каркасе выполнение начинается непосредственно после метки 'start'. Будут последовательно выполняться инструкция за инструкцией, пока не встретится операция передачи управления, такая как: jmр, jne, je, ret и так далее. Эти инструкции перенаправляют поток выполнения другим инструкциям. Когда программа выходит в Windows, ей следует вызвать API-функцию ExitProcess.
Assembler
1
ExitProcess proto uExitCode:DWORD
Строка выше называется прототипом функции. Прототип функции указывает ассемблеру/линкеру атрибуты функции, чтобы он сделал проверку типов данных и количество атрибутов передаваемых функции. Формат прототипа функции следующий:
Assembler
1
ИмяФункции PROTO [ИмяПараметра]:ТипДанных,[ИмяПараметра]:ТипДанных,...
Короче говоря, за именем функции следует ключевое слово PROTO, а затем список переменных с типом данных, разделенных запятыми. В приведенном выше примере с ExitProcess, эта функция была определена как принимающая только один параметр типа DWORD. Прототипы функций очень полезны, когда вы используете высокоуровневый синтаксический вызов — invoke. Вы можете считать об invoke как обычный вызов с проверкой типов данных. Например, если вы напишите:
Assembler
1
call ExitProcess
Линкер уведомит вас, что вы забыли положит в стек двойное слово. Я рекомендую вам использовать invoke вместо простого вызова. Синтаксис invoke следующий:
Assembler
1
invoke выражение [, аргументы]
Выражение может быть именем функции или указателем на функцию. Параметры функции разделены запятыми.
Большинство прототипов для API-функций содержатся в include-файлах. Если вы используете hutch'евский MASM32, они будут находится в директории MASM32/INCLUDE. Файлы подключения имеют расширение .inc и прототипы функций DLL находятся в .inc файле с таким же именем, как и у этой DLL.
Hапример, ExitProcess экспортируется из kernel32.lib, так что прототип ExitProcess находится в kernel32.inc.

Вы также можете создать прототипы для ваших собственных функций. Во всех моих экземплярах я использую hutch'евский windows.inc, который вы можете скачать с http://win32asm.cjb.net
Возвращаясь к ExitProcess: параметр uExitCode - это значение, которое программа вернет Windows после окончания программы. Вы можете вызвать функцию ExitProcess так:
Assembler
1
invoke ExitProcess, 0
Поместив эту строку непосредственно после стартовой метки, вы получите Win32-программу, немедленно выходящую в Windows, но тем не менее полнофункциональную.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
.386
.model flat, stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
.data
.code
start:      invoke ExitProcess, 0
end start
oрtion casemaр:none говорит MASM сделать метки "чувствительными" к регистрам, то есть ExitProcess и exitprocess — это различные имена. Отметьте новую директиву — include. После нее следует имя файла, который вы хотите вставить в то место, где эта директива располагается. В примере выше, когда MASM обрабатывает линию include \masm32\include\windows.inc, он открывает windows.inc, находящийся в директории \MASM32\INCLUDE, и далее анализирует содержимое windows.inc так, как будто вы "вклеили" подключаемый файл. Хатчевский windows.inc содержит в себе определения констант и структур, которые вам могут понадобиться для программирования под Win32. Этот файл не содержит в себе прототипов функций. Windows.inc ни в коем случае не является исчерпывающим и всеобъемлющим. Hutch и я пытаемся заполнить его как можно большим количеством констант и структур, но есть еще довольно, что следовало бы включить. Он постоянно обновляется. Заходите на хатчевскую и мою странички за свежими апдейтами. Из windows.inc, ваша программа будет брать определения констант и структур. Что касается прототипов функций, вы должны подключить другие include-файлы. Они находятся в директории \masm32\include.

В вышеприведенном примере, мы вызываем функцию, экспортированную из kernel32.dll, для чего мы должны подключить прототипы функций из kernel32.dll. Этот файл - kernel32.inc. Если вы открываете его текстовым редактором, вы увидите, что он состоит из прототипов функций из соответствующей dll. Если вы не подключите kernel32.inc, вы все еще можете вызвать ExitProcess, но уже с помощью ассемблерной команды call. Вы не сможете вызвать эту функцию с помощью invoke. Дело вот в чем: для того, чтобы вызвать функцию через invoke, вы должны поместить в исходном коде ее прототип. В примере выше, если вы не подключите kernel32.inc, вы можете определить прототип для ExitProcess где-нибудь до вызова этой функции и это будет работать. Файлы подключения нужны для того, что избавить вас от лишней работы и вам не пришлось набирать все прототипы самим.
Теперь мы встречаем новую директиву — includelib. Она работает не так, как include. Это всего лишь способ сказать ассемблеру какие библиотеки использует ваша программа должна прилинковать. Хотя вы вовсе не обязаны использовать именно этот метод. Вы можете указать имена библиотек импорта к командной строке при запуске линкера, но поверьте мне, это весьма скучно и утомительно, да и командная строка может вместить максимум 128 символов.
Теперь возьмите весь исходный текст примера этого Урока, сохраните его как msgbox.asm и ассемблируйте его так:
Код
ml /c /coff /Cp msgbox.asm
/c говорит MASM'у создать .obj файл в формате COFF. MASM использует вариант COFF (Common Object File Format), использующийся под Unix, как его собственный объектный и исполняемый формат файлов.
/Cр говорит MASM'у сохранять регистр имен, заданных пользователем. Если вы используете hutch'евский MASM32 пакет, вы можете вставить "option casemaр:none" в начале вашего исходника, сразу после директивы .model, чтобы добиться того же эффекта.
После успешной компиляции msgbox.asm, вы получите msgbox.obj. Это объектный файл, от которого один шаг до екзешника. Obj содержит инструкции/данные в двоичной форме. Отсутствуют только необходимая корректировка адресов, которая проводится линкером.
Теперь сделайте следующее:
Код
link /SUBSYSTEM:WINDOWS  /LIBPATH:c:\masm32\lib  msgbox.obj
/SUBSYSTEM:WINDOWS информирует линкер о том, какого вида является будущий исполняемый модуль.
/LIBPATH:<путь к библиотекам импорта> говорит линкеру, где находятся библиотеки импорта. Если вы используете MASM32, они будут в MASM32\lib.

Линкер читает объектный файл и корректирует его, используя адреса, взятые из библиотек импорта. После окончания линковки вы получите файл msgbox.exe. Запустите его. Вы увидите, что она ничего не делает.

Да, мы не поместили в код ничего не интересного. Hо тем не менее полноценная Windows программа. И посмотрите на размер! Hа моем PC — 1.536 байт.
Теперь мы готовы создать окно с сообщением. Прототип функции, которая нам для этого необходима следующая:
Assembler
1
MessageBox PROTO hwnd:DWORD, lpText:DWORD, lpCaption:DWORD, uType:DWORD
hwnd — это хэндл родительского окна. Вы можете считать хэндл числом, представляющим окно, к которому вы обращаетесь. Его значение для вас не важно. Вы только должны знать, что оно представляет окно. Когда вы захотите сделать что-нибудь с окном, вы должны обратиться к нему, используя его хэндл.
lрText — это указатель на текст, который вы хотите отобразить в клиентской части окна сообщения. Указатель ― это адрес чего-либо. Указатель на текстовую строку = адрес этой строки.
lpCaption — это указатель на заголовок окна сообщения.
uType — устанавливает иконку, число и вид кнопок окна.

Давайте изменим msgbox.asm для отображения сообщения.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
.data
MsgBoxCaption  db "Iczelion Tutorial #2",0
MsgBoxText     db "Win32 Assembly is Great!",0
.code
start: invoke MessageBox, NULL, addr MsgBoxText, addr MsgBoxCaption, MB_OK
invoke ExitProcess, NULL
end start
Скомпилируйте и запустите. Вы увидите окошко с сообщением "Win32 Assembly is great!".
Давайте снова взглянем на исходник.
Мы определили две оканчивающиеся NULL'ом строки в секции .data. Помните, что каждая ANSI строка в Windows должна оканчиваться NULL'ом (0 в шестнадцатеричной системе). Мы используем две константы, NULL и MB_OK. Эти константы прописаны в windows.inc, так что вы можете обратиться к ним, указав их имя, а не значение. Это улучшает читабельность кода.
Оператор addr используется для передачи адреса метки (и не только) функции. Он действителен только в контексте директивы invoke. Вы не можете использовать его, чтобы присвоить адрес метки регистру или переменной, например. В данном примере вы можете использовать offset вместо addr. Тем не менее, есть некоторые различия между ними.
1. addr не может быть использован с метками, которые определены впереди, а offset может. Например, если метка определена где-то дальше в коде, чем строка с invoke, addr не будет работать.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
invoke MessageBox,NULL, addr MsgBoxText,addr MsgBoxCaption,MB_OK
......
MsgBoxCaption  db "Iczelion Tutorial #2",0
MsgBoxText       db "Win32 Assembly is Great!",0
MASM доложит об ошибке. Если вы используете offset вместо addr, MASM без проблем скомпилирует указанный отрывок кода.
2. Addr поддерживает локальные переменные, в то время как offset нет.
Локальная переменная — это всего лишь зарезервированное место в стеке. Вы только знаете его адрес во время выполнения программы. Offset интерпретируется во время компиляции ассемблером, поэтому неудивительно, что он не поддерживает локальные переменные. Addr же работает с ними, потому что ассемблер сначала проверяет ― глобальная переменная или локальная. Если она глобальная, он помещает адрес этой переменной в объектный файл. В этом случае оператор работает как offset. Если это локальная переменная, компилятор генерирует следующую последовательность инструкций, перед тем как будет вызвана функция:
Assembler
1
2
lea eax, LocalVar
push eax
Учитывая, что lea может определить адрес метки в "рантайме", все работает прекрасно.
____________________________________________
© Iczelion, пер. Aquila.
7
Вложения
Тип файла: zip tut06.zip (2.7 Кб, 101 просмотров)
Mikl___
Автор FAQ
11795 / 6098 / 556
Регистрация: 11.11.2010
Сообщений: 11,091
06.01.2013, 05:05  [ТС] 16
Win32 API. Урок 6a. Клавиатура
Кликните здесь для просмотра всего текста
В этом Уроке мы создадим полнофункциональную Windows программу, которое выводит сообщение — "Win32 assembly is great!".
Скачайте пример здесь.
ТЕОРИЯ, МАТЬ СКЛЕРОЗА
Windows предоставляет огромное количество ресурсов Windows-программам через Windows API (Application Programming Interface). Windows API — это большая коллекция очень полезных функций, располагающихся непосредственно в операционной системе и готовых для использования программами. Эти функции находятся в нескольких динамически подгружаемых библиотеках (DLLs), таких как kernel32.dll, user32.dll и gdi32.dll. Kernel32.dll содержит API-функции, взаимодействующие с памятью и управляющие процессами. User32.dll контролирует пользовательский интерфейс. Gdi32.dll ответственен за графические операции. Кроме этих трех "основных", существуют также другие dll, которые вы можете использовать, при условии, что вы обладаете достаточным количеством информации о нужных API-функциях. Windows программы динамически подсоединяется к этим библиотекам, то есть код API-функций не включается в исполняемый файл. Информация находится в библиотеках импорта. Вы должны слинковать ваши программы с правильными библиотеками импорта, иначе они не смогут найти эти функции. Когда Windows программа загружается в память, Windows читает информацию, сохраненную в программе. Эта информация включает имена функций, которые программа использует и DLL-ей, в которых эти функции располагаются. Когда Windows находит подобную информацию в программе, она вызывает библиотеки и исправляет в программе вызовы этих функций, так что контроль всегда будет передаваться по правильному адресу.
Существует две категории API функций: одни работают с ANSI-строками, а другие с Unicode-строками. Имена API-функций использующих ANSI-строки заканчиваются на "A", например, MessageBoxA. В конце имен функций для Unicode находится "W". Windows 95/98 от природы поддерживают ANSI, а Windows NT и производные от нее 2k/XP/Vista поддерживают Unicode. Обычно мы имеем дело с ANSI строками (массивы символов, оканчивающиеся NULL-ом. размер ANSI-символа — 1 байт. В то время как ANSI достаточна для европейских языков, она не поддерживает некоторые восточные языки, в которых есть несколько тысяч уникальных символов. Вот в этих случаях в дело вступает UniCode. размер символа UNICODE — 2 байта, и поэтому может поддерживать 65536 различных символов. Hо по большей части, вы будете использовать include-файл, который может определить и выбрать подходящую для вашей платформы функцию. Просто обращайтесь к именам API-функций без постфикса.
ПРАКТИКА, МАТЬ ШИЗОФРЕНИИ
Я приведу голый скелет программы ниже. Позже мы разберем его.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
.386
.model flat, stdcall
.data
.code
start:
end start
Выполнение начинается с первой инструкции, следующей за меткой, установленной после конца директив. В вышеприведенном каркасе выполнение начинается непосредственно после метки 'start'. Будут последовательно выполняться инструкция за инструкцией, пока не встретится операция передачи управления, такая как: jmр, jne, je, ret и так далее. Эти инструкции перенаправляют поток выполнения другим инструкциям. Когда программа выходит в Windows, ей следует вызвать API-функцию ExitProcess.
Assembler
1
ExitProcess proto uExitCode:DWORD
Строка выше называется прототипом функции. Прототип функции указывает ассемблеру/линкеру атрибуты функции, чтобы он сделал проверку типов данных и количество атрибутов передаваемых функции. Формат прототипа функции следующий:
Assembler
1
ИмяФункции PROTO [ИмяПараметра]:ТипДанных,[ИмяПараметра]:ТипДанных,...
Короче говоря, за именем функции следует ключевое слово PROTO, а затем список переменных с типом данных, разделенных запятыми. В приведенном выше примере с ExitProcess, эта функция была определена как принимающая только один параметр типа DWORD. Прототипы функций очень полезны, когда вы используете высокоуровневый синтаксический вызов — invoke. Вы можете считать об invoke как обычный вызов с проверкой типов данных. Например, если вы напишите:
Assembler
1
call ExitProcess
Линкер уведомит вас, что вы забыли положит в стек двойное слово. Я рекомендую вам использовать invoke вместо простого вызова. Синтаксис invoke следующий:
Assembler
1
invoke выражение [, аргументы]
Выражение может быть именем функции или указателем на функцию. Параметры функции разделены запятыми.
Большинство прототипов для API-функций содержатся в include-файлах. Если вы используете hutch'евский MASM32, они будут находится в директории MASM32/INCLUDE. Файлы подключения имеют расширение .inc и прототипы функций DLL находятся в .inc файле с таким же именем, как и у этой DLL.
Hапример, ExitProcess экспортируется из kernel32.lib, так что прототип ExitProcess находится в kernel32.inc.

Вы также можете создать прототипы для ваших собственных функций. Во всех моих экземплярах я использую hutch'евский windows.inc, который вы можете скачать с http://win32asm.cjb.net
Возвращаясь к ExitProcess: параметр uExitCode - это значение, которое программа вернет Windows после окончания программы. Вы можете вызвать функцию ExitProcess так:
Assembler
1
invoke ExitProcess, 0
Поместив эту строку непосредственно после стартовой метки, вы получите Win32-программу, немедленно выходящую в Windows, но тем не менее полнофункциональную.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
.386
.model flat, stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
.data
.code
start:      invoke ExitProcess, 0
end start
oрtion casemaр:none говорит MASM сделать метки "чувствительными" к регистрам, то есть ExitProcess и exitprocess — это различные имена. Отметьте новую директиву — include. После нее следует имя файла, который вы хотите вставить в то место, где эта директива располагается. В примере выше, когда MASM обрабатывает линию include \masm32\include\windows.inc, он открывает windows.inc, находящийся в директории \MASM32\INCLUDE, и далее анализирует содержимое windows.inc так, как будто вы "вклеили" подключаемый файл. Хатчевский windows.inc содержит в себе определения констант и структур, которые вам могут понадобиться для программирования под Win32. Этот файл не содержит в себе прототипов функций. Windows.inc ни в коем случае не является исчерпывающим и всеобъемлющим. Hutch и я пытаемся заполнить его как можно большим количеством констант и структур, но есть еще довольно, что следовало бы включить. Он постоянно обновляется. Заходите на хатчевскую и мою странички за свежими апдейтами. Из windows.inc, ваша программа будет брать определения констант и структур. Что касается прототипов функций, вы должны подключить другие include-файлы. Они находятся в директории \masm32\include.

В вышеприведенном примере, мы вызываем функцию, экспортированную из kernel32.dll, для чего мы должны подключить прототипы функций из kernel32.dll. Этот файл - kernel32.inc. Если вы открываете его текстовым редактором, вы увидите, что он состоит из прототипов функций из соответствующей dll. Если вы не подключите kernel32.inc, вы все еще можете вызвать ExitProcess, но уже с помощью ассемблерной команды call. Вы не сможете вызвать эту функцию с помощью invoke. Дело вот в чем: для того, чтобы вызвать функцию через invoke, вы должны поместить в исходном коде ее прототип. В примере выше, если вы не подключите kernel32.inc, вы можете определить прототип для ExitProcess где-нибудь до вызова этой функции и это будет работать. Файлы подключения нужны для того, что избавить вас от лишней работы и вам не пришлось набирать все прототипы самим.
Теперь мы встречаем новую директиву — includelib. Она работает не так, как include. Это всего лишь способ сказать ассемблеру какие библиотеки использует ваша программа должна прилинковать. Хотя вы вовсе не обязаны использовать именно этот метод. Вы можете указать имена библиотек импорта к командной строке при запуске линкера, но поверьте мне, это весьма скучно и утомительно, да и командная строка может вместить максимум 128 символов.
Теперь возьмите весь исходный текст примера этого Урока, сохраните его как msgbox.asm и ассемблируйте его так:
Код
ml /c /coff /Cp msgbox.asm
/c говорит MASM'у создать .obj файл в формате COFF. MASM использует вариант COFF (Common Object File Format), использующийся под Unix, как его собственный объектный и исполняемый формат файлов.
/Cр говорит MASM'у сохранять регистр имен, заданных пользователем. Если вы используете hutch'евский MASM32 пакет, вы можете вставить "option casemaр:none" в начале вашего исходника, сразу после директивы .model, чтобы добиться того же эффекта.
После успешной компиляции msgbox.asm, вы получите msgbox.obj. Это объектный файл, от которого один шаг до екзешника. Obj содержит инструкции/данные в двоичной форме. Отсутствуют только необходимая корректировка адресов, которая проводится линкером.
Теперь сделайте следующее:
Код
link /SUBSYSTEM:WINDOWS  /LIBPATH:c:\masm32\lib  msgbox.obj
/SUBSYSTEM:WINDOWS информирует линкер о том, какого вида является будущий исполняемый модуль.
/LIBPATH:<путь к библиотекам импорта> говорит линкеру, где находятся библиотеки импорта. Если вы используете MASM32, они будут в MASM32\lib.

Линкер читает объектный файл и корректирует его, используя адреса, взятые из библиотек импорта. После окончания линковки вы получите файл msgbox.exe. Запустите его. Вы увидите, что она ничего не делает.

Да, мы не поместили в код ничего не интересного. Hо тем не менее полноценная Windows программа. И посмотрите на размер! Hа моем PC — 1.536 байт.
Теперь мы готовы создать окно с сообщением. Прототип функции, которая нам для этого необходима следующая:
Assembler
1
MessageBox PROTO hwnd:DWORD, lpText:DWORD, lpCaption:DWORD, uType:DWORD
hwnd — это хэндл родительского окна. Вы можете считать хэндл числом, представляющим окно, к которому вы обращаетесь. Его значение для вас не важно. Вы только должны знать, что оно представляет окно. Когда вы захотите сделать что-нибудь с окном, вы должны обратиться к нему, используя его хэндл.
lрText — это указатель на текст, который вы хотите отобразить в клиентской части окна сообщения. Указатель ― это адрес чего-либо. Указатель на текстовую строку = адрес этой строки.
lpCaption — это указатель на заголовок окна сообщения.
uType — устанавливает иконку, число и вид кнопок окна.

Давайте изменим msgbox.asm для отображения сообщения.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
.data
MsgBoxCaption  db "Iczelion Tutorial #2",0
MsgBoxText     db "Win32 Assembly is Great!",0
.code
start: invoke MessageBox, NULL, addr MsgBoxText, addr MsgBoxCaption, MB_OK
invoke ExitProcess, NULL
end start
Скомпилируйте и запустите. Вы увидите окошко с сообщением "Win32 Assembly is great!".
Давайте снова взглянем на исходник.
Мы определили две оканчивающиеся NULL'ом строки в секции .data. Помните, что каждая ANSI строка в Windows должна оканчиваться NULL'ом (0 в шестнадцатеричной системе). Мы используем две константы, NULL и MB_OK. Эти константы прописаны в windows.inc, так что вы можете обратиться к ним, указав их имя, а не значение. Это улучшает читабельность кода.
Оператор addr используется для передачи адреса метки (и не только) функции. Он действителен только в контексте директивы invoke. Вы не можете использовать его, чтобы присвоить адрес метки регистру или переменной, например. В данном примере вы можете использовать offset вместо addr. Тем не менее, есть некоторые различия между ними.
1. addr не может быть использован с метками, которые определены впереди, а offset может. Например, если метка определена где-то дальше в коде, чем строка с invoke, addr не будет работать.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
invoke MessageBox,NULL, addr MsgBoxText,addr MsgBoxCaption,MB_OK
......
MsgBoxCaption  db "Iczelion Tutorial #2",0
MsgBoxText       db "Win32 Assembly is Great!",0
MASM доложит об ошибке. Если вы используете offset вместо addr, MASM без проблем скомпилирует указанный отрывок кода.
2. Addr поддерживает локальные переменные, в то время как offset нет.
Локальная переменная — это всего лишь зарезервированное место в стеке. Вы только знаете его адрес во время выполнения программы. Offset интерпретируется во время компиляции ассемблером, поэтому неудивительно, что он не поддерживает локальные переменные. Addr же работает с ними, потому что ассемблер сначала проверяет ― глобальная переменная или локальная. Если она глобальная, он помещает адрес этой переменной в объектный файл. В этом случае оператор работает как offset. Если это локальная переменная, компилятор генерирует следующую последовательность инструкций, перед тем как будет вызвана функция:
Assembler
1
2
lea eax, LocalVar
push eax
Учитывая, что lea может определить адрес метки в "рантайме", все работает прекрасно.
____________________________________________
© Iczelion, пер. Aquila.
7
Вложения
Тип файла: zip tut06a.zip (3.2 Кб, 78 просмотров)
Mikl___
Автор FAQ
11795 / 6098 / 556
Регистрация: 11.11.2010
Сообщений: 11,091
06.01.2013, 05:21  [ТС] 17
Win32 API. Урок 6b. Светомузыка на клавиатуре
Кликните здесь для просмотра всего текста
В этом Уроке мы создадим полнофункциональную Windows программу, которое выводит сообщение — "Win32 assembly is great!".
Скачайте пример здесь.
ТЕОРИЯ, МАТЬ СКЛЕРОЗА
Windows предоставляет огромное количество ресурсов Windows-программам через Windows API (Application Programming Interface). Windows API — это большая коллекция очень полезных функций, располагающихся непосредственно в операционной системе и готовых для использования программами. Эти функции находятся в нескольких динамически подгружаемых библиотеках (DLLs), таких как kernel32.dll, user32.dll и gdi32.dll. Kernel32.dll содержит API-функции, взаимодействующие с памятью и управляющие процессами. User32.dll контролирует пользовательский интерфейс. Gdi32.dll ответственен за графические операции. Кроме этих трех "основных", существуют также другие dll, которые вы можете использовать, при условии, что вы обладаете достаточным количеством информации о нужных API-функциях. Windows программы динамически подсоединяется к этим библиотекам, то есть код API-функций не включается в исполняемый файл. Информация находится в библиотеках импорта. Вы должны слинковать ваши программы с правильными библиотеками импорта, иначе они не смогут найти эти функции. Когда Windows программа загружается в память, Windows читает информацию, сохраненную в программе. Эта информация включает имена функций, которые программа использует и DLL-ей, в которых эти функции располагаются. Когда Windows находит подобную информацию в программе, она вызывает библиотеки и исправляет в программе вызовы этих функций, так что контроль всегда будет передаваться по правильному адресу.
Существует две категории API функций: одни работают с ANSI-строками, а другие с Unicode-строками. Имена API-функций использующих ANSI-строки заканчиваются на "A", например, MessageBoxA. В конце имен функций для Unicode находится "W". Windows 95/98 от природы поддерживают ANSI, а Windows NT и производные от нее 2k/XP/Vista поддерживают Unicode. Обычно мы имеем дело с ANSI строками (массивы символов, оканчивающиеся NULL-ом. размер ANSI-символа — 1 байт. В то время как ANSI достаточна для европейских языков, она не поддерживает некоторые восточные языки, в которых есть несколько тысяч уникальных символов. Вот в этих случаях в дело вступает UniCode. размер символа UNICODE — 2 байта, и поэтому может поддерживать 65536 различных символов. Hо по большей части, вы будете использовать include-файл, который может определить и выбрать подходящую для вашей платформы функцию. Просто обращайтесь к именам API-функций без постфикса.
ПРАКТИКА, МАТЬ ШИЗОФРЕНИИ
Я приведу голый скелет программы ниже. Позже мы разберем его.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
.386
.model flat, stdcall
.data
.code
start:
end start
Выполнение начинается с первой инструкции, следующей за меткой, установленной после конца директив. В вышеприведенном каркасе выполнение начинается непосредственно после метки 'start'. Будут последовательно выполняться инструкция за инструкцией, пока не встретится операция передачи управления, такая как: jmр, jne, je, ret и так далее. Эти инструкции перенаправляют поток выполнения другим инструкциям. Когда программа выходит в Windows, ей следует вызвать API-функцию ExitProcess.
Assembler
1
ExitProcess proto uExitCode:DWORD
Строка выше называется прототипом функции. Прототип функции указывает ассемблеру/линкеру атрибуты функции, чтобы он сделал проверку типов данных и количество атрибутов передаваемых функции. Формат прототипа функции следующий:
Assembler
1
ИмяФункции PROTO [ИмяПараметра]:ТипДанных,[ИмяПараметра]:ТипДанных,...
Короче говоря, за именем функции следует ключевое слово PROTO, а затем список переменных с типом данных, разделенных запятыми. В приведенном выше примере с ExitProcess, эта функция была определена как принимающая только один параметр типа DWORD. Прототипы функций очень полезны, когда вы используете высокоуровневый синтаксический вызов — invoke. Вы можете считать об invoke как обычный вызов с проверкой типов данных. Например, если вы напишите:
Assembler
1
call ExitProcess
Линкер уведомит вас, что вы забыли положит в стек двойное слово. Я рекомендую вам использовать invoke вместо простого вызова. Синтаксис invoke следующий:
Assembler
1
invoke выражение [, аргументы]
Выражение может быть именем функции или указателем на функцию. Параметры функции разделены запятыми.
Большинство прототипов для API-функций содержатся в include-файлах. Если вы используете hutch'евский MASM32, они будут находится в директории MASM32/INCLUDE. Файлы подключения имеют расширение .inc и прототипы функций DLL находятся в .inc файле с таким же именем, как и у этой DLL.
Hапример, ExitProcess экспортируется из kernel32.lib, так что прототип ExitProcess находится в kernel32.inc.

Вы также можете создать прототипы для ваших собственных функций. Во всех моих экземплярах я использую hutch'евский windows.inc, который вы можете скачать с http://win32asm.cjb.net
Возвращаясь к ExitProcess: параметр uExitCode - это значение, которое программа вернет Windows после окончания программы. Вы можете вызвать функцию ExitProcess так:
Assembler
1
invoke ExitProcess, 0
Поместив эту строку непосредственно после стартовой метки, вы получите Win32-программу, немедленно выходящую в Windows, но тем не менее полнофункциональную.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
.386
.model flat, stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
.data
.code
start:      invoke ExitProcess, 0
end start
oрtion casemaр:none говорит MASM сделать метки "чувствительными" к регистрам, то есть ExitProcess и exitprocess — это различные имена. Отметьте новую директиву — include. После нее следует имя файла, который вы хотите вставить в то место, где эта директива располагается. В примере выше, когда MASM обрабатывает линию include \masm32\include\windows.inc, он открывает windows.inc, находящийся в директории \MASM32\INCLUDE, и далее анализирует содержимое windows.inc так, как будто вы "вклеили" подключаемый файл. Хатчевский windows.inc содержит в себе определения констант и структур, которые вам могут понадобиться для программирования под Win32. Этот файл не содержит в себе прототипов функций. Windows.inc ни в коем случае не является исчерпывающим и всеобъемлющим. Hutch и я пытаемся заполнить его как можно большим количеством констант и структур, но есть еще довольно, что следовало бы включить. Он постоянно обновляется. Заходите на хатчевскую и мою странички за свежими апдейтами. Из windows.inc, ваша программа будет брать определения констант и структур. Что касается прототипов функций, вы должны подключить другие include-файлы. Они находятся в директории \masm32\include.

В вышеприведенном примере, мы вызываем функцию, экспортированную из kernel32.dll, для чего мы должны подключить прототипы функций из kernel32.dll. Этот файл - kernel32.inc. Если вы открываете его текстовым редактором, вы увидите, что он состоит из прототипов функций из соответствующей dll. Если вы не подключите kernel32.inc, вы все еще можете вызвать ExitProcess, но уже с помощью ассемблерной команды call. Вы не сможете вызвать эту функцию с помощью invoke. Дело вот в чем: для того, чтобы вызвать функцию через invoke, вы должны поместить в исходном коде ее прототип. В примере выше, если вы не подключите kernel32.inc, вы можете определить прототип для ExitProcess где-нибудь до вызова этой функции и это будет работать. Файлы подключения нужны для того, что избавить вас от лишней работы и вам не пришлось набирать все прототипы самим.
Теперь мы встречаем новую директиву — includelib. Она работает не так, как include. Это всего лишь способ сказать ассемблеру какие библиотеки использует ваша программа должна прилинковать. Хотя вы вовсе не обязаны использовать именно этот метод. Вы можете указать имена библиотек импорта к командной строке при запуске линкера, но поверьте мне, это весьма скучно и утомительно, да и командная строка может вместить максимум 128 символов.
Теперь возьмите весь исходный текст примера этого Урока, сохраните его как msgbox.asm и ассемблируйте его так:
Код
ml /c /coff /Cp msgbox.asm
/c говорит MASM'у создать .obj файл в формате COFF. MASM использует вариант COFF (Common Object File Format), использующийся под Unix, как его собственный объектный и исполняемый формат файлов.
/Cр говорит MASM'у сохранять регистр имен, заданных пользователем. Если вы используете hutch'евский MASM32 пакет, вы можете вставить "option casemaр:none" в начале вашего исходника, сразу после директивы .model, чтобы добиться того же эффекта.
После успешной компиляции msgbox.asm, вы получите msgbox.obj. Это объектный файл, от которого один шаг до екзешника. Obj содержит инструкции/данные в двоичной форме. Отсутствуют только необходимая корректировка адресов, которая проводится линкером.
Теперь сделайте следующее:
Код
link /SUBSYSTEM:WINDOWS  /LIBPATH:c:\masm32\lib  msgbox.obj
/SUBSYSTEM:WINDOWS информирует линкер о том, какого вида является будущий исполняемый модуль.
/LIBPATH:<путь к библиотекам импорта> говорит линкеру, где находятся библиотеки импорта. Если вы используете MASM32, они будут в MASM32\lib.

Линкер читает объектный файл и корректирует его, используя адреса, взятые из библиотек импорта. После окончания линковки вы получите файл msgbox.exe. Запустите его. Вы увидите, что она ничего не делает.

Да, мы не поместили в код ничего не интересного. Hо тем не менее полноценная Windows программа. И посмотрите на размер! Hа моем PC — 1.536 байт.
Теперь мы готовы создать окно с сообщением. Прототип функции, которая нам для этого необходима следующая:
Assembler
1
MessageBox PROTO hwnd:DWORD, lpText:DWORD, lpCaption:DWORD, uType:DWORD
hwnd — это хэндл родительского окна. Вы можете считать хэндл числом, представляющим окно, к которому вы обращаетесь. Его значение для вас не важно. Вы только должны знать, что оно представляет окно. Когда вы захотите сделать что-нибудь с окном, вы должны обратиться к нему, используя его хэндл.
lрText — это указатель на текст, который вы хотите отобразить в клиентской части окна сообщения. Указатель ― это адрес чего-либо. Указатель на текстовую строку = адрес этой строки.
lpCaption — это указатель на заголовок окна сообщения.
uType — устанавливает иконку, число и вид кнопок окна.

Давайте изменим msgbox.asm для отображения сообщения.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
.data
MsgBoxCaption  db "Iczelion Tutorial #2",0
MsgBoxText     db "Win32 Assembly is Great!",0
.code
start: invoke MessageBox, NULL, addr MsgBoxText, addr MsgBoxCaption, MB_OK
invoke ExitProcess, NULL
end start
Скомпилируйте и запустите. Вы увидите окошко с сообщением "Win32 Assembly is great!".
Давайте снова взглянем на исходник.
Мы определили две оканчивающиеся NULL'ом строки в секции .data. Помните, что каждая ANSI строка в Windows должна оканчиваться NULL'ом (0 в шестнадцатеричной системе). Мы используем две константы, NULL и MB_OK. Эти константы прописаны в windows.inc, так что вы можете обратиться к ним, указав их имя, а не значение. Это улучшает читабельность кода.
Оператор addr используется для передачи адреса метки (и не только) функции. Он действителен только в контексте директивы invoke. Вы не можете использовать его, чтобы присвоить адрес метки регистру или переменной, например. В данном примере вы можете использовать offset вместо addr. Тем не менее, есть некоторые различия между ними.
1. addr не может быть использован с метками, которые определены впереди, а offset может. Например, если метка определена где-то дальше в коде, чем строка с invoke, addr не будет работать.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
invoke MessageBox,NULL, addr MsgBoxText,addr MsgBoxCaption,MB_OK
......
MsgBoxCaption  db "Iczelion Tutorial #2",0
MsgBoxText       db "Win32 Assembly is Great!",0
MASM доложит об ошибке. Если вы используете offset вместо addr, MASM без проблем скомпилирует указанный отрывок кода.
2. Addr поддерживает локальные переменные, в то время как offset нет.
Локальная переменная — это всего лишь зарезервированное место в стеке. Вы только знаете его адрес во время выполнения программы. Offset интерпретируется во время компиляции ассемблером, поэтому неудивительно, что он не поддерживает локальные переменные. Addr же работает с ними, потому что ассемблер сначала проверяет ― глобальная переменная или локальная. Если она глобальная, он помещает адрес этой переменной в объектный файл. В этом случае оператор работает как offset. Если это локальная переменная, компилятор генерирует следующую последовательность инструкций, перед тем как будет вызвана функция:
Assembler
1
2
lea eax, LocalVar
push eax
Учитывая, что lea может определить адрес метки в "рантайме", все работает прекрасно.
____________________________________________
© Iczelion, пер. Aquila.
8
Вложения
Тип файла: rar KbdGarland.rar (6.1 Кб, 147 просмотров)
Mikl___
Автор FAQ
11795 / 6098 / 556
Регистрация: 11.11.2010
Сообщений: 11,091
06.01.2013, 05:47  [ТС] 18
Win32 API. Урок 7. Мышь
Кликните здесь для просмотра всего текста
В этом Уроке мы создадим полнофункциональную Windows программу, которое выводит сообщение — "Win32 assembly is great!".
Скачайте пример здесь.
ТЕОРИЯ, МАТЬ СКЛЕРОЗА
Windows предоставляет огромное количество ресурсов Windows-программам через Windows API (Application Programming Interface). Windows API — это большая коллекция очень полезных функций, располагающихся непосредственно в операционной системе и готовых для использования программами. Эти функции находятся в нескольких динамически подгружаемых библиотеках (DLLs), таких как kernel32.dll, user32.dll и gdi32.dll. Kernel32.dll содержит API-функции, взаимодействующие с памятью и управляющие процессами. User32.dll контролирует пользовательский интерфейс. Gdi32.dll ответственен за графические операции. Кроме этих трех "основных", существуют также другие dll, которые вы можете использовать, при условии, что вы обладаете достаточным количеством информации о нужных API-функциях. Windows программы динамически подсоединяется к этим библиотекам, то есть код API-функций не включается в исполняемый файл. Информация находится в библиотеках импорта. Вы должны слинковать ваши программы с правильными библиотеками импорта, иначе они не смогут найти эти функции. Когда Windows программа загружается в память, Windows читает информацию, сохраненную в программе. Эта информация включает имена функций, которые программа использует и DLL-ей, в которых эти функции располагаются. Когда Windows находит подобную информацию в программе, она вызывает библиотеки и исправляет в программе вызовы этих функций, так что контроль всегда будет передаваться по правильному адресу.
Существует две категории API функций: одни работают с ANSI-строками, а другие с Unicode-строками. Имена API-функций использующих ANSI-строки заканчиваются на "A", например, MessageBoxA. В конце имен функций для Unicode находится "W". Windows 95/98 от природы поддерживают ANSI, а Windows NT и производные от нее 2k/XP/Vista поддерживают Unicode. Обычно мы имеем дело с ANSI строками (массивы символов, оканчивающиеся NULL-ом. размер ANSI-символа — 1 байт. В то время как ANSI достаточна для европейских языков, она не поддерживает некоторые восточные языки, в которых есть несколько тысяч уникальных символов. Вот в этих случаях в дело вступает UniCode. размер символа UNICODE — 2 байта, и поэтому может поддерживать 65536 различных символов. Hо по большей части, вы будете использовать include-файл, который может определить и выбрать подходящую для вашей платформы функцию. Просто обращайтесь к именам API-функций без постфикса.
ПРАКТИКА, МАТЬ ШИЗОФРЕНИИ
Я приведу голый скелет программы ниже. Позже мы разберем его.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
.386
.model flat, stdcall
.data
.code
start:
end start
Выполнение начинается с первой инструкции, следующей за меткой, установленной после конца директив. В вышеприведенном каркасе выполнение начинается непосредственно после метки 'start'. Будут последовательно выполняться инструкция за инструкцией, пока не встретится операция передачи управления, такая как: jmр, jne, je, ret и так далее. Эти инструкции перенаправляют поток выполнения другим инструкциям. Когда программа выходит в Windows, ей следует вызвать API-функцию ExitProcess.
Assembler
1
ExitProcess proto uExitCode:DWORD
Строка выше называется прототипом функции. Прототип функции указывает ассемблеру/линкеру атрибуты функции, чтобы он сделал проверку типов данных и количество атрибутов передаваемых функции. Формат прототипа функции следующий:
Assembler
1
ИмяФункции PROTO [ИмяПараметра]:ТипДанных,[ИмяПараметра]:ТипДанных,...
Короче говоря, за именем функции следует ключевое слово PROTO, а затем список переменных с типом данных, разделенных запятыми. В приведенном выше примере с ExitProcess, эта функция была определена как принимающая только один параметр типа DWORD. Прототипы функций очень полезны, когда вы используете высокоуровневый синтаксический вызов — invoke. Вы можете считать об invoke как обычный вызов с проверкой типов данных. Например, если вы напишите:
Assembler
1
call ExitProcess
Линкер уведомит вас, что вы забыли положит в стек двойное слово. Я рекомендую вам использовать invoke вместо простого вызова. Синтаксис invoke следующий:
Assembler
1
invoke выражение [, аргументы]
Выражение может быть именем функции или указателем на функцию. Параметры функции разделены запятыми.
Большинство прототипов для API-функций содержатся в include-файлах. Если вы используете hutch'евский MASM32, они будут находится в директории MASM32/INCLUDE. Файлы подключения имеют расширение .inc и прототипы функций DLL находятся в .inc файле с таким же именем, как и у этой DLL.
Hапример, ExitProcess экспортируется из kernel32.lib, так что прототип ExitProcess находится в kernel32.inc.

Вы также можете создать прототипы для ваших собственных функций. Во всех моих экземплярах я использую hutch'евский windows.inc, который вы можете скачать с http://win32asm.cjb.net
Возвращаясь к ExitProcess: параметр uExitCode - это значение, которое программа вернет Windows после окончания программы. Вы можете вызвать функцию ExitProcess так:
Assembler
1
invoke ExitProcess, 0
Поместив эту строку непосредственно после стартовой метки, вы получите Win32-программу, немедленно выходящую в Windows, но тем не менее полнофункциональную.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
.386
.model flat, stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
.data
.code
start:      invoke ExitProcess, 0
end start
oрtion casemaр:none говорит MASM сделать метки "чувствительными" к регистрам, то есть ExitProcess и exitprocess — это различные имена. Отметьте новую директиву — include. После нее следует имя файла, который вы хотите вставить в то место, где эта директива располагается. В примере выше, когда MASM обрабатывает линию include \masm32\include\windows.inc, он открывает windows.inc, находящийся в директории \MASM32\INCLUDE, и далее анализирует содержимое windows.inc так, как будто вы "вклеили" подключаемый файл. Хатчевский windows.inc содержит в себе определения констант и структур, которые вам могут понадобиться для программирования под Win32. Этот файл не содержит в себе прототипов функций. Windows.inc ни в коем случае не является исчерпывающим и всеобъемлющим. Hutch и я пытаемся заполнить его как можно большим количеством констант и структур, но есть еще довольно, что следовало бы включить. Он постоянно обновляется. Заходите на хатчевскую и мою странички за свежими апдейтами. Из windows.inc, ваша программа будет брать определения констант и структур. Что касается прототипов функций, вы должны подключить другие include-файлы. Они находятся в директории \masm32\include.

В вышеприведенном примере, мы вызываем функцию, экспортированную из kernel32.dll, для чего мы должны подключить прототипы функций из kernel32.dll. Этот файл - kernel32.inc. Если вы открываете его текстовым редактором, вы увидите, что он состоит из прототипов функций из соответствующей dll. Если вы не подключите kernel32.inc, вы все еще можете вызвать ExitProcess, но уже с помощью ассемблерной команды call. Вы не сможете вызвать эту функцию с помощью invoke. Дело вот в чем: для того, чтобы вызвать функцию через invoke, вы должны поместить в исходном коде ее прототип. В примере выше, если вы не подключите kernel32.inc, вы можете определить прототип для ExitProcess где-нибудь до вызова этой функции и это будет работать. Файлы подключения нужны для того, что избавить вас от лишней работы и вам не пришлось набирать все прототипы самим.
Теперь мы встречаем новую директиву — includelib. Она работает не так, как include. Это всего лишь способ сказать ассемблеру какие библиотеки использует ваша программа должна прилинковать. Хотя вы вовсе не обязаны использовать именно этот метод. Вы можете указать имена библиотек импорта к командной строке при запуске линкера, но поверьте мне, это весьма скучно и утомительно, да и командная строка может вместить максимум 128 символов.
Теперь возьмите весь исходный текст примера этого Урока, сохраните его как msgbox.asm и ассемблируйте его так:
Код
ml /c /coff /Cp msgbox.asm
/c говорит MASM'у создать .obj файл в формате COFF. MASM использует вариант COFF (Common Object File Format), использующийся под Unix, как его собственный объектный и исполняемый формат файлов.
/Cр говорит MASM'у сохранять регистр имен, заданных пользователем. Если вы используете hutch'евский MASM32 пакет, вы можете вставить "option casemaр:none" в начале вашего исходника, сразу после директивы .model, чтобы добиться того же эффекта.
После успешной компиляции msgbox.asm, вы получите msgbox.obj. Это объектный файл, от которого один шаг до екзешника. Obj содержит инструкции/данные в двоичной форме. Отсутствуют только необходимая корректировка адресов, которая проводится линкером.
Теперь сделайте следующее:
Код
link /SUBSYSTEM:WINDOWS  /LIBPATH:c:\masm32\lib  msgbox.obj
/SUBSYSTEM:WINDOWS информирует линкер о том, какого вида является будущий исполняемый модуль.
/LIBPATH:<путь к библиотекам импорта> говорит линкеру, где находятся библиотеки импорта. Если вы используете MASM32, они будут в MASM32\lib.

Линкер читает объектный файл и корректирует его, используя адреса, взятые из библиотек импорта. После окончания линковки вы получите файл msgbox.exe. Запустите его. Вы увидите, что она ничего не делает.

Да, мы не поместили в код ничего не интересного. Hо тем не менее полноценная Windows программа. И посмотрите на размер! Hа моем PC — 1.536 байт.
Теперь мы готовы создать окно с сообщением. Прототип функции, которая нам для этого необходима следующая:
Assembler
1
MessageBox PROTO hwnd:DWORD, lpText:DWORD, lpCaption:DWORD, uType:DWORD
hwnd — это хэндл родительского окна. Вы можете считать хэндл числом, представляющим окно, к которому вы обращаетесь. Его значение для вас не важно. Вы только должны знать, что оно представляет окно. Когда вы захотите сделать что-нибудь с окном, вы должны обратиться к нему, используя его хэндл.
lрText — это указатель на текст, который вы хотите отобразить в клиентской части окна сообщения. Указатель ― это адрес чего-либо. Указатель на текстовую строку = адрес этой строки.
lpCaption — это указатель на заголовок окна сообщения.
uType — устанавливает иконку, число и вид кнопок окна.

Давайте изменим msgbox.asm для отображения сообщения.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
.data
MsgBoxCaption  db "Iczelion Tutorial #2",0
MsgBoxText     db "Win32 Assembly is Great!",0
.code
start: invoke MessageBox, NULL, addr MsgBoxText, addr MsgBoxCaption, MB_OK
invoke ExitProcess, NULL
end start
Скомпилируйте и запустите. Вы увидите окошко с сообщением "Win32 Assembly is great!".
Давайте снова взглянем на исходник.
Мы определили две оканчивающиеся NULL'ом строки в секции .data. Помните, что каждая ANSI строка в Windows должна оканчиваться NULL'ом (0 в шестнадцатеричной системе). Мы используем две константы, NULL и MB_OK. Эти константы прописаны в windows.inc, так что вы можете обратиться к ним, указав их имя, а не значение. Это улучшает читабельность кода.
Оператор addr используется для передачи адреса метки (и не только) функции. Он действителен только в контексте директивы invoke. Вы не можете использовать его, чтобы присвоить адрес метки регистру или переменной, например. В данном примере вы можете использовать offset вместо addr. Тем не менее, есть некоторые различия между ними.
1. addr не может быть использован с метками, которые определены впереди, а offset может. Например, если метка определена где-то дальше в коде, чем строка с invoke, addr не будет работать.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
invoke MessageBox,NULL, addr MsgBoxText,addr MsgBoxCaption,MB_OK
......
MsgBoxCaption  db "Iczelion Tutorial #2",0
MsgBoxText       db "Win32 Assembly is Great!",0
MASM доложит об ошибке. Если вы используете offset вместо addr, MASM без проблем скомпилирует указанный отрывок кода.
2. Addr поддерживает локальные переменные, в то время как offset нет.
Локальная переменная — это всего лишь зарезервированное место в стеке. Вы только знаете его адрес во время выполнения программы. Offset интерпретируется во время компиляции ассемблером, поэтому неудивительно, что он не поддерживает локальные переменные. Addr же работает с ними, потому что ассемблер сначала проверяет ― глобальная переменная или локальная. Если она глобальная, он помещает адрес этой переменной в объектный файл. В этом случае оператор работает как offset. Если это локальная переменная, компилятор генерирует следующую последовательность инструкций, перед тем как будет вызвана функция:
Assembler
1
2
lea eax, LocalVar
push eax
Учитывая, что lea может определить адрес метки в "рантайме", все работает прекрасно.
____________________________________________
© Iczelion, пер. Aquila.
7
Вложения
Тип файла: zip tut07.zip (2.8 Кб, 84 просмотров)
Mikl___
Автор FAQ
11795 / 6098 / 556
Регистрация: 11.11.2010
Сообщений: 11,091
06.01.2013, 06:11  [ТС] 19
Win32 API. Урок 7a. Мышь
Кликните здесь для просмотра всего текста
В этом Уроке мы создадим полнофункциональную Windows программу, которое выводит сообщение — "Win32 assembly is great!".
Скачайте пример здесь.
ТЕОРИЯ, МАТЬ СКЛЕРОЗА
Windows предоставляет огромное количество ресурсов Windows-программам через Windows API (Application Programming Interface). Windows API — это большая коллекция очень полезных функций, располагающихся непосредственно в операционной системе и готовых для использования программами. Эти функции находятся в нескольких динамически подгружаемых библиотеках (DLLs), таких как kernel32.dll, user32.dll и gdi32.dll. Kernel32.dll содержит API-функции, взаимодействующие с памятью и управляющие процессами. User32.dll контролирует пользовательский интерфейс. Gdi32.dll ответственен за графические операции. Кроме этих трех "основных", существуют также другие dll, которые вы можете использовать, при условии, что вы обладаете достаточным количеством информации о нужных API-функциях. Windows программы динамически подсоединяется к этим библиотекам, то есть код API-функций не включается в исполняемый файл. Информация находится в библиотеках импорта. Вы должны слинковать ваши программы с правильными библиотеками импорта, иначе они не смогут найти эти функции. Когда Windows программа загружается в память, Windows читает информацию, сохраненную в программе. Эта информация включает имена функций, которые программа использует и DLL-ей, в которых эти функции располагаются. Когда Windows находит подобную информацию в программе, она вызывает библиотеки и исправляет в программе вызовы этих функций, так что контроль всегда будет передаваться по правильному адресу.
Существует две категории API функций: одни работают с ANSI-строками, а другие с Unicode-строками. Имена API-функций использующих ANSI-строки заканчиваются на "A", например, MessageBoxA. В конце имен функций для Unicode находится "W". Windows 95/98 от природы поддерживают ANSI, а Windows NT и производные от нее 2k/XP/Vista поддерживают Unicode. Обычно мы имеем дело с ANSI строками (массивы символов, оканчивающиеся NULL-ом. размер ANSI-символа — 1 байт. В то время как ANSI достаточна для европейских языков, она не поддерживает некоторые восточные языки, в которых есть несколько тысяч уникальных символов. Вот в этих случаях в дело вступает UniCode. размер символа UNICODE — 2 байта, и поэтому может поддерживать 65536 различных символов. Hо по большей части, вы будете использовать include-файл, который может определить и выбрать подходящую для вашей платформы функцию. Просто обращайтесь к именам API-функций без постфикса.
ПРАКТИКА, МАТЬ ШИЗОФРЕНИИ
Я приведу голый скелет программы ниже. Позже мы разберем его.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
.386
.model flat, stdcall
.data
.code
start:
end start
Выполнение начинается с первой инструкции, следующей за меткой, установленной после конца директив. В вышеприведенном каркасе выполнение начинается непосредственно после метки 'start'. Будут последовательно выполняться инструкция за инструкцией, пока не встретится операция передачи управления, такая как: jmр, jne, je, ret и так далее. Эти инструкции перенаправляют поток выполнения другим инструкциям. Когда программа выходит в Windows, ей следует вызвать API-функцию ExitProcess.
Assembler
1
ExitProcess proto uExitCode:DWORD
Строка выше называется прототипом функции. Прототип функции указывает ассемблеру/линкеру атрибуты функции, чтобы он сделал проверку типов данных и количество атрибутов передаваемых функции. Формат прототипа функции следующий:
Assembler
1
ИмяФункции PROTO [ИмяПараметра]:ТипДанных,[ИмяПараметра]:ТипДанных,...
Короче говоря, за именем функции следует ключевое слово PROTO, а затем список переменных с типом данных, разделенных запятыми. В приведенном выше примере с ExitProcess, эта функция была определена как принимающая только один параметр типа DWORD. Прототипы функций очень полезны, когда вы используете высокоуровневый синтаксический вызов — invoke. Вы можете считать об invoke как обычный вызов с проверкой типов данных. Например, если вы напишите:
Assembler
1
call ExitProcess
Линкер уведомит вас, что вы забыли положит в стек двойное слово. Я рекомендую вам использовать invoke вместо простого вызова. Синтаксис invoke следующий:
Assembler
1
invoke выражение [, аргументы]
Выражение может быть именем функции или указателем на функцию. Параметры функции разделены запятыми.
Большинство прототипов для API-функций содержатся в include-файлах. Если вы используете hutch'евский MASM32, они будут находится в директории MASM32/INCLUDE. Файлы подключения имеют расширение .inc и прототипы функций DLL находятся в .inc файле с таким же именем, как и у этой DLL.
Hапример, ExitProcess экспортируется из kernel32.lib, так что прототип ExitProcess находится в kernel32.inc.

Вы также можете создать прототипы для ваших собственных функций. Во всех моих экземплярах я использую hutch'евский windows.inc, который вы можете скачать с http://win32asm.cjb.net
Возвращаясь к ExitProcess: параметр uExitCode - это значение, которое программа вернет Windows после окончания программы. Вы можете вызвать функцию ExitProcess так:
Assembler
1
invoke ExitProcess, 0
Поместив эту строку непосредственно после стартовой метки, вы получите Win32-программу, немедленно выходящую в Windows, но тем не менее полнофункциональную.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
.386
.model flat, stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
.data
.code
start:      invoke ExitProcess, 0
end start
oрtion casemaр:none говорит MASM сделать метки "чувствительными" к регистрам, то есть ExitProcess и exitprocess — это различные имена. Отметьте новую директиву — include. После нее следует имя файла, который вы хотите вставить в то место, где эта директива располагается. В примере выше, когда MASM обрабатывает линию include \masm32\include\windows.inc, он открывает windows.inc, находящийся в директории \MASM32\INCLUDE, и далее анализирует содержимое windows.inc так, как будто вы "вклеили" подключаемый файл. Хатчевский windows.inc содержит в себе определения констант и структур, которые вам могут понадобиться для программирования под Win32. Этот файл не содержит в себе прототипов функций. Windows.inc ни в коем случае не является исчерпывающим и всеобъемлющим. Hutch и я пытаемся заполнить его как можно большим количеством констант и структур, но есть еще довольно, что следовало бы включить. Он постоянно обновляется. Заходите на хатчевскую и мою странички за свежими апдейтами. Из windows.inc, ваша программа будет брать определения констант и структур. Что касается прототипов функций, вы должны подключить другие include-файлы. Они находятся в директории \masm32\include.

В вышеприведенном примере, мы вызываем функцию, экспортированную из kernel32.dll, для чего мы должны подключить прототипы функций из kernel32.dll. Этот файл - kernel32.inc. Если вы открываете его текстовым редактором, вы увидите, что он состоит из прототипов функций из соответствующей dll. Если вы не подключите kernel32.inc, вы все еще можете вызвать ExitProcess, но уже с помощью ассемблерной команды call. Вы не сможете вызвать эту функцию с помощью invoke. Дело вот в чем: для того, чтобы вызвать функцию через invoke, вы должны поместить в исходном коде ее прототип. В примере выше, если вы не подключите kernel32.inc, вы можете определить прототип для ExitProcess где-нибудь до вызова этой функции и это будет работать. Файлы подключения нужны для того, что избавить вас от лишней работы и вам не пришлось набирать все прототипы самим.
Теперь мы встречаем новую директиву — includelib. Она работает не так, как include. Это всего лишь способ сказать ассемблеру какие библиотеки использует ваша программа должна прилинковать. Хотя вы вовсе не обязаны использовать именно этот метод. Вы можете указать имена библиотек импорта к командной строке при запуске линкера, но поверьте мне, это весьма скучно и утомительно, да и командная строка может вместить максимум 128 символов.
Теперь возьмите весь исходный текст примера этого Урока, сохраните его как msgbox.asm и ассемблируйте его так:
Код
ml /c /coff /Cp msgbox.asm
/c говорит MASM'у создать .obj файл в формате COFF. MASM использует вариант COFF (Common Object File Format), использующийся под Unix, как его собственный объектный и исполняемый формат файлов.
/Cр говорит MASM'у сохранять регистр имен, заданных пользователем. Если вы используете hutch'евский MASM32 пакет, вы можете вставить "option casemaр:none" в начале вашего исходника, сразу после директивы .model, чтобы добиться того же эффекта.
После успешной компиляции msgbox.asm, вы получите msgbox.obj. Это объектный файл, от которого один шаг до екзешника. Obj содержит инструкции/данные в двоичной форме. Отсутствуют только необходимая корректировка адресов, которая проводится линкером.
Теперь сделайте следующее:
Код
link /SUBSYSTEM:WINDOWS  /LIBPATH:c:\masm32\lib  msgbox.obj
/SUBSYSTEM:WINDOWS информирует линкер о том, какого вида является будущий исполняемый модуль.
/LIBPATH:<путь к библиотекам импорта> говорит линкеру, где находятся библиотеки импорта. Если вы используете MASM32, они будут в MASM32\lib.

Линкер читает объектный файл и корректирует его, используя адреса, взятые из библиотек импорта. После окончания линковки вы получите файл msgbox.exe. Запустите его. Вы увидите, что она ничего не делает.

Да, мы не поместили в код ничего не интересного. Hо тем не менее полноценная Windows программа. И посмотрите на размер! Hа моем PC — 1.536 байт.
Теперь мы готовы создать окно с сообщением. Прототип функции, которая нам для этого необходима следующая:
Assembler
1
MessageBox PROTO hwnd:DWORD, lpText:DWORD, lpCaption:DWORD, uType:DWORD
hwnd — это хэндл родительского окна. Вы можете считать хэндл числом, представляющим окно, к которому вы обращаетесь. Его значение для вас не важно. Вы только должны знать, что оно представляет окно. Когда вы захотите сделать что-нибудь с окном, вы должны обратиться к нему, используя его хэндл.
lрText — это указатель на текст, который вы хотите отобразить в клиентской части окна сообщения. Указатель ― это адрес чего-либо. Указатель на текстовую строку = адрес этой строки.
lpCaption — это указатель на заголовок окна сообщения.
uType — устанавливает иконку, число и вид кнопок окна.

Давайте изменим msgbox.asm для отображения сообщения.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
.data
MsgBoxCaption  db "Iczelion Tutorial #2",0
MsgBoxText     db "Win32 Assembly is Great!",0
.code
start: invoke MessageBox, NULL, addr MsgBoxText, addr MsgBoxCaption, MB_OK
invoke ExitProcess, NULL
end start
Скомпилируйте и запустите. Вы увидите окошко с сообщением "Win32 Assembly is great!".
Давайте снова взглянем на исходник.
Мы определили две оканчивающиеся NULL'ом строки в секции .data. Помните, что каждая ANSI строка в Windows должна оканчиваться NULL'ом (0 в шестнадцатеричной системе). Мы используем две константы, NULL и MB_OK. Эти константы прописаны в windows.inc, так что вы можете обратиться к ним, указав их имя, а не значение. Это улучшает читабельность кода.
Оператор addr используется для передачи адреса метки (и не только) функции. Он действителен только в контексте директивы invoke. Вы не можете использовать его, чтобы присвоить адрес метки регистру или переменной, например. В данном примере вы можете использовать offset вместо addr. Тем не менее, есть некоторые различия между ними.
1. addr не может быть использован с метками, которые определены впереди, а offset может. Например, если метка определена где-то дальше в коде, чем строка с invoke, addr не будет работать.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
invoke MessageBox,NULL, addr MsgBoxText,addr MsgBoxCaption,MB_OK
......
MsgBoxCaption  db "Iczelion Tutorial #2",0
MsgBoxText       db "Win32 Assembly is Great!",0
MASM доложит об ошибке. Если вы используете offset вместо addr, MASM без проблем скомпилирует указанный отрывок кода.
2. Addr поддерживает локальные переменные, в то время как offset нет.
Локальная переменная — это всего лишь зарезервированное место в стеке. Вы только знаете его адрес во время выполнения программы. Offset интерпретируется во время компиляции ассемблером, поэтому неудивительно, что он не поддерживает локальные переменные. Addr же работает с ними, потому что ассемблер сначала проверяет ― глобальная переменная или локальная. Если она глобальная, он помещает адрес этой переменной в объектный файл. В этом случае оператор работает как offset. Если это локальная переменная, компилятор генерирует следующую последовательность инструкций, перед тем как будет вызвана функция:
Assembler
1
2
lea eax, LocalVar
push eax
Учитывая, что lea может определить адрес метки в "рантайме", все работает прекрасно.
____________________________________________
© Iczelion, пер. Aquila.
8
Mikl___
Автор FAQ
11795 / 6098 / 556
Регистрация: 11.11.2010
Сообщений: 11,091
06.01.2013, 07:42  [ТС] 20
Win32 API. Урок 7b. Мышь

Кликните здесь для просмотра всего текста
В этом Уроке мы создадим полнофункциональную Windows программу, которое выводит сообщение — "Win32 assembly is great!".
Скачайте пример здесь.
ТЕОРИЯ, МАТЬ СКЛЕРОЗА
Windows предоставляет огромное количество ресурсов Windows-программам через Windows API (Application Programming Interface). Windows API — это большая коллекция очень полезных функций, располагающихся непосредственно в операционной системе и готовых для использования программами. Эти функции находятся в нескольких динамически подгружаемых библиотеках (DLLs), таких как kernel32.dll, user32.dll и gdi32.dll. Kernel32.dll содержит API-функции, взаимодействующие с памятью и управляющие процессами. User32.dll контролирует пользовательский интерфейс. Gdi32.dll ответственен за графические операции. Кроме этих трех "основных", существуют также другие dll, которые вы можете использовать, при условии, что вы обладаете достаточным количеством информации о нужных API-функциях. Windows программы динамически подсоединяется к этим библиотекам, то есть код API-функций не включается в исполняемый файл. Информация находится в библиотеках импорта. Вы должны слинковать ваши программы с правильными библиотеками импорта, иначе они не смогут найти эти функции. Когда Windows программа загружается в память, Windows читает информацию, сохраненную в программе. Эта информация включает имена функций, которые программа использует и DLL-ей, в которых эти функции располагаются. Когда Windows находит подобную информацию в программе, она вызывает библиотеки и исправляет в программе вызовы этих функций, так что контроль всегда будет передаваться по правильному адресу.
Существует две категории API функций: одни работают с ANSI-строками, а другие с Unicode-строками. Имена API-функций использующих ANSI-строки заканчиваются на "A", например, MessageBoxA. В конце имен функций для Unicode находится "W". Windows 95/98 от природы поддерживают ANSI, а Windows NT и производные от нее 2k/XP/Vista поддерживают Unicode. Обычно мы имеем дело с ANSI строками (массивы символов, оканчивающиеся NULL-ом. размер ANSI-символа — 1 байт. В то время как ANSI достаточна для европейских языков, она не поддерживает некоторые восточные языки, в которых есть несколько тысяч уникальных символов. Вот в этих случаях в дело вступает UniCode. размер символа UNICODE — 2 байта, и поэтому может поддерживать 65536 различных символов. Hо по большей части, вы будете использовать include-файл, который может определить и выбрать подходящую для вашей платформы функцию. Просто обращайтесь к именам API-функций без постфикса.
ПРАКТИКА, МАТЬ ШИЗОФРЕНИИ
Я приведу голый скелет программы ниже. Позже мы разберем его.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
.386
.model flat, stdcall
.data
.code
start:
end start
Выполнение начинается с первой инструкции, следующей за меткой, установленной после конца директив. В вышеприведенном каркасе выполнение начинается непосредственно после метки 'start'. Будут последовательно выполняться инструкция за инструкцией, пока не встретится операция передачи управления, такая как: jmр, jne, je, ret и так далее. Эти инструкции перенаправляют поток выполнения другим инструкциям. Когда программа выходит в Windows, ей следует вызвать API-функцию ExitProcess.
Assembler
1
ExitProcess proto uExitCode:DWORD
Строка выше называется прототипом функции. Прототип функции указывает ассемблеру/линкеру атрибуты функции, чтобы он сделал проверку типов данных и количество атрибутов передаваемых функции. Формат прототипа функции следующий:
Assembler
1
ИмяФункции PROTO [ИмяПараметра]:ТипДанных,[ИмяПараметра]:ТипДанных,...
Короче говоря, за именем функции следует ключевое слово PROTO, а затем список переменных с типом данных, разделенных запятыми. В приведенном выше примере с ExitProcess, эта функция была определена как принимающая только один параметр типа DWORD. Прототипы функций очень полезны, когда вы используете высокоуровневый синтаксический вызов — invoke. Вы можете считать об invoke как обычный вызов с проверкой типов данных. Например, если вы напишите:
Assembler
1
call ExitProcess
Линкер уведомит вас, что вы забыли положит в стек двойное слово. Я рекомендую вам использовать invoke вместо простого вызова. Синтаксис invoke следующий:
Assembler
1
invoke выражение [, аргументы]
Выражение может быть именем функции или указателем на функцию. Параметры функции разделены запятыми.
Большинство прототипов для API-функций содержатся в include-файлах. Если вы используете hutch'евский MASM32, они будут находится в директории MASM32/INCLUDE. Файлы подключения имеют расширение .inc и прототипы функций DLL находятся в .inc файле с таким же именем, как и у этой DLL.
Hапример, ExitProcess экспортируется из kernel32.lib, так что прототип ExitProcess находится в kernel32.inc.

Вы также можете создать прототипы для ваших собственных функций. Во всех моих экземплярах я использую hutch'евский windows.inc, который вы можете скачать с http://win32asm.cjb.net
Возвращаясь к ExitProcess: параметр uExitCode - это значение, которое программа вернет Windows после окончания программы. Вы можете вызвать функцию ExitProcess так:
Assembler
1
invoke ExitProcess, 0
Поместив эту строку непосредственно после стартовой метки, вы получите Win32-программу, немедленно выходящую в Windows, но тем не менее полнофункциональную.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
.386
.model flat, stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
.data
.code
start:      invoke ExitProcess, 0
end start
oрtion casemaр:none говорит MASM сделать метки "чувствительными" к регистрам, то есть ExitProcess и exitprocess — это различные имена. Отметьте новую директиву — include. После нее следует имя файла, который вы хотите вставить в то место, где эта директива располагается. В примере выше, когда MASM обрабатывает линию include \masm32\include\windows.inc, он открывает windows.inc, находящийся в директории \MASM32\INCLUDE, и далее анализирует содержимое windows.inc так, как будто вы "вклеили" подключаемый файл. Хатчевский windows.inc содержит в себе определения констант и структур, которые вам могут понадобиться для программирования под Win32. Этот файл не содержит в себе прототипов функций. Windows.inc ни в коем случае не является исчерпывающим и всеобъемлющим. Hutch и я пытаемся заполнить его как можно большим количеством констант и структур, но есть еще довольно, что следовало бы включить. Он постоянно обновляется. Заходите на хатчевскую и мою странички за свежими апдейтами. Из windows.inc, ваша программа будет брать определения констант и структур. Что касается прототипов функций, вы должны подключить другие include-файлы. Они находятся в директории \masm32\include.

В вышеприведенном примере, мы вызываем функцию, экспортированную из kernel32.dll, для чего мы должны подключить прототипы функций из kernel32.dll. Этот файл - kernel32.inc. Если вы открываете его текстовым редактором, вы увидите, что он состоит из прототипов функций из соответствующей dll. Если вы не подключите kernel32.inc, вы все еще можете вызвать ExitProcess, но уже с помощью ассемблерной команды call. Вы не сможете вызвать эту функцию с помощью invoke. Дело вот в чем: для того, чтобы вызвать функцию через invoke, вы должны поместить в исходном коде ее прототип. В примере выше, если вы не подключите kernel32.inc, вы можете определить прототип для ExitProcess где-нибудь до вызова этой функции и это будет работать. Файлы подключения нужны для того, что избавить вас от лишней работы и вам не пришлось набирать все прототипы самим.
Теперь мы встречаем новую директиву — includelib. Она работает не так, как include. Это всего лишь способ сказать ассемблеру какие библиотеки использует ваша программа должна прилинковать. Хотя вы вовсе не обязаны использовать именно этот метод. Вы можете указать имена библиотек импорта к командной строке при запуске линкера, но поверьте мне, это весьма скучно и утомительно, да и командная строка может вместить максимум 128 символов.
Теперь возьмите весь исходный текст примера этого Урока, сохраните его как msgbox.asm и ассемблируйте его так:
Код
ml /c /coff /Cp msgbox.asm
/c говорит MASM'у создать .obj файл в формате COFF. MASM использует вариант COFF (Common Object File Format), использующийся под Unix, как его собственный объектный и исполняемый формат файлов.
/Cр говорит MASM'у сохранять регистр имен, заданных пользователем. Если вы используете hutch'евский MASM32 пакет, вы можете вставить "option casemaр:none" в начале вашего исходника, сразу после директивы .model, чтобы добиться того же эффекта.
После успешной компиляции msgbox.asm, вы получите msgbox.obj. Это объектный файл, от которого один шаг до екзешника. Obj содержит инструкции/данные в двоичной форме. Отсутствуют только необходимая корректировка адресов, которая проводится линкером.
Теперь сделайте следующее:
Код
link /SUBSYSTEM:WINDOWS  /LIBPATH:c:\masm32\lib  msgbox.obj
/SUBSYSTEM:WINDOWS информирует линкер о том, какого вида является будущий исполняемый модуль.
/LIBPATH:<путь к библиотекам импорта> говорит линкеру, где находятся библиотеки импорта. Если вы используете MASM32, они будут в MASM32\lib.

Линкер читает объектный файл и корректирует его, используя адреса, взятые из библиотек импорта. После окончания линковки вы получите файл msgbox.exe. Запустите его. Вы увидите, что она ничего не делает.

Да, мы не поместили в код ничего не интересного. Hо тем не менее полноценная Windows программа. И посмотрите на размер! Hа моем PC — 1.536 байт.
Теперь мы готовы создать окно с сообщением. Прототип функции, которая нам для этого необходима следующая:
Assembler
1
MessageBox PROTO hwnd:DWORD, lpText:DWORD, lpCaption:DWORD, uType:DWORD
hwnd — это хэндл родительского окна. Вы можете считать хэндл числом, представляющим окно, к которому вы обращаетесь. Его значение для вас не важно. Вы только должны знать, что оно представляет окно. Когда вы захотите сделать что-нибудь с окном, вы должны обратиться к нему, используя его хэндл.
lрText — это указатель на текст, который вы хотите отобразить в клиентской части окна сообщения. Указатель ― это адрес чего-либо. Указатель на текстовую строку = адрес этой строки.
lpCaption — это указатель на заголовок окна сообщения.
uType — устанавливает иконку, число и вид кнопок окна.

Давайте изменим msgbox.asm для отображения сообщения.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
.data
MsgBoxCaption  db "Iczelion Tutorial #2",0
MsgBoxText     db "Win32 Assembly is Great!",0
.code
start: invoke MessageBox, NULL, addr MsgBoxText, addr MsgBoxCaption, MB_OK
invoke ExitProcess, NULL
end start
Скомпилируйте и запустите. Вы увидите окошко с сообщением "Win32 Assembly is great!".
Давайте снова взглянем на исходник.
Мы определили две оканчивающиеся NULL'ом строки в секции .data. Помните, что каждая ANSI строка в Windows должна оканчиваться NULL'ом (0 в шестнадцатеричной системе). Мы используем две константы, NULL и MB_OK. Эти константы прописаны в windows.inc, так что вы можете обратиться к ним, указав их имя, а не значение. Это улучшает читабельность кода.
Оператор addr используется для передачи адреса метки (и не только) функции. Он действителен только в контексте директивы invoke. Вы не можете использовать его, чтобы присвоить адрес метки регистру или переменной, например. В данном примере вы можете использовать offset вместо addr. Тем не менее, есть некоторые различия между ними.
1. addr не может быть использован с метками, которые определены впереди, а offset может. Например, если метка определена где-то дальше в коде, чем строка с invoke, addr не будет работать.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
invoke MessageBox,NULL, addr MsgBoxText,addr MsgBoxCaption,MB_OK
......
MsgBoxCaption  db "Iczelion Tutorial #2",0
MsgBoxText       db "Win32 Assembly is Great!",0
MASM доложит об ошибке. Если вы используете offset вместо addr, MASM без проблем скомпилирует указанный отрывок кода.
2. Addr поддерживает локальные переменные, в то время как offset нет.
Локальная переменная — это всего лишь зарезервированное место в стеке. Вы только знаете его адрес во время выполнения программы. Offset интерпретируется во время компиляции ассемблером, поэтому неудивительно, что он не поддерживает локальные переменные. Addr же работает с ними, потому что ассемблер сначала проверяет ― глобальная переменная или локальная. Если она глобальная, он помещает адрес этой переменной в объектный файл. В этом случае оператор работает как offset. Если это локальная переменная, компилятор генерирует следующую последовательность инструкций, перед тем как будет вызвана функция:
Assembler
1
2
lea eax, LocalVar
push eax
Учитывая, что lea может определить адрес метки в "рантайме", все работает прекрасно.
____________________________________________
© Iczelion, пер. Aquila.
6
Вложения
Тип файла: zip tut07b.zip (4.0 Кб, 93 просмотров)
06.01.2013, 07:42
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
06.01.2013, 07:42

Выключается сам по себе
После очистки компа от пыли (снимал кулер, проц, оперативку и видюху, так же...

Вырубается ПК сам по себе
Нужна помощь в проблеме. Пару недель назад кулер в БП сильно шумел время от...

Компьютер выключается сам по себе!
Симптомы: выключается сам по себе, без видимых причин, не открываются из пуска...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
20
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2018, vBulletin Solutions, Inc.
Рейтинг@Mail.ru