Форум программистов, компьютерный форум, киберфорум
Assembler, MASM, TASM
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск  
 
 
Рейтинг 5.00/1: Рейтинг темы: голосов - 1, средняя оценка - 5.00
Ушел с форума
Автор FAQ
 Аватар для Mikl___
16377 / 7689 / 1080
Регистрация: 11.11.2010
Сообщений: 13,769
14.01.2013, 10:06  [ТС]
Студворк — интернет-сервис помощи студентам
Win32 API. Урок 12. Память и файлы
Кликните здесь для просмотра всего текста
Этот Урок предполагает, что читатель знает, как использовать 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
Вложения
Тип файла: zip tut12.zip (4.2 Кб, 130 просмотров)
4
cpp_developer
Эксперт
20123 / 5690 / 1417
Регистрация: 09.04.2010
Сообщений: 22,546
Блог
14.01.2013, 10:06
Ответы с готовыми решениями:

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

Уроки Iczelion'a на FASM
Уроки Iczelion'a на FASM Урок первый. MessageBox на FASM format PE GUI include 'win32ax.inc' ; import data in the same...

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

120
Ушел с форума
Автор FAQ
 Аватар для Mikl___
16377 / 7689 / 1080
Регистрация: 11.11.2010
Сообщений: 13,769
14.01.2013, 10:41  [ТС]
Win32 API. Урок 12a. Память и файлы
Кликните здесь для просмотра всего текста
В этом Уроке мы создадим полнофункциональную 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 и ассемблируйте его так:
Code
1
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 содержит инструкции/данные в двоичной форме. Отсутствуют только необходимая корректировка адресов, которая проводится линкером.
Теперь сделайте следующее:
Code
1
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.
Изображения
 
3
Ушел с форума
Автор FAQ
 Аватар для Mikl___
16377 / 7689 / 1080
Регистрация: 11.11.2010
Сообщений: 13,769
14.01.2013, 11:25  [ТС]
Win32 API. Урок 13. Memory Mapped файлы
Я покажу вам, что такое Memory Mapped файлы и как использовать их для вашей выгоды. Использование MMF достаточно просто, как вы увидите из этого туторила.

Скачайте пример здесь.
ТЕОРИЯ ― МАТЬ СКЛЕРОЗА
Если вы хорошо изучили пример из прошлого туториала, вы увидите, что у него есть серьезный недостаток: что, если файл, который вы хотите прочитать больше, чем зарезервированный блок памяти? или если строка, которую вы хотите найти будет обрезана посередине, потому что кончился блок памяти? Традиционный ответ на первый вопрос ― это то, что вам нужно последовательно читать данные из файла, пока он не кончится. Ответом на второй вопрос является то, что вы должны обрабатывать подобную возможность. Это называется проблемой пограничного значения. Она представляет собой головную большую для программистов и вызывает неисчислимое количество ошибок в программе.
Было бы неплохо, если бы мы могли зарезервировать очень большой блок памяти, достаточный для того, чтобы сохранить весь файл, но наша программа стала бы очень прожорливой в плане ресурсов. File maрing ― это спасение. Используя его, вы можете считать весь файл уже загруженным в память и использовать указатель на память, чтобы читать или писать данные в файл. Очень просто. Hет нужды использовать API памяти и файловые API одновременно, в File maрing это одно и то же. File maрing также используется для обмена данными между процессами. При использовании File maрing таким образом, реально не используется никакой файл. Это больше похоже на блок памяти, который могут видеть все процессы. Hо обмен данными между процессами ― весьма деликатный предмет. Вы должны будете обеспечить синхронизацию между процессами и ветвями, иначе ваше приложение очень скоро повиснет.

Мы не будем касаться того, как использовать File maрing для создания общего pегиона памяти в этом туториале. Мы сконцентрируемся на том, как использовать File maрing для "загрузки" файла в память. Фактически, PE-загрузчик использует File maрing для загрузки исполняемых файлов в память. Это очень удобно, так как только необходимые порции файла будут считываться с диска. Под Win32 вам следует использовать File maрing так часто, как это возможно.

Правда, существует несколько ограничений при использовании File maрing. Как только вы создали такой файл, его размер не может изменяться до закрытия сессии. Поэтому File maрing прекрасно подходит для файлов из которых нужно только читать или файловых операций, которые не изменяют размер файла. Это не значит, что вы не можете использовать File maрing, если хотите увеличить pазмеp файла. Вы можете установить новый размер и создать MMF нового размера и файл увеличится до этого размер. Это просто неудобно, вот и все.

Достаточно объяснений. Давайте перейдем к реализации File maрing. Для того, чтобы его использовать, должны быть выполнены следующие шаги.
  1. Вызов CreateFile для открытия файла.
  2. Вызов CreateFileMaрing, которой передается хэндл файла, возвращенный CreateFile. Эта функция создает File maрing-объект из файла, созданного CreateFile'ом.
  3. Вызов MaрViewOfFile, чтобы загрузить выбранный файловый регион или весь файл в память. Эта функция возвращает указатель на первый байт промэппированного файлового региона.
  4. Используйте указатель, чтобы писать или читать из файла.
  5. Вызовите UnmaрViewOfFile, чтобы выгрузить файл.
  6. Вызов CloseHandle, передав ему хэндл промэппированного файла в качестве одного из параметра, чтобы закрыть его.
  7. Вызов CloseHandle снова, передав ему в этот раз хэндл файла, возвращенный CreateFile, чтобы закрыть сам файл.
ПРАКТИКА ― СЕСТРА ШИЗОФРЕНИИ
Программа, листинг которой приведен ниже, позволит вам открыть файл с помощью окна открытия файла. Она откроет файл, используя File maрing, если это удастся, заголовок окна изменится на имя открытого файла. Вы можете сохранить файл под другим именем, выбрав пункт меню File/Save. Программа скопирует все содержимое открытого файла в новый файл. Учтите, что вы не должны вызывать GlobalAlloc для резервирования блока памяти в этой программе.
Кликните здесь для просмотра всего текста
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
 .386
.model flat,stdcall
 
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
 
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib
 
.const
IDM_OPEN equ 1
IDM_SAVE equ 2
IDM_EXIT equ 3
MAXSIZE equ 260
 
.data
ClassName db "Win32ASMFileMappingClass",0
AppName db "Win32 ASM File Mapping Example",0
MenuName db "FirstMenu",0
 
ofn OPENFILENAME <>
FilterString db "All Files",0,"*.*",0
db "Text Files",0,"*.txt",0,0
buffer db MAXSIZE dup(0)
 
hMaрFile HANDLE 0 ; Указатель на MMF, должен быть инициализирован
; нулем, так как мы также используем его в качестве флага в секции WM_DESTROY
 
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hFileRead HANDLE ? ; Хэндл источника
hFileWrite HANDLE ? ; Хэндл выходного файла
hMenu HANDLE ?
pMemory DWORD ? ; Указатель на данные в исходном файле
SizeWritten DWORD ? ; количество байтов
actually written by WriteFile
 
.code
start: invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
 
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdL­ine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
 
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName, \
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USE­DEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp
 
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
 
.IF uMsg==WM_CREATE
invoke GetMenu,hWnd ; Получаем хэндл меню
mov hMenu,eax
mov ofn.lStructSize,SIZEOF ofn
push hWnd
pop ofn.hWndOwner
push hInstance
pop ofn.hInstance
mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,MAXSIZE
.ELSEIF uMsg==WM_DESTROY
.if hMapFile!=0
call CloseMapFile
.endif
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if lParam==0
.if ax==IDM_OPEN
mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetOpenFileName, ADDR ofn
 
.if eax==TRUE
invoke CreateFile,ADDR buffer,GENERIC_READ ,0,\
NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIV­E,NULL
mov hFileRead,eax
invoke CreateFileMapping,hFileRead,NULL,PAGE_RE­ADONLY,0,0,NULL
mov hMapFile,eax
mov eax,OFFSET buffer
movzx edx,ofn.nFileOffset
add eax,edx
invoke SetWindowText,hWnd,eax
invoke EnableMenuItem,hMenu,IDM_OPEN,MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_SAVE,MF_ENABLED
.endif
.elseif ax==IDM_SAVE
mov ofn.Flags,OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetSaveFileName, ADDR ofn
.if eax==TRUE
invoke CreateFile,ADDR buffer,GENERIC_READ or \
GENERIC_WRITE,FILE_SHARE_READ or FILE_SHARE_WRITE,NULL,\
CREATE_NEW,FILE_ATTRIBUTE_ARCHIVE,NULL
mov hFileWrite,eax
invoke MapViewOfFile,hMapFile,FILE_MAP_READ,0,0­,0
mov pMemory,eax
invoke GetFileSize,hFileRead,NULL
invoke WriteFile,hFileWrite,pMemory,eax,ADDR SizeWritten,NULL
invoke UnmapViewOfFile,pMemory
call CloseMapFile
invoke CloseHandle,hFileWrite
invoke SetWindowText,hWnd,ADDR AppName
invoke EnableMenuItem,hMenu,IDM_OPEN,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_SAVE,MF_GRAYED
.endif
.else
invoke DestroyWindow, hWnd
.endif
.endif
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
 
CloseMapFile PROC
invoke CloseHandle,hMapFile
mov hMapFile,0
invoke CloseHandle,hFileRead
ret
CloseMapFile endp
end start
Разбор полётов
Кликните здесь для просмотра всего текста
Assembler
1
2
 invoke CreateFile,ADDR buffer,GENERIC_READ,0,\
NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIV­E,NULL
Когда пользователь выбирает файл в окне открытия файла, мы вызываем CreateFile, чтобы открыть его. Заметьте, что мы указываем GENERIC_READ, чтобы открыть этот файл в режиме read-only, потому что мы не хотим, чтобы какие-либо другие процессы изменяли файл во время нашей работы с ним.
Кликните здесь для просмотра всего текста
Assembler
1
 invoke CreateFileMapping,hFileRead,NULL,PAGE_RE­ADONLY,0,0,NULL
Затем мы вызываем CreateFileMaрing, чтобы создать MMF из открытого файла.
CreateFileMapping имеет следующий синтаксис:
Кликните здесь для просмотра всего текста
Assembler
1
2
 CreateFileMapping proto hFile:DWORD, lpFileMappingAttributes:DWORD,flProtect:­DWORD,\
dwMaximumSizeHigh:DWORD, dwMaximumSizeLow:DWORD, lpName:DWORD
  • Вам следует знать, что CreateFileMaрing не обязана мэппировать весь файл в память. Вы можете промэппировать только часть файла. Размер мэппируемого файла вы задаете параметрами dwMaximumSizeHigh и dwMaximumSizeLow. Если вы зададите размер больше, чем его действительный размер, файл будет увеличен до нового размера. Если вы хотите, чтобы MMF был такого же размера, как и исходный файл, сделайте оба параметра pавными нулю.
  • Вы можете использовать NULL в lpFileMappingAttributes, чтобы Windows создали MMF со значениями безопасности по умолчанию.
  • flProtect ― определяет желаемую защиту для MMF. В нашем примере, мы используем PAGE_READONLY, чтобы разрешить только операции чтения над MMF. Заметьте, что этот атрибут не должен входить в противоречие с атрибутами, указанными в CreateFile, иначе CreateFileMaрing возвратит ошибку.
  • lрName ― указывает на имя MMF. Если вы хотите разделять этот файл с другими процессами, вы должны присвоить ему имя. Hо в нашем примере другие процессы не будут его использовать, поэтому мы игнорируем этот параметр.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
 mov eax,OFFSET buffer
movzx edx,ofn.nFileOffset
add eax,edx
invoke SetWindowText,hWnd,eax
Если CreateFileMapping выполнилась успешно, мы изменяем название окна на имя открытого файла. Имя файла с полным путем сохраняется в буфере, мы же хотим отобразить только собственно имя файла, поэтому мы должны добавить значение параметра nFileOffset структуры OPENFILENAME к адресу буфера.
Кликните здесь для просмотра всего текста
Assembler
1
2
 invoke EnableMenuItem,hMenu,IDM_OPEN,MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_SAVE,MF_ENABLED
В качестве меры предосторожности, мы не хотим чтобы пользователь мог открыть несколько файлов за pаз, поэтому делаем пункт меню Open недоступным для выбора и делаем доступным пункт Save. EnableMenuItem используется для изменения атрибутов пункта меню. После этого, мы ждем, пока пользователь выберет File/Save или закроет программу.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
 .ELSEIF uMsg==WM_DESTROY
 
.if hMapFile!=0
call CloseMapFile
.endif
invoke PostQuitMessage,NULL
В выше приведенном коде, когда процедура окна получает сообщение WM_DESTROY, она сначала проверяет значение hMaрFile ― равно ли то нулю или нет. Если оно не равно нулю, она вызывает функцию CloseMapFile, которая содержит следующий код:
Кликните здесь для просмотра всего текста
[asm] CloseMapFile PROC
invoke CloseHandle,hMapFile
mov hMapFile,0
invoke CloseHandle,hFileRead
ret
CloseMapFile endp[/asm
] CloseMaрFile закрывает MMF и сам файл, так что наша программа не оставляет за собой следов при выходе из Windows. Если пользователь выберет сохранение информации в другой файл, программа покажет ему окно сохранения файла. После он сможет напечатать имя нового файла, который и будет создать функцией CreateFile.
Кликните здесь для просмотра всего текста
Assembler
1
2
 invoke MapViewOfFile,hMapFile,FILE_MAP_READ,0,0­,0
mov pMemory,eax
Сpазу же после создания выходного файла, мы вызываем MapViewOfFile, чтобы промэппировать желаемую порцию MMF в память. Эта функция имеет следующий синтаксис:
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
 MapViewOfFile proto hFileMappingObject:DWORD,\
dwDesiredAccess:DWORD,\
dwFileOffsetHigh:DWORD,\
dwFileOffsetLow:DWORD,\
dwNumberOfBytesToMap:DWORD
  • dwDesiredAccess определяет, какую операцию мы хотим совершить над файлом. В нашем примере мы хотим только прочитать данные, поэтому мы используем FILE_MAP_READ.
  • dwFileOffsetHigh и dwFileOffsetLow задают стартовый файловое смещение файловой порции, которую вы хотите загрузить в память. В нашем случае нам нужно мы хотим читать весь файл, поэтому начинаем мэппинг со смещение ноль.
  • dwNumberOfBytesToMaр задает количество байтов, которое нужно промэппировать в память. Чтобы сделать это со всем файлом, передайте ноль MaрViewOfFile.
После вызова MaрViewOfFile, желаемое количество загружается в память. Вы получите указатель на блок памяти, который содержит данные из файла.
Кликните здесь для просмотра всего текста
Assembler
1
 invoke GetFileSize,hFileRead,NULL
Теперь узнаем, какого размера наш файл. Размер файла возвращается в eax. Если файл больше, чем 4 GB, то верхнее двойное слово размера файла сохраняется в FileSizeHighWord. Так как мы не ожидаем встретить таких больших файлов, мы можем проигнорировать это.
Кликните здесь для просмотра всего текста
Assembler
1
 invoke WriteFile,hFileWrite,pMemory,eax,ADDR SizeWritten,NULL
Запишем данные в выходной файл.
Кликните здесь для просмотра всего текста
Assembler
1
 invoke UnmapViewOfFile,pMemory
Когда мы заканчиваем со входным файлом, вызываем UnmapViewOfFile.
Кликните здесь для просмотра всего текста
Assembler
1
2
 call CloseMapFile
invoke CloseHandle,hFileWrite
И закрываем все файлы.
Кликните здесь для просмотра всего текста
Assembler
1
 invoke SetWindowText,hWnd,ADDR AppName
Восстанавливаем оригинальное название окна.
Кликните здесь для просмотра всего текста
Assembler
1
2
 invoke EnableMenuItem,hMenu,IDM_OPEN,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_SAVE,MF_GRAYED
разрешаем доступ к пункту меню Oрen и запрещаем к Save As.
_______________________________
© Iczelion, пер. Aquila.
3
Ушел с форума
Автор FAQ
 Аватар для Mikl___
16377 / 7689 / 1080
Регистрация: 11.11.2010
Сообщений: 13,769
15.01.2013, 06:49  [ТС]
Win32 API. Урок 13a. Файлы, проецируемые в память (Memory Mapped Files)ТЕОРИЯ ― МАТЬ СКЛЕРОЗА
Скачайте пример здесь.
Процессор не может работать с содержимым файла, находящегося непосредственно на диске. для обращения к какому-либо участку файла этот участок необходимо сначала прочитать в память. Если же требуется не просто обрабатывать данные из файла, а модифицировать его содержимое, то к операции чтения файла добавляется еще и операция его записи. Стандартная для DOS- и 16-разрядных Windows-приложений процедура работы с файлом на диске включала следующие этапы:
  1. открытие файла на диске;
  2. чтение требуемого участка файла в созданную заранее программную переменную;
  3. модификация этой программной переменной по заданному алгоритму;
  4. запись модифицированной переменной на то же место в файле на диске.
Обычно работа с файлом требует обращений не к одному его участку, а к нескольким, и тогда перечисленные выше операции придется выполнять повторно. Во многих случаях, особенно при обслуживании огромных баз данных, программа по ходу выполнения запросит различные участки файла сотни или тысячи раз, и если запросы происходят не упорядоченно, а «в разбивку», то каждое обращение к файлу требует выполнения операций физического чтения/записи, что существенно замедлит скорость выполнения программы.
Значительно более эффективным будет чтение всего файла в оперативную память. В этом случае потребуется только одна операция физического чтения в начале сеанса и одна операция физической записи в конце сеанса. Однако такая методика возможна только с файлами относительно небольшого размера; к тому же в этом случае резко снижается надежность системы, так как новые, модифицированные данные записываются на диск только в конце сеанса и любой сбой программы приведет к потере всех модификаций.
Windows предоставляет механизм для работы с файлами исключающий операции физической записи/чтения, который сводится к тому, что конкретный файл включается в состав физической памяти, затем в адресном пространстве приложения резервируется регион достаточного размера и часть физической памяти, включающая в себя файл, отображается на этот регион (передается региону). Дальнейшие операции с файлом заменяются на операции с его отображением (проекцией) в адресном пространстве приложения.

Процедура создания и использования проекции файла распадается на следующие этапы:
  1. открытие (создание) файла обычным образом с помощью CreateFile;
  2. создание в физической памяти объекта «проекция файла» с помощью CreateFileMapping;
  3. создание в виртуальной (линейной) памяти приложения региона адресного пространства и отображения созданной проекции на адресное пространство процесса с помощью MapViewOfFile;
  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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
.586p
.model tiny
include windows.inc
;for WinXP - 2039 bytes
du macro string
local bslash
bslash=0
irpc c,<string>
if bslash eq 0
if '&c' eq "\";;управляющая последовательность символов
bslash=1
elseif '&c' gt 127
db ('&c'- 0B0h),4;;кириллица
else
dw '&c' ;;латиница
endif
else
bslash=0
if '&c' eq "n" ;; \n=новая строка
DW 0Dh,0Ah
elseif '&c' eq "\";; \\=обратная косая черта (\)
dw '\'
elseif '&c' eq "r";; \r=возврат каретки
dw 0Dh
elseif '&c' eq "l";; \l=LF
dw 0Ah
elseif '&c' eq "s"
dw 20h
elseif '&c' eq "c"
dw 3Bh
elseif '&c' eq "t";; \t=табуляция*
dw 9
endif
endif
endm
dw 0
endm
; -----------------------------------------------------------------
exebase equ 400000h
MI_OPEN equ 0
MI_SAVE equ 1
MI_SAVEAS equ 2
MI_CLOSE equ 3
MI_NEW equ 4
MI_EXIT equ 5
MAXSIZE equ 260
IDC_MENU equ 30
EditID equ 1
MEMSIZE equ 865535
;------------------------------------------------------------------------
.code
main:
include capito_res.asm
;--------------------------------------------
start: xor ebx,ebx
mov esi,exebase
mov edi,offset wTitle+exebase
;------------------------------
; registering the window class
;------------------------------
invoke RegisterClass,esp,ebx,offset window_procedure+exebase,\
ebx,ebx,esi,ebx,10011h,COLOR_WINDOW+1,ID­C_MENU,edi
;--------------------------+
; creating the main window
;--------------------------+
push ebx
push esi
shl esi,9
invoke CreateWindowEx,WS_EX_CLIENTEDGE,edi,edi,­\
WS_OVERLAPPEDWINDOW or WS_VISIBLE,esi,esi,esi,esi,ebx,ebx
mov ofn.hWndOwner+exebase,eax
invoke GetMenu,eax;eax=hWnd
mov hMenu+exebase,eax
mov ebp,esp
;---------------------------+
;entering the message loop |
;---------------------------+
message_loop: invoke GetMessage,ebp,ebx,ebx,ebx
invoke TranslateMessage,ebp
invoke DispatchMessage,ebp
jmp message_loop
;----------------------+
; the window procedure |
;----------------------+
window_procedure:
hWnd equ dword ptr [ebp+8h]
uMsg equ dword ptr [ebp+0Ch]
wParam equ dword ptr [ebp+10h]
lParam equ dword ptr [ebp+14h]
push ebp
mov ebp,esp
mov eax,uMsg
dec eax
je wmCREATE
dec eax;cmp [uMsg],WM_DESTROY
je wmDESTROY
sub eax,WM_SIZE-WM_DESTROY
je wmSIZE
sub eax,WM_COMMAND-WM_SIZE; cmp [uMsg],WM_COMMAND
je wmCOMMAND
leave
jmp DefWindowProc+exebase
wmSIZE: push TRUE
mov ax,word ptr lParam+2
push eax
mov ax,word ptr lParam
invoke MoveWindow,hwndEdit+exebase,ebx,ebx,eax
jmp wmBYE
wmCREATE: invoke CreateWindowEx,ebx,offset EditClass+exebase,ebx,\
WS_VISIBLE or WS_CHILD or ES_LEFT or ES_MULTILINE or \
ES_AUTOHSCROLL or ES_AUTOVSCROLL,ebx,ebx,ebx,ebx,hWnd,Edit­ID,\
exebase,ebx
mov hwndEdit+exebase,eax
jmp wmBYE
wmCOMMAND: mov eax,wParam
cmp lParam,ebx
jne wmBYE
mov edi,offset ofn+exebase
mov esi,offset buffer+exebase
jmp dword ptr [menu_handlers+eax*4+exebase]
OPEN: mov dword ptr [edi+OPENFILENAME.Flags],OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or \
OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetOpenFileName,edi
test eax,eax
je wmBYE
invoke CreateFile,esi,GENERIC_READ or GENERIC_WRITE,\
FILE_SHARE_READ or FILE_SHARE_WRITE,\
ebx,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,­ebx
mov hFile+exebase,eax
;создание в памяти объекта "проекция файла"
invoke CreateFileMapping,eax,ebx,PAGE_READWRITE­,ebx,ebx,ebx
mov hMapFile+exebase,eax
;отображение проекции файла на адресное пространство процесса
invoke MapViewOfFile,eax,FILE_MAP_ALL_ACCESS,eb­x,ebx,ebx
mov pMemory+exebase,eax;pointer for the starting address of mapped file
invoke SendMessage,hwndEdit+exebase,WM_SETTEXT,­ebx,eax;pMemory+exebase
;-----------------------------
invoke CloseHandle,hFile+exebase
movzx eax,word ptr [edi+OPENFILENAME.nFileOffset]
add eax,esi
invoke SetWindowText,hWnd,eax
mov esi,MF_GRAYED
jmp @f
CLOSE: ;уничтожение отображения файла на адресное пространство
invoke UnmapViewOfFile,pMemory+exebase
;закрыть проекцию файла
invoke CloseHandle,hMapFile+exebase
invoke SetWindowText,hwndEdit+exebase,ebx
mov [esi],ebx
invoke SendMessage,hwndEdit+exebase,WM_SETTEXT,­ebx,ebx
mov esi,MF_ENABLED
jmp @f
NEW: jmp wmBYE
SAVEAS: mov dword ptr [edi+OPENFILENAME.Flags],OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetSaveFileName,edi;ofn
test eax,eax
je wmBYE
SAVE: invoke CreateFile,esi,GENERIC_READ or GENERIC_WRITE,\
FILE_SHARE_READ or FILE_SHARE_WRITE,ebx,OPEN_ALWAYS,\
FILE_ATTRIBUTE_ARCHIVE,ebx
mov hFile+exebase,eax;file write handle
invoke SendMessage,hwndEdit+exebase,WM_GETTEXT,­MEMSIZE-1,pMemory+exebase
invoke WriteFile,hFile+exebase,pMemory+exebase,­eax,offset SizeWritten+exebase,ebx
;закрыть файл на диске
invoke CloseHandle,hFile+exebase
movzx eax,word ptr [edi+OPENFILENAME.nFileOffset]
add eax,esi
invoke SetWindowText,hWnd,eax
mov esi,MF_ENABLED
@@: invoke EnableMenuItem,hMenu+exebase,ebx,esi
xor esi,1;MF_ENABLED <--> MF_GRAYED
push MI_NEW
pop ebx
@@: invoke EnableMenuItem,hMenu+exebase,ebx,esi
dec ebx
jnz @b
jmp wmBYE
EXIT: invoke DestroyWindow,hWnd
wmBYE: leave
retn 10h
wmDESTROY:;уничтожение отображения файла на адресное пространство
invoke UnmapViewOfFile,pMemory+exebase
;закрыть проекцию файла
invoke CloseHandle,hMapFile+exebase
invoke ExitProcess,ebx
;--------данные------------------------------
wTitle db 'Iczelion Tutorial #13:Memory Mapped Files in MASM',0;name of our window
dOTitle db 'Open File',0
FilterString db 'All Files (*.*)',0,'*.*',0,0
buffer db MAXSIZE dup(0)
ofn OPENFILENAME <sizeof(OPENFILENAME),?,exebase,offset FilterString+exebase,\
?,?,?,offset buffer+exebase,MAXSIZE,0,?,?,?,?,?,?,0,,?,?>
EditClass db "edit",0
menu_handlers dd OPEN+exebase,SAVE+exebase,SAVEAS+exebase­,CLOSE+exebase,NEW+exebase,EXIT+exebase
;-------------------------------------------------------------------------------------------
MFR_END equ 80h
MFR_POPUP equ 1
MFT_STRING equ 0
MFS_ENABLED equ 0
MFT_SEPARATOR equ 800h
RT_MENU equ 4
 
POPUP equ 10h
MENUBREAK equ 40h
ENDMENU equ 80h
DS_SETFONT equ 40h
align 4
resource:
Characteristics0 dd 0
TimeDateStamp0 dd 0
MajorVersion0 dw 0
MinorVersion0 dw 0
NumberOfNamedEntries0 dw 0
NumberOfIdEntries0 dw 1
dw RT_MENU,0
dw x-resource,8000h
x:
Characteristics1 dd 0
TimeDateStamp1 dd 0
MajorVersion1 dw 0
MinorVersion1 dw 0;
NumberOfNamedEntries1 dw 0;количество ресурсов с именами
NumberOfIdEntries1 dw 1;количество ресурсов с идентификаторами
;на этом уровне идентификатор ресурсов является идентификатором меню
dw IDC_MENU,0
dw x1-resource,8000h; если во 2-ом слове установлен старший бит - есть ссылка
;на оглавление третьего уровня. В 1-ом слове смещение третьего оглавления
;относительно начала раздела ресурсов
x1:
Characteristics2 dd 0
TimeDateStamp2 dd 0
MajorVersion2 dw 0
MinorVersion2 dw 0;
NumberOfNamedEntries2 dw 0;количество ресурсов с именами
NumberOfIdEntries2 dw 1;количество ресурсов с идентификаторами
;на этом уровне идентификатор ресурсов является идентификатором языка, который
;используется данным ресурсом 16 * SUBLANG_ + LANG_
dw 40Ah,0,x2-resource,0
x2:;struct _IMAGE_RESOURCE_DATA_ENTRY
OffsetToData0 dd menu
Size0 dd end_menu-menu
CodePage0 dd 0
Reserved0 dd 0
menu dw 0, 0,POPUP or MFR_END
du <&File>
dw MFT_STRING or MFS_ENABLED,MI_OPEN
du <Op&en>
dw MFT_STRING or MF_GRAYED,MI_SAVE
du <&Save>
dw MFT_STRING or MF_GRAYED,MI_SAVEAS
du <&Save as>
dw MFT_STRING or MF_GRAYED,MI_CLOSE
du <C&lose>
dw MFT_STRING or MF_GRAYED,MI_NEW
du <&New>
dw MENUBREAK,0,0;NOTEXT
dw MFT_STRING or MFS_ENABLED or MFR_END,MI_EXIT
du <&Exit>
end_menu:
end_resource:
;---------------------------------------------------------------------
import:
;dd 0,0,0,
hFile dd 0;file read handle
hMapFile dd 0;file mapped handle
dd 0,user32_dll
dd user_table
;dd 0,0,0,
hMenu dd 0;menu handle
pMemory dd 0
SizeWritten dd 0;number of bytes actually read or write
dd kernel32_dll
dd kernel_table
hwndEdit dd 0
dd 0,0,comdlg32_dll
dd comdlg_table
dd 0,0,0,0
user_table:
RegisterClass dd _RegisterClass
CreateWindowEx dd _CreateWindowEx
GetMessage dd _GetMessage
DispatchMessage dd _DispatchMessage
DefWindowProc dd _DefWindowProc
DestroyWindow dd _DestroyWindow
SetWindowText dd _SetWindowText
SendMessage dd _SendMessage
MoveWindow dd _MoveWindow
TranslateMessage dd _TranslateMessage
GetMenu dd _GetMenu
EnableMenuItem dd _EnableMenuItem,0
 
kernel_table:
CreateFileMapping dd _CreateFileMapping
MapViewOfFile dd _MapViewOfFile
UnmapViewOfFile dd _UnmapViewOfFile
CreateFile dd _CreateFile
WriteFile dd _WriteFile
CloseHandle dd _CloseHandle
ExitProcess dd _ExitProcess,0
 
comdlg_table:
GetSaveFileName dd _GetSaveFileName
GetOpenFileName dd _GetOpenFileName
dw 0
_RegisterClass db 0,0,'RegisterClassA'
_CreateWindowEx db 0,0,'CreateWindowExA'
_GetMessage db 0,0,'GetMessageA'
_DispatchMessage db 0,0,'DispatchMessageA'
_DefWindowProc db 0,0,'DefWindowProcA'
_DestroyWindow db 0,0,"DestroyWindow"
_SetWindowText db 0,0,'SetWindowTextA'
_GetMenu db 0,0,'GetMenu'
_SendMessage db 0,0,'SendMessageA'
_TranslateMessage db 0,0,'TranslateMessage'
_MoveWindow db 0,0,'MoveWindow'
_EnableMenuItem db 0,0,'EnableMenuItem',0
user32_dll db 'user32'
_CreateFileMapping db 0,0,'CreateFileMappingA'
_MapViewOfFile db 0,0,'MapViewOfFile'
_UnmapViewOfFile db 0,0,'UnmapViewOfFile'
_CreateFile db 0,0,'CreateFileA'
_WriteFile db 0,0,'WriteFile'
_CloseHandle db 0,0,'CloseHandle'
_ExitProcess db 0,0,'ExitProcess',0
kernel32_dll db 'kernel32'
_GetSaveFileName db 0,0,'GetSaveFileNameA'
_GetOpenFileName db 0,0,'GetOpenFileNameA',0
comdlg32_dll db 'comdlg32'
end_import:
end main
_____________________________________
© Mikl___ 2013
Вложения
Тип файла: zip tut13a.zip (4.5 Кб, 116 просмотров)
2
Ушел с форума
Автор FAQ
 Аватар для Mikl___
16377 / 7689 / 1080
Регистрация: 11.11.2010
Сообщений: 13,769
15.01.2013, 09:32  [ТС]
Win32 API. Урок 14. Процесс
Здесь мы изучим, что такое процесс, как его создать и как прервать.
Скачайте пример здесь.
ТЕОРИЯ - МАТЬ СКЛЕРОЗА
Что такое процесс? Я процитирую определение из справочника по Win32 API.
"Процесс ― это выполняющееся приложение, которое состоит из личного виртуального адресного пространства, кода, данных и других ресурсов операционной системы, таких как файлы, пайпы и синхронизационные объекты, видимые для процесса."
Как вы можете видеть из вышеприведенного определения, у процесса есть несколько объектов: адресное пространство, выполняемый модуль (модули) и все, что эти модули создают или открывают. Как минимум, процесс должен состоять из выполняющегося модуля, личного адресного пространства и ветви. У каждого процесса по крайней мере одна ветвь. Что такое ветвь?
Фактически, ветвь ― это выполняющаяся очередь. Когда Windows впервые создает процесс, она делает только одну ветвь на процесс. Эта ветвь обычно начинает выполнение с первой инструкции в модуле. Если в дальнейшем понадобится больше ветвей, он может сам создать их.
Когда Windows получает команду для создания процесса, она создает личное адресное пространство для процесса, а затем она загружает исполняемый файл в пространство. После этого она создает основную ветвь для процесса.
Под Win32 вы также можете создать процессы из своих программ с помощью функции Createprocess. Она имеет следующих синтаксис:
Кликните здесь для просмотра всего текста
Assembler
1
2
3
Createprocess proto lpApplicationName:DWORD,lpCommandLine:DW­ORD,\
lpProcessAttributes:DWORD,lpThreadAttrib­utes:DWORD,bInheritHandles:DWORD,dwCreat­ionFlags:DWORD,\
lpEnvironment:DWORD, lpCurrentDirectory:DWORD, lpStartupInfo:DWORD, lpProcessInformation:DWORD
Не пугайтесь количества параметров. Большую их часть мы можем игнорировать.
  • lpAplicationName ― имя исполняемого файла с или без пути, который вы хотите запустить. Если параметр равен нулю, вы должны предоставить имя исполняемого файла в параметре lpCommandLine.
  • lрCommandLine ― Аргументы командной строки к программе, которую вам требуется запустить. Заметьте, что если lрAрlicationName равен нулю, этот параметр должен содержать также имя исполняемого файла. Например так: "notepad.exe readme.txt".
  • lрProcessAttributes и lрThreadAttributes ― укажите атрибуты безопасности для процесса и основной ветви. Если они равны NULL'ам, то используются атрибуты безопасности по умолчанию.
  • bInheritHandles ― флаг, который указывает, хотите ли вы, чтобы новый процесс наследовал все открытые хэндлы из вашего процесса.
  • dwCreationFlags ― несколько флагов, которые определяют поведение процесса, который вы хотите создать, например, хотите ли вы, чтобы процесс был создан, но тут же приостановлен, чтобы вы могли проверить его или изменить, прежде, чем он запустится. Вы также можете указать класс приоритета ветви(ей) в новом процессе. Этот класс приоритета используется, чтобы определить планируемый приоритет ветвей внутри процесса. Обычно мы используем флаг NORMAL_PRIORITY_CLASS.
  • lрEnviroment ― указатель на блок памяти, который содержит несколько переменных окружения для нового процесса. Если этот параметр равен NULL, новый процесс наследует их от родительского процесса.
  • lрCurrentDirectory ― указатель на строку, которая указывает текущий диск и директорию для дочернего процесса. NULL ― если вы хотите, чтобы дочерний процесс унаследовал их от родительского процесса.
  • lрStartuрInfo ― указывает на структуру STARTUPINFO, которая определяет, как должно появиться основное окно нового процесса. Эта структура содержит много членов, которые определяют появление главного окна дочернего процесса. Если вы не хотите ничего особенного, вы можете заполнить данную структуру значениями родительского процесса, вызвав функцию GetStartupInfo.
  • lрProcessInformation ― указывает на структуру PROCESS_INFORMATION, которая получает идентификационную информацию о новом процессе. Структура PROCESS_INFORMATION имеет следующие параметры:
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
 PROCESS_INFORMATION STRUCT
hProcess HANDLE ? ;хэндл дочернего процесса
hThread HANDLE ?;хэндл основной ветви дочернего процесса
dwProcessId DWORD ? ;ID дочернего процесса
dwThreadId DWORD ? ;ID основной ветви
PROCESS_INFORMATION ENDS
Хэндл процесса и ID процесса ― это две разные вещи. ID процесса ― это уникальный идентификатор процесса в системе. Хэндл процесса ― это значение, возвращаемое Windows для использования другими API-функциями, связанными с процессами. Хэндл процесса не может использоваться для идентификации процесса, так как он не уникален.
После вызова функции CreateProcess, создается новый процесс и функция сразу же возвращается. Вы можете проверить, является ли еще процесс активным, вызвав функцию GetExitCodeProcess, которая имеет следующий синтаксис:
Кликните здесь для просмотра всего текста
Assembler
1
 GetExitCodeprocess proto hprocess:DWORD, lpExitCode:DWORD
Если вызов этой функции успешен, lрExitcode будет содержать код выхода запрашиваемого процесса. Если значение в lрExitCode pавно STILL_ACTIVE, тогда это означает, что процесс по-прежнему запущен.


Вы можете принудительно прервать процесс, вызвав функцию TerminateProcess.
У нее следующий синтаксис:
Кликните здесь для просмотра всего текста
Assembler
1
 TerminateProcess proto hprocess:DWORD, uExitCode:DWORD
Вы можете указать желаемый код выхода для процесса, любое значение, какое захотите. TerminateProcess ― не лучший путь прервать процесс, так как любые используемые им dll не будут уведомлены о том, что процесс был прерван.
Практика ― сестра шизофрении
Следующий пример создаст новый процесс, когда пользователь выберет пункт меню "create process". Он попытается запустить "msgbox.exe". Если пользователь захочет прервать новый процесс, он может выбрать пункт меню "terminate рrocess". Программа будет сначала проверять, уничтожен ли уже новый процесс, если нет, программ вызовет TerminateProcess для этого.
Кликните здесь для просмотра всего текста
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
 .386
.model flat,stdcall
option casemap:none
 
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
.const
IDM_CREATE_pROCESS equ 1
IDM_TERMINATE equ 2
IDM_EXIT equ 3
 
.data
ClassName db "Win32ASMprocessClass",0
AppName db "Win32 ASM process Example",0
MenuName db "FirstMenu",0
processInfo pROCESS_INFORMATION <&gt;
programname db "msgbox.exe",0
 
.data?
hInstance HINSTANCE ?
CommandLine LpSTR ?
hMenu HANDLE ?
ExitCode DWORD ?; содержит код выхода процесса после
; вызова функции GetExitCodeprocess
.code
start: invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke Exitprocess,eax
 
WinMain proc hInst:HINSTANCE,hprevInst:HINSTANCE,CmdL­ine:LpSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
 
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndproc, OFFSET Wndproc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_AppLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAppEDWINDOW,CW_USEDEFAULT,CW_USE­DEFAULT,300,200,NULL,NULL,\
hInst,NULL
 
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
invoke GetMenu,hwnd
mov hMenu,eax
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wparam
ret
WinMain endp
Wndproc proc hWnd:HWND, uMsg:UINT, wparam:WpARAM, lparam:LpARAM
LOCAL startInfo:STARTUpINFO
.IF uMsg==WM_DESTROY
invoke postQuitMessage,NULL
.ELSEIF uMsg==WM_INITMENUpOpUp
invoke GetExitCodeprocess,processInfo.hprocess,­ADDR ExitCode
.if eax==TRUE
.if ExitCode==STILL_ACTIVE
invoke EnableMenuItem,hMenu,IDM_CREATE_pROCESS,­MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_EN­ABLED
.else
invoke EnableMenuItem,hMenu,IDM_CREATE_pROCESS,­MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GR­AYED
.endif
.else
invoke EnableMenuItem,hMenu,IDM_CREATE_pROCESS,­MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GR­AYED
.endif
.ELSEIF uMsg==WM_COMMAND
mov eax,wparam
.if lparam==0
.if ax==IDM_CREATE_pROCESS
.if processInfo.hprocess!=0
invoke CloseHandle,processInfo.hprocess
mov processInfo.hprocess,0
.endif
invoke GetStartupInfo,ADDR startInfo
invoke Createprocess,ADDR programname,NULL,NULL,NULL,FALSE,\
NORMAL_pRIORITY_CLASS,\
NULL,NULL,ADDR startInfo,ADDR processInfo
invoke CloseHandle,processInfo.hThread
.elseif ax==IDM_TERMINATE
invoke GetExitCodeprocess,processInfo.hprocess,­ADDR ExitCode
.if ExitCode==STILL_ACTIVE
invoke Terminateprocess,processInfo.hprocess,0
.endif
invoke CloseHandle,processInfo.hprocess
mov processInfo.hprocess,0
.else
invoke DestroyWindow,hWnd
.endif
.endif
.ELSE
invoke DefWindowproc,hWnd,uMsg,wparam,lparam
ret
.ENDIF
xor eax,eax
ret
Wndproc endp
end start
Разбор полетов:
Программа создает основное окно и получает хэндл меню для последующего использования. Затем она ждет, пока пользователь выберет команду в меню.
Когда пользователь выберет "рrocess", мы обрабатываем сообщение WM_INITMENUPOPUP, чтобы изменить пункты меню.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
 .ELSEIF uMsg==WM_INITMENUpOpUp
invoke GetExitCodeprocess,processInfo.hprocess,­ADDR ExitCode
.if eax==TRUE
.if ExitCode==STILL_ACTIVE
invoke EnableMenuItem,hMenu,IDM_CREATE_pROCESS,­MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_EN­ABLED
.else
invoke EnableMenuItem,hMenu,IDM_CREATE_pROCESS,­MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GR­AYED
.endif
.else
invoke EnableMenuItem,hMenu,IDM_CREATE_pROCESS,­MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GR­AYED
.endif
Почему мы хотим обработать это сообщение? Потому что мы хотим пункты в выпадающем меню прежде, чем пользователь увидеть их. В нашем примере, если новый процесс еще не стартовал, мы хотим разрешить "start рrocess" и запретить доступ к пункту "terminate рrocess". Мы делаем обратное, если программа уже запущена.
Вначале мы проверяем, активен ли еще новый процесс, вызывая функцию GetExitCodeрrocess и передавая ей хэндл процесса, полученный при вызове CreateProcess. Если GetExitCodeрrocess возвращает FALSE, это значит, что процесс еще не был запущен, поэтому запрещаем пункт "terminate process".
Если GetExitCodeProcess возвращает TRUE, мы знаем, что новый процесс уже стартовал, мы должны проверить, выполняется ли он еще. Поэтому мы сравниваем значение в ExitCode со значением STILL_ACTIVE, если они равны, процесс еще выполняется: мы должны запретить пункт меню "start process", так как мы не хотим, чтобы запустилось несколько совпадающих процессов.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
 .if ax==IDM_CREATE_pROCESS
.if processInfo.hprocess!=0
invoke CloseHandle,processInfo.hprocess
mov processInfo.hprocess,0
.endif
invoke GetStartupInfo,ADDR startInfo
invoke Createprocess,ADDR programname,NULL,NULL,NULL,FALSE,\
NORMAL_pRIORITY_CLASS,\
NULL,NULL,ADDR startInfo,ADDR processInfo
invoke CloseHandle,processInfo.hThread
Когда пользователь выбирает пункт "start рrocess", мы вначале проверяем, закрыт ли уже параметр hProcess структуры PROCESS_INFORMATION. Если это в первый раз, значение hProcess будет всегда равно нулю, так как мы определяем структуру PROCESS_INFORMATION в секции .data. Если значение параметра hProcess не равно нулю, это означает, что дочерний процесс вышел, но мы не закрыли его хэндл. Поэтому пришло время сделать это.

Мы вызываем функцию GetSturtuрInfo, чтобы заполнить структуру sturtupinfo, которую передаем функцию CreateProcess. После этого мы вызываем функцию CreateProcess. Заметьте, что я не проверил возвращаемое ей значение, потому что это усложнило бы пример. Вам следует проверять это значение. Сразу же после CreateProcess, мы закрываем хэндл основной ветви, возвращаемой в структуре рrocessInfo. Закрытие хэндла не означает, что мы прерываем ветвь, только то, что мы не хотим использовать хэндл для обращения к ветви из нашей программы. Если мы не закроем его, это вызовет потерю ресурсов.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
 .elseif ax==IDM_TERMINATE
invoke GetExitCodeProcess,processInfo.hProcess,­ADDR ExitCode
.if ExitCode==STILL_ACTIVE
 
invoke TerminateProcess,processInfo.hProcess,0
.endif
invoke CloseHandle,processInfo.hProcess
mov processInfo.hProcess,0
Когда пользователь выберет пункт меню "terminate рrocess", мы проверяем, активен ли еще новый процесс, вызвав функцию GetExitCodeProcess. Если он еще активен, мы вызываем функцию TerminateProcess, чтобы убить его. Также мы закрываем хэндл дочернего процесса, так как он больше нам не нужен.
________________________________________ ­__
© Iczelion, пер. Aquila.
Вложения
Тип файла: zip tut14.zip (4.1 Кб, 108 просмотров)
3
Ушел с форума
Автор FAQ
 Аватар для Mikl___
16377 / 7689 / 1080
Регистрация: 11.11.2010
Сообщений: 13,769
15.01.2013, 10:33  [ТС]
Win32 API. Урок 14a. Процесс
Скачайте пример здесь.
Практика ― сестра шизофрении
Кликните здесь для просмотра всего текста
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
.586p
.model tiny
include windows.inc
;for WinXP - 1153 bytes
du macro string
local bslash
bslash=0
irpc c,<string>
if bslash eq 0
if '&c' eq "/"
bslash=1
elseif '&c'gt 127
db ('&c'- 0B0h),4
else
dw '&c'
endif
else
bslash=0
if '&c' eq "n"
DW 0Dh,0Ah
elseif '&c' eq "/"
dw '/'
elseif '&c' eq "r"
dw 0Dh
elseif '&c' eq "l"
dw 0Ah
elseif '&c' eq "s"
dw 20h
elseif '&c' eq "c"
dw 3Bh
elseif '&c' eq "t"
dw 9
endif
endif
endm
dw 0
endm
.code
exebase equ 400000h
MI_PROCESS_CREATE equ 0
MI_PROCESS_TERMINATE equ 1
MI_EXIT equ 2
IDC_MENU equ 30
main:
include capito_res.asm
;---------------------------------------------------------------------
start: xor ebx,ebx
mov esi,exebase
mov edi,offset wTitle+exebase
;------------------------------
; registering the window class
;------------------------------
invoke RegisterClass,esp,ebx,offset window_procedure+exebase,ebx,\
ebx,esi,ebx,10011h,COLOR_WINDOW+1,IDC_ME­NU,edi
; +--------------------------+
; | creating the main window |
; +--------------------------+
push ebx
push esi
shl esi,9
invoke CreateWindowEx,WS_EX_CLIENTEDGE,edi,edi,­\
WS_OVERLAPPEDWINDOW or WS_VISIBLE,esi,esi,300,200,ebx,ebx
invoke GetMenu,eax;[hWnd]
mov hMenu+exebase,eax
mov ebp,esp
; +---------------------------+
; | entering the message loop |
; +---------------------------+
message_loop: invoke GetMessage,ebp,ebx,ebx,ebx
invoke DispatchMessage,ebp
jmp message_loop
; +----------------------+
; | the window procedure |
; +----------------------+
window_procedure:
hWnd equ ebp+8h
uMsg equ ebp+0Ch
wParam equ ebp+10h
lParam equ ebp+14h
enter sizeof(STARTUPINFO),0
mov eax,[uMsg]
mov edi,offset processInfo+exebase
mov esi,offset proExitCode+exebase
dec eax
dec eax; cmp uMsg,WM_DESTROY
je wmDESTROY
sub eax,WM_COMMAND-WM_DESTROY; cmp uMsg,WM_PAINT
je wmCOMMAND
sub eax,WM_INITMENUPOPUP-WM_COMMAND;cmp [uMsg],WM_INITMENUPOPUP
je wmINITMENUPOPUP
leave
jmp DefWindowProc+exebase
 
wmINITMENUPOPUP: invoke GetExitCodeProcess,[edi+PROCESS_INFORMATION.hProcess],esi
xchg eax,ecx
jecxz a2;GetExitCodeProcess_TRUE
cmp dword ptr [esi],STILL_ACTIVE;cmp [proExitCode],STILL_ACTIVE
jne a2; GetExitCodeProcess_STILL_ACTIVE
push ebx
push MF_GRAYED
jmp a5
a2: push MF_GRAYED
push ebx;MF_ENABLED
a5: invoke EnableMenuItem,[hMenu+exebase],ebx;MI_PROCESS_CREATE
invoke EnableMenuItem,[hMenu+exebase],MI_PROCESS_TERMINATE
jmp wmBYE
wmCOMMAND: mov ax,[wParam]
cmp dword ptr [lParam],ebx
jne wmBYE
jmp dword ptr [menu_handlers+eax*4+exebase]
PROCESS_CREATE: cmp [edi+PROCESS_INFORMATION.hProcess],ebx
je pi_hProcess_IS_0;a3;
invoke CloseHandle,[edi+PROCESS_INFORMATION.hProcess]
mov [edi+PROCESS_INFORMATION.hProcess],ebx
pi_hProcess_IS_0: mov esi,esp;[progStartInfo]
invoke GetStartupInfo,esi
invoke CreateProcess,offset progName+exebase,ebx,ebx,ebx,ebx,\
NORMAL_PRIORITY_CLASS,ebx,ebx,esi,edi
invoke CloseHandle,[edi+PROCESS_INFORMATION.hThread]
jmp wmBYE
TERMINATE: invoke GetExitCodeProcess,[edi+PROCESS_INFORMATION.hProcess],esi;proExitCode
cmp dword ptr [esi],STILL_ACTIVE
jne proExitCode_NOT_STILL_ACTIVE;a4;
invoke TerminateProcess,[edi+PROCESS_INFORMATION.hProcess],ebx
proExitCode_NOT_STILL_ACTIVE: invoke CloseHandle,[edi+PROCESS_INFORMATION.hProcess]
mov [edi+PROCESS_INFORMATION.hProcess],ebx;0
jmp wmBYE
EXIT: invoke DestroyWindow,dword ptr [hWnd]
wmBYE: leave
retn 10h
wmDESTROY: invoke ExitProcess,ebx
;--------данные------------------------------
wTitle db 'Iczelion Tutorial #14:Process in MASM',0;name of our window
menu_handlers dd PROCESS_CREATE+exebase, TERMINATE+exebase, EXIT+exebase
progName db 'tut02.exe',0
;-------------------------------------------------------------------------------------------
MFR_END equ 80h
MFR_POPUP equ 1
MFT_STRING equ 0
MFS_ENABLED equ 0
MFT_SEPARATOR equ 800h
RT_MENU equ 4
 
POPUP equ 0010h
MENUBREAK equ 0040h
ENDMENU equ 0080h
DS_SETFONT equ 0040h
align 4
resource:
Characteristics0 dd 0
TimeDateStamp0 dd 0
MajorVersion0 dw 0
MinorVersion0 dw 0
NumberOfNamedEntries0 dw 0
NumberOfIdEntries0 dw 1
dw RT_MENU,0
dw x-resource,8000h
x:
Characteristics1 dd 0
TimeDateStamp1 dd 0
MajorVersion1 dw 0
MinorVersion1 dw 0
NumberOfNamedEntries1 dw 0
NumberOfIdEntries1 dw 1
dw IDC_MENU,0
dw x1-resource,8000h
x1:
Characteristics2 dd 0
TimeDateStamp2 dd 0
MajorVersion2 dw 0
MinorVersion2 dw 0
NumberOfNamedEntries2 dw 0
NumberOfIdEntries2 dw 1
dw 40Ah,0,x2-resource,0
x2:;struct _IMAGE_RESOURCE_DATA_ENTRY
OffsetToData0 dd menu
Size0 dd end_menu-menu
CodePage0 dd 0
Reserved0 dd 0
menu dw 0,0,POPUP or MFR_END
du <&Process>
dw MFT_STRING or MFS_ENABLED,MI_PROCESS_CREATE
du <&Create Process>
dw MFT_STRING or MFS_ENABLED or MF_GRAYED,MI_PROCESS_TERMINATE
du <&Terminate Process>
dw MENUBREAK,0,0;NOTEXT
dw MFT_STRING or MFS_ENABLED or MFR_END,MI_EXIT
du <&Exit>
end_menu:
end_resource:
;---------------------------------------------------------------------
import:
hMenu dd 0 ;menu handle
proExitCode dd 0 ;process exit code
dd 0,user32_dll ,user_table
dd 0,0,0,kernel32_dll,kernel_table
processInfo PROCESS_INFORMATION <0>;dd 0,0,0,0
user_table:
RegisterClass dd _RegisterClass
CreateWindowEx dd _CreateWindowEx
GetMessage dd _GetMessage
DispatchMessage dd _DispatchMessage
DefWindowProc dd _DefWindowProc
DestroyWindow dd _DestroyWindow
GetMenu dd _GetMenu
EnableMenuItem dd _EnableMenuItem,0
 
kernel_table:
GetExitCodeProcess dd _GetExitCodeProcess
GetStartupInfo dd _GetStartupInfo
CreateProcess dd _CreateProcess
TerminateProcess dd _TerminateProcess
CloseHandle dd _CloseHandle
ExitProcess dd _ExitProcess,0
 
_RegisterClass db 0,0,'RegisterClassA'
_CreateWindowEx db 0,0,'CreateWindowExA'
_GetMessage db 0,0,'GetMessageA'
_DispatchMessage db 0,0,'DispatchMessageA'
_DefWindowProc db 0,0,'DefWindowProcA'
_DestroyWindow db 0,0,"DestroyWindow"
_GetMenu db 0,0,'GetMenu'
_EnableMenuItem db 0,0,'EnableMenuItem',0
user32_dll db 'user32'
_GetExitCodeProcess db 0,0,'GetExitCodeProcess'
_GetStartupInfo db 0,0,'GetStartupInfoA'
_CreateProcess db 0,0,'CreateProcessA'
_TerminateProcess db 0,0,'TerminateProcess'
_CloseHandle db 0,0,'CloseHandle'
_ExitProcess db 0,0,'ExitProcess',0
kernel32_dll db 'kernel32'
end_import:
end main
_____________________________________
© Mikl___ 2013
Вложения
Тип файла: zip tut14a.zip (4.4 Кб, 121 просмотров)
3
Ушел с форума
Автор FAQ
 Аватар для Mikl___
16377 / 7689 / 1080
Регистрация: 11.11.2010
Сообщений: 13,769
15.01.2013, 11:09  [ТС]
Win32 API. 15. Треды (ветви)
Мы узнаем, как создать мультитредную программу. Мы также изучим методы, с помощью которых треды могут общаться друг с другом.

Скачайте пример здесь.
Теория ― мать склероза
В предыдущем туториале, вы изучили процесс, состоящий по крайней мере из одного треда: основного. Тред ― это цепь инструкций. Вы также можете создавать дополнительные треды в вашей программе. Вы можете считать мультитрединг как многозадачность внутри одной программы. Если говорить в терминах непосредственной реализации, тред ― это функция, которая выполняется параллельно с основной программой. Вы можете запустить несколько экземпляров одной и той же функции или вы можете запустить несколько функций одновременно, в зависимости от ваших требований. Мультитрединг свойственен Win32, под Win16 аналогов не существует.

Треды выполняются в том же процесс, поэтому они имеют доступ ко всем ресурсам процесса: глобальным переменным, хэндлам и т.д. Тем не менее, каждый тред имеет свой собственный стек, так что локальные переменные в каждом треде приватны. Каждый тред также имеет свой собственный набор регистров, поэтому когда Windows переключается на другой тред, предыдущий "запоминает" свое состояние и может "восстановить" его, когда он снова получает контроль. Это обеспечивается внутренними средствами Windows. Мы можем поделить треды на две категории:
  1. Тред интерфейса пользователя: тред такого типа создает свое собственное окно, поэтому он получает оконные сообщения. Он может отвечать пользователю с помощью своего окна. Этот тип тредов действуют согласно Win16 Mutex правилу, которое позволяет только один тред пользовательского интерфейсав 16-битном пользовательском и gdi-ядре. Пока один подобный тред выполняет код 16-битного пользовательского и gdi-ядра, другие UI треды не могут использовать сервисы этого ядра. Заметьте, что этот Win16 Mutex свойственен Windows 9x, так как его функции обращаются к 16-битному коду. В Windows NT нет Win16 Mutex'а, поэтому треды пользовательского интерфейса под NT работают более плавно, чем под Windows 95.
  2. рабочий тред: Этот тип тредов не создает окно, поэтому он не может принимать какие-либо windows-сообщения. Он существует только для того, чтобы делать предназначенную ему работу на заднем фоне (согласно своему названию).
Я советую следующую стратегию при использовании мультитредовых способностей Win32: позвольте основному треду делать все, что связанно с пользовательским интерфейсом, а остальным делать тяжелую работу в фоновом режиме. В этому случае, основной тред ― Правитель, другие треды ― его помощники. Правитель поручает им определенные задания, в то время как сам общается с публикой. Его помощники послушно выполняют работу и докладывают об этом Правителю. Если бы Правитель делал всю работу сам, он бы не смог уделять достаточно внимания народу или прессе. Это похоже на окно, которое занято продолжительной работой в основном треде: оно не отвечает пользователю, пока работа не будет выполнена. Такая программа может быть улучшена созданием дополнительного треда, который возьмет часть работы на себя и позволит основной ветви отвечать на команды пользователя.
Мы можем создать тред с помощью вызова функции CreateThread, которая имеет следующий синтаксис:
Кликните здесь для просмотра всего текста
Assembler
1
2
 CreateThread proto lpThreadAttributes:DWORD,dwStackSize:DWO­RD,lpStartAddress:DWORD,\
lpparameter:DWORD,dwCreationFlags:DWORD, lpThreadId:DWORD
Функция CreateThread похожа на CreateProcess.
  • lpThreadAttributes ― Вы можете использовать NULL, если хотите, чтобы у треда были установки безопасности по умолчанию.
  • dwStackSize ― укажите размер стека треда. Если вы хотите, чтобы тред имел такой же размер стека, как и у основного, используйте NULL в качестве параметра.
  • lрStartAddress ― адрес функции треда. Эта функция будет выполнять предназначенную для треда работу. Эта функция должна получать один и только один 32-битный параметр и возвращать 32-битное значение.
  • lрParametr ― Параметр, который вы хотите передать функции треда.
  • dwCreationFlags ― 0 означает, что тред начинает выполняться сразу же после его создания. Для обратного можно использовать флаг CREATE_SUSPEND.
  • lpThreadId ― CreateThread поместит сюда ID созданного треда.
Если вызов CreateThread прошел успешно, она возвращает хэндл созданного треда, в противном случае она возвращает NULL.

Функция треда запускается так скоро, как только заканчивается вызов CreateThread, если только вы не указали флаг CREATE_SUSPENDED. В этом случае тред будет заморожен до вызова функции ResumThread.

Когда функция треда возвращается (с помощью инструкции ret) Windows косвенно вызывает ExitThread для функции треда. Вы можете сами вызвать ExitThread, но в этом немного смысла.
Вы можете получить код выхода треда с помощью функции GetExitCodeThread.

Если вы хотите прервать тред из другого треда, вы можете вызвать функцию TerminateThread. Hо вы должны использовать эту функцию только в экстремальных условиях, так как эта функция немедленно прерывать тред, не давая ему шанса произвести необходимую чистку за собой.

Теперь давайте рассмотрим методы коммуникации между тредами. Вот три из них:
  • Использование глобальных переменных
  • Windows-сообщения
  • События
Треды разделяют ресурсы процесса, включая глобальные переменные, поэтому треды могут использовать их для того, чтобы взаимодействовать друг с другом. Тем не менее, этот метод должен использоваться осторожно.
Синхронизацию нужно внимательно спланировать. Hапример, если два треда используют одну и ту же структуру из 10 членов, что произойдет, если Windows вдруг передаст управление от одного треда другому, когда структура обновлена еще только наполовину. Другой тред получит неправильную информацию!
Hе сделайте никакой ошибки, мультитредовые программы тяжелее отлаживать и поддерживать. Этот тип багов случается непредсказуемо и их очень трудно отловить.
Вы также можете использовать windows-сообщения, чтобы осуществлять взаимодействие между тредами. Если все треды имеют юзерский интерфейс, то нет проблем: этот метод может использоваться для двухсторонней коммуникации. Все, что вам нужно сделать ― это определить один или более дополнительных windows-сообщений, которые будут использоваться тредами.
Вы определяете сообщение, используя значение WM_USER как базовое, например так:
Кликните здесь для просмотра всего текста
Assembler
1
 WM_MYCUSTOMMSG equ WM_USER+100h
Windows не использует сообщения с номером выше WM_USER, поэтому мы можем использовать значение WM_USER и выше для наших собственных сообщений.
Если один из тредов имеет пользовательский интерфейс, а другой является рабочим, вы не можете использовать данный метод для двухстороннего общения, так как у рабочего треда нет своего окна, а следовательно и очереди сообщений. Вы можете использовать следующие схемы:
  • Тред с пользовательским интерфейсом https://www.cyberforum.ru/cgi-bin/latex.cgi?\rightarrow глобальная переменная(ные) https://www.cyberforum.ru/cgi-bin/latex.cgi?\rightarrow рабочий тред
  • рабочий тред https://www.cyberforum.ru/cgi-bin/latex.cgi?\rightarrow windows-сообщение https://www.cyberforum.ru/cgi-bin/latex.cgi?\rightarrow Тред с пользовательским интерфейсом

Фактически, мы будем использовать этот метод в нашем примере.

Последний метод, используемый для коммуникации ― это объект события. Вы можете рассматривать его как своего рода флаг. Если объект события "не установлен", значит тред спит. Когда объект события "установлен", Windows "пробуждает" тред и он начинает выполнять свою работу.
Практика ― сестра шизофрении
Вам следует скачать zip-файл с примером запустить thread1.exe. Hажмите на пункт меню "Savage Calculation". Это даст команду программе выполнить "add eax,eax" 600.000.000 раз. Заметьте, что во время этого времени вы не сможете ничего сделать с главным окном: вы не сможете его двигать, активировать меню и т.д. Когда вычисление закончится, появится окно с сообщением. После этого окно будет нормально реагировать на ваши команды.

Чтобы избежать подобного неудобства для пользователя, мы должны поместить процедуру вычисления в отдельный рабочий тред и позволить основному треду продолжать взаимодействие с пользователем. Вы можете видеть, что хотя основное окно отвечает медленнее, чем обычно, оно все же делает это.
Кликните здесь для просмотра всего текста
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
 .386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
 
.const
IDM_CREATE_THREAD equ 1
IDM_EXIT equ 2
WM_FINISH equ WM_USER+100h
 
.data
ClassName db "Win32ASMThreadClass",0
AppName db "Win32 ASM MultiThreading Example",0
MenuName db "FirstMenu",0
SuccessString db "The calculation is completed!",0
 
.data?
hInstance HINSTANCE ?
CommandLine LpSTR ?
hwnd HANDLE ?
ThreadID DWORD ?
 
.code
start: invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke Exitprocess,eax
 
WinMain proc hInst:HINSTANCE,hprevInst:HINSTANCE,CmdL­ine:LpSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
 
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndproc, OFFSET Wndproc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_AppLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAppEDWINDOW,CW_USEDEFAULT,CW_USE­DEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wparam
ret
WinMain endp
Wndproc proc hWnd:HWND, uMsg:UINT, wparam:WpARAM, lparam:LpARAM
.IF uMsg==WM_DESTROY
invoke postQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wparam
.if lparam==0
.if ax==IDM_CREATE_THREAD
mov eax,OFFSET Threadproc
invoke CreateThread,NULL,NULL,eax,0,ADDR ThreadID
invoke CloseHandle,eax
.else
invoke DestroyWindow,hWnd
.endif
.endif
.ELSEIF uMsg==WM_FINISH
invoke MessageBox,NULL,ADDR SuccessString,ADDR AppName,MB_OK
.ELSE
invoke DefWindowproc,hWnd,uMsg,wparam,lparam
ret
.ENDIF
xor eax,eax
ret
Wndproc endp
 
 
Threadproc proc uses ecx param:DWORD
mov ecx,600000000
Loop1: add eax,eax
dec ecx
jz Get_out
jmp Loop1
Get_out: invoke postMessage,hwnd,WM_FINISH,NULL,NULL
ret
Threadproc endp
end start
Разбор полетов
Основную программу пользователь воспринимает как обычное окно с меню. Если пользователь выбирает в последнем пункт "Создать тред", программа создает тред:
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
 .if ax==IDM_CREATE_THREAD
mov eax,OFFSET Threadproc
invoke CreateThread,NULL,NULL,eax,\
NULL,0,ADDR ThreadID
0invoke CloseHandle,eax
Вышеприведенная функция создает тред, который запустит процедуру под названием Threadрroc параллельно с основным тредом. Если вызов функции прошел успешно, CreateThread немедленно возвращается и Threadproc начинает выполняться. Так как мы не используем хэндл треда, нам следует закрыть его, чтобы не допустить бессмысленное расходование памяти.
Закрытие хэндла не прерывает сам тред. Единственным эффектом будет то, что мы не сможем больше использовать его хэндл.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
 ThreadProc proc uses ecx param:DWORD
mov ecx,600000000
Loop1: add eax,eax
dec ecx
jz Get_out
jmp Loop1
Get_out: invoke postMessage,hwnd,WM_FINISH,NULL,NULL
ret
ThreadProc endp
Как вы можете видеть ThreadProc выполняет подсчет, требующий некоторого времени, и когда она заканчивает его, она отправляет сообщение WM_FINISH основному окну. WM_FINISH ― это наше собственное сообщение, определенное следующим образом:
Кликните здесь для просмотра всего текста
Assembler
1
 WM_FINISH equ WM_USER+100h
Вам не обязательно добавлять к WM_USER 100h, но будет лучше сделать это. Сообщение WM_FINISH имеет значение только в пределах нашей программы. Когда основное окно получает WM_FINISH, она реагирует на это показом окна с сообщением о том, что подсчет закончен.

Вы можете создать несколько тредов, выбрав "Create Thread" несколько раз. В этом примере применяется односторонняя коммуникация, то есть только тред может уведомлять основное окно о чем-либо. Если вы хотите, что основной тред слал команды рабочему, вы должны сделать следующее:
  • добавить пункт меню "Kill Thread".
  • добавить глобальную переменную, используемую в качестве флага. TRUE=остановить тред, FALSE=продолжить тред.
  • Изменить ThreadProc так, чтобы та проверяла в цикле значение флага.
Когда пользователь выберет "Kill Thread", основная программа установит флаг в TRUE. Когда Threadproc видит, что значение флага равно TRUE, она выходит из цикла и возвращается, что заканчивает действие треда.
_____________________________________
© Iczelion, пер. Aquila.
Вложения
Тип файла: zip tut15.zip (4.5 Кб, 105 просмотров)
3
Ушел с форума
Автор FAQ
 Аватар для Mikl___
16377 / 7689 / 1080
Регистрация: 11.11.2010
Сообщений: 13,769
15.01.2013, 11:28  [ТС]
Win32 API. 15a. Треды (ветви)
Скачайте пример здесь.
Вам следует скачать zip-файл с примером запустить thread1.exe. Hажмите на пункт меню "Savage Calculation". Это даст команду программе выполнить "add eax,eax" 600.000.000 раз. Заметьте, что во время этого времени вы не сможете ничего сделать с главным окном: вы не сможете его двигать, активировать меню и т.д. Когда вычисление закончится, появится окно с сообщением. После этого окно будет нормально реагировать на ваши команды.
Чтобы избежать подобного неудобства для пользователя, мы должны поместить процедуру вычисления в отдельный рабочий тред и позволить основному треду продолжать взаимодействие с пользователем. Вы можете видеть, что хотя основное окно отвечает медленнее, чем обычно, оно все же делает это.
Кликните здесь для просмотра всего текста
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
.586p
.model tiny
include windows.inc
;for WinXP - 948 bytes
du macro string
local bslash
bslash=0
irpc c,<string>
if bslash eq 0
if '&c' eq "/"
bslash=1
elseif '&c'gt 127
db ('&c'- 0B0h),4
else
dw '&c'
endif
else
bslash=0
if '&c' eq "n"
DW 0Dh,0Ah
elseif '&c' eq "/"
dw '/'
elseif '&c' eq "r"
dw 0Dh
elseif '&c' eq "l"
dw 0Ah
elseif '&c' eq "s"
dw 20h
elseif '&c' eq "c"
dw 3Bh
elseif '&c' eq "t"
dw 9
endif
endif
endm
dw 0
endm
.code
exebase equ 400000h
IDM_CREATE_THREAD equ 0
IDM_EXIT equ 1
IDC_MENU equ 30
WM_FINISH equ WM_USER+100h
main:
include capito_res.asm
;---------------------------------------------------------------------
start: xor ebx,ebx
mov esi,exebase
mov edi,offset wTitle+exebase
;------------------------------
; registering the window class
;------------------------------
invoke RegisterClass,esp,ebx,offset window_procedure+exebase,\
ebx,ebx,esi,ebx,10011h,COLOR_WINDOW+1,ID­C_MENU,edi
; +--------------------------+
; | creating the main window |
; +--------------------------+
push ebx
push esi
shl esi,9
invoke CreateWindowEx,WS_EX_CLIENTEDGE,edi,edi,­\
WS_OVERLAPPEDWINDOW or WS_VISIBLE,esi,esi,450,200,ebx,ebx
mov hWnd+exebase,eax
mov ebp,esp
; +---------------------------+
; | entering the message loop |
; +---------------------------+
message_loop: invoke GetMessage,ebp,ebx,ebx,ebx
invoke DispatchMessage,ebp
jmp message_loop
; +----------------------+
; | the window procedure |
; +----------------------+
window_procedure:
hwnd equ dword ptr [ebp+8h]
uMsg equ dword ptr [ebp+0Ch]
wParam equ dword ptr [ebp+10h]
lParam equ dword ptr [ebp+14h]
enter sizeof(STARTUPINFO),0
mov eax,uMsg
dec eax
dec eax; cmp uMsg,WM_DESTROY
je wmDESTROY
sub eax,WM_COMMAND-WM_DESTROY
je wmCOMMAND
sub eax,WM_FINISH-WM_COMMAND
je wmFINISH
leave
jmp DefWindowProc+exebase
wmFINISH: invoke MessageBox,ebx,offset wTitle+exebase,offset wTitle+exebase,ebx
jmp wmBYE
wmCOMMAND: movzx eax,word ptr wParam
cmp lParam,ebx
jne wmBYE
jmp dword ptr [menu_handlers+eax*4+exebase]
CREATE_THREAD: invoke CreateThread,ebx,ebx,offset ThreadProc+exebase,ebx,\
NORMAL_PRIORITY_CLASS,offset ThreadID+exebase
invoke CloseHandle,eax
jmp wmBYE
EXIT: invoke DestroyWindow,hwnd
wmBYE: leave
retn 10h
wmDESTROY: invoke ExitProcess,ebx
menu_handlers dd CREATE_THREAD+exebase,EXIT+exebase
ThreadProc proc
mov ecx,30000000
@@: add eax,eax
loop @b
invoke SendMessage,hWnd+exebase,WM_FINISH,ebx,bx
ret
ThreadProc ENDP
;--------данные------------------------------
wTitle db 'Iczelion Tutorial #15: Multithreading Programming',0
;--------------------------------------------------------------
MFR_END equ 80h
MFR_POPUP equ 1
MFT_STRING equ 0
MFS_ENABLED equ 0
MFT_SEPARATOR equ 800h
RT_MENU equ 4
 
POPUP equ 0010h
MENUBREAK equ 0040h
ENDMENU equ 0080h
DS_SETFONT equ 0040h
align 4
resource:
Characteristics0 dd 0
TimeDateStamp0 dd 0
MajorVersion0 dw 0
MinorVersion0 dw 0
NumberOfNamedEntries0 dw 0;количество ресурсов с именами
NumberOfIdEntries0 dw 1;количество ресурсов с идентификаторами
;на этом уровне идентификатор ресурсов является типом ресурса
dw RT_MENU,0;номер типа ресурса
dw x-resource,8000h; если во 2-ом слове установлен старший бит - есть ссылка
;на оглавление второго уровня. В 1-ом слове смещение второго оглавления
;относительно начала раздела ресурсов
x:
Characteristics1 dd 0
TimeDateStamp1 dd 0
MajorVersion1 dw 0
MinorVersion1 dw 0
NumberOfNamedEntries1 dw 0;количество ресурсов с именами
NumberOfIdEntries1 dw 1;количество ресурсов с идентификаторами
;на этом уровне идентификатор ресурсов является идентификатором меню
dw IDC_MENU,0
dw x1-resource,8000h; если во 2-ом слове установлен старший бит - есть ссылка
;на оглавление третьего уровня. В 1-ом слове смещение третьего оглавления
;относительно начала раздела ресурсов
x1:
Characteristics2 dd 0
TimeDateStamp2 dd 0
MajorVersion2 dw 0
MinorVersion2 dw 0
NumberOfNamedEntries2 dw 0;количество ресурсов с именами
NumberOfIdEntries2 dw 1;количество ресурсов с идентификаторами
;на этом уровне идентификатор ресурсов является идентификатором языка, который
;используется данным ресурсом 16 * SUBLANG_ + LANG_
dw 40Ah,0,x2-resource,0
x2:;struct _IMAGE_RESOURCE_DATA_ENTRY
OffsetToData0 dd menu
Size0 dd end_menu-menu
CodePage0 dd 0
Reserved0 dd 0
menu dw 0,0,POPUP or MFR_END
du <&Process>
dw MFT_STRING or MFS_ENABLED,IDM_CREATE_THREAD
du <&Create Thread>
dw MENUBREAK,0,0;NOTEXT
dw MFT_STRING or MFS_ENABLED or MFR_END,IDM_EXIT
du <&Exit>
end_menu:
end_resource:
;---------------------------------------------------------------------
import:
ThreadID dd 0 ;process exit code
hWnd dd 0
dd 0,user32_dll
dd user_table
dd 0,0,0,kernel32_dll
dd kernel_table
dd 0,0,0,0
user_table:
CreateWindowEx dd _CreateWindowEx
DefWindowProc dd _DefWindowProc
DestroyWindow dd _DestroyWindow
DispatchMessage dd _DispatchMessage
GetMessage dd _GetMessage
MessageBox dd _MessageBox
SendMessage dd _SendMessage
RegisterClass dd _RegisterClass,0
kernel_table:
CreateThread dd _CreateThread
CloseHandle dd _CloseHandle
ExitProcess dd _ExitProcess
dw 0
_RegisterClass db 0,0,'RegisterClassA'
_CreateWindowEx db 0,0,'CreateWindowExA'
_GetMessage db 0,0,'GetMessageA'
_DispatchMessage db 0,0,'DispatchMessageA'
_DefWindowProc db 0,0,'DefWindowProcA'
_DestroyWindow db 0,0,"DestroyWindow"
_SendMessage db 0,0,'SendMessageA'
_MessageBox db 0,0,'MessageBoxA',0
user32_dll db 'user32'
_CreateThread db 0,0,'CreateThread'
_CloseHandle db 0,0,'CloseHandle'
_ExitProcess db 0,0,'ExitProcess',0
kernel32_dll db 'kernel32'
end_import:
end main
_____________________________________
© Mikl___ 2013
Вложения
Тип файла: zip tut15a.zip (3.8 Кб, 95 просмотров)
3
Ушел с форума
Автор FAQ
 Аватар для Mikl___
16377 / 7689 / 1080
Регистрация: 11.11.2010
Сообщений: 13,769
15.01.2013, 12:25  [ТС]
Win32 API. Урок 16. Объект события
Мы изучим, что такое объект события и как использовать его в мультитредной программе.
Скачайте пример здесь.
Теория ― мать склероза
В предыдущем туториале я продемонстрировал, как треды взаимодействуют друг с другом через собственные windows-сообщения. Я пропустил два других метода: глобальная переменная и объект события. В этом туториале мы используем оба.
Объект события ― это что-то вроде переключателя: у него есть только два состояния: вкл и выкл. Вы создаете объект события и помещаете его в коде соответствующего треда, где наблюдаете за состояние объекта. Если объект события выключен, ждущие его треды "спать". В подобном состоянии треды мало загружают CPU.
Вы можете создать объект события, вызвав функцию CreateEvent, которая имеет следующий синтаксис:
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
 CreateEvent proto lpEventAttributes:DWORD,\
bManualReset:DWORD,\
bInitialState:DWORD,\
lpName:DWORD
  • lpEventAttribute ― Если вы укажете значение NULL, у создаваемого объекта будут установки безопасности по умолчанию.
  • bManualReset ― Если вы хотите, чтобы Windows автоматически переключал объект события в "выключено", вы должны присвоить этому параметру значение FALSE. Иначе вам надо будет выключить объект вручную с помощью вызова ResetEvent.
  • bInitialStae ― Если вы хотите, чтобы объект события при создании был установлен в положение "включено", укажите TRUE в качестве данного параметра, в противном случае объект события будет установлен в положение "выключен".
  • lpName ― Указатель на ASCIIZ-строку, которая будет именем объекта события. Это имя будет использоваться, когда вы захотите вызвать OpenEvent.
Если вызов прошел успешно, CreateEvent возвратит хэндл на созданный объект события. В противном случае она возвратит NULL.
Вы можете изменять состояние объекта события с помощью двух API-функций:
SetEvent и ResetEvent. Функция SetEvent устанавливает объект события в положение "включено". ResetEvent делает обратное.

Когда объект события создан, вы должны поместить вызов функции WaitForSingleObject в тред, который должен следить за состоянием объекта события. Эта функция имеет следующий синтаксис:
Assembler
1
 WaitForSingleObject proto hObject:DWORD, dwTimeout:DWORD
  • hObject ― Хэндл одного из синхронизационных объектов. Объект события ― это вид синхронизационного события.
  • dwTimeout ― Указывает в миллисекундах время, которое эта функция будет ждать, пока объект события не перейдет во включенное состояние. Если указанное время пройдет, а объект события все еще выключен, WaitForSingleObject вернет управление. Если вы хотите, чтобы функция наблюдала за объектом бесконечно, вы должны указать значение INFINITE в качестве этого параметра.
Практика ― сестра шизофрении
Нижеприведенный пример отображает окно, ожидающее пока пользователь не выберет какую-либо команду из меню. Если пользователь нажмет на "run thread", тред начнет подсчет. Когда счет закончится, появится сообщение, информирующее пользователя о том, что работа выполнена. Во время того, как проводится подсчет, пользователь может выбрать команду "stop thread", чтобы остановить тред.
Кликните здесь для просмотра всего текста
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
 .386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
 
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
 
.const
IDM_START_THREAD equ 1
IDM_STOp_THREAD equ 2
IDM_EXIT equ 3
WM_FINISH equ WM_USER+100h
 
.data
ClassName db "Win32ASMEventClass",0
AppName db "Win32 ASM Event Example",0
MenuName db "FirstMenu",0
SuccessString db "The calculation is completed!",0
StopString db "The thread is stopped",0
EventStop BOOL FALSE
 
.data?
hInstance HINSTANCE ?
CommandLine LpSTR ?
hwnd HANDLE ?
hMenu HANDLE ?
ThreadID DWORD ?
ExitCode DWORD ?
hEventStart HANDLE ?
 
.code
start: invoke GetModuleHandle, NULL
 
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
 
WinMain proc hInst:HINSTANCE,hprevInst:HINSTANCE,CmdL­ine:LpSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
 
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndproc, OFFSET Wndproc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_AppLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,\
ADDR AppName,WS_OVERLAppEDWINDOW,CW_USEDEFAUL­T,\
CW_USEDEFAULT,300,200,NULL,NULL,hInst,NU­LL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
invoke GetMenu,hwnd
mov hMenu,eax
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wparam
ret
WinMain endp
 
Wndproc proc hWnd:HWND, uMsg:UINT, wparam:WpARAM, lparam:LpARAM
.IF uMsg==WM_CREATE
invoke CreateEvent,NULL,FALSE,FALSE,NULL
mov hEventStart,eax
mov eax,OFFSET Threadproc
invoke CreateThread,NULL,NULL,eax,NULL,0,ADDR ThreadID
invoke CloseHandle,eax
.ELSEIF uMsg==WM_DESTROY
invoke postQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wparam
.if lparam==0
.if ax==IDM_START_THREAD
invoke SetEvent,hEventStart
invoke EnableMenuItem,hMenu,IDM_START_THREAD,MF­_GRAYED
invoke EnableMenuItem,hMenu,IDM_STOp_THREAD,MF_­ENABLED
.elseif ax==IDM_STOP_THREAD
mov EventStop,TRUE
invoke EnableMenuItem,hMenu,IDM_START_THREAD,MF­_ENABLED
invoke EnableMenuItem,hMenu,IDM_STOP_THREAD,MF_­GRAYED
.else
invoke DestroyWindow,hWnd
.endif
.endif
.ELSEIF uMsg==WM_FINISH
invoke MessageBox,NULL,ADDR SuccessString,ADDR AppName,MB_OK
.ELSE
invoke DefWindowproc,hWnd,uMsg,wparam,lparam
ret
.ENDIF
xor eax,eax
ret
Wndproc endp
Threadproc proc uses ecx param:DWORD
invoke WaitForSingleObject,hEventStart,INFINITE
mov ecx,600000000
.WHILE ecx!=0
.if EventStop!=TRUE
add eax,eax
dec ecx
.else
invoke MessageBox,hwnd,ADDR StopString,ADDR AppName,MB_OK
mov EventStop,FALSE
jmp Threadproc
.endif
.ENDW
invoke PostMessage,hwnd,WM_FINISH,NULL,NULL
invoke EnableMenuItem,hMenu,IDM_START_THREAD,MF­_ENABLED
invoke EnableMenuItem,hMenu,IDM_STOp_THREAD,MF_­GRAYED
jmp Threadproc
ret
Threadproc endp
end start
Анализ:
В этом примере я демонстрирую другую технику работы с тредами.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
 .IF uMsg==WM_CREATE
invoke CreateEvent,NULL,FALSE,FALSE,NULL
mov hEventStart,eax
mov eax,OFFSET Threadproc
invoke CreateThread,NULL,NULL,eax,\
NULL,0,ADDR ThreadID
invoke CloseHandle,eax
Вы можете видеть, что я создал объект события и тред во время обработки сообщения WM_CREATE. Я создаю объект события, установленного в состояние "выключено" и обладающего свойством автоматического выключения. После того, как объект события создан, я создаю тред. Тем не менее, тред не начинает выполняться немедленно, так как он ждет, пока не включится объект события:
Кликните здесь для просмотра всего текста
Assembler
1
2
3
 Threadproc proc uses ecx param:DWORD
invoke WaitForSingleObject,hEventStart,INFINITE
mov ecx,600000000
Первая строка процедуры треда ― это вызов WainForSingleObject. Она ждет, пока не включится объект события, а затем возвращается. Это означает, что даже если тред создан, мы помещаем его в спящее состояние.
Когда пользователь выбирает в меню команду "run thread", мы включаем объект события:
Кликните здесь для просмотра всего текста
Assembler
1
2
 .if ax==IDM_START_THREAD
invoke SetEvent,hEventStart
Вызов SetEvent включает объект события, после чего WainForSingleObject возвращается и тред начинает выполняться. Когда пользователь выбирает команду "stoр thread", мы устанавливаем значение глобальной переменной в TRUE.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
 .if EventStop==FALSE
add eax,eax
dec ecx
.else
invoke MessageBox,hwnd,ADDR StopString,ADDR AppName,MB_OK
mov EventStop,FALSE
jmp Threadproc
.endif
Это останавливает тред и снова передает управление функции WaitForSingleObject. Заметьте, что мы не должны вручную выключать объект, так как мы указали при вызове функции CreateEvent, что значение bManualReset равно FALSE.
_____________________________________
© Iczelion, пер. Aquila.
Вложения
Тип файла: zip tut16.zip (3.8 Кб, 94 просмотров)
3
Ушел с форума
Автор FAQ
 Аватар для Mikl___
16377 / 7689 / 1080
Регистрация: 11.11.2010
Сообщений: 13,769
15.01.2013, 13:03  [ТС]
Win32 API. Урок 16a. Объект события
Скачайте пример здесь.
Практика ― сестра шизофрении
Кликните здесь для просмотра всего текста
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
.586p
.model tiny
include windows.inc
;for WinXP - 1263 bytes
du macro string
local bslash
bslash=0
irpc c,<string>
if bslash eq 0
if '&c' eq "/"
bslash=1
elseif '&c'gt 127
db ('&c'- 0B0h),4
else
dw '&c'
endif
else
bslash=0
if '&c' eq "n"
DW 0Dh,0Ah
elseif '&c' eq "/"
dw '/'
elseif '&c' eq "r"
dw 0Dh
elseif '&c' eq "l"
dw 0Ah
elseif '&c' eq "s"
dw 20h
elseif '&c' eq "c"
dw 3Bh
elseif '&c' eq "t"
dw 9
endif
endif
endm
dw 0
endm
.code
exebase equ 400000h
IDM_START_THREAD equ 0
IDM_STOP_THREAD equ 1
IDM_EXIT equ 2
IDC_MENU equ 30
WM_FINISH equ WM_USER+100h
main:
include capito_res.asm
;---------------------------------------------------------------------
start: xor ebx,ebx
mov esi,exebase
mov edi,offset wTitle+exebase
;------------------------------
; registering the window class
;------------------------------
invoke RegisterClass,esp,ebx,offset window_procedure+exebase,\
ebx,ebx,esi,ebx,10011h,COLOR_WINDOW+1,ID­C_MENU,edi
;+--------------------------+
;| creating the main window |
;+--------------------------+
push ebx
push esi
shl esi,9
invoke CreateWindowEx,WS_EX_CLIENTEDGE,edi,edi,­\
WS_OVERLAPPEDWINDOW or WS_VISIBLE,esi,esi,400,200,ebx,ebx
mov hwnd+exebase,eax
invoke GetMenu,eax;[hWnd]
mov hMenu+exebase,eax
invoke CreateEvent,ebx,ebx,ebx,ebx
mov hEventStart+exebase,eax
invoke CreateThread,ebx,ebx,offset ThreadProc+exebase,\
ebx,NORMAL_PRIORITY_CLASS,offset ThreadID+exebase
mov hThread+exebase,eax
mov ebp,esp
;+---------------------------+
;| entering the message loop |
;+---------------------------+
message_loop: invoke GetMessage,ebp,ebx,ebx,ebx
invoke DispatchMessage,ebp
jmp message_loop
;+----------------------+
;| the window procedure |
;+----------------------+
window_procedure:
hWnd equ dword ptr [ebp+8h]
uMsg equ dword ptr [ebp+0Ch]
wParam equ dword ptr [ebp+10h]
lParam equ dword ptr [ebp+14h]
push ebp
mov ebp,esp
mov eax,uMsg
dec eax
dec eax; cmp uMsg,WM_DESTROY
je wmDESTROY
sub eax,WM_COMMAND-WM_DESTROY
je wmCOMMAND
sub eax,WM_FINISH-WM_COMMAND
je wmFINISH
leave
jmp DefWindowProc+exebase
wmDESTROY: invoke ExitProcess,ebx
wmCOMMAND: mov eax,wParam
cmp lParam,ebx
jne wmBYE
jmp dword ptr [menu_handlers+eax*4+exebase]
START_THREAD: invoke SetEvent,hEventStart+exebase
xor esi,esi;esi=MF_GRAYED
inc esi
jmp @f
STOP_THREAD: or EventStop+exebase,TRUE
xor esi,esi;esi=MF_ENABLED
@@: invoke EnableMenuItem,hMenu+exebase,ebx,esi
xor esi,1
invoke EnableMenuItem,hMenu+exebase,IDM_STOP_TH­READ,esi
jmp wmBYE
wmFINISH: invoke MessageBox,ebx,offset SuccessString+exebase,offset wTitle+exebase,ebx;MB_OK=0
jmp wmBYE
EXIT: invoke DestroyWindow,hWnd+exebase
wmBYE: leave
retn 10h
ThreadProc: invoke WaitForSingleObject,hEventStart+exebase,­INFINITE
mov ecx,600000000
@@: cmp EventStop+exebase,ebx;.if EventStop+exebase==FALSE
jne @f
add eax,eax
loop @b
invoke PostMessage,hwnd+exebase,WM_FINISH,ebx,bx
invoke EnableMenuItem,hMenu+exebase,ebx,ebx;IDM­_START_THREAD,MF_ENABLED
invoke EnableMenuItem,hMenu+exebase,IDM_STOP_TH­READ,MF_GRAYED
jmp ThreadProc
@@: invoke MessageBox,hwnd+exebase,offset StopString+exebase,offset wTitle+exebase,ebx;MB_OK=0
mov EventStop+exebase,ebx;FALSE
jmp ThreadProc
;--------данные------------------------------
wTitle db 'Iczelion Tutorial #16: ASM Event Example',0;name of our window
menu_handlers dd START_THREAD+exebase, STOP_THREAD+exebase, EXIT+exebase
SuccessString db "The calculation is completed!",0
StopString db "The thread is stopped",0
;-------------------------------------------------------------------------------------------
MFR_END equ 80h
MFR_POPUP equ 1
MFT_STRING equ 0
MFS_ENABLED equ 0
MFT_SEPARATOR equ 800h
RT_MENU equ 4
 
POPUP equ 0010h
MENUBREAK equ 0040h
ENDMENU equ 0080h
DS_SETFONT equ 0040h
align 4
resource:
Characteristics0 dd 0
TimeDateStamp0 dd 0
MajorVersion0 dw 0
MinorVersion0 dw 0;
NumberOfNamedEntries0 dw 0;количество ресурсов с именами
NumberOfIdEntries0 dw 1;количество ресурсов с идентификаторами
;на этом уровне идентификатор ресурсов является типом ресурса
dw RT_MENU,0;номер типа ресурса
dw x-resource,8000h; если во 2-ом слове установлен старший бит - есть ссылка
;на оглавление второго уровня. В 1-ом слове смещение второго оглавления
;относительно начала раздела ресурсов
x:
Characteristics1 dd 0
TimeDateStamp1 dd 0
MajorVersion1 dw 0
MinorVersion1 dw 0;
NumberOfNamedEntries1 dw 0;количество ресурсов с именами
NumberOfIdEntries1 dw 1;количество ресурсов с идентификаторами
;на этом уровне идентификатор ресурсов является идентификатором меню
dw IDC_MENU,0
dw x1-resource,8000h; если во 2-ом слове установлен старший бит - есть ссылка
;на оглавление третьего уровня. В 1-ом слове смещение третьего оглавления
;относительно начала раздела ресурсов
x1:
Characteristics2 dd 0
TimeDateStamp2 dd 0
MajorVersion2 dw 0
MinorVersion2 dw 0;
NumberOfNamedEntries2 dw 0;количество ресурсов с именами
NumberOfIdEntries2 dw 1;количество ресурсов с идентификаторами
;на этом уровне идентификатор ресурсов является идентификатором языка, который
;используется данным ресурсом 16 * SUBLANG_ + LANG_
dw 40Ah,0,x2-resource,0
x2:;struct _IMAGE_RESOURCE_DATA_ENTRY
OffsetToData0 dd menu
Size0 dd end_menu-menu
CodePage0 dd 0
Reserved0 dd 0
menu dw 0,0,POPUP or MFR_END
du <&Thread>
dw MFT_STRING or MFS_ENABLED,IDM_START_THREAD
du <&Run Thread>
dw MFT_STRING or MFS_ENABLED or MF_GRAYED,IDM_STOP_THREAD
du <&Stop Thread>
dw MENUBREAK,0,0;NOTEXT
dw MFT_STRING or MFS_ENABLED or MFR_END,IDM_EXIT
du <&Exit>
end_menu:
end_resource:
;---------------------------------------------------------------------
import:
hMenu dd 0 ;menu handle
EventStop BOOL FALSE
hEventStart dd 0
dd user32_dll
dd user_table
hwnd dd 0
ThreadID dd 0
hThread dd 0
dd kernel32_dll
dd kernel_table
dd 0,0,0,0
user_table:
RegisterClass dd _RegisterClass
PostMessage dd _PostMessage
CreateWindowEx dd _CreateWindowEx
GetMessage dd _GetMessage
DispatchMessage dd _DispatchMessage
DefWindowProc dd _DefWindowProc
DestroyWindow dd _DestroyWindow
GetMenu dd _GetMenu
MessageBox dd _MessageBox
EnableMenuItem dd _EnableMenuItem,0
 
kernel_table:
SetEvent dd _SetEvent
CreateEvent dd _CreateEvent
CreateThread dd _CreateThread
WaitForSingleObject dd _WaitForSingleObject
ExitProcess dd _ExitProcess
dw 0
_RegisterClass db 0,0,'RegisterClassA'
_PostMessage db 0,0,'PostMessageA'
_CreateWindowEx db 0,0,'CreateWindowExA'
_GetMessage db 0,0,'GetMessageA'
_DispatchMessage db 0,0,'DispatchMessageA'
_DefWindowProc db 0,0,'DefWindowProcA'
_DestroyWindow db 0,0,"DestroyWindow"
_GetMenu db 0,0,'GetMenu'
_MessageBox db 0,0,'MessageBoxA'
_EnableMenuItem db 0,0,'EnableMenuItem',0
user32_dll db 'user32'
_SetEvent db 0,0,'SetEvent'
_CreateEvent db 0,0,'CreateEventA'
_CreateThread db 0,0,'CreateThread'
_WaitForSingleObject db 0,0,'WaitForSingleObject'
_ExitProcess db 0,0,'ExitProcess',0
kernel32_dll db 'kernel32'
end_import:
end main
_____________________________________
© Mikl___ 2013
Вложения
Тип файла: zip tut16a.zip (4.3 Кб, 96 просмотров)
3
Ушел с форума
Автор FAQ
 Аватар для Mikl___
16377 / 7689 / 1080
Регистрация: 11.11.2010
Сообщений: 13,769
16.01.2013, 05:08  [ТС]
Win32 API. Урок 17. Динамические библиотеки
В этом туториале мы узнаем о dll, что это такое и как их создавать.

Вы можете скачать пример здесь.
ТЕОРИЯ ― МАТЬ СКЛЕРОЗА

Если вы программируете достаточно долго, вы заметите, что программы, которые вы пишете, зачастую используют один и те же общие процедуры. Из-за того, что вам приходиться переписывать их снова и снова, вы теряете время. Во времена DOS'а программисты сохраняли эти общие процедуры в одной или более библиотеках. Когда они хотели использовать эти функции, они всего лишь прилинковывали библиотеку к объектному файлу и линкер извлекал функции прямо из библиотек и вставлял их в финальный файл. Этот процесс называется статической линковкой. Хорошим примером являются стандартные библиотеки в C. У этого метода есть изъян ― то, что в каждой программе у вас находятся абсолютно одинаковые копии функций. Впрочем, для ДОС-овских программ это не очень большой недостаток, так как только одна программа могла быть активной в памяти, поэтому не происходила трата драгоценной памяти.

Под Windows ситуация стала более критичной, так как у вас может быть несколько программ, выполняющихся одновременно. Память будет быстро пожираться, если ваша программа достаточно велика. У Windows есть решение этой проблемы: динамические библиотеки (dynamic link librariesdll).
Динамическая библиотека ― это что-то вроде сборника общих функций. Windows не будет загружать несколько копий DLL в память; даже если одновременно выполняются несколько экземпляров вашей программы, будет только одна копия DLL в памяти. Здесь я должен остановиться и разъяснить чуть поподробнее. В реальности, у всех процессов, использующих одну и ту же dll есть своя копия этой библиотеки, однако Windows делает так, чтобы все процессы разделяли один и тот же код этой dll. Впрочем, секция данных копируется для каждого процесса.

Программа линкуется к DLL во время выполнения в отличии от того, как это осуществлялось в старых статических библиотеках. Вы также можете выгрузить DLL во время выполнения, если она вам больше не нужна. Если программа одна использует эту DLL, тогда та будет выгружена немедленно. Hо если ее еще используют какие-то другие программы, DLL останется в памяти, пока ее не выгрузит последняя из использующих ее программ.

Как бы то ни было, перед линкером стоит сложная задача, когда он проводит фиксирование адресов в конечном исполняемом файле. Так как он не может "извлечь" функции и вставить их в финальный исполняемый файл, он должен каким-то образом сохранить достаточно информации о DLL и используемых функциях в выходном файле, чтобы тот смог найти и загрузить верную DLL во время выполнения.

И тут в дело вступают библиотеки импорта. Библиотека импорта содержит информацию о DLL, которую она представляет. Линкер может получить из нее необходимую информацию и вставить ее в исполняемый файл.

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

Вы можете загрузить DLL самостоятельно, не полагаясь на Windows-загрузчик.
  • В этом случае вам не потребуется библиотека импорта, поэтому вы сможете загружать и использовать любую DLL, даже если к ней не прилагается библиотеки импорта. Тем не менее, вам все равно нужно знать какие функции находятся внутри нее, сколько параметров они принимают и тому подобную информацию.
  • Когда вы поручаете Windows загружать DLL, если та отсутствует, Windows выдаст сообщение "Требуемый .DLL-файл, xxxxx.dll отсутствует" и все! Ваша программ не может сделать ничего, что изменить это, даже если ваша dll не является необходимой. Если же вы будете загружать DLL самостоятельно и библиотека не будет найдена, ваша программа может выдать пользователю сообщение, уведомляющее об этом, и продолжить работу.
  • Вы можете вызывать недокументированные функции, которые не включены в библиотеки импорта, главное, чтобы у вас было достаточно информации об этих функциях.
  • Если вы используете LoadLibrary, вам придется вызывать GetprocAddress для каждой функции, которую вы заходите вызвать. GetprocAddress получает адрес входной точки функции в определенной DLL. Поэтому ваш код будет чуть-чуть больше и медленнее, но не намного.
Теперь, рассмотрев преимущества и недостатки использования LoadLibrary, мы подробно рассмотрим как создать DLL.

Следующий код является каркасом DLL.
Содержимое файла DLLSkeleton.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
.386
.model flat,stdcall
 
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
 
 
.data
.code
DllEntry proc hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORD
mov eax,TRUE
 
ret
DllEntry Endp
 
;----------------------------------------------------------------------------
; Это функция-пустышка - она ничего не делает. Я поместил ее сюда, чтобы
; показать, как вставляют функции в DLL.
;----------------------------------------------------------------------------
TestFunction proc
ret
TestFunction endp
 
 
End DllEntry
Содержимое файла DLLSkeleton.def
Кликните здесь для просмотра всего текста
Code
1
2
 LIBRARY DLLSkeleton
EXPORTS TestFunction
Вышеприведенная программа ― это каркас DLL. Каждая DLL должна иметь стартовую функцию. Windows вызывает эту функцию каждый pаз, когда:
  • DLL загружена в первый раз
  • DLL выгружена
  • Создается тред в том же процессе
  • Тред разрушен в том же процессе
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
 DllEntry proc hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORD
 
mov eax,TRUE
ret
DllEntry Endp
Вы можете назвать стартовую функцию как пожелаете, главное чтобы был END. Эта функция получает три параметра, только первые два из них важны.
  • hInstDLL ― это хэндл модуля DLL. Это не тоже самое, что хэндл процесса. Вам следует сохранить это значение, так как оно понадобится вам позже. Вы не сможете ее получить в дальнейшем легко.
  • reason ― может иметь одно из следующих четырех значений:
    • DLL_PROCESS_ATTACH ― DLL получает это значение, когда впервые загружается в адресное пространство процесса. Вы можете использовать эту возможность для того, чтобы осуществить инициализацию.
    • DLL_PROCESS_DETACK ― DLL получает это значение, когда выгружается из адресного пространства процесса. Вы можете использовать эту возможность для того, чтобы "почистить" за собой: освободить память и так далее.
    • DLL_THREAD_ATTACK ― DLL получает это значение, когда процесс создает новую ветвь.
    • DLL_THREAD_DETACK ― DLL получает это значение, когда ветвь в процессе уничтожена.
Вы возвращаете TRUE в eax, если вы хотите, чтобы DLL продолжала выполняться. Если вы возвратите FALSE, DLL не будет загружена. Например, если ваш инициализационный код должен зарезервировать память и он не может это сделать, стартовой функции следует возвратить FALSE, чтобы показать, что DLL не может запуститься.

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

DLL требуется DEF-файл на стадии разработки. Мы сейчас посмотрим, что это такое.
Кликните здесь для просмотра всего текста
Code
1
2
 LIBRARY DLLSkeleton
EXPORTS TestFunction
Обычно у вас должна быть первая строка. Ключевое слово LIBRARY определяет внутреннее имя модуля DLL. Желательно, чтобы оно совпадало с именем файла.

EXPORTS говорит линкеру, какие функции в DLL экспортируются, то есть, могут вызываться из других программ. В прилагающемся примере нам нужно, чтобы другие модули могли вызывать TestFunction, поэтому мы указываем здесь ее имя.

Другое отличие заключается в параметрах, передаваемых линкеру. Вы должны указать ключи /DLL и /DEF:.

Code
1
 link/DLL /SUBSYSTEM:WINDOWS/DEF:DLLSkeleton.def/LIBpATH:c:\masm32\lib DLLSkeleton.obj
Параметры ассемблера те же самые, обычно /c /coff /Cp. После компиляции вы получите .dll и .lib. Последний файл ― это библиотека импорта, которую вы можете использовать, чтобы прилинковать к другим программам функции из соответствующей .dll.
ПРАКТИКА ― СЕСТРА ШИЗОФРЕНИИ

Далее я покажу вам как использовать LoadLibrary, чтобы загрузить DLL.
Содержимое файла UseDLL.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
55
56
57
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
 
.data
LibName db "DLLSkeleton.dll",0
FunctionName db "TestHello",0
DllNotFound db "Cannot load library",0
AppName db "Load Library",0
FunctionNotFound db "TestHello function not found",0
 
.data?
hLib dd ? ; хэндл библиотеки (DLL)
TestHelloAddr dd ? ; адрес функции TestHello
 
.code
start: invoke LoadLibrary,addr LibName
;-------------------------------------------------------------------------------
; Вызываем LoadLibrary и передаем имя желаемой DLL. Если вызов проходит успешно,
; будет возвращен хэндл библиотеки (DLL). Если нет, то будет возвращен NULL.
; Вы можете передать хэндл библиотеки функции GetрrocAddress или любой другой
; функции, которая требует его в качестве одного из параметров.
;-------------------------------------------------------------------------------
.if eax==NULL
invoke MessageBox,NULL,addr DllNotFound,addr AppName,MB_OK
.else
mov hLib,eax
invoke GetprocAddress,hLib,addr FunctionName
;------------------------------------------------------------------------------
; Когда вы получаете хэндл библиотеки, вы передаете его GetрrocAddress вместе
; с именем функции в этой dll, которую вы хотите вызвать. Она возвратит адрес
; функции, если вызов пройдет успешно. В противном случае, она возвратит NULL.
; Адреса функций не изменятся, пока вы не перезагрузите библиотеку. Поэтому
; их можно поместить в глобальные переменные для будущего использования.
;------------------------------------------------------------------------------
.if eax==NULL
invoke MessageBox,NULL,addr FunctionNotFound,addr AppName,MB_OK
.else
mov TestHelloAddr,eax
call [TestHelloAddr]
;----------------------------------------------------------------------------
; Затем мы вызываем функцию с помощью call и переменной, содержащей адрес
; функции в качестве операнда.
;----------------------------------------------------------------------------
.endif
invoke FreeLibrary,hLib
;------------------------------------------------------------------------------
; Когда вам больше не требуется библиотека, выгружате ее с помощью FreeLibrary.
;------------------------------------------------------------------------------
.endif
invoke Exitprocess,NULL
end start
Как вы можете видеть, использование LoadLibrary чуть сложнее, но гораздо гибче.
________________________________________
© Iczelion, пер. Aquila.
Вложения
Тип файла: zip tut17.zip (5.1 Кб, 177 просмотров)
5
Ушел с форума
Автор FAQ
 Аватар для Mikl___
16377 / 7689 / 1080
Регистрация: 11.11.2010
Сообщений: 13,769
16.01.2013, 05:36  [ТС]
Win32 API. Урок 17a. Динамические библиотеки

Кликните здесь для просмотра всего текста
Этот Урок предполагает, что читатель знает, как использовать 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
Миниатюры
Сам себе Iczelion   Сам себе Iczelion  
Изображения
  
Вложения
Тип файла: zip tut17a.zip (12.0 Кб, 126 просмотров)
5
Ушел с форума
Автор FAQ
 Аватар для Mikl___
16377 / 7689 / 1080
Регистрация: 11.11.2010
Сообщений: 13,769
16.01.2013, 09:20  [ТС]
Win32 API. Урок 18. Common Control'ы
Мы узнаем, что такое common control'ы и как их использовать. Этот туториал является не более, чем поверхностным введением в данную тему.

Скачайте код примера здесь.
ТЕОРИЯ ― МАТЬ СКЛЕРОЗА
Windows 95 принесла несколько новых элементов пользовательского интерфейса, сделавших GUI более разнообразным. Некоторые из них широко использовались и в Windows 3.1, но программисты должны были программировать их самостоятельно. Теперь Microsoft включил их в Windows 9x и NT. Мы изучим их в этом туториале.

Вот список новых элементов управления:
  • Toolbar
  • Tooltip
  • Status bar
  • property sheet
  • property page
  • Tree view
  • List view
  • Animation
  • Drag list
  • Header
  • Hot-key
  • Image list
  • progress bar
  • Right edit
  • Tab
  • Trackbar
  • Up-down
Так как новых элементов управления довольно много, их загрузка в память и регистрация была бы бессмысленной тратой ресурсов. Все эти элементы управления, за исключением rich edit'а, находятся в comctl32.dll, чтобы приложения могли загружать их, когда они им нужны. Rich edit находится в своей собственной dll, richedXX.dll, так как он слишком сложен и поэтому больше, чем остальные.

Вы можете вызвать comctl32.dll, поместив вызов функции IntiCommonControls в вашу программу. InitCommonControls ― это функция в comctl32.dll, поэтому ее вызов в любом месте вашего кода заставит PE-загрузчик загрузить comctl32.dll, когда ваша программ запустится. Вам не нужно выполнять эту функцию, просто поместите ее где-нибудь. Эта функция ничего не делает! Ее единственной инструкцией является "ret". Ее главная цель ― это создание ссылки на comctl32.dll в секции импорта, чтобы PE-загрузчик загружал ее всегда, когда будет загружаться программа. Главным следствием будет являться то, что стартовая функция DLL зарегистрирует все классы элементов управления при загрузке dll. Элементы управления создаются на основе этих классов, как и другие дочерние элементы окон, например, edit control, listbox и так далее.

С rich edit'ом дел обстоит совершенно по другому. Если вы хотите использовать его, вы должны вызвать LoadLibrary, чтобы загрузить его и FreeLibrary, чтобы выгрузить.
Теперь давайте научимся создавать элементы управления. Вы можете использовать редактор ресурсов, чтобы внедрить их в диалоговое окно, или создать их самостоятельно. Почти все элементы управления создаются с помощью вызова CreateWindowEx или CreateWindow, путем передачи имени класса элемента управления. У некоторых элементов управления есть специальные функции для создания, хотя, на самом деле, они являются функциями-обертками вокруг CreateWindowEx, чтобы сделать создание элемента управления легче. Такие функции перечислены ниже:
  • CreateToolbarEx
  • CreateStatusWindow
  • CreatepropertySheetpage
  • propertySheet
  • ImageList_Create
Чтобы создавать элементы управления, вы должны знать их имена. Они перечислены ниже:
Кликните здесь для просмотра всего текста
Имя классаCommon Control
ToolbarWindow32 Toolbar
tooltips_class32 Tooltip
msctls_statusbar32 Status bar
SysTreeView32 Tree view
SysListView32 List view
SysAnimate32 Animation
SysHeader32 Header
msctls_hotkey32 Hot-key
msctls_progress32 progress bar
RICHEDIT Rich edit
msctls_updown32 Up-down
SysTabControl32Tab
рroрerty sheet'ы и рroрerty рage'ы и контрол image list имеют собственные функции создания. Drag list control ― это усовершенствованный listbox, поэтому у него нет своего собственного класса. Вышеприведенные имена проверены путем проверки скриптов ресурсов, генерируемых редактором ресурсов, входящего в Visual C++. Они отличаются от имен, приведенных в справочнике по Win32 API от Borland'а и тех, что указаны в книге Charles Petzold's "Programming Windows 95". Вышеприведенный список является точной версией.

Эти common control'ы могут использовать общие стили окна, такие как WS_CHILD и т.п. У них также есть специальные стили, такие как TVS_XXXXX для tree view control'а, LVS_xxxx для list view control'а и т.д. Справочник по Win32 API ваше лучшее руководство в данном случае.

Теперь, когда мы знаем, как создать common control'ы, мы можем перейти к тому, как взаимодействуют common control'ы и их родители. В отличие от дочерних элементов управления, common control'ы не взаимодействую с родительским окно через WM_COMMAND. Вместо этого они используют сообщение WM_NOTIFY, посылаемое родительскому окну, когда происходит какое-то интересное событие. "родитель" может контролировать "детей", посылая им определенные сообщения, которые введено достаточно много. Вам следует обратиться к справочнику по Win32 API за конкретными деталями.

Давайте посмотрим, как создать рrogress bar и status bar.
ПРАКТИКА ― СЕСТРА ШИЗОФРЕНИИ
Кликните здесь для просмотра всего текста
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
 .386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comctl32.inc
includelib \masm32\lib\comctl32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
 
WinMain pROTO :DWORD,:DWORD,:DWORD,:DWORD
 
.const
IDC_pROGRESS equ 1 ; control IDs
IDC_STATUS equ 2
IDC_TIMER equ 3
 
.data
ClassName db "CommonControlWinClass",0
AppName db "Common Control Demo",0
progressClass db "msctls_progress32",0 ; the class name of the progress bar
Message db "Finished!",0
TimerID dd 0
 
.data?
hInstance HINSTANCE ?
hwndprogress dd ?
hwndStatus dd ?
CurrentStep dd ?
 
.code
start: invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke Exitprocess,eax
invoke InitCommonControls
WinMain proc hInst:HINSTANCE,hprevInst:HINSTANCE,CmdL­ine:LpSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
 
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndproc, OFFSET Wndproc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_AppWORKSPACE
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_AppLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,WS_OVERLAppED+\
WS_CApTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_­MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT, \
CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,hI­nst,NULL
mov hwnd,eax
.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wparam
ret
WinMain endp
 
Wndproc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.if uMsg==WM_CREATE
invoke CreateWindowEx,NULL,ADDR progressClass,NULL,WS_CHILD+WS_VISIBLE,\
100,200,300,20,hWnd,IDC_pROGRESS,hInstan­ce,NULL
mov hwndprogress,eax
mov eax,1000 ; the lparam of pBM_SETRANGE message contains the range
mov CurrentStep,eax
shl eax,16 ; the high range is in the high word
invoke SendMessage,hwndprogress,pBM_SETRANGE,0,­eax
invoke SendMessage,hwndprogress,pBM_SETSTEp,10,­0
invoke CreateStatusWindow,WS_CHILD+WS_VISIBLE,N­ULL,hWnd,IDC_STATUS
mov hwndStatus,eax
invoke SetTimer,hWnd,IDC_TIMER,100,NULL ; create a timer
mov TimerID,eax
.elseif uMsg==WM_DESTROY
invoke postQuitMessage,NULL
.if TimerID!=0
invoke KillTimer,hWnd,TimerID
.endif
.elseif uMsg==WM_TIMER ; when a timer event occurs
invoke SendMessage,hwndprogress,pBM_STEpIT,0,0 ; step up the progress in
sub CurrentStep,10 ; the progress bar
.if CurrentStep==0
invoke KillTimer,hWnd,TimerID
mov TimerID,0
invoke SendMessage,hwndStatus,SB_SETTEXT,0,addr Message
invoke MessageBox,hWnd,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION
invoke SendMessage,hwndStatus,SB_SETTEXT,0,0
invoke SendMessage,hwndprogress,pBM_SETpOS,0,0
.endif
.else
invoke DefWindowproc,hWnd,uMsg,wparam,lparam
ret
.endif
xor eax,eax
ret
Wndproc endp
end start
Разбор полетов
Кликните здесь для просмотра всего текста
Assembler
1
2
3
 invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax
invoke InitCommonControls
Я специально поместил InitCommonControls после ExitProcess, чтобы продемонстрировать то, что эта функция необходима только для создания ссылки на comctl32.dll в секции импорта. Как вы можете видеть, common control'ы работают, даже если функция InitCommonControls не запускалась.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
 .if uMsg==WM_CREATE
invoke CreateWindowEx,NULL,ADDR ProgressClass,NULL,\
WS_CHILD+WS_VISIBLE,100,200,300,20,hWnd,­IDC_PROGRESS,\
hInstance,NULL
mov hwndprogress,eax
Здесь мы создаем common control. Заметьте, что вызов CreateWindowEx содержит hWnd в качеств хэндла родительского окна. Он также задает ID контрола, для идентификации последнего. Тем не менее, так как у нас есть хэндл окна контрола, этот ID не используется. Все дочерние окна должны иметь стиль WS_CHILD.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
 mov eax,1000
mov CurrentStep,eax
shl eax,16
invoke SendMessage,hwndprogress,pBM_SETRANGE,0,­eax
invoke SendMessage,hwndprogress,pBM_SETSTEp,10,­0
После того, как создан progress bar, мы можем установить его диапазон. Диапазон по умолчанию равен от 0 до 100. Если это вас не устраивает, вы можете указать ваш собственный диапазон с помощью сообщения PBM_SETRANGE. lрaram этого сообщения содержит диапазон, максимальное значение в верхнем слове и минимальное в нижнем. Вы также можете указать шаг, используя сообщение рBM_SETSTEр. Этот пример устанавливает его в 10, что означает то, что когда вы посылаете сообщение рBM_STEрIT прогресс бару, индикатор прогресса будет повышаться на 10. Вы также можете установить положение индикатора, послав сообщение PBM_SETPOS. Это сообщение дает вам полный контроль над рrogress bar'ом.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
 invoke CreateStatusWindow,WS_CHILD+WS_VISIBLE,N­ULL,hWnd,IDC_STATUS
mov hwndStatus,eax
invoke SetTimer,hWnd,IDC_TIMER,100,NULL ; create a timer
mov TimerID,eax
Затем мы создаем status bar, вызывая CreateStatusWindow. Этот вызов легко понять, поэтому я не буду комментировать его. После того, как status window создан, мы создаем таймер. В этом примере мы будем обновлять progress bar каждые 100 ms, поэтому нам нужно создать таймеp.
Кликните здесь для просмотра всего текста
Assembler
1
 SetTimer pROTO hWnd:DWORD, TimerID:DWORD, TimeInterval:DWORD, lpTimerproc:DWORD
  • hWnd : хэндл родительского окна
  • TimerID : не равный нулю идентификатор таймера. Вы можете создать свой собственный идентификатор.
  • TimerInteral : временной интервал в миллисекундах, который должен пройти, прежде чем таймер вызовет процедуру таймер или пошлет сообщение WM_TIMER.
  • lpTimeproc : адрес функции таймера, которая будет вызываться при истечении временного интервала. Если параметр равен нулю, таймер вместо этого будет посылать родительскому окну сообщение WM_TIMER.
Если вызов прошел успешно, функция возвратит TimerID. В противном случае, будет возвращен ноль. Вот почему идентификатор таймера не должен быть равен нулю.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
 .elseif uMsg==WM_TIMER
invoke SendMessage,hwndprogress,pBM_STEpIT,0,0
sub CurrentStep,10
.if CurrentStep==0
invoke KillTimer,hWnd,TimerID
mov TimerID,0
invoke SendMessage,hwndStatus,SB_SETTEXT,0,addr Message
invoke MessageBox,hWnd,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION
invoke SendMessage,hwndStatus,SB_SETTEXT,0,0
invoke SendMessage,hwndprogress,PBM_SETPOS,0,0
.endif
Когда истекает указанный временной интервал, таймер посылает сообщение WM_TIMER. Вы можете поместить здесь свой код, который будет выполнен.
В данном пример, мы обновляем рrogress bar, а затем проверяем, было ли достигнуто максимальное значение. Если это так, мы убиваем таймеp, после чего устанавливаем текст статус-окна с помощью сообщения SB_SETTEXT. Отображается message box, и когда юзер кликает OK, мы очищаем текст в status bar'е и progress bar'е.
____________________________________
© Iczelion, пер. Aquila.
Вложения
Тип файла: zip tut18.zip (3.1 Кб, 102 просмотров)
3
Ушел с форума
Автор FAQ
 Аватар для Mikl___
16377 / 7689 / 1080
Регистрация: 11.11.2010
Сообщений: 13,769
16.01.2013, 09:30  [ТС]
Win32 API. Урок 18a. Common Control'ы

Кликните здесь для просмотра всего текста
В этом Уроке мы создадим полнофункциональную 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 и ассемблируйте его так:
Code
1
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 содержит инструкции/данные в двоичной форме. Отсутствуют только необходимая корректировка адресов, которая проводится линкером.
Теперь сделайте следующее:
Code
1
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.
Миниатюры
Сам себе Iczelion  
Вложения
Тип файла: zip tut18a.zip (3.7 Кб, 94 просмотров)
3
Ушел с форума
Автор FAQ
 Аватар для Mikl___
16377 / 7689 / 1080
Регистрация: 11.11.2010
Сообщений: 13,769
16.01.2013, 11:02  [ТС]
Win32 API. Урок 19. Tree View Control
Кликните здесь для просмотра всего текста
В этом Уроке мы создадим полнофункциональную 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 и ассемблируйте его так:
Code
1
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 содержит инструкции/данные в двоичной форме. Отсутствуют только необходимая корректировка адресов, которая проводится линкером.
Теперь сделайте следующее:
Code
1
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.
3
Ушел с форума
Автор FAQ
 Аватар для Mikl___
16377 / 7689 / 1080
Регистрация: 11.11.2010
Сообщений: 13,769
17.01.2013, 09:25  [ТС]
Win32 API. Урок 19a. Tree View Control

Кликните здесь для просмотра всего текста
В этом Уроке мы создадим полнофункциональную 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 и ассемблируйте его так:
Code
1
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 содержит инструкции/данные в двоичной форме. Отсутствуют только необходимая корректировка адресов, которая проводится линкером.
Теперь сделайте следующее:
Code
1
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.
Изображения
 
Вложения
Тип файла: zip tut19a.zip (10.0 Кб, 92 просмотров)
3
Ушел с форума
Автор FAQ
 Аватар для Mikl___
16377 / 7689 / 1080
Регистрация: 11.11.2010
Сообщений: 13,769
18.01.2013, 11:08  [ТС]
Win32 API. Урок 20. Сабклассинг окна
В этом туториале мы изучим сабклассинг окна, что это такое, и как это использовать нам на пользу.

Скачайте пример здесь.
Теория:
Если вы уже некоторое время программируете в Windows, вы уже могли столкнуться с ситуацией, когда окно имеет почти все атрибуты, которые вам нужны, но не все. Сталкивались ли вы с ситуацией, когда вам требуется специальный вид edit control'а, который бы отфильтровывал ненужный текст? Первое, что может придти в голову, это написать свое собственное окно. Hо это действительно тяжелая работа, требующая значительного времени. Выходом является сабклассинг окна.

Вкратце, сабклассинг окна позволяет получить контроль над сабклассированным окном. У вас будет абсолютный контроль над ним. Давайте рассмотрим пример, что прояснить данное утверждение. Предположите, что вам нужен text box, в котором можно вводить только шестнадцатиричные числа. Если вы будете использовать обычный edit control, максимум, что вы сможете сделать, если юзер введет неверную букву, это стереть исходную строку и вывести ее снова в отредактированном виде. По меньшей мере, это непрофессионально. Фактически вам требуется получить возможность проверять каждый символ, который юзер набирает в text box'е, как pаз в тот момент, когда он делает это.

Теперь мы изучим как это сделать. Когда пользователь печатает что-то в text box'е, Windows посылает сообщение WM_CHAR процедуре edit control'а. Эта процедура окна находится внутри Windows, поэтому мы не можем модифицировать ее. Hо мы можем перенаправить поток сообщений к нашей оконной процедуре. Поэтому наша процедура окна первой получит возможность обработать сообщение, которое Windows пошлет edit control'у. Если наша процедура решит обработать сообщение, она так и сделает. Hо если она не захочет его обрабатывать, она может передать его оригинальной оконной процедуре. Таким образом, наша функция будет стоять между Windows и edit control'ом. Посмотрите на условную схему внизу.

До сабклассинга: Windows https://www.cyberforum.ru/cgi-bin/latex.cgi?\rightarrow процедура edit control'а
После сабклассинга: Windows https://www.cyberforum.ru/cgi-bin/latex.cgi?\rightarrow наша оконная процедура https://www.cyberforum.ru/cgi-bin/latex.cgi?\rightarrow процедура edit control'а

Теперь мы можем рассмотреть то, каким образом происходит сабклассинг окна. Заметьте, что сабклассинг не ограничивается контролами, он может использоваться с любым окном. Давайте подумаем о том, как Windows узнает, где находится процедура edit box'а. Hу?.. Поле lрfnWndрroc в структуре WNDCLASSEX. Если мы сможем поменять значение этого поля на адрес собственной структуры, Windows пошлет сообщение нашей процедуре окна вместо этого. Мы можем сделать это, вызвав SetWindowLong.
Кликните здесь для просмотра всего текста
Assembler
1
SetWindowLong PROTO hWnd:DWORD, nIndex:DWORD, dwNewLong:DWORD
  • hWnd - хэндл окна, чьи свойства мы хотим поменять.
  • nIndex - значение, которое нужно изменить.
  • dwNewLong новое значение.У каждого окна есть ассоциированное с ним 32-битное значение, предназначенное для использования приложением в своих целях.
GWL_EXSTYLE Установка нового расширенного стиля окна.
GWL_STYLE Установка нового стиля окна.
GWL_WNDPROC Установка нового адреса для процедуры окна.
GWL_HINSTANCE Установка нового хэндла приложения.
GWL_ID Установка нового идентификатора окна.
GWL_USERDATA Установка 32-битного значения, ассоциирующегося с окном.
Таким образом, наша работа проста: мы создаем процедуру окна, которая будет обрабатывать сообщения для edit control'а и затем вызывать SetWindowLong с флагом GWL_WNDPROC, которому передается адрес нашего окна в качестве третьего параметра. В случае, если вызов функции прошел нормально, возвращаемым значением является прежнее значение замещаемого параметра, в нашем случае - это адрес оригинальной процедуры окна. Hам нужно сохранить это значение, чтобы использовать его внутри нашей процедуры.

Помните, что есть сообщения, которые нам не нужно будет обрабатывать. Их мы будем передавать оригинальной процедуре. Мы можем сделать это с помощью вызова функции CallWindowproc.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
 CallWindowproc PROTO lpPrevWndFunc:DWORD, \
hWnd:DWORD,\
Msg:DWORD,\
wparam:DWORD,\
lparam:DWORD
  • lpPrevWndFunc - адрес оригинальной процедуры окна.
  • Остальные четыре значения - это те, что передаются нашей процедуре окна. Мы передаем их CallWindowproc.
Пpимеp:
Кликните здесь для просмотра всего текста
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
 .386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comctl32.inc
includelib \masm32\lib\comctl32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
 
WinMain pROTO :DWORD,:DWORD,:DWORD,:DWORD
EditWndproc pROTO :DWORD,:DWORD,:DWORD,:DWORD
 
.data
ClassName db "SubclassWinClass",0
AppName db "Subclassing Demo",0
EditClass db "EDIT",0
Message db "You pressed Enter in the text box!",0
 
.data?
hInstance HINSTANCE ?
hwndEdit dd ?
OldWndproc dd ?
 
.code
start: invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke Exitprocess,eax
 
WinMain proc hInst:HINSTANCE,hprevInst:HINSTANCE,CmdL­ine:LpSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
 
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndproc, OFFSET Wndproc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_AppWORKSpACE
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_AppLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAppED+WS_CApTION+WS_SYSMENU+WS_M­INIMIZEBOX+WS_MAXIMIZEBOX+\
WS_VISIBLE,CW_USEDEFAULT,350,200,NULL,NU­LL,hInst,NULL
mov hwnd,eax
.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wparam
ret
WinMain endp
 
Wndproc proc hWnd:HWND, uMsg:UINT, wparam:WpARAM, lparam:LpARAM
.if uMsg==WM_CREATE
 
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR EditClass,NULL,WS_CHILD+\
WS_VISIBLE+WS_BORDER,20,20,300,25,hWnd,N­ULL,hInstance,NULL
 
mov hwndEdit,eax
invoke SetFocus,eax
;-----------------------------------------
; Subclass it!
;-----------------------------------------
invoke SetWindowLong,hwndEdit,GWL_WNDpROC,addr EditWndproc
mov OldWndproc,eax
.elseif uMsg==WM_DESTROY
invoke postQuitMessage,NULL
.else
invoke DefWindowproc,hWnd,uMsg,wparam,lparam
ret
.endif
xor eax,eax
ret
Wndproc endp
 
EditWndproc pROC hEdit:DWORD,uMsg:DWORD,wparam:DWORD,lpar­am:DWORD
.if uMsg==WM_CHAR
mov eax,wparam
.if (al>="0" && al<="9") || (al>="A" && al<="F") || (al>="a" && al<="f") || al==VK_BACK
.if al>="a" && al<="f"
sub al,20h
.endif
invoke CallWindowproc,OldWndproc,hEdit,uMsg,eax­,lparam
ret
.endif
.elseif uMsg==WM_KEYDOWN
mov eax,wparam
.if al==VK_RETURN
invoke MessageBox,hEdit,addr Message,addr
AppName,MB_OK+MB_ICONINFORMATION
invoke SetFocus,hEdit
.else
invoke CallWindowproc,OldWndproc,hEdit,uMsg,wpa­ram,lparam
ret
.endif
.else
invoke CallWindowproc,OldWndproc,hEdit,uMsg,wpa­ram,lparam
ret
.endif
xor eax,eax
ret
EditWndproc endp
end start
Анализ:
Кликните здесь для просмотра всего текста
Assembler
1
2
 invoke SetWindowLong,hwndEdit,GWL_WNDpROC,addr EditWndproc
mov OldWndproc,eax
После того, как edit control создан, мы сабклассим его, вызывая SetWindowLong и замещая адрес оригинальной процедуры окна нашим собственным адресом. Заметьте, что мы сохраняем значение адреса оригинальной процедуры, чтобы впоследствии использовать его при вызове CallWindowproc. Заметьте, что EditWndрroc - это обычная оконная процедура.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
 .if uMsg==WM_CHAR
mov eax,wparam
.if (al>="0" && al<="9") || (al>="A" && al<="F") || (al>="a" && al<="f") || al==VK_BACK
.if al>="a" && al<="f"
sub al,20h
.endif
invoke CallWindowproc,OldWndproc,hEdit,uMsg,eax­,lparam
ret
.endif
Внутри EditWndрroc, мы фильтруем сообщения WM_CHAR. Если введен символ в диапазоне 0-9 или a-f, мы передаем его оригинальной процедуре окна. Если это символ нижнего регистра, мы конвертируем его в верхний, добавляя 20h. Заметьте, что если символ не тот, который мы ожидали, мы пропускаем его. Мы не передаем его оригинальной процедуре окна. Поэтому, когда пользователь печатат что-нибудь отличное от 0-9 или a-f, символ не появляется в edit control'е.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
 .elseif uMsg==WM_KEYDOWN
mov eax,wparam
.if al==VK_RETURN
invoke MessageBox,hEdit,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION
invoke SetFocus,hEdit
.else
invoke CallWindowproc,OldWndproc,hEdit,uMsg,wpa­ram,lparam
ret
.end
Я хочу продемонстрировать силу сабклассинга через перехват клавиши Enter. EditWndрroc проверяет сообщение WM_KEYDOWN, не равно ли оно VK_RETURN (клавиша Enter). Если это так, она отображает окно с сообщением "You pressed the Enter key in the text box!". Если это не клавиша Enter, она передает сообщение оригинальной процедуре.

Вы можете использовать сабклассинг окна, чтобы получить контроль над другими окнами. Эту мощную технику вам следует иметь в своем арсенале.
_______________________________
© Iczelion, пер. Aquila.
Вложения
Тип файла: zip tut20.zip (3.1 Кб, 95 просмотров)
3
Ушел с форума
Автор FAQ
 Аватар для Mikl___
16377 / 7689 / 1080
Регистрация: 11.11.2010
Сообщений: 13,769
18.01.2013, 11:19  [ТС]
Win32 API. Урок 20a. Сабклассинг окна
Кликните здесь для просмотра всего текста
Этот Урок предполагает, что читатель знает, как использовать 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
Вложения
Тип файла: zip tut20a.zip (2.9 Кб, 82 просмотров)
3
Ушел с форума
Автор FAQ
 Аватар для Mikl___
16377 / 7689 / 1080
Регистрация: 11.11.2010
Сообщений: 13,769
21.01.2013, 06:45  [ТС]
Win32 API. Урок 21. Пайп
Кликните здесь для просмотра всего текста
Этот Урок предполагает, что читатель знает, как использовать 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
Вложения
Тип файла: zip tut21.zip (5.6 Кб, 90 просмотров)
3
Ушел с форума
Автор FAQ
 Аватар для Mikl___
16377 / 7689 / 1080
Регистрация: 11.11.2010
Сообщений: 13,769
21.01.2013, 09:53  [ТС]
Win32 API. Урок 22. Суперклассинг
Кликните здесь для просмотра всего текста
Этот Урок предполагает, что читатель знает, как использовать 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
Вложения
Тип файла: zip tut22.zip (3.5 Кб, 85 просмотров)
4
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
raxper
Эксперт
30234 / 6612 / 1498
Регистрация: 28.12.2010
Сообщений: 21,154
Блог
21.01.2013, 09:53

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

А не навред(ж)ю ли я сам себе?
Когда регистрируешься то лучше через каждые 20-30 каталогов менять описание сайта и ключевиков! ТАК!!? B-) вот какой вопрос возник: ...

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

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

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


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

Или воспользуйтесь поиском по форуму:
60
Ответ Создать тему
Новые блоги и статьи
Программа опроса у.з. расходомера SLS-720F
Argus19 02.09.2026
Программа опроса у. з. расходомера SLS-720F Программа опрашивает один раз в минуту три ультразвуковых расходомера SLS-720F через интерфейс RS-485 по протоколу Modbus RTU. Опрашиваются регистры. . .
Hyper-V: Компьютер должен поддерживать доверенный платформенный модуль 2.0.
Maks 31.08.2026
При установке Windows 11 на виртуальную машину Hyper-V 2-го поколения вылезла такая ошибка: Решение: в параметрах виртуальной машины, в разделе "Безопасность" (Security) активировать флаг. . .
Архитектура биовида Стива в Майнкрафте: Зачем бонобо кубический каннибализм
anaschu 30.08.2026
Кубический Вагинокапитализм в Minecraft: Математический инвариант ОДУ и рок Стивов-бонобо Главная задача разработанной «Модели Всего» — наглядно продемонстрировать наличие системной «судьбы». . .
Оттачиваю умение писать js программы.
russiannick 30.08.2026
Проектом выходного дня стало написание Книги шифров Виженера. Итогом стала версия 200, синий туман. Синий туман назван так, потому что замораживает текст под собой. Нажатие синих кнопок управляют. . .
мат медиц модель 30. презентация проекта
anaschu 27.08.2026
хоп хоп хоп хидахоп, а я кладую))
Как у меня протекала болезнь
zorxor 27.08.2026
Здравствуйте, друзья! Эта запись блога предназначена именно для вас - для моих дорогих друзей, которые знали меня лично. Чтобы ответить на вопрос - а что же со мной произошло на самом деле? Я учился. . .
Нашел вот забавное видео о измерениях. Лучшее что я видел на эту тему
kumehtar 26.08.2026
ILETXiw9bMQ Основная суть и тезисы по измерениям: 0D (Нулевое измерение): точка, не имеющая длины, ширины, высоты или объема. Объект не может перемещаться в 0D. 1D (Первое измерение):. . .
[EasyBuilder Pro] Памятка по разработке для панелей Weintek
ФедосеевПавел 26.08.2026
Памятка по разработке для панелей Weintek ВВЕДЕНИЕ Ранее, при реализации проектов основное внимание уделял разработке управляющей программы для контроллера, а панели оператора доставалось время. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru