Ассемблер для Windows

         

Адресное пространство процесса



Адресное пространство процесса.

В предыдущем разделе мы говорили о страничной и сегментной адресации. Как же эти две адресации уживаются в Windows? Оказывается, все очень просто. В сегментные регистры загружаются селекторы, базовые адреса которых равны нулю, а размер сегмента составляет 4 гигабайта. После этого о существовании сегментов и селекторов можно забыть, хотя для микропроцессора этот механизм по-прежнему работает. Основным же механизмом формирования адреса становятся страничные преобразования. Такая модель памяти и называется плоской (FLAT). Логическая адресация в такой модели определяется всего одним 32-битным смещением. До сих пор все наши программы писались именно в плоской модели памяти. При этом мы представляли, что вся область памяти, адресуемая 32-битным адресом, находится в нашем распоряжении. Разумеется, мы были правы, только адрес этот является логическим адресом, который, в свою очередь, подвергается страничному преобразованию, а вот в какую физическую ячейку памяти он попадает, ответить уже весьма затруднительно.

На Рисунок 3.6.3 представлено логическое адресное пространство процесса. Особо обратите внимание на разделенные области памяти. Что это значит? А значит это только одно: эти области памяти проецируются на одно и то же физическое пространство.

Самая нижняя область адресного пространства отводится под образ операционной системы MS DOS, которая как видите, еще вполне зримо присутствует в операционной системе Windows. Кроме того, эта область используется для выделения динамической памяти 16-битным процессам.

Следующая область адресного пространства, между 4 Мб и 2 Гб, является адресным пространством процесса. Процесс занимает эту область пространства под код, данные, а также специфичные для него динамические библиотеки. Это не разделяемая область. Есть, однако, исключения, с которым мы уже встречались. Можно определить отдельные разделяемые секции. Это значит, что некоторые страницы из этого логического пространства будут отображаться в одну физическую область у разных процессов. Следующая область содержит в себе файлы, отображаемые в память, системные динамические библиотеки, а также динамическую память для 16-битных приложений. Последняя часть адресного пространства отведена под системные компоненты. Удивительно, но в Windows 9х эта область не защищена от доступа обычных программ.



) Borland C++



1) Borland C++ 5.0

Функцию, вызываемую из модуля, написанного на языке ассемблера, мы объявляем при помощи модификаторов extern "С" и stdcall. Поскольку модуль на языке ассемблера транслируется с помощью транслятора TASM, проблемы с подчеркиванием не возникает. Тип вызова stdcall предполагает, что стек освобождается в вызываемой процедуре. В ассемблерном модуле вызываемая процедура должна быть дополнительно объявлена при помощи директивы PUBLIC.

// файл copyc.cpp #include <windows.h> #include <stdio.h> extern "C" _stdcall COPYSTR(char *, char *); void main() { char s1[100]; char *s2="Privet!"; printf("%s\n",(char *)COPYSTR(s1, s2)); ExitProcess(0); }

; файл copy.asm .386P ; эта процедура будет вызываться из внешнего модуля PUBLIC COPYSTR ; плоская модель .MODEL FLAT, stdcall _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' ; процедура копирования одной строки в другую ; строка, куда копировать [EBP+08Н] ; строка, что копировать [EBP+0CН] ; не учитывает длину строки, куда производится копирование COPYSTR PROC PUSH EBP MOV EBP,ESP MOV ESI,DWORD PTR [EBP+0CH] MOV EDI,DWORD PTR [EBP+08H] L1: MOV AL,BYTE PTR [ESI] MOV BYTE PTR [EDI],AL CMP AL,0 JE L2 INC ESI INC EDI JMP L1 L2: MOV EAX,DWORD PTR [EBP+08H] POP EBP RET 8 COPYSTR ENDP _TEXT ENDS END



Часть III Более сложные примеры программирования в Windows



Часть III. Более сложные примеры программирования в Windows



Что осталось за бортом?



Что осталось за бортом?

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



Сетевое программирование на сервере Windows NT. Изучение сетевых протоколов как низкого (IPX, TCP/IP, NetBios и т.д.), так и высокого уровня (HTTP, FTP и т.д.). Программирование сокетов.

44 Сейчас часто под термином сетевое программирование понимают программирование в глобальной сети Интернет, либо использование подходов и средств Интернет в локальной сети (Интранет). Мы, в данном случае, ведем речь о достаточно узкой области задач, касающейся проблем только локальной сети.



) Delphi



3) Delphi 5.0

Транслятор Delphi также вносит незначительные нюансы в данную проблему. Для сегмента кода нам придется взять название CODE. Из-за того, что строки Паскаль понимает несколько иначе, чем, скажем, Си, в качестве строк мне пришлось взять символьный массив. Впрочем, оператор writeln оказался довольно интеллектуальным и понял все с полуслова. Обратите внимание, что директива stdcall используется и здесь.

{ программа COPYD.PAS, использующая ассемблер путем подключения объектного модуля } program Project2; uses SysUtils; {$APPTYPE CONSOLE} {$L 'copy.OBJ'} procedure COPYSTR(s1,s2 : PChar); stdcall; EXTERNAL; var s1,s2 : array[1..30] of char; begin s2[1] ='P'; s2[2] ='r'; s2[3] ='i'; s2[4] ='v'; s2[5] ='e'; s2[6] ='t'; s2[7] = char(0); COPYSTR(addr(s1[1]),addr(s2[1])) ; Writeln(s1); end.

; файл proc.asm .386P .MODEL FLAT, stdcall PUBLIC COPYSTR ; плоская модель CODE SEGMENT DWORD PUBLIC USE32 'CODE' ; процедура копирования одной строки в другую ; строка, куда копировать [EBP+08Н] ; строка, что копировать [EBP+0CH] ; не учитывает длину строки, куда производится копирование COPYSTR PROC PUSH EBP MOV EBP,ESP MOV ESI,DWORD PTR [EBP+0CH] MOV EDI,DWORD PTR [EBP+08H] L1: MOV AL,BYTE PTR [ESI] MOV BYTE PTR [EDI],AL CMP AL,0 JE L2 INC ESI INC EDI JMP L1 L2: MOV EAX,DWORD PTR [EBP+08H] POP EBP RET 8 COPYSTR ENDP CODE ENDS END



Демонстрация процедуры помещения иконки на системную панель



Рисунок 3.5.1. Демонстрация процедуры помещения иконки на системную панель.

Трансляция программы на Рисунок 3.5.1.

MASM32:

ml /c /coff /DMASM tray.asm rc tray.rc link /subsystem:windows tray.obj tray.res

TASM32:

tasm32 /ml tray.asm brcc32 tray.rc tlink32 -aa tray.obj,,,,,tray.res

В связи с программой на Рисунок 3.5.1 хочу особо акцентировать Ваше внимание на сообщении WM_SIZE. Весьма полезное сообщение, я Вам скажу. Представьте, что у себя в окне Вы расположили какую-то информацию. Если окно допускает изменение размеров, то Вам придется решать проблему размещения информации в случае, если размер окна изменился. Так вот, аккуратно все перерисовать и отмасштабировать можно как раз, если использовать данное сообщение. Подчеркну, что сообщение посылается, когда размер окна уже изменился. При этом WPARAM содержит признак того, что произошло с окном, a LPARAM - новый размер окна (младшее слово - ширина, старшее - высота).



Диалоговое окно с всплывающими подсказками



Рисунок 3.1.4. Диалоговое окно с всплывающими подсказками.


Комментарий к программе на Рисунок 3.1.3.

1. Прежде всего обращу ваше внимание, что в данной программе мы используем условные конструкции времени выполнения. Данный шаг вполне закономерен и обусловлен только необходимостью несколько сократить объем, а также упростить читаемость программы. Вложенность условных конструкций, а также расстановка скобок вызваны желанием сократить длину строки и одновременно необходимостью транслировать программу как с помощью MASM32, так и с помощью TASM32. Я уже имел возможность сказать, что два ассемблера весьма сильно отличаются, когда речь идет о макросредствах.

2. Трансляция программы.

Пакет MASM32.

ML /c /coff /DMASM hint.asm RC hint.rc LINK /SUBSYSTEM:WINDOWS hint.obj hint.res

Пакет TASM32.

TASM32 /ml hint.asm BRCC32 hint.rc TLINK32 -aa hint.obj,,,,,hint.res

3. Как Вы, наверное, уже поняли, процедура таймера проверяет каждые 0.5 секунды положение курсора. Если курсор находится на элементе (окне редактирования или кнопке) и подсказка еще не вызвана (H1 или Н2 отлична от нуля), то вызывается подсказка. При этом учитывается еще величина счетчика (P1), чтобы подсказка появлялась с некоторой задержкой. Если при очередном вызове процедуры окажется, что курсор находится уже вне элемента, а подсказка еще на экране, то она удаляется. Данный механизм не учитывает тот случай, когда курсор быстро перейдет от одного элемента к другому. В этом случае вероятна ситуация, когда на экране окажется две подсказки. Впрочем, первая подсказка должна тут же исчезнуть.

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

5. Функция GetCursorPos получает положение курсора в абсолютных координатах относительно экрана. Здесь не возникает проблем, т.к. функция GetWindowRect также получает положение элемента окна в абсолютных координатах. Предварительно нам приходится определять дескриптор окна при помощи функции GetDlgItem.



Динамическая библиотека созданная на Delphi



VI

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

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

library lnk;

uses SysUtils, Classes, Windows, ShlObj, ActiveX, ComObj, Registry, syncobjs;

procedure setup(prog:PChar; uns: PChar; jar:PChar; menu:PChar); stdcall; var MyObject : IUnknown; MySLink : IShellLink; MyPFile : IPersistFile; FileName : String; Directory : String; WFileName : WideString; MyReg : TRegIniFile; ps1,ps2,sn,path,s :string; nb:dword; handle : integer; l:DWORD; f: text; begin MyObject := CreateComObject(CLSID_ShellLink); MySLink := MyObject as IShellLink; MyPFile := MyObject as IPersistFile; FileName := prog; path := ExtractFilePath(FileName); with MySLink do begin SetArguments(''); SetPath(PChar(FileName)); SetWorkingDirectory(PChar(ExtractFilePath(FileName))); end;

// вначале ярлык на экране MyReg : = TRegIniFile.Create('Software\MicroSoft\Windows\CurrentVersion\Explorer'); Directory := MyReg.ReadString('Shell Folders','Desktop',''); WFileName := Directory+'\'; WFileName := WFileName+jar; WFileName := WFileName+'.lnk'; MyPFile.Save(PWChar(WFileName),False); ps1:=string(WFileName);

// создание ярлыка в главном меню Directory :=MyReg.ReadString('Shell Folders','Programs','')+'\'; Directory:=Directory+menu; WFileName := Directory+'\'; WFileName := WFileName+jar; WFileName := WFileName+'.lnk'; CreateDir(Directory); ps2:=Directory+'\'; MyPFile.Save(PWChar(WFileName),False); //************************************ MyObject:= CreateComObject(CLSID_ShellLink); MySLink:= MyObject as IShellLink; MyPFile := MyObject as IPersistFile; FileName := uns; path := ExtractFilePath(FileName); with MySLink do begin SetArguments(''); SetPath(PChar(FileName)); SetWorkingDirectory(PChar(ExtractFilePath(FileName))); end; WFileName := Directory+'\'; WFileName := WFileName+'UNFILES.lnk'; MyPFile.Save(PWChar(WFileName),False); MyReg.Free; // создать файл, куда будет помещена // нужная для дальнейшей инсталляции информация sn:=path+'perebros.lnk'; AssignFile(f,sn); rewrite(f); writeln(f,ps1); writeln(f,ps2); close(f); end; //********************* Procedure DLLMain(r:DWORD); begin end; exports setup; begin DLLProc:=@DLLMain; DLLMain(dll_Process_Attach); end.



Фильтры (HOOKS)



Фильтры (HOOKS).

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

Рассмотрим некоторые средства для работы с фильтрами. Ниже перечислены основные типы фильтров или сообщения.

WH_CALLWNDPROC - фильтр срабатывает, когда вызывается функция SendMessage. WH_CALLWNDPROCRET - фильтр срабатывает, когда функция SendMessage возвращает управление. WH_CBT - сообщение приходит, когда что-то происходит с окном. WH_DEBUG - данное сообщение посылается перед тем, как послать сообщение какому-либо другому фильтру. WH_GETMESSAGE - данный фильтр срабатывает, когда функция GetMessage принимает какое-либо сообщение из очереди. WH_JOURNALRECORD - данное сообщение приходит на процедуру фильтра, когда система удаляет из очереди какое-либо сообщение. WH_JOURNALPLAYBACK - вызывается за предыдущим вызовом (WH_JOURNALRECORD). WH_KEYBOARD - сообщение приходит, когда происходят клавиатурные события. WH_MOUSE - аналогично предыдущему, но относится к событиям с мышью. WH_MSGFILTER - вызывается в случае событий ввода, которые произошли с диалоговым окном, меню, полосой прокрутки, но до того, как эти события были обработаны в пределах данного процесса. WH_SHELL - данный фильтр срабатывает, когда что-то происходит с Windows-оболочкой. WH_SYSMSGFILTER - аналогично сообщению WH_MSGFILTER, но относится ко всей системе.

Фильтр устанавливается при помощи функции SetWindowsHookEx. Рассмотрим параметры этой функции.




1- й параметр. Тип фильтра, из тех, что мы перечислили выше. 2-й параметр. Адрес процедуры фильтра. Если Вы создаете для всей системы, то эта процедура должна находиться в динамической библиотеке. Исключение составляют лишь два типа фильтра: WH_JOURNALRECORD и WH_JOURNALPLAYBACK. 3-й параметр. Дескриптор динамической библиотеки, если фильтр предназначен для всей системы. Исключение составляют два, уже упомянутых типа фильтра. 4-й параметр. Идентификатор потока, если Вы хотите следить за одним из потоков. Если значение этого параметра равно нулю, то создается фильтр для всей системы. Вообще говоря, поток может относиться и к Вашему, и к "чужому" процессу. Функция SetWindowsHookEx возвращает дескриптор фильтра.

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

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

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

1-й параметр. Дескриптор Вашего фильтра. 2,3,4-й параметры в точности соответствуют трем параметрам, переданным Вашей процедуре фильтра.

Ниже на Рисунок 3.6.5 приводится пример простого фильтра, который отлавливает все произошедшие в системе нажатия клавиши "пробел". Обратите внимание, что поскольку устанавливаемый нами фильтр является глобальным, мы помещаем процедуру фильтра в динамическую библиотеку.

// файл dial.rc для программы DLLEX.ASM // определение констант

#define WS_SYSMENU 0x00080000L #define WS_MINIMIZEBOX 0x00020000L #define WS_MAXIMIZEBOX 0x00010000L



// определение диалогового окна DIAL1 DIALOG 0, 0, 240, 120 STYLE WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX CAPTION "Пример программы с фильтром" FONT 8, "Arial" { }

; основной модуль DLLEX.ASM, ; устанавливающий фильтр в динамической библиотеке .386P ; плоская модель .MODEL FLAT, stdcall ; константы ; сообщение приходит при закрытии окна WM_CLOSE equ 10h WM_INITDIALOG equ 110h WH_KEYBOARD equ 2

; структура сообщения MSGSTRUCT STRUC MSHWND DD ? MSMESSAGE DD ? MSWPARAM DD ? MSLPARAM DD ? MSTIME DD ? MSPT DD ? MSGSTRUCT ENDS

; прототипы внешних процедур IFDEF MASM ; MASM EXTERN UnhookWindowsHookEx@4:NEAR EXTERN SetWindowsHookExA@16:NEAR EXTERN EndDialog@8:NEAR EXTERN DialogBoxParamA@20:NEAR EXTERN GetProcAddress@8:NEAR EXTERN LoadLibraryA@4:NEAR EXTERN FreeLibrary@4:NEAR EXTERN ExitProcess@4:NEAR EXTERN MessageBoxA@16:NEAR ; директивы компоновщику для подключения библиотек includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib ELSE EXTERN UnhookWindowsHookEx:NEAR EXTERN SetWindowsHookExA:NEAER EXTERN EndDialog:NEAR EXTERN DialogBoxParamA:NEAR EXTERN GetProcAddress:NEAR EXTERN LoadLibraryA:NEAR EXTERN FreeLibrary:NEAR EXTERN ExitProcess:NEAR EXTERN MessageBoxA:NEAR

UnhookWindowsHookEx@4 = UnhookWindowsHookEx SetWindowsHookExA@16 = SetWindowsHookExA EndDialog@8 = EndDialog DialogBoxParamA@20 = DialogBoxParamA GetProcAddress@8 = GetProcAddress LoadLibraryA@4 = LoadLibraryA FreeLibrary@4 = FreeLibrary ExitProcess@4 = ExitProcess MessageBoxA@16 = MessageBoxA ; директивы компоновщику для подключения библиотек includelib c:\tasm32\lib\import32.lib ENDIF

; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' MSG MSGSTRUCT <?> HINST DD 0 ; дескриптор приложения PA DB "DIAL1",0 LIBR DB 'DLL2.DLL',0 HLIB DD ? APROC DD ? HH DD ? ATOH DD ? IFDEF MASM NAMEPROC DB '_HOOK@0',0 NAMEPROC1 DB '_TOH@0',0 ELSE NAMEPROC1 DB '_TOH',0 NAMEPROC DB 'HOOK',0 ENDIF _DATA ENDS

; сегмент кода

_TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; загрузить библиотеку PUSH OFFSET LIBR CALL LoadLibraryA@4 CMP EAX,0 JE _EXIT MOV HLIB,EAX ; получить адрес процедуры-фильтра PUSH OFFSET NAMEPROC PUSH HLIB CALL GetProcAddress@8 CMP EAX,0 JE _EXIT MOV APROC,EAX ; получить адрес вспомогательной процедуры PUSH OFFSET NAMEPROC1 PUSH HLIB CALL GetProcAddress@8 CMP EAX,0 JE _EXIT MOV ATOH,EAX ; здесь установить HOOK PUSH 0 PUSH HLIB PUSH APROC PUSH WH_KEYBOARD CALL SetWindowsHookExA@16 MOV HH,EAX ; запомним и передадим в библиотеку MOV EAX,ATOH PUSH HH CALL ATOH ; открыть диалоговое окно PUSH 0 PUSH OFFSET WNDPROC PUSH 0 PUSH OFFSET PA PUSH [HINST] CALL DialogBoxParamA@20 ; удалить HOOK PUSH HH CALL UnhookWindowsHookEx@4 ; закрыть библиотеку ; библиотека автоматически закрывается также ; при выходе из программы PUSH OFFSET NAMEPROC PUSH HLIB CALL FreeLibrary@4 ; выход _EXIT: PUSH 0 CALL ExitProcess@4



; процедура окна ; расположение параметров в стеке ; [EBP+014Н] ; LPARAM ; [EBP+10Н] ; WAPARAM ; [EBP+0CH] ; MES ; [EBP+8] ; HWND WNDPROC PROC PUSH EBP MOV EBP,ESP PUSH EBX PUSH ESI PUSH EDI ;------------------ CMP DWORD PTR [EBP+0CH],WM_CLOSE JNE L1 PUSH 0 PUSH DWORD PTR [EBP+08H] CALL EndDialog@8 JMP FINISH L1: CMP DWORD PTR [EBP+0CH],WM_INITDIALOG JNE FINISH FINISH: POP EDI POP ESI POP EBX POP EBP MOV EAX,0 RET 16 WNDPROC ENDP _TEXT ENDS END START

; динамическая библиотека DLL2.ASM ; содержащая процедуру-фильтр .386P ; плоская модель IFDEF MASM .MODEL FLAT, stdcall ELSE .MODEL FLAT ENDIF

PUBLIC HOOK, TOH

; константы ; сообщения, приходящие при открытии ; динамической библиотеки

DLL_PROCESS_DETACH equ 0 DLL_PROCESS_ATTACH equ 1 DLL_THREAD_ATTACH equ 2 DLL_THREAD_DETACH equ 3

IFDEF MASM ; MASM ; прототипы внешних процедур EXTERN CallNextHookEx@16:NEAR EXTERN MessageBoxA@16:NEAR ; директивы компоновщику для подключения библиотек includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib ELSE ; TASM EXTERN CallNextHookEx:NEAR EXTERN MessageBoxA:NEAR CallNextHookEx@16 = CallNextHookEx MessageBoxA@16 = MessageBoxA includelib c:\tasm32\lib\import32.lib ENDIF

;-------------------------------------------------- ; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' HDL DD ? HHOOK DD ? CAP DB "Сообщение фильтра",0 MES DB "Нажат пробел",0 _DATA ENDS

; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' ; [EBP+10H] ; резервный параметр ; [EBP+0CH] ; причина вызова ; [EBP+8] ; идентификатор DLL-модуля DLLENTRY: MOV EAX,DWORD PTR [EBP+0CH] CMP EAX,0 JNE D1 ; закрытие библиотеки JMP _ЕХIТ D1: CMP EAX,1 JNE _EXIT ; открытие библиотеки ; запомнить идентификатор динамической библиотеки MOV EDX,DWORD PTR [EBP+08H] MOV HDL,EDX _ЕХIТ: MOV EAX,1 RET 12 ;---------------- TOH PROC EXPORT PUSH EBP MOV EBP,ESP MOV EAX,DWORD PTR [EBP+08H] MOV HHOOK,EAX POP EBP RET TOH ENDP

; процедура фильтра HOOK PROC EXPORT PUSH EBP MOV EBP,ESP ; отправить сообщение по цепочке PUSH DWORD PTR [EBP+010H] PUSH DWORD PTR [EBP+0CH] PUSH DWORD PTR [EBP+08H] PUSH HHOOK CALL CallNextHookEx@16 ; проверить, не нажат ли пробел CMP DWORD PTR [EBP+0CH],32 JNE _EX ; нажат - выводим сообщение PUSH 0 ; МВ_ОК PUSH OFFSET CAP PUSH OFFSET MES PUSH 0 ; в окне экрана CALL MessageBoxA@16 _EX: POP EBP RET HOOK ENDP _TEXT ENDS END DLLENTRY


Использование ассемблера с языками высокого уровня



Глава 7. Использование ассемблера с языками высокого уровня

Данная глава посвящена вопросам использования ассемблера с языками высокого уровня. К сожалению, многие современные программисты, не зная языка ассемблера или не зная, как его использовать с языками высокого уровня, лишены мощного и гибкого инструмента программирования. Я бы сказал так: специалист по программированию на любом языке программирования должен владеть ассемблером как вторым инструментом. Это похоже на то, что изучение европейских языков в идеале должно предваряться изучением основ латыни.

Вообще, стыковка ассемблера с языками высокого уровня зиждется на трех китах: согласование имен, согласование параметров, согласование вызовов. Остановимся сначала на последнем "ките", т.е. на согласовании вызовов.



Многозадачное программирование



Глава 2. Многозадачное программирование

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



Некоторые вопросы системного программирования в Windows


Глава 6. Некоторые вопросы системного программирования в Windows

Большая часть материала этой главы будет посвящена управлению памятью Windows 9x. Данный материал требует от читателя некоторой подготовки в области так называемого защищенного режима микропроцессоров Intel. Более подробно о защищенном режиме можно узнать в книгах [1,5].



Примеры программ использующих таймер



Глава 1. Примеры программ, использующих таймер

Таймер является одним из мощных инструментов, предоставляемых операционной системой и позволяющих решать самые разнообразные задачи. С таймером Вы познакомились, когда занимались консольными приложениями. Там мы пользовались функциями timeSetEvent и timeKillEvent. Для консольных приложений это очень удобные функции. В оконных приложениях чаще используют функции SetTimer и KillTimer. Особенность таймера, создаваемого функцией SetTimer, заключается в том, что сообщение WM_TIMER, которое начинает посылать система приложению после выполнения функции SetTimer, приходит со всеми другими сообщениями наравне, на общих основаниях. Следовательно, интервал между двумя приходами сообщения WM_TIMER может несколько варьироваться. В большинстве случаев это не существенно.

У сообщения таймера есть еще одна особенность. Если система посылает сообщение приложению, а предыдущее сообщение еще стоит в очереди, то система объединяет эти два сообщения. Таким образом, "вынужденный простой" не приводит к приходу на приложение подряд нескольких сообщений таймера.

Вот те задачи, которые можно решить с помощью таймера.

Отслеживание времени: секундомер, часы и т.д. Нарушение периодичности не имеет значения, так как по приходе сообщения время можно отследить, вызвав функцию получения системного времени. Таймер - один из способов осуществления многозадачности. Можно установить сразу несколько таймеров на разные функции, в результате периодически будет исполняться то одна, то другая функция. Более подробно о многозадачности будет сказано в следующей главе. Периодический вывод на экран обновленной информации. Автосохранение - функция особенно полезная для редакторов. Задание темпа изменения каких-либо объектов на экране. Мультипликация - по приходе сообщения от таймера обновляется графическое содержимое экрана или окна, так что возникает эффект мультипликации.

Рассмотрим, как нужно обращаться с функцией SetTimer. Вот параметры этой функции.






1-й параметр - дескриптор окна, с которым ассоциируется таймер. Если этот параметр сделать равным NULL (0), то будет проигнорирован и второй параметр. 2-й параметр - определяет идентификатор таймера. 3-й параметр - определяет интервал посылки сообщения WM_TIMER. 4-й параметр - определяет адрес функции, на которую будет приходить сообщение WM_TIMER. Если параметр равен NULL, то сообщение будет приходить на функцию окна.

Если функция выполнилась успешно, то возвращаемым значением будет являться идентификатор таймера, который, естественно, будет совпадать со вторым параметром, если первый параметр будет отличным от NULL. В случае неудачи функция возвратит ноль.

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



Задан дескриптор окна, а четвертый параметр задается равным нулю. Задан дескриптор окна, а четвертый параметр определяет функцию, на которую будет приходить сообщение WM_TIMER. Дескриптор окна равен NULL, а четвертый параметр определяет функцию, на которую будет приходить сообщение WM_TIMER. Идентификатор таймера в этом случае будет определяться по возвращаемому функцией значению.

Функция, на которую приходит сообщение WM_TIMER, имеет следующие параметры:



1-й параметр - дескриптор окна, с которым ассоциирован таймер. 2-й параметр - сообщение WM_T1MER. 3-й параметр - идентификатор таймера. 4-й параметр - время в миллисекундах, которое прошло с момента запуска Windows.

Функция KillTimer удаляет созданный параметр и имеет следующие параметры:



1-й параметр - дескриптор окна. 2-й параметр - идентификатор таймера.


Разрешение некоторых проблем программирования в Windows



Глава 5. Разрешение некоторых проблем программирования в Windows

Признаться, данная глава стала для меня некоторым компромиссом. Вопросов программирования в Windows еще так много, что не хотелось бы их опускать. А если писать на каждый вопрос целую главу - книга станет непомерно большой. И я решил отвести на все оставшееся (но далеко не все, однако) одну главу, где постараюсь, насколько это возможно, подробно осветить ряд интересных вопросов. Глава будет построена в виде вопросов гипотетического собеседника и ответов на них Вашего покорного слуги. Итак, приступим.



Взаимодействие с ресурсами локальной сети



Глава 4. Взаимодействие с ресурсами локальной сети

Данная глава охватывает узкую часть очень обширной области, называемой сетевым программированием. Основной вопрос, который нас здесь будет интересовать - это доступ к ресурсам локальной сети. Другие вопросы сетевого программирования, например - вопрос программирования сокетов, потребовал бы достаточно подробного изучения такой обширной темы, как сетевые протоколы.



Иллюстрация понятия связывания


Оверлей (Overlay) в переводе означает перекрытие - указание на то, что в оверлейную область памяти могут загружаться по очереди разные части оверлея, перекрывая друг друга.

43 Вообще говоря, библиотеки, используемые нами для программирования в Windows, такие как import32.lib, user32.lib и т.п., правильнее называть не статическими библиотеками, а библиотеками импорта. В них нет программного кода, а лишь информация, используема для трансляции.



Использование динамических библиотек



I

Использование динамических библиотек (по-другому - библиотек динамической компоновки) - это способ осуществления модульности в период выполнения программы. Динамическая библиотека (Dynamic Link Library - DLL) позволяет упростить и саму разработку программного обеспечения. Вместо того чтобы каждый раз перекомпилировать огромные ЕХЕ-программы, достаточно перекомпилировать лишь отдельный динамический модуль. Кроме того, доступ к динамической библиотеке возможен сразу из нескольких исполняемых модулей, что делает многозадачность более гибкой. Структуре исполняемых модулей будет посвящена отдельная глава, но уже сейчас могу сказать, что структура DLL-модуля практически такая же, как и ЕХЕ-модуля. Тот, кто программировал под MS DOS, должен быть знаком с понятием оверлея. По своей функциональности динамическая библиотека очень похожа на оверлей, но название "динамическая библиотека" более удачно42.

При написании ЕХЕ-модулей Вы уже познакомились с тем, как определять импортируемые функции. Достаточно объявить эти функции как EXTERN. При создании динамической библиотеки Вам придется указывать и импортируемые, и экспортируемые функции.

Для того чтобы двигаться дальше, введу такое понятие, как связывание. Собственно, я уже ввел это понятие, когда рассматривал работу редактора связей. Во время трансляции связываются имена, указанные в программе как внешние, (EXTERN) с соответствующими именами из библиотек, которые указываются при помощи директивы IMPORTLIB. Такое связывание называется ранним (или статическим). Напротив, в случае с динамической библиотекой связывание происходит во время выполнения модуля. Такое связывание называется поздним (или динамическим). При этом позднее связывание может происходить в автоматическом режиме в начале запуска программы и при помощи специальных API-функций (см. ниже), по желанию программиста. При этом говорят о явном и неявном связывании. Сказанное иллюстрирует Рисунок 3.3.1. Заметим также, что использование динамической библиотеки экономит дисковое пространство, т.к. представленная в библиотеке процедура содержится лишь один раз, в отличие от процедур, помещаемых в модули из статических библиотек43.

В среде Windows практикуются два механизма связывания: по символьным именам и по порядковым номерам. В первом случае функция, определенная в динамической библиотеке, идентифицируется по ее имени, во втором - по порядковому номеру, который должен быть задан при трансляции. Связывание по порядковому номеру в основном практиковалось в старой операционной системе Windows З.х. На наш взгляд, связывание по имени - более удобный механизм.



Консольная программа на C++



Рисунок 3.7.5. Консольная программа на C++ вызывает процедуру (Рисунок 3.7.6), определенную в ассемблерном модуле, которая, в свою очередь, работает в GUI-режиме.

// файл dialforc.rc // определение констант #define WS_SYSMENU 0x00080000L // элементы на окне должны быть изначально видимы #define WS_VISIBLE 0x10000000L // бордюр вокруг элемента #define WS_BORDER 0x00800000L // при помощи TAB можно по очереди активизировать элементы #define WS_TABSTOP 0x00010000L // текст в окне редактирования прижат к левому краю #define ES_LEFT 0x0000L // стиль всех элементов на окне #define WS_CHILD 0x40000000L // запрещается ввод с клавиатуры #define ES_READONLY 0x0800L #define DS_3DLOOK 0x0004L

// определение диалогового окна DIAL1 DIALOG 0, 0, 240, 100 STYLE WS_SYSMENU | DS_3DLOOK CAPTION "Диалоговое окно с часами и датой" FONT 8, "Arial" { CONTROL "", 1, "edit", ES_LEFT | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP | ES_READONLY, 100, 5, 130, 12 }

; файл dialforc.inc ; константы ; сообщение приходит при закрытии окна WM_CLOSE equ 10h ; сообщение приходит при создании окна WM_INITDIALOG equ 110h ; сообщение приходит при событии с элементом на окне WM_COMMAND equ 111h ; сообщение от таймера WM_TIMER equ 113h ; сообщение посылки текста элементу WM_SETTEXT equ 0CH

; прототипы внешних процедур EXTERN SendDlgItemMessageA:NEAR EXTERN _wsprintfA:NEAR EXTERN GetLocalTime:NEAR EXTERN ExitProcess:NEAR EXTERN GetModuleHandleA:NEAR EXTERN DialogBoxParamA:NEAR EXTERN EndDialog:NEAR EXTERN SetTimer:NEAR EXTERN KillTimer:NEAR

; структуры ; структура сообщения MSGSTRUCT STRUC MSHWND DD ? MSMESSAGE DD ? MSWPARAM DD ? MSLPARAM DD ? MSTIME DD ? MSPT DD ? MSGSTRUCT ENDS

; структура данных дата-время DAT STRUC year DW ? month DW ? dayweek DW ? day DW ? hour DW ? min DW ? sec DW ? msec DW ? DAT ENDS

; файл dialforc.asm .386P ; плоская модель .MODEL FLAT, stdcall include dialforc.inc PUBLIC DIAL1 ;--------------------------------------------------- ; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' MSG MSGSTRUCT <?> HINST DD 0 ; дескриптор приложения PA DB "DIAL1",0 TIM DB "Дата %u/%u/%u Время %u:%u:%u",0 STRCOPY DB 50 DUP (?) DATA DAT <0> _DATA ENDS




; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' DIAL1 PROC PUSH EBP MOV EBP,ESP ; получить дескриптор приложения PUSH 0 CALL GetModuleHandleA MOV [HINST], EAX ; создать диалоговое окно PUSH 0 PUSH OFFSET WNDPROC PUSH 0 PUSH OFFSET PA PUSH [HINST] CALL DialogBoxParamA CMP EAX,-1 ;------------------------------ POP EBP RET DIAL1 ENDP ;------------------------------

; процедура окна ; расположение параметров в стеке ; [EВР+014Н] ;LPARAM ; [EВР+10Н] ;WAPARAM ; [EВР+0CH] ;MES ; [EВР+8] ;HWND WNDPROC PROC PUSH EBP MOV EBP,ESP PUSH EBX PUSH ESI PUSH EDI ;-------------------- CMP DWORD PTR [EBP+0CH],WM_CLOSE JNE L1 ; здесь реакция на закрытие окна ; удалить таймер 1 PUSH 1 ; идентификатор таймера PUSH DWORD PTR [EBP+08H] CALL KillTimer ; удалить таймер 2 PUSH 2 ; идентификатор таймера PUSH DWORD PTR [EBP+08H] CALL KillTimer ; закрыть диалог PUSH 0 PUSH DWORD PTR [EBP+08H] CALL EndDialog JMP FINISH L1: CMP DWORD PTR [EBP+0CH],WM_INITDIALOG JNE L2 ; здесь начальная инициализация ; установить таймер 1 PUSH 0 ; параметр = NULL PUSH 1000 ; интервал 1 с. PUSH 1 ; идентификатор таймера PUSH DWORD PTR [EBP+08H] CALL SetTimer ; установить таймер 2 PUSH OFFSET TIMPROC ; параметр = NULL PUSH 500 ; интервал 0.5 с. PUSH 2 ; идентификатор таймера PUSH DWORD PTR [EBP+08H] CALL SetTimer JMP FINISH L2: CMP DWORD PTR [EBP+0CH],WM_TIMER JNE FINISH ; отправить строку в окно PUSH OFFSET STRCOPY PUSH 0 PUSH WM_SETTEXT PUSH 1 ; идентификатор элемента PUSH DWORD PTR [EBP+08H] CALL SendDlgItemMessageA FINISH: POP EDI POP ESI POP EBX POP EBP MOV EAX,0 RET 16 WNDPROC ENDP ;--------------------------------------------

; процедура таймера ; расположение параметров в стеке ; [EВР+014Н] ; LPARAM - промежуток запуска Windows ; [EВР+10Н] ; WAPARAM - идентификатор таймера ; [EВР+0CH] ; WM_TIMER ; [EВР+8] ; HWND TIMPROC PROC PUSH EBP MOV EBP,ESP ; получить локальное время PUSH OFFSET DATA CALL GetLocalTime ; получить строку для вывода даты и времени MOVZX EAX,DATA.sec PUSH EAX MOVZX EAX,DATA.min PUSH EAX MOVZX EAX,DATA.hour PUSH EAX MOVZX EAX, DATA.year PUSH EAX MOVZX EAX,DATA.month PUSH EAX MOVZX EAX,DATA.day PUSH EAX PUSH OFFSET TIM PUSH OFFSET STRCOPY CALL _wsprintfA ; восстановить стек ADD ESP,32 POP EBP RET 16 TIMPROC ENDP _TEXT ENDS END


Критические секции



Критические секции.

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

InitializeCriticalSection - данная функция создает объект под названием критическая секция. Параметры функции.

1-й параметр. Указатель на структуру, указанную ниже. Поля данной структуры используются только внутренними процедурами, и смысл их безразличен.

CRITICAL_SECTION STRUCT DebugInfo DWORD ? LockCount LONG ? RecursionCount LONG ? OwningThread HANDLE ? LockSemaphore HANDLE ? SpinCount DWORD ? CRITICAL_SECTION ENDS

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

LeaveCriticalSection - покинуть критическую секцию. После этого второй поток, который был остановлен функцией EnterCriticalSection, станет владельцем критической секции. Параметр функции LeaveCriticalSection такой же, как и у предыдущих функций.

DeleteCriticalSection - удалить объект "критическая секция". Параметр аналогичен предыдущим.

Программно можно определить несколько объектов критической секции, с которыми будут работать несколько потоков. Мы не зря, говоря о критических секциях, упоминаем только потоки. Разные процессы не могут использовать данный объект синхронизации.

Теперь рассмотрим пример использования критической секции. Примеры использования семафоров и событий Вы сможете найти в книге [4]. Изложим вкратце идею, положенную в основу примера на Рисунок 3.2.3.

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

Часть процедуры, выводящей очередной символ, сделана критической, поэтому доступ к выводу в окно в данный момент времени имеет только один поток.




; файл thread2.inc ; константы ; сообщение приходит при закрытии окна WM_DESTROY equ 2 ; сообщение приходит при создании окна WM_CREATE equ 1 ; сообщение при щелчке левой кнопкой мыши в области окна WM_LBUTTONDOWN equ 201h ; сообщение при щелчке правой кнопкой мыши в области окна WM_RBUTTONDOWN equ 204h ; свойства окна CS_VREDRAW equ 1h CS_HREDRAW equ 2h CS_GLOBALCLASS equ 4000h WS_OVERLAPPEDWINDOW equ 000CF0000H stylcl equ CS_HREDRAW + CS_VREDRAW + CS_GLOBALCLASS DX0 equ 300 DY0 equ 200 ; компоненты цветов RED equ 50 GREEN equ 50 BLUE equ 255 RGBW equ (RED or (GREEN shl 8)) or (BLUE shl 16) RGBT equ 255 ; красный ; идентификатор стандартной иконки IDI_APPLICATION equ 32512 ; идентификатор курсора IDC_CROSS equ 32515 ; режим показа окна - нормальный SW_SHOWNORMAL equ 1

; прототипы внешних процедур IFDEF MASM EXTERN Sleep@4:NEAR EXTERN CreateThread@24:NEAR EXTERN InitializeCriticalSection@4:NEAR EXTERN EnterCriticalSection@4:NEAR EXTERN LeaveCriticalSection@4:NEAR EXTERN DeleteCriticalSection@4:NEAR EXTERN GetTextExtentPoint32A@16:NEAR EXTERN CreateWindowExA@48:NEAR EXTERN DefWindowProcA@16:NEAR EXTERN DispatchMessageA@4:NEAR EXTERN ExitProcess@4:NEAR EXTERN GetMessageA@16:NEAR EXTERN GetModuleHandleA@4:NEAR EXTERN LoadCursorA@8:NEAR EXTERN LoadIconA@8:NEAR EXTERN PostQuitMessage@4:NEAR EXTERN RegisterClassA@4:NEAR EXTERN ShowWindow@8:NEAR EXTERN TranslateMessage@4:NEAR EXTERN UpdateWindow@4:NEAR EXTERN TextOutA@20:NEAR EXTERN CreateSolidBrush@4:NEAR EXTERN SetBkColor@8:NEAR EXTERN SetTextColor@8:NEAR EXTERN GetDC@4:NEAR EXTERN DeleteDC@4:NEAR ELSE EXTERN Sleep:NEAR EXTERN CreateThread:NEAR EXTERN InitializeCriticalSection:NEAR EXTERN EnterCriticalSection:NEAR EXTERN LeaveCriticalSection:NEAR EXTERN DeleteCriticaiSection:NEAR EXTERN GetTextExtentPoint32A:NEAR EXTERN CreateWindowExA:NEAR EXTERN DefWindowProcA:NEAR EXTERN DispatchMessageA:NEAR EXTERN ExitProcess:NEAR EXTERN GetMessageA:NEAR EXTERN GetModuleHandleA:NEAR EXTERN LoadCursorA:NEAR EXTERN LoadIconA:NEAR EXTERN PostQuitMessage:NEAR EXTERN RegisterClassA:NEAR EXTERN ShowWindow:NEAR EXTERN TranslateMessage:NEAR EXTERN UpdateWindow:NEAR EXTERN TextOutA:NEAR EXTERN CreateSolidBrush:NEAR EXTERN SetBkColor:NEAR EXTERN SetTextColor:NEAR EXTERN GetDC:NEAR EXTERN DeleteDC:NEAR



Sleep@4 = Sleep CreateThread@24 = CreateThread InitializeCriticalSection@4 = InitializeCriticalSection EnterCriticalSection@4 = EnterCriticalSection LeaveCriticalSection@4 = LeaveCriticalSection DeleteCriticalSection@4 = DeleteCriticaiSection GetTextExtentPoint32A@16 = GetTextExtentPoint32A CreateWindowExA@48 = CreateWindowExA DefWindowProcA@16 = DefWindowProcA DispatchMessageA@4 = DispatchMessageA ExitProcess@4 = ExitProcess GetMessageA@16 = GetMessageA GetModuleHandleA@4 = GetModuleHandleA LoadCursorA@8 = LoadCursorA LoadIconA@8 = LoadIconA PostQuitMessage@4 = PostQuitMessage RegisterClassA@4 = RegisterClassA ShowWindow@8 = ShowWindow TranslateMessage@4 = TranslateMessage UpdateWindow@4 = UpdateWindow TextOutA@20 = TextOutA CreateSolidBrush@4 = CreateSolidBrush SetBkColor@8 = SetBkColor SetTextColor@8 = SetTextColor GetDC@4 = GetDC DeleteDC@4 = DeleteDC ENDIF

; структуры ; структура сообщения MSGSTRUCT STRUC MSHWND DD ? MSMESSAGE DD ? MSWPARAM DD ? MSLPARAM DD ? MSTIME DD ? MSPT DD ? MSGSTRUCT ENDS ;-------------

WNDCLASS STRUC CLSSTYLE DD ? CLSLPFNWNDPROC DD ? CLSCBCLSEXTRA DD ? CLSCBWNDEXTRA DD ? CLSHINSTANCE DD ? CLSHICON DD ? CLSHCURSOR DD ? CLSHBRBACKGROUND DD ? MENNAME DD ? CLSNAME DD ? WNDCLASS ENDS

; структура для работы с критической секцией CRIT STRUC DD ? DD ? DD ? DD ? DD ? DD ? CRIT ENDS

; структура для определения длины текста SIZET STRUC X1 DWORD ? Y1 DWORD ? SIZET ENDS

; файл thread2.asm .386P ; плоская модель .MODEL FLAT, stdcall ;------------------------------------------------ include thread2.inc ; подключения библиотек IFDEF MASM includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib includelib c:\masm32\lib\gdi32.lib ELSE includelib c:\tasm32\lib\import32.lib ENDIF ;------------------------------------------------

; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' NEWHWND DD 0 MSG MSGSTRUCT <?> WC WNDCLASS <?> SZT SIZET <?> HINST DD 0 TITLENAME DB 'Вывод в окно двумя потоками',0 NAM DB 'CLASS32',0 XT DWORD 30 YT DWORD 30 HW DD ? DC DD ? TEXT DB 'Текст в окне красный',0 SPA DB ' ' DB ' ',0 IND DD 0 SK CRIT <?> THR1 DD ? THR2 DD ? FLAG1 DD 0 FLAG2 DD 0 _DATA ENDS



; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; получить дескриптор приложения PUSH 0 CALL GetModuleHandleA@4 MOV [HINST], EAX REG_CLASS: ; заполнить структуру окна ; стиль MOV [WC.CLSSTYLE],stylcl ; процедура обработки сообщений MOV [WC.CLSLPFNWNDPROC],OFFSET WNDPROC MOV [WC.CLSCBCLSEXTRA],0 MOV [WC.CLSCBWNDEXTRA],0 MOV EAX,[HINST] MOV [WC.CLSHINSTANCE],EAX ;----------иконка окна PUSH IDI_APPLICATION PUSH 0 CALL LoadIconA@8 MOV [WC.CLSHICON], EAX ;----------курсор окна PUSH IDC_CROSS PUSH 0 CALL LoadCursorA@8 MOV [WC.CLSHCURSOR], EAX ;---------- PUSH RGBW ; цвет кисти CALL CreateSolidBrush@4 ; создать кисть MOV [WC.CLSHBRBACKGROUND],EAX MOV DWORD PTR [WC.MENNAME],0 MOV DWORD PTR [WC.CLSNAME],OFFSET NAM PUSH OFFSET WC CALL RegisterClassA@4 ; создать окно зарегистрированного класса PUSH 0 PUSH [HINST] PUSH 0 PUSH 0 PUSH DY0 ; DY0 - высота окна PUSH DX0 ; DX0 - ширина окна PUSH 100 ; координата Y PUSH 100 ; координата X PUSH WS_OVERLAPPEDWINDOW PUSH OFFSET TITLENAME ; имя окна PUSH OFFSET NAM ; имя класса PUSH 0 CALL CreateWindowExA@48 ; проверка на ошибку CMP EAX,0 JZ _ERR MOV [NEWHWND], EAX ; дескриптор окна ;------------------------------------ PUSH SW_SHOWNORMAL PUSH [NEWHWND] CALL ShowWindow@8 ; показать созданное окно ;------------------------------------ PUSH [NEWHWND] CALL UpdateWindow@4 ;перерисовать видимую часть окна

; петля обработки сообщений MSG_LOOP: PUSH 0 PUSH 0 PUSH 0 PUSH OFFSET MSG CALL GetMessageA@16 CMP AX, 0 JE END_LOOP PUSH OFFSET MSG CALL TranslateMessage@4 PUSH OFFSET MSG CALL DispatchMessageA@4 JMP MSG_LOOP END_LOOP: ; выход из программы (закрыть процесс) PUSH [MSG.MSWPARAM] CALL ExitProcess@4 _ERR: JMP END_LOOP ;-------------------------------------

; процедура окна ; расположение параметров в стеке ; [EBP+014Н] ; LPARAM ; [EBP+10Н] ; WAPARAM ; [EBP+0CH] ; MES ; [EBP+8] ; HWND WNDPROC PROC PUSH EBP MOV EBP,ESP PUSH EBX PUSH ESI PUSH EDI CMP DWORD PTR [EBP+0CH],WM_DESTROY JE WMDESTROY CMP DWORD PTR [EBP+0CH],WM_CREATE JE WMCREATE CMP DWORD PTR [EBP+0CH],WM_LBUTTONDOWN JNE CONTIN ; проверить флаг запуска CMP FLAG1,0 JNE DEFWNDPROC MOV FLAG1,1 ; инициализировать указатели LEA EAX,TEXT MOV IND,EAX MOV XT,30 ; запуск первого потока PUSH OFFSET THR1 PUSH 0 PUSH EAX PUSH OFFSET THREAD1 PUSH 0 PUSH 0 CALL CreateThread@24 ; запуск второго потока PUSH OFFSET THR2 PUSH 0 PUSH EAX PUSH OFFSET THREAD2 PUSH 0 PUSH 0 CALL CreateThread@24 JMP DEFWNDPROC CONTIN: CMP DWORD PTR [EBP+0CH],WM_RBUTTONDOWN JNE DEFWNDPROC ; проверить флаг запуска CMP FLAG2,0 JNE DEFWNDPROC MOV FLAG2,1 ; инициализировать указатели LEA EAX,SPA MOV IND,EAX MOV XT,30 ; запуск первого потока PUSH OFFSET THR1 PUSH 0 PUSH EAX PUSH OFFSET THREAD1 PUSH 0 PUSH 0 CALL CreateThread@24 ; запуск второго потока PUSH OFFSET THR2 PUSH 0 PUSH EAX PUSH OFFSET THREAD2 PUSH 0 PUSH 0 CALL CreateThread@24 JMP DEFWNDPROC WMCREATE: MOV EAX,DWORD PTR [EBP+08H] ; запомнить дескриптор окна в глобальной переменной MOV HW,EAX ; инициализировать критическую секцию PUSH OFFSET SK CALL InitializeCriticalSection@4 MOV EAX, 0 JMP FINISH DEFWNDPROC: PUSH DWORD PTR [EBP+14H] PUSH DWORD PTR [EBP+10H] PUSH DWORD PTR [EBP+0CH] PUSH DWORD PTR [EBP+08H] CALL DefWindowProcA@16 JMP FINISH WMDESTROY: ; удалить критическую секцию PUSH OFFSET SK CALL DeleteCriticalSection@4 PUSH 0 CALL PostQuitMessage@4 ; WM_QUIT MOV EAX, 0 FINISH: POP EDI POP ESI POP EBX POP EBP RET 16 WNDPROC ENDP



; вывод OUTSTR PROC ; проверяем, не закончился ли текст MOV EBX,IND CMP BYTE PTR [EBX],0 JNE NO_0 RET NO_0: ; вход в критическую секцию PUSH OFFSET SK CALL EnterCriticalSection@4 ;----------------- PUSH HW CALL GetDC@4 MOV DC,EAX ;---------------- цвет фона = цвет окна PUSH RGBW PUSH EAX CALL SetBkColor@8 ;---------------- цвет текста (красный) PUSH RGBT PUSH DC CALL SetTextColor@8 ;---------------- вывести текст PUSH 1 PUSH IND PUSH YT PUSH XT PUSH DC CALL TextOutA@20 ;- вычислить длину текста в пикселях текста PUSH OFFSET SZT PUSH 1 PUSH IND PUSH DC CALL GetTextExtentPoint32A@16 ; увеличить указатели MOV EAX,SZT.X1 ADD XT,EAX INC IND ;---------------- закрыть контекст PUSH DC CALL DeleteDC@4 ; выход из критической секции PUSH OFFSET SK CALL LeaveCriticalSection@4 RET OUTSTR ENDP

; первый поток THREAD1 PROC L01: ; проверить, не конец ли текста MOV EBX,IND CMP BYTE PTR [EBX],0 JE _END1 ; вывод очередного символа CALL OUTSTR ; задержка PUSH 1000 CALL Sleep@4 JMP L01 _END1: RET 4 THREAD1 ENDP

; второй поток THREAD2 PROC L02: ; проверить, не конец ли текста MOV EBX,IND CMP BYTE PTR [EBX],0 JE _END2 ; вывод очередного символа CALL OUTSTR ; задержка PUSH 1000 CALL Sleep@4 JMP L02 _END2: RET 4 THREAD2 ENDP _TEXT ENDS END START


Модуль на языке ассемблера для компоновки с помощью пакета Visual C++



Рисунок 3.7.2. Модуль на языке ассемблера для компоновки с помощью пакета Visual C++ 6.0.

Комментарий к программе на Рисунок 3.7.2.

Явное указание параметров в заголовке процедуры приводит:

К имени процедуры в объектном модуле автоматически добавляется @8 (а не @0). Ассемблер автоматически управляется со стеком.

Трансляция.

В проект Visual C++ включается уже объектный модуль, откомпилированный первоначально с помощью ml.exe. Если Вы хотите включить в проект ассемблерный текст. Вам придется для него указать способ трансляции, т.е. командную строку для программы ML.EXE.



Мы здесь рассмотрим только вызывающую программу



III

Хотя я указал, что, на мой взгляд, неявное связывание менее гибко, я нашел необходимым привести пример неявного связывания. Мы здесь рассмотрим только вызывающую программу, так как вызываемая программа, естественно не меняется. Как видите, текст программы стал несколько проще. Здесь важно заметить, что, во-первых, необходимо объявить вызываемую из динамической библиотеки процедуру как внешнюю, а, во-вторых, подключить статическую библиотеку DLLP1.LIB.

.386P ; плоская модель IFDEF MASM .MODEL FLAT, stdcall ELSE .MODEL FLAT ENDIF

; константы ; прототипы внешних процедур includelib dll1.lib IFDEF MASM ; MASM EXTERN DLLP1@0:NEAR EXTERN ExitProcess@4:NEAR ; директивы компоновщику для подключения библиотек includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib ELSE ; директивы компоновщику для подключения библиотек includelib c:\tasm32\lib\import32.lib EXTERN DLLP1:NEAR EXTERN ExitProcess:NEAR DLLP1@0 = DLLP1 ExitProcess@4 = ExitProcess ENDIF

;----------------------------------------------------- ; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' _DATA ENDS ; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: PUSH 1 ; параметр CALL DLLP1@0 ; выход _EXIT: PUSH 0 CALL ExitProcess@4 _TEXT ENDS END START



О страничной и сегментной адресации



О страничной и сегментной адресации.

Начну изложение с некоторого исторического экскурса. Семейство микропроцессоров Intel47 ведет свое начало с микропроцессора Intel 8086. В настоящее время во всю работает уже седьмое поколение. Каждое новое поколение отличалось от предыдущего в программном отношении, главным образом, расширением набора команд. Но были в этой восходящей лестнице и две ступени, сыгравшие огромную роль в развитии компьютеров на базе микропроцессоров Intel. Это микропроцессор 80286 (защищенный режим) и микропроцессор 80386 (страничная адресация).

До появления микропроцессора 80286 микропроцессоры использовались в так называемом реальном режиме адресации. Кратко изложу, в чем заключался этот режим. Для программирования использовался так называемый логический адрес, состоящий из двух 16-битных компонент: сегмента и смещения. Сегментный адрес мог храниться в одном из трех сегментных регистров CS,DS, SS,ES. Смещение хранилось в одном из индексных регистров DI, SI,BX,BP,SP48. При обращении к памяти логический адрес подвергался преобразованию, заключающемуся в том, что к смещению прибавлялся сегментный адрес, сдвинутый на четыре бита влево. В результате получался 20-битный адрес, который, как легко заметить, мог охватывать всего около 1 Мб памяти49. Операционная система MS DOS и была изначально рассчитана для работы в таком адресном пространстве. Получаемый 20-битный адрес назывался линейным, и при этом фактически совпадал с физическим адресом ячейки памяти. Разумеется, с точки зрения развития операционных систем это был тупик. Должна быть, по крайней мере, возможность расширять память, и не просто расширять, а сделать все адресное пространство равноправным. Выход был найден с введением так называемого защищенного режима.

Гениальность подхода заключалась в том, что на первый взгляд ничего не изменилось. По-прежнему логический адрес формировался при помощи сегментных регистров и регистров, где хранилось смещение. Однако сегментные регистры хранили теперь не сегментный адрес, а так называемый селектор, часть которого (13 бит) представляла собой индекс в некоторой таблице, называемой дескрипторной. Индекс указывал на дескриптор, в котором хранилась полная информация о сегменте. Размер дескриптора был достаточен для адресации уже гораздо большего объема памяти.



Основной модуль и динамическая библиотека Передача параметров



Рисунок 3.3.5. Основной модуль и динамическая библиотека. Передача параметров.

Трансляция программы на Рисунок 3.3.5.

MASM32.

ml /c /coff /DMASM dllex2.asm link /subsystem:windows dllex2.obj

TASM32.

tasm32 /ml dllex2.asm tlink32 -aa -Tpd dllex2.obj

Ниже мы рассмотрим весьма интересный пример (Рисунок 3.3.6). Основной процесс использует ресурсы загруженной им динамической библиотеки. Я уже говорил, что файлы шрифтов, по сути, являются динамическими библиотеками. Не правда ли удобно: ресурсы можно поместить отдельно от основной программы в динамическую библиотеку, загружая их по мере необходимости? Наша программа вначале загружает иконку из ресурсов динамической библиотеки и устанавливает ее на окно. Если Вы будете щелкать правой кнопкой мыши, направив курсор на окно, то будет вызываться процедура из динамической библиотеки, которая будет поочередно устанавливать то один, то другой значок на окно.

// файл dll3.rc // идентификаторы

#define IDI_ICON1 3 #define IDI_ICON2 10

// определили иконку IDI_ICON1 ICON "ico1.ico" IDI_ICON2 ICON "ico2.ico"

; динамическая библиотека DLL3.ASM .386P PUBLIC SETIC ; плоская модель IFDEF MASM .MODEL FLAT, stdcall ELSE .MODEL FLAT ENDIF

; константы WM_SETICON equ 80h

IFDEF MASM ; MASM ; прототипы внешних процедур EXTERN LoadIconA@8:NEAR EXTERN PostMessageA@16:NEAR ; директивы компоновщику для подключения библиотек includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib ELSE ; TASM ; прототипы внешних процедур EXTERN LoadIconA:NEAR EXTERN PostMessageA:NEAR ; директивы компоновщику для подключения библиотек includelib c:\tasm32\lib\import32.lib LoadIconA@8 = LoadIconA PostMessageA@16 = PostMessageA ENDIF ;-------------------------------------------------

; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' PRIZ DB 0 _DATA ENDS

; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' DLLENTRY: MOV EAX,1 RET 12 ; [EBP+8] ; [EBP+0CH] SETIC PROC EXPORT PUSH EBP MOV EBP,ESP ; выбрать, какую иконку устанавливать CMP PRIZ,0 JZ IC_1 MOV PRIZ,0 PUSH 3 JMP CONT IC_1: MOV PRIZ,1 PUSH 10 CONT: ; загрузить иконку из ресурсов библиотеки PUSH DWORD PTR [EBP+0CH] ; идентификатор ; динамической ; библиотеки CALL LoadIconA@8 ; установить значок окна PUSH EAX PUSH 0 PUSH WM_SETICON PUSH DWORD PTR [EBP+08H] ; дескриптор окна CALL PostMessageA@16 POP EBP RET 8 SETIC ENDP _TEXT ENDS END DLLENTRY




// файл dllex3.rc // определение констант

#define WS_SYSMENU 0x00080000L #define WS_MINIMIZEBOX 0x00020000L #define WS_MAXIMIZEBOX 0x00010000L #define DS_3DLOOK 0x0004L

// определение диалогового окна

DIAL1 DIALOG 0, 0, 340, 120 STYLE WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | DS_3DLOOK CAPTION "Диалоговое окно с иконкой из динамической библиотеки" FONT 8, "Arial" { }

; основной модуль DLLEX3.ASM, вызывающий ; процедуру из динамической библиотеки

.386P ; плоская модель IFDEF MASM .MODEL FLAT, stdcall ELSE .MODEL FLAT ENDIF

; константы ; сообщение приходит при закрытии окна WM_CLOSE equ 10h WM_INITDIALOG equ 110h WM_SETICON equ 80h WM_LBUTTONDOWN equ 201h

; прототипы внешних процедур IFDEF MASM ; MASM EXTERN PostMessageA@16:NEAR EXTERN GetProcAddress@8:NEAR EXTERN LoadLibraryA@4:NEAR EXTERN FreeLibrary@4:NEAR EXTERN ExitProcess@4:NEAR EXTERN GetModuleHandleA@4:NEAR EXTERN DialogBoxParamA@20:NEAR EXTERN EndDialog@8:NEAR EXTERN LoadIconA@8:NEAR ; директивы компоновщику для подключения библиотек includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib ELSE ; директивы компоновщику для подключения библиотек includelib c:\tasm32\lib\import32.lib EXTERN PostMessageA:NEAR EXTERN GetProcAddress:NEAR EXTERN LoadLibraryA:NEAR EXTERN FreeLibrary:NEAR EXTERN ExitProcess:NEAR EXTERN GetModuleHandleA:NEAR EXTERN DialogBoxParamA:NEAR EXTERN EndDialog:NEAR EXTERN LoadIconA:NEAR PostMessageA@16 = PostMessageA LoadIconA@8 = LoadIconA EndDialog@8 = EndDialog GetModuleHandleA@4 = GetModuleHandleA DialogBoxParamA@20 = DialogBoxParamA GetProcAddress@8 = GetProcAddress LoadLibraryA@4 = LoadLibraryA FreeLibrary@4 = FreeLibrary ExitProcess@4 = ExitProcess ENDIF ;------------------------------------------------

; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' LIBR DB 'DLL3.DLL',0 HLIB DD ? HINST DD ? PA DB "DIAL1",0 IFDEF MASM NAMEPROC DB "_SETIC@0",0 ELSE NAMEPROC DB "SETIC",0 ENDIF _DATA ENDS

; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; получить дескриптор приложения PUSH 0 CALL GetModuleHandleA@4 ; создать диалог MOV [HINST], EAX PUSH 0 PUSH OFFSET WNDPROC PUSH 0 PUSH OFFSET PA PUSH [HINST] CALL DialogBoxParamA@20 ; выход _EXIT: PUSH 0 CALL ExitProcess@4 ; процедура окна ; расположение параметров в стеке ; [EBP+014Н] ; LPARAM ; [EBP+10Н] ; WAPARAM ; [EBP+0CH] ; MES ; [EBP+8] ; HWND WNDPROC PROC PUSH EBP MOV EBP,ESP PUSH EBX PUSH ESI PUSH EDI ;------------------- CMP DWORD PTR [EBP+0CH],WM_CLOSE JNE L1 ; закрыть библиотеку ; библиотека автоматически закрывается также ; при выходе из программы PUSH HLIB CALL FreeLibrary@4 PUSH 0 PUSH DWORD PTR [EBP+08H] CALL EndDialog@8 JMP FINISH L1: CMP DWORD PTR [EBP+0CH],WM_INITDIALOG JNE L2 ; загрузить библиотеку PUSH OFFSET LIBR CALL LoadLibraryA@4 MOV HLIB,EAX ; загрузить иконку PUSH 3 ; идентификатор иконки PUSH [HLIB] ; идентификатор процесса CALL LoadIconA@8 ; установить иконку PUSH EAX PUSH 0 ; тип иконки (маленькая) PUSH WM_SETICON PUSH DWORD PTR [EBP+08H] CALL PostMessageA@16 JMP FINISH L2: CMP DWORD PTR [EBP+0CH],WM_LBUTTONDOWN JNE FINISH ; получить адрес процедуры из динамической библиотеки PUSH OFFSET NAMEPROC PUSH HLIB CALL GetProcAddress@8 ; вызвать процедуру с двумя параметрами PUSH [HLIB] PUSH DWORD PTR [EBP+08H] CALL EAX FINISH: POP EDI POP ESI POP EBX POP EBP MOV EAX,0 RET 16 WNDPROC ENDP _TEXT ENDS END START


Перейдем теперь к разбору программных примеров динамических библиотек



II

Перейдем теперь к разбору программных примеров динамических библиотек. На Рисунок 3.3.2 приводится пример простейшей динамической библиотеки. Данная динамическая библиотека, по сути, ничего не делает. Просто при загрузке библиотеки, ее выгрузки, а также вызове процедуры DLLP1 будет вызвано обычное Windows-сообщение. Обратите внимание, как определяется процесс загрузки и выгрузки библиотеки. Заметим также, что процедура входа должна возвращать ненулевое значение. Процедура DLLP1 обрабатывает также один параметр, передаваемый через стек обычным способом.

.386P ; плоская модель IFDEF MASM .MODEL FLAT, stdcall ELSE .MODEL FLAT ENDIF

PUBLIC DLLP1

; константы ; сообщения, приходящие при открытии ; динамической библиотеки

DLL_PROCESS_DETACH equ 0 DLL_PROCESS_ATTACH equ 1 DLL_THREAD_ATTACH equ 2 DLL_THREAD_DETACH equ 3

IFDEF MASM ; MASM ; прототипы внешних процедур

EXTERN MessageBoxA@16:NEAR

; директивы компоновщику для подключения библиотек includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib ELSE ; TASM EXTERN MessageBoxA:NEAR MessageBoxA@16 = MessageBoxA includelib c:\tasm32\lib\import32.lib ENDIF ;--------------------------------------------------

; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' TEXT1 DB 'Вход в библиотеку',0 TEXT2 DB 'Выход из библиотеки',0 MS DB 'Сообщение из библиотеки',0 TEXT DB 'Вызов процедуры из DLL',0 _DATA ENDS

; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' ; [EBP+10H] ; резервный параметр ; [EBP+0CH] ; причина вызова ; [EBP+8] ; идентификатор DLL-модуля DLLENTRY: MOV EAX,DWORD PTR [EBP+0CH] CMP EAX,0 JNE D1 ; закрытие библиотеки PUSH 0 PUSH OFFSET MS PUSH OFFSET TEXT2 PUSH 0 CALL MessageBoxA@16 JMP _EXIT D1: CMP EAX,1 JNE _EXIT ; открытие библиотеки PUSH 0 PUSH OFFSET MS PUSH OFFSET TEXT1 PUSH 0 CALL MessageBoxA@16 _EXIT: MOV EAX,1 RET 12

;——————————————————— ; [EBP+8] ; параметр процедуры DLLP1 PROC EXPORT PUSH EBP MOV EBP,ESP CMP DWORD PTR [EBP+8],1 JNE _EX PUSH 0 PUSH OFFSET MS PUSH OFFSET TEXT PUSH 0 CALL MessageBoxA@16 _EX: POP EBP RET 4 DLLP1 ENDP _TEXT ENDS END DLLENTRY



Первый пример



I

Первый пример, рассматриваемый в данном разделе, представляет простейший пример таймера. Таймер отсчитывает десять тиков и закрывает диалоговое окно, выдавая MessageBox с сообщением об окончании работы программы. Данная программа являет собой пример организации таймера на базе самой функции окна.

Заметим, что начиная с данной главы большинство приводимых программ могут транслироваться и в пакете MASM32, и в пакете TASM32 без всяких изменений.

// файл timer.rc // определение констант #define WS_SYSMENU 0x00080000L #define WS_MINIMIZEBOX 0x00020000L #define WS_MAXIMIZEBOX 0x00010000L

// стиль - кнопка #define BS_PUSHBUTTON 0x00000000L // кнопка в окне должна быть видимой #define WS_VISIBLE 0x10000000L // центрировать текст на кнопке #define BS_CENTER 0x00000300L // стиль кнопки #define WS_CHILD 0х40000000L // возможность фокусировать элемент // при помощи клавиши TAB #define WS_TABSTOP 0x00010000L #define DS_3DLOOK 0x0004L //определение диалогового окна DIAL1 DIALOG 0, 0, 240, 120 STYLE WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | DS_3DLOOK CAPTION "Пример диалогового окна с таймером" FONT 8, "Arial" { // кнопка, идентификатор 5 CONTROL "Выход", 5, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 180, 76, 50, 14 }

; файл timer.inc ; константы ; сообщение приходит при закрытии окна WM_CLOSE equ 10h WM_INITDIALOG equ 110h WM_COMMAND equ 111h WM_TIMER equ 113h

; прототипы внешних процедур IFDEF MASM EXTERN ReleaseDC@8:NEAR EXTERN GetDC@4:NEAR EXTERN TextOutA@20:NEAR EXTERN MessageBoxA@16:NEAR EXTERN ExitProcess@4:NEAR EXTERN GetModuleHandleA@4:NEAR EXTERN DialogBoxParamA@20:NEAR EXTERN EndDialog@8:NEAR EXTERN SendMessageA@16:NEAR EXTERN SetTimer@16:NEAR EXTERN KillTimer@8:NEAR ELSE EXTERN ReleaseDC:NEAR EXTERN GetDC:NEAR EXTERN TextOutA:NEAR EXTERN MessageBoxA:NEAR EXTERN ExitProcess:NEAR EXTERN GetModuleHandleA:NEAR EXTERN DialogBoxParamA:NEAR EXTERN EndDialog:NEAR EXTERN SendMessageA:NEAR EXTERN SetTimer:NEAR EXTERN KillTimer:NEAR ReleaseDC@8 = ReleaseDC GetDC@4 = GetDC TextOutA@20 = TextOutA MessageBoxA@16 = MessageBoxA ExitProcess@4 = ExitProcess GetModuleHandleA@4 = GetModuleHandleA DialogBoxParamA@20 = DialogBoxParamA EndDialog@8 = EndDialog SendMessageA@16 = SendMessageA SetTimer@16 = SetTimer KillTimer@8 = KillTimer ENDIF




; структуры ; структура сообщения MSGSTRUCT STRUC MSHWND DD ? MSMESSAGE DD ? MSWPARAM DD ? MSLPARAM DD ? MSTIME DD ? MSPT DD ? MSGSTRUCT ENDS

; файл timer.asm

.386P ; плоская модель .MODEL FLAT, stdcall include timer.inc ; директивы компоновщику для подключения библиотек IFDEF MASM ; для компоновщика LINK.EXE includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib includelib c:\masm32\lib\gdi32.lib ELSE ; для компоновщика TLINK32.EXE includelib c:\tasm32\lib\import32.lib ENDIF ;-------------------------------------------------

; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' MSG MSGSTRUCT <?> HINST DD 0 ; дескриптор приложения PA DB "DIAL1",0 COUNT DD 0 TEXT DB 0 CAP DB 'Сообщение',0 MES DB 'Выход по таймеру',0 _DATA ENDS

; сегмент кода

_TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; получить дескриптор приложения PUSH 0 CALL GetModuleHandleA@4 MOV [HINST], EAX ;---------------------------------------- PUSH 0 PUSH OFFSET WNDPROC PUSH 0 PUSH OFFSET PA PUSH [HINST] CALL DialogBoxParamA@20 CMP EAX,-1 JNE KOL KOL: ;---------------------------------------- PUSH 0 CALL ExitProcess@4 ;----------------------------------------

; процедура окна ; расположение параметров в стеке ; [EBP+014Н] ; LPARAM ; [EBP+10Н] ; WAPARAM ; [EBP+0CH] ; MES ; [EBP+8] ; HWND WNDPROC PROC PUSH EBP MOV EBP,ESP PUSH EBX PUSH ESI PUSH EDI ;--------------- CMP DWORD PTR [EBP+0CH],WM_CLOSE JNE L1 ; здесь реакция на закрытие окна L3: ; удалить таймер PUSH 1 ; идентификатор таймера PUSH DWORD PTR [EBP+08H] CALL KillTimer@8 ; закрыть диалог PUSH 0 PUSH DWORD PTR [EBP+08H] CALL EndDialog@8 JMP FINISH L1: CMP DWORD PTR [EBP+0CH], WM_INITDIALOG JNE L5 ; здесь начальная инициализация ; установить таймер PUSH 0 ; параметр = NULL PUSH 1000 ; интервал 1 с. PUSH 1 ; идентификатор таймера PUSH DWORD PTR [EBP+08H] CALL SetTimer@16 JMP FINISH L5: CMP DWORD PTR [EBP+0CH], WM_COMMAND JNE L2 ; кнопка выхода? CMP WORD PTR [EBP+10H],5 JNE FINISH JMP L3 L2: CMP DWORD PTR [EBP+0CH],WM_TIMER JNE FINISH ; не пора ли заканчивать ? CMP COUNT,9 ; выход без предупреждения JA L3 ; выход через сообщение JE L4 ; пришло сообщение таймера ; подготовить текст MOV EAX, COUNT ADD EAX,49 MOV TEXT, AL ; получить контекст PUSH DWORD PTR [EBP+08H] CALL GetDC@4 ; запомнить контекст PUSH EAX ; вывести значение счетчика PUSH 1 PUSH OFFSET TEXT PUSH 10 PUSH 10 PUSH EAX CALL TextOutA@20 ; удалить контекст POP EAX PUSH EAX PUSH DWORD PTR [EBP+08H] CALL ReleaseDC@8 ; увеличить счетчик INC COUNT JMP FINISH L4: INC COUNT ; сообщение о выходе по таймеру PUSH 0 PUSH OFFSET CAP PUSH OFFSET MES PUSH DWORD PTR [EBP+08H] ; дескриптор окна CALL MessageBoxA@16 JMP L3 FINISH: POP EDI POP ESI POP EBX POP EBP MOV EAX,0 RET 16 WNDPROC ENDP ;------------------------------------------------- _TEXT ENDS END START


Под процессом будем понимать объект



I

Под процессом будем понимать объект, создаваемый операционной системой Windows обычно при загрузке исполняемого модуля и получающий в единоличное пользование:

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

Думаю, данное определение весьма ясно раскрывает суть понятия процесс. Но для большинства рассматриваемых в данной главе проблем, достаточно было бы дать и более простое определение. Например, такое: всякий исполняемый модуль (.ЕХЕ), запущенный в операционной системе Windows, становится процессом.

Теперь что касается подпроцесса. Смысл его достаточно прост: каждый процесс в отведенном для него адресном пространстве может порождать еще процессы. Эти процессы выполняются независимо друг от друга и от порождающего их процесса. Однако порождающий процесс может при желании "убить" любой из порожденных им процессов. Такие процессы называют еще потоками, а также цепочками или нитями40. Теперь мы понимаем, что в предыдущей главе при помощи таймера мы создавали потоки. Однако в операционной системе Windows для создания потоков есть и специальные средства, речь о которых пойдет ниже.

Теперь немного поговорим о типах многозадачности. В старой 16-битной Windows переключение между задачами происходило только тогда, когда задача отдавала управление операционной системе. Такая многозадачность называется невытесняющей. В определенном смысле это было даже хуже, чем в операционной системе MS DOS. Там элементы многозадачности осуществлялись при помощи так называемых TSR-программ (см. [1]). Такие программы назывались еще резидентными. Они перехватывали прерывание от таймера, клавиатуры или другого устройства и имели возможность время от времени получать управление.

Положение, существовавшее в старой операционной системе Windows, требовало от программиста выполнения джентльменского правила - не захватывать надолго время микропроцессора. Некоторым решением проблемы являлось использование таймеров (в чем мы уже убедились), а также использование функции PeekMessage вместо GetMessage. Функция PeekMessage, в отличие от GetMessage, возвращает управление сразу, даже если в очереди нет ни одного сообщения.




В 32-битных операционных системах Windows ( Windows 9x, Windows NT, Windows 2000) реализована вытесняющая схема многозадачности, в которой переключением между процессами и потоками занимается операционная система. Если процесс слишком долго выполняет некоторую операцию, то курсор над окном процесса преобразуется в песочные часы. При этом другие процессы будут по-прежнему выполняться, и Вы сможете переключаться на них. А вот доступ к окну данного процесса может оказаться затруднительным. Решить данную проблему можно уже упомянутым способом, заменив в цикле ожидания GetMessage на PeekMessage. Однако более правильным решением будет разбиение процесса на некоторое количество потоков.

Созданием потоков мы займемся в следующих разделах, а оставшаяся часть данного раздела будет посвящена созданию процессов. Ваше приложение может создавать процессы, запустив ту или иную ЕХЕ-программу, которые будут работать независимо от основного приложения. Одновременно Ваше приложение может при необходимости удалить запущенное им приложение из памяти. Запустить приложение (создать процесс) можно при помощи функции CreateProcess. Сейчас мы дадим описание этой функции. Ниже объясняются ее параметры.



1-й параметр - указывает на имя запускаемой программы. Имя может содержать полный путь к программе. 2-й параметр - его значение зависит от того, является первый параметр NULL (0) или нет. Если первый параметр указывает на строку, то данный параметр трактуется как командная строка запуска (без имени программы). Если первый параметр равен NULL, то данный параметр рассматривается как командная строка, первый элемент которой представляет собой имя программы. Если путь к программе не указан, то функция CreateProcess осуществляет поиск программы по определенному алгоритму:

Поиск в каталоге, откуда была запущена программа. Поиск в текущем каталоге. Поиск в системном каталоге (можно получить через GetSystemDirectory). Обычно системным каталогом является C:\WINDOWS\SYSTEM. Поиск в каталоге Windows (можно получить через GetWindowsDirectory). Обычно этим каталогом является C:\WINDOWS. Поиск в каталогах, перечисленных в параметре PATH окружения.



3-й и 4- й параметры. Используются для задания атрибутов доступа порождаемого процесса. Обычно полагают равным 0. 5-й параметр. Если этот параметр 0, то порождаемый процесс не наследует дескрипторы порождающего процесса, в противном случае порождаемый процесс наследует дескрипторы. 6-й параметр. Может менять свойства порождаемого процесса. Если параметр равен нулю, то свойства задаются по умолчанию. В силу большого количества значений этого параметра, мы отсылаем желающих к соответствующим справочным руководствам. 7-й параметр. Является указателем на буфер, содержащий параметры среды. Если параметр равен 0, то порождаемый процесс наследует параметры среды порождающего процесса. 8-й параметр. Задает текущее устройство и каталог для порождаемого процесса. Если параметр равен NULL, порождаемый процесс наследует текущее устройство и каталог порождающего процесса. 9-й параметр. Представляет указатель на структуру, которая содержит информацию об окне создаваемого процесса. Ниже будут рассмотрены поля этой структуры.


10-й параметр. Указывает на структуру, заполняемую при выполнении запуск приложения. Вот эта структура:

PROCINF STRUC hProcess DD ? ; дескриптор созданного процесса. hThread DD ? ; дескриптор главного потока нового процесса. Idproc DD ? ; идентификатор созданного процесса. idThr DD ? ; идентификатор главного потока нового процесса. PROCINF ENDS

Основное отличие дескриптора от идентификатора заключается в том, что дескриптор уникален лишь в пределах данного процесса, идентификатор же является глобальной величиной. Посредством идентификатора может быть найдена база данных текущего процесса. У читателя, я думаю, сразу возникнет вопрос: а чем дескриптор приложения, который мы получаем при помощи функции GetModuleHandle, от только что упомянутых величин? Дескриптор приложения, или дескриптор модуля есть величина локальная, т.е. действующая в пределах данного процесса и, как правило, равная адресу загрузки модуля в виртуальное адресное пространство. Дескриптор модуля имеется у любого модуля, загруженного в память, в том числе и у подчиненных DLL-библиотек.



Рассмотрим теперь структуру, на которую указывает 9-й параметр функции CreateProcess. Вот эта структура:

STARTUP STRUC cb DD 0 lpReserved DD 0 lpDesktop DD 0 lpTitle DD 0 dwX DD 0 dwY DD 0 dwXSize DD 0 dwYSize DD 0 dwXCountChars DD 0 dwYCountChars DD 0 dwFillAttribute DD 0 dwFlags DD 0 wShowWindow DW 0 cbReserved2 DW 0 lpReserved2 DD 0 hStdInput DD 0 hStdOutput DD 0 hStdError DD 0 STARTUP ENDS

Итак, разберем смысл полей этой структуры.

cb - размер данной структуры в байтах. Заполняется обязательно.

lpReserved - резерв, должно быть равно нулю.

lpDesktop - имя рабочего стола (и рабочей станции). Имеет смысл только для Windows NT.

lpTitle - название окна для консольных приложений, создающих свое окно. Для остальных приложений должно быть равно 0.

dwX - координата X левого верхнего угла окна.

dwY - координата Y левого верхнего угла окна.

dwXSize - размер окна по X.

dwYSize - размер окна по Y.

dwXCountChars - размер буфера консоли по X.

dwYCountChars - размер буфера консоли по Y.

dwFillAttribute - начальный цвет текста. Имеет значение только для консольных приложений.

dwFlags - флаг значения полей. Вот значение этого флага.

Макро-значение
флагаЗначение
константыСмысл значения wShowWindow - определяет способ отображения окна.

cbReserved2 - резерв, должно быть равно 0.

hStdInput - дескриптор ввода (для консоли).

hStdOutput - дескриптор вывода (для консоли).

hStdError - дескриптор вывода сообщения об ошибке (для консоли).

Следующий пример (Рисунок 3.2.1) представляет собой простейший пример создания процесса. В качестве программы, порождающей процесс, взят редактор WINWORD.EXE. Для проверки правильности работы примера Вам придется указать путь к WINWORD на Вашем компьютере (переменная PATH). Обратите внимание на то, что приложение появляется на экране в свернутом виде и как это достигается. Как видите, функция CreateProcess совсем не так уж страшна, как кажется на первый взгляд.



// файл proces.rc

// определение констант #define WS_SYSMENU 0x00080000L #define WS_POPUP 0x80000000L #define DS_3DLOOK 0x0004L

//ресурс - меню MENUP MENU { POPUP "&Запуск Word" { MENUITEM "&3апустить", 1 MENUITEM "&Удалить", 2 MENUITEM "Выход из &программы", 3 } }

// определение диалогового окна DIAL1 DIALOG 0, 0, 240, 120 STYLE WS_POPUP | WS_SYSMENU | DS_3DLOOK CAPTION "Пример немодального диалогового окна" FONT 8, "Arial" { }

; файл proces.inc

; константы STARTF_USESHOWWINDOW equ 1h SW_SHOWMINIMIZED equ 2h

; сообщение приходит при закрытии окна WM_CLOSE equ 10h WM_INITDIALOG equ 110h WM_COMMAND equ 111h

; прототипы внешних процедур IFDEF MASM ; для MASM EXTERN TerminateProcess@8:NEAR EXTERN CreateProcessA@40:NEAR EXTERN DialogBoxParamA@20:NEAR EXTERN EndDialog@8:NEAR EXTERN MessageBoxA@16:NEAR EXTERN ExitProcess@4:NEAR EXTERN GetModuleHandleA@4:NEAR EXTERN LoadMenuA@8:NEAR EXTERN SetMenu@8:NEAR EXTERN TranslateMessage@4:NEAR ELSE ; для TASM EXTERN TerminateProcess:NEAR EXTERN CreateProcessA:NEAR EXTERN DialogBoxParamA:NEAR EXTERN EndDialog:NEAR EXTERN MessageBoxA:NEAR EXTERN ExitProcess:NEAR EXTERN GetModuleHandleA:NEAR EXTERN LoadMenuA:NEAR EXTERN SetMenu:NEAR EXTERN TranslateMessage:NEAR TerminateProcess@8 = TerminateProcess CreateProcessA@40 = CreateProcessA DialogBoxParamA@20 = DialogBoxParamA EndDialog@8 = EndDialog MessageBoxA@16 = MessageBoxA ExitProcess@4 = ExitProcess GetModuleHandleA@4 = GetModuleHandleA LoadMenuA@8 = LoadMenuA SetMenu@8 = SetMenu TranslateMessage@4 = TranslateMessage ENDIF

; структуры ; структура сообщения MSGSTRUCT STRUC MSHWND DD ? MSMESSAGE DD ? MSWPARAM DD ? MSLPARAM DD ? MSTIME DD ? MSPT DD ? MSGSTRUCT ENDS

; структура для CreateProcess STARTUP STRUC cb DD 0 lpReserved DD 0 lpDesktop DD 0 lpTitle DD 0 dwX DD 0 dwY DD 0 dwXSize DD 0 dwYSize DD 0 dwXCountChars DD 0 dwYCountChars DD 0 dwFillAttribute DD 0 dwFlags DD 0 wShowWindow DW 0 cbReserved2 DW 0 lpReserved2 DD 0 hStdInput DD 0 hStdOutput DD 0 hStdError DD 0 STARTUP ENDS



; структура - информация о процессе PROCINF STRUC hProcess DD ? hThread DD ? Idproc DD ? idThr DD ? PROCINF ENDS

; файл proces.asm .386P ; плоская модель .MODEL FLAT, stdcall include proces.inc ; директивы компоновщику для подключения библиотек IFDEF MASM ; директивы MASM includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib ELSE ; директивы TASM includelib c:\tasm32\lib\import32.lib ENDIF

; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' NEWHWND DD 0 MSG MSGSTRUCT <?> STRUP STARTUP <?> INF PROCINF <?> HINST DD 0 ; дескриптор приложения PA DB "DIAL1",0 PMENU DB "MENUP",0 PATH DB "C:\Program Files\Office\WINWORD.EXE",0 _DATA ENDS

; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; получить дескриптор приложения PUSH 0 CALL GetModuleHandleA@4 MOV [HINST], EAX ; создать модальный диалог PUSH 0 PUSH OFFSET WNDPROC PUSH 0 PUSH OFFSET PA PUSH [HINST] CALL DialogBoxParamA@20 PUSH 0 CALL ExitProcess@4 ;-----------------------------------

; процедура окна ; расположение параметров в стеке ; [EBP+014Н] ; LPARAM ; [EBP+10Н] ; WAPARAM ; [EBP+0CH] ; MES ; [EBP+8] ; HWND WNDPROC PROC PUSH EBP MOV EBP,ESP PUSH EBX PUSH ESI PUSH EDI ; сообщение при закрытии окна CMP DWORD PTR [EBP+0CH],WM_CLOSE JNE L1 ; закрыть диалоговое окно JMP L5 L1: ; сообщение при инициализации окна CMP DWORD PTR [EBP+0CH],WM_INITDIALOG JNE L3 ; загрузить меню PUSH OFFSET PMENU PUSH [HINST] CALL LoadMenuA@8 ; установить меню PUSH EAX PUSH DWORD PTR [EBP+08H] CALL SetMenu@8 JMP FINISH ; проверяем, не случилось ли чего с пунктами ; меню в диалоговом окне L3: CMP DWORD PTR [EBP+0CH],WM_COMMAND JNE FINISH CMP WORD PTR [EBP+10H],3 JE L5 CMP WORD PTR [EBP+10H],2 JE L7 CMP WORD PTR [EBP+10H],1 JE L6 JMP FINISH ; закрыть диалоговое окно L5: PUSH 0 PUSH DWORD PTR [EBP+08H] CALL EndDialog@8 JMP FINISH ; запустить программу WORD L6: ; заполняем структуру для запуска ; окно должно появляться в свернутом виде MOV STRUP.cb,68 MOV STRUP.lpReserved,0 MOV STRUP.lpDesktop,0 MOV STRUP.lpTitle,0 MOV STRUP.dwFlags, STARTF_USESHOWWINDOW MOV STRUP.cbReserved2,0 MOV STRUP.lpReserved2,0 MOV STRUP.wShowWindow,SW_SHOWMINIMIZED ; запуск приложения Winword PUSH OFFSET INF PUSH OFFSET STRUP PUSH 0 PUSH 0 PUSH 0 PUSH 0 PUSH 0 PUSH 0 PUSH OFFSET PATH PUSH 0 CALL CreateProcessA@40 JMP FINISH ; удалить из памяти процесс L7: PUSH 0 ; код выхода PUSH INF.hProcess CALL TerminateProcess@8 FINISH: MOV EAX,0 POP EDI POP ESI POP EBX POP EBP RET 16 WNDPROC ENDP _TEXT ENDS END START


Поговорим теперь о многопотоковой программе



III

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

Нам уже пришлось столкнуться с ситуацией, когда два параллельных процесса взаимодействовали друг с другом посредством глобальных переменных. Точнее, один процесс готовил данные для другого процесса. Здесь не было никакой сложности: просто один процесс с некоторой периодичностью менял содержимое переменной, а второй процесс, с другой периодичностью, читал из этой переменной. Если период обновления данных меньше периода взятия данных, то мы почти с достоверностью (почти!) получаем, что второй процесс будет получать всегда свежие данные. Иногда это, т.е. соотношение между периодом обновления и периодом получения данных, как в нашем случае, вообще не имеет никакого значения.

Часто случается, что данные невозможно получать периодически. Они могут, например, зависеть от деятельности третьего процесса. Как же второй процесс узнает, что данные уже готовы для передачи? На первый взгляд проблема решается введением дополнительной переменной, назовем ее FLAG. Примем, что при FLAG=0 данные не готовы, а при FLAG=1 данные готовы. Далее действует весьма простая схема.

NO_DAT: CMP FLAG,1 JNE NO_DAT ... ;передача данных ... MOV FLAG,0 ...

Это фрагмент, как Вы понимаете, для второго потока. Первый же поток также должен проверять переменную FLAG и, если FLAG=0, поместить новые данные и установить значение переменной FLAG, равное единице. Данная схема совсем не плоха, например, когда один процесс ждет окончания работы другого процесса. Другими словами, данные являются результатом всей работы этого процесса. Например, запущен компилятор, а другой поток ждет окончания его работы, дабы вывести результаты этой работы на некое устройство. Эта ситуация весьма распространена, но все же случай этот частный.




А что будет, если процесс передачи данных должен производится многократно? Легко видеть, что данная схема будет работать и в более сложном случае. Важно, однако, чтобы второй процесс менял содержимое FLAG только после того, как он возьмет данные, а первый процесс - после того, как положит данные. Если нарушить это правило, то может возникнуть коллизия, когда, например, второй процесс еще не взял данные, а они уже изменились.

Такой подход можно осуществить и в более общем случае, когда два потока (или - два процесса) должны поочередно получать доступ к одному ресурсу. Как легко видеть, данный подход предполагает, что ресурс открыт либо для одного, либо для другого процесса. Если бы поставить задачу несколько иным образом - процесс либо открыт, либо закрыт для доступа, то возникло бы некоторое затруднение. Действительно, вероятна такая ситуация, когда оба потока ожидают открытия ресурса. Другими словами, они непрерывно осуществляют проверку переменной FLAG (CMP FLAG,1). Может статься, что они оба почти одновременно обратятся к ресурсу. Совершенно ясно, что здесь возникает необходимость в "третьей силе", которая бы занималась распределением доступа к ресурсу. Например, посылала бы сообщение вначале одному потоку и, если он ожидает доступа, давала доступ именно ему, а затем подобный процесс повторяется со вторым потоком.

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

Оставшаяся часть данного раздела будет всецело посвящена средствам синхронизации операционной системы Windows.


Преобразование линейного адреса


. Конечно, читатель уже догадался, что для каждого процесса должен существовать свой каталог таблиц страниц. Переключение же между процессами можно осуществлять посредством изменения содержимого регистра CR3. Однако это не совсем рационально, так как требует большого объема памяти. В реальной ситуации для переключения между процессами производится изменение каталога таблиц страниц.

Обратимся теперь к структуре дескрипторов страниц (дескриптор таблицы страниц имеет ту же самую структуру).

Биты 12-31 - адрес страницы, который в дальнейшем складывается со смещением, предварительно сдвигаясь на двенадцать бит. Биты 9-11 - для использования операционной системой. Биты 7-8 - зарезервированы и должны быть равны нулю. Бит 6 - устанавливается, если была осуществлена запись в каталог или страницу. Бит 5 - устанавливается перед чтением и записью на страницу. Бит 4 - запрещение кэширования. Бит 3 - бит сквозной записи. Бит 2 - если значение этого бита равно 0, то страница относится к супервизору, если 1, то страница относится к рабочему процессу. Этим устанавливается два уровня доступа. Бит 1 - если бит установлен, то запись на страницу разрешена. Бит 0. Если бит установлен, то страница присутствует в памяти. Страницы, содержащие данные сбрасываются на диск и считываются, когда происходит обращение к ним. Страницы, содержащие код, на диск не сбрасываются, но могут подкачиваться из соответствующих модулей на диске. Поэтому память, занятая этими страницами, также может рационально использоваться.

47 Я имею в виду и микропроцессоры совместимые с Intel, выпускаемые другими фирмами.

48 В узком смысле слова индексными регистрами называются DI и SI.

49 Когда-то казалось, что один мегабайт памяти это много.

50 В старых моделях регистр содержал всего 2 байта.

51 Размер страницы в операционной системе Windows NT может отличаться от 4 Кб, что, впрочем, почти никогда не сказывается на программировании.



Пример диалогового окна с всплывающими подсказками



Рисунок 3.1.3. Пример диалогового окна с всплывающими подсказками.



Пример динамической библиотеки написанной на Delphi



Рисунок 3.7.12. Пример динамической библиотеки, написанной на Delphi.

; файл setup.asm .386P ; плоская модель .MODEL FLAT, stdcall ; константы ; прототипы внешних процедур IFDEF MASM ; MASM EXTERN GetProcAddress@8:NEAR EXTERN LoadLibraryA@4:NEAR EXTERN FreeLibrary@4:NEAR EXTERN ExitProcess@4:NEAR EXTERN MessageBoxA@16:NEAR includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib ELSE includelib c:\tasm32\lib\import32.lib EXTERN GetProcAddress:NEAR EXTERN LoadLibraryA:NEAR EXTERN FreeLibrary:NEAR EXTERN ExitProcess:NEAR EXTERN MessageBoxA:NEAR GetProcAddress@8 = GetProcAddress LoadLibraryA@4 = LoadLibraryA FreeLibrary@4 = FreeLibrary ExitProcess@4 = ExitProcess MessageBoxA@16 = MessageBoxA ENDIF ;-------------------------------------------- ; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' TXT DB 'Ошибка динамической библиотеки',0 MS DB 'Сообщение',0 LIBR DB 'LNK.DLL',0 HLIB DD ? PAR1 DB "C:\PROG\FILES.EXE",0 PAR2 DB "C:\PROG\UNINST.EXE",0 PAR3 DB "Универсальный поиск",0 PAR4 DB "Программа универсального поиска",0 NAMEPROC DB 'setup',0 _DATA ENDS

; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' ; [EBP+10Н] ; резервный параметр ; [EBP+0CH] ; причина вызова ; [EBP+8] ; идентификатор DLL-модуля START: ;загрузить библиотеку PUSH OFFSET LIBR CALL LoadLibraryA@4 CMP EAX,0 JE _ERR MOV HLIB,EAX ;получить адрес PUSH OFFSET NAMEPROC PUSH HLIB CALL GetProcAddress@8 CMP EAX,0 JNE YES_NAME ; сообщение об ошибке _ERR: PUSH 0 PUSH OFFSET MS PUSH OFFSET TXT PUSH 0 CALL MessageBoxA@16 JMP _EXIT YES_NAME: PUSH OFFSET PAR4 PUSH OFFSET PAR3 PUSH OFFSET PAR2 PUSH OFFSET PAR1 CALL EAX ; закрыть библиотеку ; библиотека автоматически закрывается также ; при выходе из программы PUSH HLIB CALL FreeLibrary@4 ; выход _EXIT: PUSH 0 CALL ExitProcess@4 _TEXT ENDS END START



Пример использования директивы



Рисунок 3.7.11. Пример использования директивы ASM и команд сопроцессора в программе на языке Си (Borland C++ 5.0).



Пример использования директивы



Рисунок 3.7.10. Пример использования директивы ASM и команд сопроцессора в программе на языке Паскаль (Delphi 5.0).

#include <windows.h> #include <stdio.h> double soproc(double f);

void main() { double w=-3.14; while(w<=3.14) { printf("%f- %f\n", w, soproc(w)); w=w+0.1; } ExitProcess(0); }

double soproc(double f) { double d; asm { FLD f FSIN FSTP d } return d; }



Пример использования двух таймеров



Рисунок 3.1.2. Пример использования двух таймеров.

Трансляция программы.

Пакет MASM32.

ML /c /coff /DMASM timer2.asm RC timer2.rc LINK /SUBSYSTEM:WINDOWS timer2.obj timer2.res

Пакет TASM32.

TASM32 /ml timer2.asm BRCC32 timer2.rc TLINK32 -aa timer2.obj,,,,,timer2.res



Пример использования подклассов



Рисунок 3.5.2. Пример использования подклассов.

Трансляция программы на Рисунок 3.5.2.

MASM32:

ml /c /coff /DMASM edit.asm link /subsystem:windows edit.obj

TASM32:

tasm32 /ml edit.asm tlink32 -aa edit.obj

Разбирая программу, представленную на Рисунок 3.2.2, обратите внимание, что представленная схема позволяет делать с окном редактирования практически любые трюки. К примеру. Вы можете заблокировать любой символ, послав вместо него код 0, или вместо одного символа подставить другой и т.д.



Пример использования процедуры из внешнего модуля Используется транслятор BORLAND C++



Рисунок 3.7.1. Пример использования процедуры из внешнего модуля. Используется транслятор BORLAND C++ 5.0.

Трансляция модулей на 3.7.1.

1-й способ.

В начале TASM32 /ml copy.asm

Затем в проект Borland C++ включить файл copy.obj.

2-й способ.

В проект включить сразу файл copy.asm, тогда транслятор автоматически вызовет TASM32.EXE.

3-й способ. Трансляция из командной строки. Предварительно готовим командный файл copy. Содержимое файла:

copyc.cpp copy.obj

Далее вызываем bcc32 @сору.

4-й способ. В командном файле вместо copy.obj помещаем файл copy.asm.

2)



Пример использования разделяемой памяти в динамической библиотеке



Рисунок 3.3.7. Пример использования разделяемой памяти в динамической библиотеке.

Трансляция программ на Рисунок 3.3.7.

MASM32.

ml /c /coff /DMASM dll4.asm link /subsystem:windows /DLL /section:.data,SRW dll4.obj ml /c /coff /DMASM dllex4.asm link /subsystem:windows dllex4.obj

Атрибуты опции SECTION: S-SHARED, R-READ, W-WRITE.

TASM32.

tasm32 /ml dll4.asm tlink32 -aa -Tpd dll4.obj,,,,dll4.def tasm32 /ml dllex4.asm tlink32 -aa dllex4.obj

Содержимое DEF-файла:

SECTIONS .DATA SHARED EXPORTS DLLP1



Пример использования в ассемблерном модуле APIфункций и ресурсов



Рисунок 3.7.6. Пример использования в ассемблерном модуле API-функций и ресурсов.

Трансляция.

1-й способ.

tasm32 /ml dialforc.asm brcc32 dialforc.rc

Далее включаем в проект для программы на Рисунок 3.7.5 файлы dialforc.obj и dialforc.res.

2-й способ.

Отличается от предыдущего тем, что в проект включается файл dialforc.rc, а не dialforc.res. Разумеется, определения констант оттуда следует убрать, т.к. они имеются в подключаемых Си head-файлах.



Пример подключения объектного модуля к программе на Delphi



Рисунок 3.7.3. Пример подключения объектного модуля к программе на Delphi.

Трансляция.

Вам достаточно только подготовить объектный модуль (с помощью транслятора TASM32) и указать его в директиве {$L copy.obj} - далее транслятор сделает все сам.



Пример программы на языке



Рисунок 3.7.13. Пример программы на языке ассемблера, осуществляющей вызов динамической библиотеки на Рисунок 3.8.12.

Трансляция.

MASM32:

ml /c /coff /DMASM setup.asm link /subsystem:windows setup.obj

TASM32:

tasm32 /ml setup.asm tlink32 -aa setup.obj

Заключая данную главу, отмечу, что мы здесь рассмотрели далеко не все возможные случаи и проблемы стыковки ассемблера с языками высокого уровня. Разобрав, однако, предложенные примеры, Вы сможете самостоятельно решить все подобные задачи.



Пример программы с выделением динамической памяти



Рисунок 3.6.4. Пример программы с выделением динамической памяти.

Трансляция программы на Рисунок 3.6.4.

MASM32:

ML /С /coff /DMASM MEM.ASM LINK /SUBSYSTEM:CONSOLE MEM.OBJ

TASM32:

TASM32 /ml MEM.ASM TLINK32 -ap MEM.OBJ

Операционная система Windows предоставляет также группу функций, осуществляющих управление виртуальной памятью. Основной функцией этой группы является функция VirtualAlloc. Вот параметры этой функции:

1-й параметр. Адрес блока памяти для резервирования или передачи ему физической памяти. 2-й параметр. Размер блока. 3-й параметр. Может быть равен MEM_RESERVE - для резервирования блока, или MEM_COMMIT - для резервирования и передачи ему физической памяти. 4-й параметр. Определяет уровень защиты блока. Он может быть, например, равен PAGE_READONLY или PAGE_READWRITE, или другой константе, определенной в документации Windows. Возвращает функция виртуальный адрес блока памяти.

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

Другая функция, VirtualFree, может освобождать блоки, задействованные функцией VirtualAlloc. Первым параметром этой функции является адрес блока. Вторым параметром функции является размер освобождаемого блока. Третий параметр функции может принимать значение МЕМ_DЕСОММIТ либо значение MEM_RELEASE. В первом случае блок (или его часть) перестает быть отображаемым. Во втором случае весь блок перестает быть зарезервированным. При этом значении второй параметр обязательно должен быть равен нулю.



Пример работы программы на Рисунок



Рисунок 3.5.5. Пример работы программы на Рисунок 3.5.4.




Пример работы программыкалькулятора (Рисунок ) V



V

Теперь поговорим о встроенном ассемблере. Это весьма мощное средство. Надо только иметь в виду, что встроенные ассемблеры часто несколько отстают от обычных ассемблеров в части поддержки новых команд микропроцессоров. Это вполне объяснимо, так как разработка новой версии пакета, скажем C++ Builder, требует гораздо больше времени, чем пакета TASM. В примерах на Рисунок 3.7.10 и Рисунок 3.7.11 мы используем команды арифметического сопроцессора.

program Project2; {$APPTYPE CONSOLE} uses SysUtils; var d:double; function soproc(f:double): double; var res:double; begin asm FLD f FSIN FSTP res end; soproc:=res; end;

begin d:=-pi; while (d<=pi) do begin writeln(d:10:2,'-',soproc(d):10:2); d:=d+0.1; end; end.



Пример реализации простейшего таймера



Рисунок 3.1.1 Пример реализации простейшего таймера.

Прокомментируем программу на Рисунок 3.1.1. Организация таймера здесь проста и очевидна. Единственное, что может вызвать трудность понимания, это то, как удается оставить MessageBox и одновременно закрыть диалоговое окно. Но здесь тоже все достаточно просто: сообщение появляется при COUNT=9, а когда приходит следующее сообщение, то COUNT уже больше 9, и выполняется ветка закрытия диалогового окна.

Трансляция программы.

Пакет MASM32.

ML /c /coff /DMASM timer.asm RC timer.rc LINK /SUBSYSTEM:WINDOWS timer.obj timer.res

Пакет TASM32.

TASM32 /ml timer.asm BRCC32 timer.rc TLINK32 -aa timer.obj,,,,,timer.res



Пример регистрового соглашения вызова процедуры



Рисунок 3.7.4. Пример регистрового соглашения вызова процедуры.

Трансляция модулей на Рисунок 3.7.4.

tasm32 /ml add.asm

Включаем add.obj в проект addc (Borland C++ 5.0)



Пример синхронизации двух потоков посредством критической секции



Рисунок 3.2.3. Пример синхронизации двух потоков посредством критической секции.

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

Трансляция программы THREAD2.ASM на Рисунок 3.2.3.

Пакет MASM32.

ML /c /coff /DMASM thread2.asm LINK /SUBSYSTEM:WINDOWS thread2.obj

Пакет TASM32.

TASM32 /ml thread2.asm TLINK32 -aa thread2.obj

При нажатии левой кнопки мыши начинается вывод текстовой строки. При нажатии правой кнопки мыши - выведенная строка стирается. Флаги FLAG1 и FLAG2 введены для того, чтобы вывод строки и вывод пустой строки можно было производить только один раз.

Для того чтобы несколько замедлить вывод текста, мы вводим задержку (Sleep) в цикл вызова процедуры OUTSTR в каждом потоке.

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

После в обоих потоках срабатывает задержка (функция Sleep).

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



Пример создания потока



Рисунок 3.2.2. Пример создания потока.

Комментарий к программе на Рисунок 3.2.2.

Трансляция программы THREAD.ASM на Рисунок 3.2.2.

Пакет MASM32.

ML /c /coff /DMASM thread.asm RC thread.rc LINK /SUBSYSTEM:WINDOWS thread.obj thread.res

Пакет TASM32.

TASM32 /ml thread.asm BRCC32 thread.rc TLINK32 -aa thread.obj,,,,,thread.res

Прошу читателя взять на вооружение весьма полезную функцию Sleep. Эта функция особенно часто используется именно в потоках, дабы несколько высвободить процессорное время.



Пример создания процесса



Рисунок 3.2.1. Пример создания процесса.

Трансляция программы PROCES.ASM на Рисунок 3.2.1.

Пакет MASM32.

ML /c /coff /DMASM proces.asm RC proces.rc LINK /SUBSYSTEM:WINDOWS proces.obj proces.res

Пакет TASM32.

TASM32 /ml proces.asm BRCC32 proces.rc TLINK32 -aa proces.obj,,,,,proces.res

Думаю, что другой комментарий для программы на Рисунок 3.2.1 не требуется.

40 В английской терминологии поток называется "THREAD", что буквально переводится как нить.



Пример взаимодействия с консольным процессом через PIPE



Рисунок 3.5.3. Пример взаимодействия с консольным процессом через PIPE.

Трансляция программы на Рисунок 3.5.3.

MASM32:

ml /c /coff /DMASM pipe.asm rc pipe.rc link /subsystem:windows pipe.obj pipe.res

TASM32:

tasm32 /ml pipe.asm brcc32 pipe.rc tlink32 -aa pipe.obj,,,,,pipe.res

Комментарий к программе на Рисунок 3.5.3.

Вообще, запуск консольного приложения - дело довольно запутанное. Мы не будем вдаваться в детали. В нашей программе этот запуск почти не отличается от запуска программы Word.exe в главе 3.2. Отмечу новое для Вас в этой программе. Обратите внимание, что управляющий элемент EditBox выступает в несколько новой ипостаси. По сути, этот элемент играет роль консоли вывода. Для этого мы указали свойство ES_MULTILINE, что дает возможность помещать в окно целый текст, который отправляется в окно при помощи сообщения EM_REPLACESEL. Для чтения информации мы используем довольно большой буфер. В принципе, как и в случае с файлами, можно читать несколькими порциями, проверяя количество считанных байт.

46 Так называемые именованные каналы реализованы в Windows NT.



Программа осуществляющая соединение с сетевым ресурсам



Рисунок 3.4.3. Программа, осуществляющая соединение с сетевым ресурсам.

Трансляция программы на Рисунок 3.4.3.

MASM32:

ml /c /coff /DMASM net.asm link /subsystem:console net.obj

TASM32:

tasm32 /ml net.asm tlink32 -ap net.obj

Рассматривая программу на Рисунок 3.4.3, прежде всего обратите внимание на использование локальных меток. MASM распознает локальные метки автоматически. TASM требует, чтобы локальная метка имела в начале два символа "@@". Кроме того, в начале программы требуется директива LOCALS. Отметим одну существенную особенность функции WNetAddConnection2A@16. При заполнении структуры NETRESOURCE поле dwType для операционной системы Windows NT должно заполняться нулем, а не RESOURCETYPE_DISK. К сожалению, имеются и другие подобные нюансы при работе с ресурсами локальной сети. Если Вы всерьез займетесь программированием в локальной сети, Вам придется проверять свою программу на разных компьютерах и разных сетях. Данная программа далека от совершенства. Попробуйте над ней поработать. В частности, не мешало бы проверять, является ли локальное устройство сетевым или нет, и если является, спросить разрешение на переподключение данного устройства. В этом случае необходимо вначале отключить устройство от старого сетевого ресурса при помощи известной Вам функции WNetCancelConnection2. Если необходимо подключать сетевой принтер, то поле dwType должно быть равно RESOURCETYPE_PRINT.

Следующая программа (Рисунок 3.4.4) осуществляет рекурсивный поиск сетевых ресурсов. Работая в консольном режиме, она выдает на экран название провайдера и удаленное имя ресурса. Данная программа должна правильно работать и в сетях Microsoft, и в сетях Novel.

;программа NET1, осуществляющая поиск сетевых ресурсов .386P ; плоская модель .MODEL FLAT, stdcall ; константы STD_OUTPUT_HANDLE equ -11 RESOURCETYPE_DISK equ 1h RESOURCE_GLOBALNET equ 2h RESOURCETYPE_ANY equ 0h

; прототипы внешних процедур IFDEF MASM EXTERN CharToOemA@8:NEAR EXTERN RtlMoveMemory@12:NEAR EXTERN WNetCloseEnum@4:NEAR EXTERN WNetEnumResourceA@16:NEAR EXTERN WNetOpenEnumA@20:NEAR EXTERN lstrcpy@8:NEAR EXTERN lstrcat@8:NEAR EXTERN lstrlen@4:NEAR EXTERN GetStdHandle@4:NEAR EXTERN WriteConsoleA@20:NEAR EXTERN ExitProcess@4:NEAR EXTERN GetCommandLineA@0:NEAR ELSE EXTERN CharToOemA:NEAR EXTERN RtlMoveMemory:NEAR EXTERN WNetCloseEnum:NEAR EXTERN WNetEnumResourceA:NEAR EXTERN WNetOpenEnumA:NEAR EXTERN lstrcpy:NEAR EXTERN lstrcat:NEAR EXTERN lstrlen:NEAR EXTERN GetStdHandle:NEAR EXTERN WriteConsoleA:NEAR EXTERN ExitProcess:NEAR EXTERN GetCommandLineA:NEAR CharToOemA@8 = CharToOemA RtlMoveMemory@12 = RtlMoveMemory WNetCloseEnum@4 = WNetCloseEnum WNetEnumResourceA@16 = WNetEnumResourceA lstrcpy@8 = lstrcpy WNetOpenEnumA@20 = WNetOpenEnumA lstrcat@8 = lstrcat lstrlen@4 = lstrlen GetStdHandle@4 = GetStdHandle WriteConsoleA@20 = WriteConsoleA ExitProcess@4 = ExitProcess GetCommandLineA@0 = GetCommandLineA ENDIF




; структуры NETRESOURCE STRUC dwScope DWORD ? dwType DWORD ? dwDisplayType DWORD ? dwUsage DWORD ? lpLocalName DWORD ? lpRemoteName DWORD ? lpComment DWORD ? lpProvider DWORD ? NETRESOURCE ENDS

; директивы компоновщику для подключения библиотек IFDEF MASM includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib includelib c:\masm32\lib\mpr.lib ELSE includelib c:\tasm32\lib\import32.lib ENDIF

;———————————————————————————————————————————————

; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' LENS DWORD ? ; количество выведенных символов HANDL DWORD ? NR NETRESOURCE <0> ENT DB 13,10,0 BUF DB 100 dup (0) _DATA ENDS

; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; получить дескриптор выхода вывода PUSH STD_OUTPUT_HANDLE CALL GetStdHandle@4 MOV HANDL,EAX ; запустить процедуру поиска сетевых ресурсов PUSH 0 PUSH OFFSET NR CALL POISK _END: PUSH 0 CALL ExitProcess@4 ; процедура поиска PAR1 EQU [EBP+8] ; указатель на структуру PAR2 EQU [EBP+0CH] ; признак ; локальные переменные HANDLP EQU [EBP-4] ; дескриптор поиска CC EQU [EBP-8] NB EQU [EBP-12] NR1 EQU [EBP-44] ; структура BUFER EQU [EBP-144] ; буфер RS EQU [EBP-32144] ; массив структур POISK PROC PUSH EBP MOV EBP,ESP SUB ESP,32144 CMP DWORD PTR PAR2,0 JNE SECOND ; при первом запуске NULL XOR EBX,EBX JMP FIRST SECOND: ; запуск при рекурсивном вызове ; вначале скопировать структуру в локальную ; переменную, хотя для данной программы это излишне PUSH 32 PUSH DWORD PTR PAR1 LEA EAX,DWORD PTR NR1 PUSH EAX CALL RtlMoveMemory@12 ; при вторичном поиске указатель на структуру LEA EBX,DWORD PTR NR1 FIRST: ; запуск при первом вызове LEA EAX,HANDLP PUSH EAX PUSH EBX PUSH 0 PUSH RESOURCETYPE_ANY PUSH RESOURCE_GLOBALNET CALL WNetOpenEnumA@20 CMP EAX,0 JNE _EN ; здесь осуществляется основной поиск REPI: ; запуск функции WNetEnumResource ; объем массива структур NETRESOURCE MOV DWORD PTR NB,32000 LEA EAX,NB PUSH EAX LEA EAX,RS PUSH EAX ; искать максимальное количество объектов MOV DWORD PTR CC,0FFFFFFFFH LEA EAX,CC PUSH EAX PUSH DWORD PTR HANDLP CALL WNetEnumResourceA@16 CMP EAX,0 JNE _CLOSE ; цикл по полученному массиву MOV ESI,CC SHL ESI,5 ; умножаем на 32 MOV EDI,0 L00: CMP EDI,ESI JE REPI ; вывод информации ; провайдер MOV EBX,DWORD PTR RS[EDI]+28 CALL SETMSG ; удаленное имя MOV EBX, DWORD PTR RS[EDI]+20 CALL SETMSG ; сохранить нужные регистры PUSH ESI PUSH EDI ; теперь рекурсивный вызов PUSH 1 LEA EAX,DWORD PTR RS[EDI] PUSH EAX CALL POISK ; восстановить регистры POP EDI POP ESI ADD EDI,32 JMP L00 ;----------------------------------- JMP REPI ;----------------------------------- _CLOSE: PUSH DWORD PTR HANDLP CALL WNetCloseEnum@4 _EN: MOV ESP,EBP POP EBP RET 8 POISK ENDP

; вывод сообщения ; EBX -> строка SETMSG PROC ; скопировать текст в отдельный буфер PUSH EBX PUSH OFFSET BUF CALL lstrcpy@8 LEA EBX,BUF ; перекодировать для консоли PUSH EBX PUSH EBX CALL CharToOemA@8 ; добавить перевод строки PUSH OFFSET ENT PUSH EBX CALL lstrcat@8 ; определить длину строки PUSH EBX CALL lstrlen@4 ; вывести строку PUSH 0 PUSH OFFSET LENS PUSH EAX PUSH EBX PUSH HANDL CALL WriteConsoleA@20 RET SETMSG ENDP _TEXT ENDS END START


Программа поиска процессов



Рисунок 3.5.4. Программа поиска процессов.

Трансляция программы на Рисунок 3.5.4.

MASM32:

ml /c /coff /DMASM PROC.asm rc proc.rc link /subsystem:windows PROC.obj proc.res

TASM32:

tasm32 /ml proc.asm brcc32 proc.rc tlink32 -aa proc.obj,,,,,proc.res



Простейшая DLLбиблиотека



Рисунок 3.3.2. Простейшая DLL-библиотека.

Программа на Рисунок 3.3.2 может быть оттранслирована как с помощью MASM32, так и TASM32 (см. ниже). На этом стоит остановиться более подробно. Прежде всего обратите внимание, что за процедурой, вызываемой из другого модуля, мы указали ключевое слово EXPORT. Это слово необходимо для правильной трансляции в MASM. Для TASM этого не нужно, но, к счастью, этот транслятор просто не замечает наличия какого-либо слова после PROC. Зато для TASM процедура DLLP1 должна быть определена как PUBLIC, кроме того, для трансляции в пакете TASM необходимо подготовить DEF-файл и указать его в командной строке TLINK32. Для создания динамических библиотек в строке link следует указать ключ /DLL, а в строке tlink32 -Tpd (no умолчанию работает ключ -Tpe). Ключ /ENTRY:DLLENTRY в строке link можно опустить, так как точка входа определяется из директивы END DLLENTRY.

Трансляция динамической библиотеки на Рисунок 3.3.2.

MASM32.

ml /c /coff /DMASM dll1.asm link /subsystem:windows /DLL /ENTRY:DLLENTRY dll1.obj

TASM32.

tasm32 /ml dll1.asm tlink32 -aa -Tpd dll1.obj,,,,dll1.def

Содержимое dll1.def:

EXPORTS DLLP1

Ниже на Рисунок 3.3.3 представлена программа, которая загружает динамическую библиотеку, показанную на Рисунок 3.3.2. Это пример позднего связывания. Библиотека должна быть вначале загружена при помощи функции LoadLibrary. Затем определяется адрес процедуры с помощью функции GetProcAddress, после чего можно осуществлять вызов. Как и следовало ожидать, MASM помещает в динамическую библиотеку вместо DLLP1 имя _DLLP1@0, тогда как TASM помещает имя без искажения. Это мы учитываем в нашей программе. Мы учитываем также возможность ошибки при вызове функций LoadLibrary и GetProcAddress. В этой связи укажем, как (в какой последовательности) ищет библиотеку функция LoadLibrary:

Поиск в каталоге, откуда была запущена программа. Поиск в текущем каталоге. В системном директории (GetSystemDirectory). В директории Windows (GetWindowsDirectory). В каталогах, указанных в окружении (PATH).




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

.386P ; плоская модель .MODEL FLAT, stdcall ; константы ; прототипы внешних процедур IFDEF MASM ;MASM EXTERN GetProcAddress@8:NEAR EXTERN LoadLibraryA@4:NEAR EXTERN FreeLibrary@4:NEAR EXTERN ExitProcess@4:NEAR EXTERN MessageBoxA@16:NEAR ; директивы компоновщику для подключения библиотек includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib ELSE ; TASM EXTERN GetProcAddress:NEAR EXTERN LoadLibraryA:NEAR EXTERN FreeLibrary:NEAR EXTERN ExitProcess:NEAR EXTERN MessageBoxA:NEAR ; директивы компоновщику для подключения библиотек includelib c:\tasm32\lib\import32.lib GetProcAddress@8 = GetProcAddress LoadLibraryA@4 = LoadLibraryA FreeLibrary@4 = FreeLibrary ExitProcess@4 = ExitProcess MessageBoxA@16 = MessageBoxA ENDIF ;-----------------------------------------

; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' TXT DB 'Ошибка динамической библиотеки',0 MS DB 'Сообщение',0 LIBR DB 'DLL1.DLL',0 HLIB DD ? IFDEF MASM NAMEPROC DB '_DLLP1@0',0 ELSE NAMEPROC DB 'DLLP1',0 ENDIF _DATA ENDS

; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; загрузить библиотеку PUSH OFFSET LIBR CALL LoadLibraryA@4 CMP EAX,0 JE _ERR MOV HLIB,EAX ; получить адрес процедуры PUSH OFFSET NAMEPROC PUSH HLIB CALL GetProcAddress@8 CMP EAX,0 JNE YES_NAME ; сообщение об ошибке _ERR: PUSH 0 PUSH OFFSET MS PUSH OFFSET TXT PUSH 0 CALL MessageBoxA@16 JMP _EXIT YES_NAME: PUSH 1 ; параметр CALL EAX ; закрыть библиотеку PUSH HLIB CALL FreeLibrary@4 ; библиотека автоматически закрывается также ; при выходе из программы ; выход _EXIT: PUSH 0 CALL ExitProcess@4 _TEXT ENDS END START


Простой пример определения типа устройств



Рисунок 3.4.1. Простой пример определения типа устройств.

Трансляция программы на Рисунок 3.4.1.

MASM32:

ml /c /coff /DMASM driv.asm rc driv.rc link /subsystem:windows driv.obj driv.res

TASM32:

tasm32 /ml driv.asm brcc32 driv.rc tlink32 -aa driv.obj,,,,,driv.res



Простой пример построения глобального фильтра



Рисунок 3.6.5. Простой пример построения глобального фильтра.

Трансляция программ на Рисунок 3.6.5.

MASM32:

Динамическая библиотека

ml /c /coff /DMASM dll2.asm link /subsystem:windows /DLL dll2.obj

Основная программа.

ml /c /coff /DMASM dllex.asm rc dial.rc link /subsystem:windows dllex.obj dial.res

TASM32:

Динамическая библиотека

TASM32 /ml dll2.asm tlink32 /subsystem:windows -aa -Tpd dll2.obj

Основная программа.

TASM32 /ml dllex.asm brcc32 dial.rc tlink32 -aa dllex.obj,,,,,dial.res

При разборе программ на Рисунок 3.6.5 обратите внимание на роль, которую играет процедура TOH. Заметьте также, что второй и третий параметр процедуры фильтра в точности соответствует значению аналогичных параметров сообщения WM_KEYDOWN. Кстати, надеюсь, Вы понимаете, почему при нажатии клавиши пробел появляются два сообщения - по одному на нажатие и отпускание.

52 Hook можно перевести как ловушка, да и по смыслу это ближе к понятию ловушка.



Рассмотрим теперь вопрос о том как используют динамическую библиотеку



V

Рассмотрим теперь вопрос о том, как используют динамическую библиотеку различные экземпляры приложения или разные процессы. Если Вы немного знакомы с принципом функционирования операционной системы Windows, то, возможно, такая постановка вопроса у Вас вызовет недоумение. "У каждого приложения свое адресное пространство, куда загружается динамическая библиотека", - скажете Вы. Конечно, это не совсем рационально, но зато безопасно. О памяти мы еще подробно будем говорить в гл. 3.6, здесь же заметим, что, вообще говоря, приложение может инициализировать так называемую разделяемую память. Мы вернемся к этому вопросу еще неоднократно, сейчас же рассмотрим этот вопрос чисто технически, применительно к динамическим библиотекам. Рассмотрим конкретную ситуацию. Запускаемое приложение загружает динамическую библиотеку и вызывает процедуру из динамической библиотеки, которая меняет данные, расположенные опять же в динамической библиотеке. Запустим теперь второй экземпляр приложения. Оно загружает еще один экземпляр динамической библиотеки. Могут быть ситуации, когда желательно, чтобы второе запущенное приложение "знало", что по команде первого приложения данные уже изменились. Ясно, что в этом случае данные, которыми оперирует динамическая библиотека, должны быть общими. Технически это делается очень просто. У редактора связей LINK есть опция /section: имя, атрибуты, которая позволяет объявить явно свойства данной секции. Мы будем говорить о секциях далее, здесь же достаточно сказать, секция - это просто сегмент в старом понимании. В редакторе связей TLINK32 то же действие можно осуществить с помощью файла .DEF.

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




; динамическая библиотека DLL4.ASM .386P ; плоская модель IFDEF MASM .MODEL FLAT, stdcall ELSE .MODEL FLAT ENDIF

PUBLIC DLLP1

IFDEF MASM ; MASM ; прототипы внешних процедур EXTERN MessageBoxA@16:NEAR ; директивы компоновщику для подключения библиотек includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib ELSE ; TASM EXTERN MessageBoxA:NEAR MessageBoxA@16 = MessageBoxA includelib c:\tasm32\lib\import32.lib ENDIF ;--------------------------------------------------

; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' TEXT DB "В динамической библиотеке",0 MS DB "Сообщение",0 _DATA ENDS

; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' ; [EBP+10H] ; резервный параметр ; [EBP+0CH] ; причина вызова ; [EBP+8] ; идентификатор DLL-модуля DLLENTRY: MOV EAX,1 RET 12 ;------------------ ;адреса параметров DLLP1 PROC EXPORT PUSH EBP MOV EBP,ESP PUSH 0 PUSH OFFSET MS PUSH OFFSET TEXT PUSH 0 CALL MessageBoxA@16 ; изменим строку, расположенную в разделяемой памяти MOV TEXT,'И' MOV TEXT+1,'з' POP EBP RET DLLP1 ENDP _TEXT ENDS END DLLENTRY

; основной модуль DLLEX4.ASM, вызывающий ; процедуру из динамической библиотеки .386P ; плоская модель .MODEL FLAT, stdcall ; константы ; прототипы внешних процедур IFDEF MASM ; MASM EXTERN GetProcAddress@8:NEAR EXTERN LoadLibraryA@4:NEAR EXTERN FreeLibrary@4:NEAR EXTERN ExitProcess@4:NEAR EXTERN MessageBoxA@16:NEAR ; директивы компоновщику для подключения библиотек includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib ELSE ; директивы копоновщику для подключения библиотек includelib c:\tasm32\lib\import32.lib EXTERN GetProcAddress:NEAR EXTERN LoadLibraryA:NEAR EXTERN FreeLibrary:NEAR EXTERN ExitProcess:NEAR EXTERN MessageBoxA:NEAR GetProcAddress@8 = GetProcAddress LoadLibraryA@4 = LoadLibraryA FreeLibrary@4 = FreeLibrary ExitProcess@4 = ExitProcess MessageBoxA@16 = MessageBoxA ENDIF ;----------------------------------

; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' TXT DB 'Ошибка динамической библиотеки',0 MS DB 'Сообщение',0 LIBR DB 'DLL4.DLL',0 HLIB DD ? IFDEF MASM NAMEPROC DB '_DLLP1@0',0 ELSE NAMEPROC DB 'DLLP1',0 ENDIF _DATA ENDS

; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' ; [EBP+10H] ; резервный параметр ; [EBP+0CH] ; причина вызова ; [EBP+8] ; идентификатор DLL-модуля START: ; загрузить библиотеку PUSH OFFSET LIBR CALL LoadLibraryA@4 CMP EAX,0 JE _ERR MOV HLIB,EAX ; получить адрес PUSH OFFSET NAMEPROC PUSH HLIB CALL GetProcAddress@8 CMP EAX,0 JNE YES_NAME ; сообщение об ошибке _ERR: PUSH 0 PUSH OFFSET MS PUSH OFFSET TXT PUSH 0 CALL MessageBoxA@16 JMP _EXIT YES_NAME: CALL EAX PUSH 0 PUSH OFFSET MS PUSH OFFSET MS PUSH 0 CALL MessageBoxA@16 ; закрыть библиотеку ; библиотека автоматически закрывается также ; при выходе из программы PUSH OFFSET NAMEPROC PUSH HLIB CALL FreeLibrary@4 ; выход _EXIT: PUSH 0 CALL ExitProcess@4 _TEXT ENDS END START


Рекурсивный поиск сетевых ресурсов в локальной сети



Рисунок 3.4.4. Рекурсивный поиск сетевых ресурсов в локальной сети.

Трансляция программы на Рисунок 3.4.4.

MASM32:

ml /c /coff /DMASM net1.asm link /subsystem:console /STACK:1000000,1000000 net1.obj

TASM32:

tasm32 /ml net1.asm tlink32 -ap -S:1000000 -Sc:1000000 net1.obj

Программа на Рисунок 3.4.4 довольно сложна и требует серьезных пояснений. Прежде всего хочу сказать, что, если читатель действительно хочет разобраться в сетевом программировании (я в данном случае имею в виду локальную сеть)

44, ему необходимо самостоятельно написать несколько программ. Мои программы должны служить отправными точками.

Нам уже приходилось сталкиваться с локальными переменными, когда мы рассматривали поиск файлов по дереву каталогов. Данная задача весьма похожа, но есть и отличие. В данном случае мы используем слишком большой объем для локальных переменных. По этой причине мы явно указываем (заказываем) большой объем стека (опции STACK и S,Sc). По умолчанию компоновщик устанавливает всего 8 Кб, что явно не достаточно. Функция WNetEnumResource требует указать своим параметром массив структур NETRESOURCE. Объем одной структуры 32 байта. Мы резервируем тысячу таких структур. Не много ли, скажете Вы? Честно говоря, я не встречал локальной сети с тысячью сетевых компьютеров. Однако я встречал локальную сеть, где на одном из серверов было создано около восьмисот сетевых каталогов. Если говорить начистоту, то здесь я все же демонстрирую не лучший стиль программирования. Более корректный путь заключается в том, что функция WNetEnumResource вначале вызывается с указанием объема буфера меньше, чем 32 байта, - в этом случае в переменную, содержащую объем буфера, будет возвращен необходимый объем. Зная необходимый объем, программа должна запросить у системы нужный и вторично запустить WNetEnumResource. Данный подход более корректен, но более сложен. При рекурсивном вызове процедуры POISK первым параметром является указатель на элемент массива структур NETRESOURCE. Мы копируем элемент массива в локальную переменную NR1. В принципе, в данной программе можно этого не делать, а сразу воспользоваться полученным указателем. В более сложных программах, однако, скорее всего, придется этот делать. Обратите внимание, что в процедуре вывода информации мы копируем строку в буфер, который потом используем для вывода. Это не прихоть, а необходимость. Дело в том, что перед выводом мы добавляем в конец строки коды 13 и 10. Поскольку мы выводим строки, которые потом используем для дальнейшего поиска, нам приходится использовать для вывода дополнительный буфер.



Результат работы программы



II

В данном разделе мы рассмотрим вопрос о доступе к ресурсам локальной сети. При этом следует выделить две проблемы: поиск ресурсов в локальной сети и подключение к ресурсам. Начну с того, что перечислю основные функции для работы с сетевыми ресурсами. Это не все функции, но их вполне достаточно, чтобы Ваша программа самостоятельно искала сетевые ресурсы и подключалась к ним. Конечно, я предполагаю, что вы умеете работать в сети, знаете, что такое сетевое устройство, сетевой компьютер и т.п.

Прежде всего рассмотрим структуру, которая используется в данных функциях.

NETRESOURCE STRUC dwScope DWORD ? dwType DWORD ? dwDisplayType DWORD ? dwUsage DWORD ? lpLocalName DWORD ? lpRemoteName DWORD ? lpComment DWORD ? lpProvider DWORD ? NETRESOURCE ENDS

dwScope - может принимать одно из трех значений:

RESOURCE_CONNECTED - ресурс подсоединен в настоящее время. RESOURCE_REMEMBERED - ресурс, запоминаемый системой, чтобы при запуске автоматически подсоединяться к нему.

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

dwType - тип ресурса. Возможны следующие значения:

RESOURCETYPE_ANY - любой ресурс. RESOURCETYPE_DISK - диск. RESOURCETYPE_PRINT - сетевой принтер.

dwDisplayType - как данный ресурс должен быть представлен сетевым браузером. Типов довольно много. Например, для сетевого компьютера определен тип RESOURCEDISPLAYTYPE_SERVER, для группы - RESOURCEDISPLAYTYPE_GROUP и т.д.

dwUsage - чаще всего полагают равным 0.

lpLocalName - локальное имя устройства, например Е:, LPT1: и т.п.

lpRemoteName - сетевое имя, например \\SUPER, \\NDI\EPSON и т.д.

lpComment - комментарий к сетевому ресурсу.

lpProvider - имя провайдера. В настоящее время имя может принимать одно из двух значений: Microsoft Network и NetWare, но возможны и другие имена.

WNetAddConnection2 - можно подсоединить к Вашему компьютеру сетевой ресурс (диск или принтер).

1-й параметр. Адрес структуры NETRESOURCE, значение полей которой было разобрано выше. Должны быть заполнены следующие поля: dwType, lpLocalName, lpRemoteName, lpProvider (обычно NULL). Ниже будет приведен пример заполнения. 2-й параметр. Пароль, необходимый для соединения с ресурсом. В случае пустой сроки - соединение беспарольное, в случае NULL - берется пароль, ассоциированный с именем (см. ниже). 3-й параметр. Имя пользователя. Если значение NULL, то берется имя по умолчанию. 4-й параметр. Данный параметр определяет, будет ли система потом автоматически подсоединяться к данному ресурсу. В случае значения 0, такого подсоединения не происходит.




При успешном завершении функция возвращает 0 (NO_ERROR). Это касается и всех остальных рассмотренных ниже функций.

WNetCancelConnection2 - отсоединить ресурс.

1-й параметр. Содержит указатель на строку, содержащую имя ресурса. Причем если имя локальное, то разрывается данное локальное соединение. Если это имя удаленного ресурса, то разрываются все соединения с данным ресурсом. 2-й параметр. Определяет, будет ли система и далее подсоединяться к данному ресурсу. Если 0, то подсоединение (если было) будет возобновляться при следующем запуске системы. 3-й параметр. Если значение не нулевое, то отсоединение произойдет, даже если на сетевом диске имеются открытые файлы или сетевой принтер выполняет задание с данного компьютера.

WNetOpenEnum - открыть поиск сетевых ресурсов. Вообще говоря, сетевые ресурсы представляют собой структуру в виде дерева. Об этом мы будем говорить ниже, поэтому поиск сетевых ресурсов весьма напоминает поиск файлов по дереву каталогов.

1-й параметр. dwScope (см. структуру NETRESOURCE) - обычно полагают RESOURCE_GLOBALNET. 2-й параметр. dwType - для поиска всяких ресурсов следует положить равным RESOURCETYPE_ANY. 3-й параметр. dwUsage - обычно следует положить равным нулю. 4-й параметр. Адрес структур NETRESOURCE. Если адрес равен 0 (NULL), то поиск будет начинаться с самого нижнего уровня (корня), в противном случае поиск начнется с уровня, определяемого полями lpRemoteName и lpProvider. 5-й параметр. Указатель на переменную, которая должна получить дескриптор для дальнейшего поиска.

WNetCloseEnum - закрыть поиск. Единственным параметром этой функции является дескриптор, полученный при выполнении функции WNetOpenEnum.

WNetEnumResource - функция, осуществляющая непосредственный поиск сетевых ресурсов.

1-й параметр. Дескриптор поиска. 2-й параметр. Указатель на переменную, которая должна содержать: какое максимальное количество ресурсов должно быть найдено за один раз. Обычно переменную полагают равной 0FFFFFFFFH - для поиска всех возможных ресурсов. При успешном завершении данной функции переменная будет содержать количество реально найденных ресурсов. 3-й параметр. Указатель на массив, каждым элементом которого является структура NETRESOURCE. Ясно, что данный массив должен быть достаточно большим, чтобы вместить столько данных о ресурсе, сколько Вам нужно. Поскольку размер структуры составляет 32 байта, то в случае большой сети размер массива должен составлять не менее 32000 байт. 4-й параметр. Адрес переменной, содержащей объем массива. Если объем окажется мал, то переменная будет содержать реально требуемый объем.



Лишний раз подчеркну, что данная функция осуществляет поиск не всех ресурсов, а лишь ресурсов данного иерархического уровня. Так что без рекурсии не обойтись.

WNetGetConnection - с помощью данной функции можно получить информацию о данном соединении.

1-й параметр. Адрес локального имени (A:,C:,LPT2 и т.п.). 2-й параметр. Адрес буфера, куда будет помещено удаленное имя. 3-й параметр. Указатель на переменную, содержащую размер буфера.

Завершая краткий обзор сетевых функций и переходя к программированию, замечу, что нами описана лишь часть наиболее важных сетевых функций. Кроме того, в Windows NT имеются еще и свои функции для работы с сетевыми ресурсами, которые отсутствуют в операционной системе Windows 9x. Кстати, будьте готовы к тому, что поведение описанных функций может несколько отличаться в Windows 9x, Windows NT и Windows 2000. О некоторых таких тонкостях мы скажем ниже.

Ниже представлена программа, позволяющая подключаться к сетевым дискам. Командная строка программы: NET \\SERVER\CC Z:. Первый параметр - имя подключаемого устройства, включающее имя сетевого сервера. Второй параметр - локальный диск, на который будет спроецирован сетевой диск.

; программа NET, осуществляющая подсоединение к ; сетевому ресурсу: NET \\SUPER\\D Z:

.386P ; плоская модель .MODEL FLAT, stdcall ; константы STD_OUTPUT_HANDLE equ -11 RESOURCETYPE_DISK equ 1h ; прототипы внешних процедур IFDEF MASM EXTERN lstrcat@8:NEAR EXTERN lstrlen@4:NEAR EXTERN GetStdHandle@4:NEAR EXTERN WriteConsoleA@20:NEAR EXTERN ExitProcess@4:NEAR EXTERN GetCommandLineA@0:NEAR EXTERN WNetAddConnection2A@16:NEAR ELSE LOCALS EXTERN lstrcat:NEAR EXTERN lstrlen:NEAR EXTERN GetStdHandle:NEAR EXTERN WriteConsoleA:NEAR EXTERN ExitProcess:NEAR EXTERN GetCommandLineA:NEAR EXTERN WNetAddConnection2A:NEAR lstrcat@8 = lstrcat lstrlen@4 = lstrlen GetStdHandle@4 = GetStdHandle WriteConsoleA@20 = WriteConsoleA ExitProcess@4 = ExitProcess GetCommandLineA@0 = GetCommandLineA WNetAddConnection2A@16 = WNetAddConnection2A ENDIF



; структуры NETRESOURCE STRUC dwScope DWORD ? dwType DWORD ? dwDisplayType DWORD ? dwUsage DWORD ? lpLocalName DWORD ? lpRemoteName DWORD ? lpComment DWORD ? lpProvider DWORD ? NETRESOURCE ENDS

; директивы компоновщику для подключения библиотек IFDEF MASM includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib includelib c:\masm32\lib\mpr.lib ELSE includelib c:\tasm32\lib\import32.lib ENDIF ;-------------------------------------------------- ;сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' BUF1 DB 100 dup (0) BUF2 DB 100 dup (0) LENS DWORD ? ; количество выведенных символов HANDL DWORD ? NR NETRESOURCE <0> PUSTO DB 0 ERR2 DB "Ошибка!",0 ERR1 DB "Мало параметров!",0 ST1 DB "->",0 _DATA ENDS

; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; получить дескриптор выхода вывода PUSH STD_OUTPUT_HANDLE CALL GetStdHandle@4 MOV HANDL,EAX ; получить количество параметров CALL NUMPAR CMP EAX,3 JNB PAR_OK LEA EBX,ERR1 CALL SETMSG JMP _END PAR_OK: ; получить параметры MOV EDI,2 LEA EBX,BUF1 CALL GETPAR MOV EDI,3 LEA EBX,BUF2 CALL GETPAR ; пытаемся произвести подсоединение ; вначале заполняем структуру NETRESOURCE ; для Windows NT NR.dwType = 0 MOV NR.dwType, RESOURCETYPE_DISK LEA EAX,BUF2 MOV NR.lpLocalName,EAX LEA EAX,BUF1 MOV NR.lpRemoteName,EAX MOV NR.lpProvider,0 ; вызов функции, осуществляющей соединение PUSH 0 PUSH OFFSET PUSTO PUSH OFFSET PUSTO PUSH OFFSET NR CALL WNetAddConnection2A@16 CMP EAX,0 JE _OK ; сообщение об ошибке LEA EBX,ERR2 CALL SETMSG JMP _END _OK: ; сообщение об успешном соединении PUSH OFFSET ST1 PUSH OFFSET BUF1 CALL lstrcat@8 PUSH OFFSET BUF2 PUSH OFFSET BUF1 CALL lstrcat@8 LEA EBX,BUF1 CALL SETMSG _END: PUSH 0 CALL ExitProcess@4

; определить количество параметров (->EAX) NUMPAR PROC CALL GetCommandLineA@0 MOV ESI,EAX ; указатель на строку XOR ECX,ECX ; счетчик MOV EDX,1 ; признак @@L1: CMP BYTE PTR [ESI],0 JE @@L4 CMP BYTE PTR [ESI],32 JE @@L3 ADD ECX,EDX ; номер параметра MOV EDX,0 JMP @@L2 @@L3: OR EDX,1 @@L2: INC ESI JMP @@L1 @@L4 : MOV EAX,ECX RET NUMPAR ENDP

; получить параметр ; EBX - указывает на буфер, куда будет помещен параметр ; в буфер помещается строка с нулем на конце ; EDI - номер параметра GETPAR PROC CALL GetCommandLineA@0 MOV ESI,EAX ; указатель на строку XOR ECX,ECX ; счетчик MOV EDX,1 ; признак @@L1: CMP BYTE PTR [ESI],0 JE @@L4 CMP BYTE PTR [ESI],32 JE @@L3 ADD ECX,EDX ; номер параметра MOV EDX,0 JMP @@L2 @@L3: OR EDX,1 @@L2: CMP ECX,EDI JNE @@L5 MOV AL,BYTE PTR [ESI] CMP AL,32 JE @@L5 MOV BYTE PTR [EBX],AL INC EBX @@L5: INC ESI JMP @@L1 @@L4: MOV BYTE PTR [EBX],0 RET GETPAR ENDP

; вывод сообщения ; EBX -> строка SETMSG PROC PUSH EBX CALL lstrlen@4 PUSH 0 PUSH OFFSET LENS PUSH EAX PUSH EBX PUSH HANDL CALL WriteConsoleA@20 RET SETMSG ENDP _TEXT ENDS END START


З Пример загрузки ресурса из динамической библиотеки



Рисунок З.З.6. Пример загрузки ресурса из динамической библиотеки.

Трансляция программ на Рисунок 3.3.6.

MASM32:

ml /c /coff /DMASM dllex3.asm rc dllex3.rc link /subsystem:windows dllex3.obj dllex3.res

ml /c /coff /DMASM dll3.asm rc dll3.rc link /subsystem:windows /DLL /ENTRY:DLLENTRY dll3.obj dll3.res

TASM32:

tasm32 /ml dllex3.asm brcc32 dllex3.rc tlink32 -aa -Tpe dllex3.obj,,,,,dllex3.res

tasm32 /ml dll3.asm brcc32 dll3.rc tlink32 -aa -Tpd dll3.obj,,,,dll3.def,dll3.res

Содержимое файла dll3.def:

EXPORTS SETIC

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



Семафоры



Семафоры.

Семафор представляет собой глобальный объект, позволяющий синхронизировать работу двух или нескольких процессов или потоков. Для программиста семафор - это просто счетчик. Если счетчик равен N, это означает, что к ресурсу имеют доступ N процессов. Рассмотрим функции для работы с семафорами.

CreateSemaphore - создает глобальный объект-семафор. Возвращает дескриптор семафора. Параметры функции:

1-й параметр. Указатель на структуру, определяющую атрибуты доступа. Может иметь значение для Windows NT. Обычно данный параметр полагается равным NULL. 2-й параметр. Начальное значение счетчика семафора. Определяет, сколько задач имеют доступ к ресурсу вначале. 3-й параметр. Количество задач, которые имеют одновременный доступ к ресурсу. 4-й параметр. Указатель на строку, содержащую имя семафора.

OpenSemaphore - открыть уже созданный семафор. Возвращает дескриптор семафора. Данную функцию используют не так часто. Обычно создают семафор и присваивают его дескриптор глобальной переменной, а потом используют этот дескриптор в порождаемых потоках. Параметры функции:

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

SEMAPHORE_MODIFY_STATE = 2Н, разрешить использование функции ReleaseSemaphore,

SYNCHRONIZE = 100000Н, разрешить использование любой функции ожидания, только для Windows NT,
SEMAPHORE_ALL_ACCESS = 0F0000h + SYNCHRONIZE + 3H, специфицирует все возможные флаги доступа к семафору. (в книге пропущен) 2-й параметр. Указывает, может ли наследоваться дескриптор семафора, возвращаемый данной функцией, процессом, созаваемым функцией CreateProcess; 0 - не может. (также в книге пропущен) 3-й параметр. Указатель на ASCIIZ-строку, содержащую имя семафора.

WaitForSingleObject - ожидать открытие семафора. При успешном завершении, т.е. открытии доступа к объекту, функция возвращает 0. Значение 102h будет означать, что закончился заданный период ожидания. Параметры функции:

1-й параметр. Дескриптор семафора.

2-й параметр. Время ожидания в миллисекундах. Если параметр равен INFINITE = 0FFFFFFFFh, то время ожидания не ограничено.




ReleaseSemaphore - освободить семафор и позволить получить доступ к ресурсу другим процессам. Параметры функции:

1-й параметр. Дескриптор семафора. 2-й параметр. Определяет, какое значение должно быть добавлено к счетчику семафора. Чаще всего этот параметр равен единице. 3-й параметр. Указатель на переменную, куда должно быть помещено предыдущее значение счетчика.

Рассмотрим алгоритм работы с семафором. Сначала при помощи функции CreateSemaphore создается семафор и его дескриптор присваивается глобальной переменной. Перед попыткой обращения к ресурсам, доступ к которым необходимо ограничить, поток должен вызвать функцию WaitForSingleObject. При открытии доступа функция возвращает 0. По окончании работы с ресурсом следует вызвать функцию ReleaseSemaphore. Тем самым увеличивается счетчик доступа на 1. С помощью семафора можно регулировать количество потоков, которые одновременно могут иметь доступ к ресурсу. Максимальное значение счетчика как раз и определяет, сколько потоков могут получить доступ к ресурсу одновременно. Но обычно, как я уже говорил, максимальное значение полагают равным 1.


Схема преобразования логического


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

Дескриптор сегмента содержал, в частности, поле доступа, которое определяло тип индексируемого сегмента (сегмент кода, сегмент данных, системный сегмент и т.д.). Здесь же можно, например, указать, что данный сегмент доступен только для чтения. Учитывалась также возможность, что сегмент может отсутствовать в памяти, т.е. временно находиться на диске. Тем самым закладывалась возможность виртуальной памяти.

Подытожим, что же давал нам защищенный режим.

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

Предполагалась, что сегменты могут быть защищены от записи.

В поле доступа можно также указать уровень доступа. Всего возможно четыре уровня доступа. Смысл уровня доступа заключался в том, что задача не может получить доступ к сегменту, у которого уровень доступа выше, чем у данной задачи.

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

Обратимся опять к Рисунок 3.6.1. Из схемы видно, что результатом преобразования является линейный адрес. Но если для микропроцессора 80286 линейный адрес можно отождествить с физическим адресом, для микропроцессора 80386 это уже не так.

Начиная с микропроцессора 80386 появился еще один механизм преобразования адресов - это страничная адресация. Чтобы механизм страничной адресации заработал, старший бит системного регистра CR0 должен быть равен 1.

Обратимся к Рисунок 3.6.2. Линейный адрес, получаемый путем дескрипторного преобразования, делится на три части. Старшие 10 бит адреса используются как индекс в таблице, которая называется каталог таблиц страниц. Расположение каталога страниц определяется содержимым регистра CR3. Каталог состоит из дескрипторов. Максимальное количество дескрипторов 1024. Самих же каталогов может быть бесчисленное множество, но в данный момент работает каталог, на который указывает регистр CR3.



Симодуль для программы простейшего


Трансляция.

tasm32 /ml calc.asm brcc32 calc.rc

Затем в проект к программе calcc.cpp (на Рисунок 3.8.7) добавим файлы calc.obj и calc.res.



Следующая программа несколько сложнее предыдущей



II

Следующая программа несколько сложнее предыдущей. Здесь действуют два таймера. Можно считать, что запускаются одновременно две задачи39. Одна задача с периодичностью 0.5 сек. получает системное время и формирует строку для вывода (STRCOPY). Эта задача имеет свою собственную функцию, на которую приходит сообщение WM_TIMER. Вторая задача работает в рамках функции окна. Эта задача с периодичностью 1 сек. выводит время и дату в окно редактирования, расположенное на диалоговом окне. Таким образом, две задачи взаимодействуют друг с другом посредством глобальной переменной STRCOPY.

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

// файл timer2.rc // определение констант #define WS_SYSMENU 0x00080000L // элементы на окне должны быть изначально видимы #define WS_VISIBLE 0x10000000L // бордюр вокруг элемента #define WS_BORDER 0x00800000L // при помощи TAB можно по очереди активизировать элементы #define WS_TABSTOP 0x00010000L // текст в окне редактирования прижат к левому краю #define ES_LEFT 0x0000L // стиль всех элементов на окне #define WS_CHILD 0x40000000L // запрещается ввод с клавиатуры #define ES_READONLY 0x0800L

#define DS_3DLOOK 0x0004L

// определение диалогового окна DIAL1 DIALOG 0, 0, 240, 100 STYLE WS_SYSMENU | DS_3DLOOK CAPTION "Диалоговое окно с часами и датой" FONT 8, "Arial" { CONTROL "", 1, "edit", ES_LEFT | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP | ES_READONLY, 100, 5, 130, 12 }

; файл timer2.inc ; константы ; сообщение приходит при закрытии окна WM_CLOSE equ 10h ;сообщение приходит при создании окна WM_INITDIALOG equ 110h ;сообщение приходит при событии с элементом на окне WM_COMMAND equ 111h ;сообщение от таймера WM_TIMER equ 113h ; сообщение посылки текста элементу WM_SETTEXT equ 0Ch

; прототипы внешних процедур IFDEF MASM EXTERN SendDlgItemMessageA@20:NEAR EXTERN wsprintfA:NEAR EXTERN GetLocalTime@4:NEAR EXTERN ExitProcess@4:NEAR EXTERN GetModuleHandleA@4:NEAR EXTERN DialogBoxParamA@20:NEAR EXTERN EndDialog@8:NEAR EXTERN SetTimer@16:NEAR EXTERN KillTimer@8:NEAR ELSE EXTERN SendDlgItemMessageA:NEAR EXTERN _wsprintfA:NEAR EXTERN GetLocalTime:NEAR EXTERN ExitProcess:NEAR EXTERN GetModuleHandleA:NEAR EXTERN DialogBoxParamA:NEAR EXTERN EndDialog:NEAR EXTERN SetTimer:NEAR EXTERN KillTimer:NEAR SendDlgItemMessageA@20 = SendDlgItemMessageA wsprintfA = _wsprintfA GetLocalTime@4 = GetLocalTime ExitProcess@4 = ExitProcess GetModuleHandleA@4 = GetModuleHandleA DialogBoxParamA@20 = DialogBoxParamA EndDialog@8 = EndDialog SetTimer@16 = SetTimer KillTimer@8 = KillTimer ENDIF




; структуры ; структура сообщения MSGSTRUCT STRUC MSHWND DD ? MSMESSAGE DD ? MSWPARAM DD ? MSLPARAM DD ? MSTIME DD ? MSPT DD ? MSGSTRUCT ENDS

; структура данных дата-время DAT STRUC year DW ? month DW ? dayweek DW ? day DW ? hour DW ? min DW ? sec DW ? msec DW ? DAT ENDS

; файл timer2.asm .386P ; плоская модель .MODEL FLAT, stdcall include timer2.inc ; директивы компоновщику для подключения библиотек IFDEF MASM ; для компоновщика LINK.EXE includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib includelib c:\masm32\lib\gdi32.lib ELSE ; для компоновщика TLINK32.EXE includelib c:\tasm32\lib\import32.lib ENDIF ;------------------------------------------------------

; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' MSG MSGSTRUCT <?> HINST DD 0 ; дескриптор приложения PA DB "DIAL1",0 TIM DB "Дата %u/%u/%u Время %u:%u:%u",0 STRCOPY DB 50 DUP (?) DATA DAT <0> _DATA ENDS

; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; получить дескриптор приложения PUSH 0 CALL GetModuleHandleA@4 MOV [HINST], EAX ; создать диалоговое окно PUSH 0 PUSH OFFSET WNDPROC PUSH 0 PUSH OFFSET PA PUSH [HINST] CALL DialogBoxParamA@20 CMP EAX,-1 JNE KOL ; сообщение об ошибке KOL: ;-------------------------------------- PUSH 0 CALL ExitProcess@4 ;-------------------------------------- ; процедура окна ; расположение параметров в стеке ; [EBP+014Н] ; LPARAM ; [EBP+10Н] ; WAPARAM ; [EBP+0CH] ; MES ; [EBP+8] ; HWND WNDPROC PROC PUSH EBP MOV EBP,ESP PUSH EBX PUSH ESI PUSH EDI ;----------------- CMP DWORD PTR [EBP+0CH],WM_CLOSE JNE L1 ; здесь реакция на закрытие окна ; удалить таймер 1 PUSH 1 ; идентификатор таймера PUSH DWORD PTR [EBP+08H] CALL KillTimer@8 ; удалить таймер 2 PUSH 2 ; идентификатор таймера PUSH DWORD PTR [EBP+08H] CALL KillTimer@8 ; закрыть диалог PUSH 0 PUSH DWORD PTR [EBP+08H] CALL EndDialog@8 JMP FINISH L1: CMP DWORD PTR [EBP+0CH], WM_INITDIALOG JNE L2 ; здесь начальная инициализация ; установить таймер 1 PUSH 0 ; параметр = NULL PUSH 1000 ; интервал 1 с. PUSH 1 ; идентификатор таймера PUSH DWORD PTR [EBP+08H] CALL SetTimer@16 ; установить таймер 2 PUSH OFFSET TIMPROC ; параметр = NULL PUSH 500 ; интервал 0.5 с. PUSH 2 ; идентификатор таймера PUSH DWORD PTR [EBP+08H] CALL SetTimer@16 JMP FINISH L2: CMP DWORD PTR [EBP+0CH],WM_TIMER JNE FINISH ; отправить строку в окно PUSH OFFSET STRCOPY PUSH 0 PUSH WM_SETTEXT PUSH 1 ; идентификатор элемента PUSH DWORD PTR [EBP+08H] CALL SendDlgItemMessageA@20 FINISH: POP EDI POP ESI POP EBX POP EBP MOV EAX,0 RET 16 WNDPROC ENDP ;--------------------------------

;процедура таймера ; расположение параметров в стеке ; [EBP+014Н] ; LPARAM - промежуток запуска Windows ; [EBP+10Н] ; WAPARAM - идентификатор таймера ; [EBP+0CH] ; WM_TIMER ; [EBP+8] ; HWND TIMPROC PROC PUSH EBP MOV EBP,ESP ; получить локальное время PUSH OFFSET DATA CALL GetLocalTime@4 ; получить строку для вывода даты и времени MOVZX EAX,DATA.sec PUSH EAX MOVZX EAX,DATA.min PUSH EAX MOVZX EAX,DATA.hour PUSH EAX MOVZX EAX,DATA.year PUSH EAX MOVZX EAX,DATA.month PUSH EAX MOVZX EAX,DATA.day PUSH EAX PUSH OFFSET TIM PUSH OFFSET STRCOPY CALL wsprintfA ; восстановить стек ADD ESP,32 POP EBP RET 16 TIMPROC ENDP _TEXT ENDS END START


Событие является



События.

Событие является объектом, очень похожим на семафор, но в несколько видоизмененном виде. Рассмотрим функции для работы с событиями.
CreateEvent - создает объект-событие. Параметры функции.

1-й параметр. Имеет тот же смысл, что и первый параметр функции CreateSemaphore. Обычно полагается равным NULL.

2-параметр. Если параметр не равен нулю, то событие может быть сброшено при помощи функции ResetEvent. Иначе событие сбрасывается при доступе к нему какого либо процесса.

3-й параметр. Если параметр равен 0, то событие инициализируется как сброшенное, в противном случае сразу же подается сигнал о наступлении соответствующей ситуации.

4-й параметр. Указатель на строку, которая содержит имя события.
Ожидание события осуществляется, как и в случае с семафором, функцией WaitForSingleObject.
Функция OpenEvent аналогична функции OpenSemaphore, и на ней мы останавливаться не будем.
SetEvent - подать сигнал о наступлении события. Параметры функции.

1-й параметр. Дескриптор события.

Согласование имен



Согласование имен.

Согласование вызовов, как мы убедились, снято с повестки дня, а вот согласование имен год от года только усложнялось. Кое-что Вы уже знаете. Транслятор MASM, как известно, добавляет в конце имени @N, где N количество передаваемых в стек параметров. То же делает и компилятор Visual C++. Т.о. трудности возникают уже при согласовании двух ассемблерных модулей. В этом смысле TASM является более гибким компилятором, т.к. при желании в конце любого имени можно добавить @N, тем самым согласовав имена.

Другая проблема - подчеркивание перед именем. Транслятор MASM генерирует подчеркивание автоматически, если в начале программы устанавливается тип вызова stdcall (Standard Call, т.е. стандартный вызов). Транслятор TASM этого не делает, следовательно, при необходимости это нужно делать прямо в тексте программы, что, на мой взгляд, является положительным моментом. Интересно, что между фирмами Borland и Microsoft здесь полное разночтение.

Еще одна проблема - согласование заглавных и прописных букв. Как Вы помните, при трансляции с помощью TASM мы используем ключ /ml как раз для того, чтобы различать буквы прописные и заглавные. Транслятор MASM делает это автоматически. Как известно, и в стандарте языка Си с самого начала предполагалось различие между заглавными и прописными буквами. В Паскале же прописные и заглавные буквы не различаются. В этом есть своя логика: Турбо Паскаль и Delphi не создают стандартных объектных модулей, зато могут подключать их. При создании же динамических библиотек туда помещается имя так, как оно указано в заголовке процедуры.

Наконец последняя проблема, связанная с согласованием имен, - это уточняющие имена в Си++. Дело в том, что в Си++ возможна так называемая перегрузка. Это значит, что одно и тоже имя может относиться к разным функциям. В тексте программы эти функции отличаются друг от друга по количеству и типу параметров и типу возвращаемого значения. Поэтому компилятор Си++ автоматически делает в конце имени добавку - так, чтобы разные по смыслу функции различались при компоновке. Разумеется, фирмы Borland и Microsoft и тут не пожелали согласовать свои позиции и делают в конце имени совершенно разные добавки. Обойти эту проблему не так сложно, нужно использовать модификатор EXTERN "С" (см. примеры далее).



Согласование параметров



Согласование параметров.

В таблице ниже представлены основные соглашения по передаче параметров в процедуру. Заметим в этой связи, что во всех наших ассемблерных программах мы указывали тип передачи параметров как stdcall. Однако, по сути, это никак и нигде не использовалось - так передача и извлечение параметров делалась нами явно, без помощи транслятора. Когда мы имеем дело с языками высокого уровня, это необходимо учитывать и знать, как работают те или иные соглашения.

Таблица, представляющая соглашения о вызовах

STARTF_USESHOWWINDOW1hРазрешить поле dwShowWindow
STARTF_USESIZE2hРазрешить dwXSize и dwYSize
STARTF_USEPOSITI0N4hРазрешить dwX и dwY
STARTF_USECOUNTCHARS8hРазрешить dwXCountChars и dwYCountChars
STARTF_USEFILLATTR1BUTE10hРазрешить dwFillAttribute
STARTF_FORCEONFEEDBACK40hВключить возврат курсора
STARTF_FORCEOFFFEEDBACK80hВыключить возврат курсора
STARTF_USESTDHANDLES100hРазрешить hStdInput

Соглашение

Параметры

Очистка стека

Регистры

Таблица довольно ясно объясняет соглашения о передаче параметров, и здесь более добавить нечего.

Остановлюсь еще на весьма важном моменте - типе возвращаемых функцией данных. С точки зрения ассемблера здесь все предельно просто: в регистре EAX возвращается значение, которое может быть либо числом, либо указателем на некую переменную или структуру. Если возвращаемое число типа WORD, то оно содержится в младшем слове регистра EAX. Однако имея дело с Си, Вам надо очень аккуратно обращаться с такой проблемой, как преобразование типов. Преобразование типов — это целая наука, на которой мы не можем останавливаться в данной книге.



Согласование вызовов



Согласование вызовов.

В операционной системе MS DOS вызываемая процедура могла находиться либо в том же сегменте, что и команда вызова, тогда вызов назывался близким (NEAR) или внутрисегментным, либо в другом сегменте, тогда вызов назывался дальним (FAR) или межсегментным. Разница заключалась в том, что адрес в первом случае формировался из двух байт, а во втором - из четырех байт. Соответственно, возврат из процедуры мог быть либо близким (RETN), т.е. адрес возврата формировался на основе двух байт, взятых из стека, либо дальним (RETF), и в этом случае адрес формировался на основе четырех байт, взятых опять же из стека. Ясно, что вызов и возврат должны быть согласованы друг с другом. В рамках единой программы это, как правило, не вызывало больших проблем. Но вот когда необходимо было подключить или какую-то библиотеку, или объектный модуль, здесь могли возникнуть трудности. Если в объектном модуле возврат осуществлялся по RETN, вы должны были компоновать объектные модули так, чтобы сегмент, где находится процедура, был объединен с сегментом, откуда осуществляется вызов. Вызов в этом случае, разумеется, должен быть близким (см. [1]). Если же возврат из процедуры осуществлялся по команде RETF, то и вызов этой процедуры должен быть дальним. При этом при компоновке вызов и сама процедура должны были попасть в разные сегменты. Данная проблема усугублялась еще и тем, что ошибка обнаруживалась не при компоновке, а при исполнении программы. С этим были связаны и так называемые модели памяти в языке Си, что также было головной болью многих начинающих программистов. Если, кстати, Вы посмотрите на каталог библиотек Си для DOS, то обнаружите, что для каждой модели памяти там существовала своя библиотека. Сегментация памяти приводила в Си еще к одной проблеме - проблеме указателей, но это уже совсем другая история. В Турбо Паскале пошли по другому пути. Там приняли, что в программе должен существовать один сегмент данных и несколько сегментов кода. Если же Вам не хватало одного сегмента для хранения данных, то предлагалось использовать динамическую память. При переходе к Windows мы получили значительный подарок в виде плоской модели памяти. Теперь все вызовы по типу являются близкими, т.е. осуществляющимися в рамках одного огромного сегмента. Тем самым была снята проблема согласования вызовов, и мы более к этой проблеме обращаться не будем.



Текст динамической библиотеки и программы



IV

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

; динамическая библиотека DLL2.ASM .386P ; плоская модель IFDEF MASM .MODEL FLAT, stdcall ELSE .MODEL FLAT ENDIF

PUBLIC DLLP1

; константы ; сообщения, приходящие при открытии ; динамической библиотеки DLL_PROCESS_DETACH equ 0 DLL_PROCESS_ATTACH equ 1 DLL_THREAD_ATTACH equ 2 DLL_THREAD_DETACH equ 3

IFDEF MASM ; MASM ; прототипы внешних процедур EXTERN MessageBoxA@16:NEAR ; директивы компоновщику для подключения библиотек includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib ELSE ; TASM EXTERN MessageBoxA:NEAR MessageBoxA@16 = MessageBoxA includelib c:\tasm32\lib\import32.lib ENDIF ;--------------------------------------------------

; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' TEXT DB "Строка в динамической библиотеке",0 _DATA ENDS

; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' ; [EBP+10H] ; резервный параметр ; [EBP+0CH] ; причина вызова ; [EBP+8] ; идентификатор DLL-модуля DLLENTRY: MOV EAX,DWORD PTR [EBP+0CH] CMP EAX,0 JNE D1 ; закрытие библиотеки JMP _EXIT D1: CMP EAX,1 JNE _EXIT ; открытие библиотеки _EXIT: MOV EAX,1 RET 12 ;--------------------- ; адреса параметров ; [EBP+8] ; [EBP+0CH] DLLP1 PROC EXPORT PUSH EBP MOV EBP,ESP PUSH 0 PUSH DWORD PTR [EBP+0CH] PUSH DWORD PTR [EBP+8] PUSH 0 CALL MessageBoxA@16 POP EBP LEA EAX,TEXT RET 8 DLLP1 ENDP _TEXT ENDS END DLLENTRY

; основной модуль DLLEX2.ASM, вызывающий ; процедуру из динамической библиотеки .386P ; плоская модель .MODEL FLAT, stdcall ; константы ; прототипы внешних процедур IFDEF MASM ; MASM EXTERN GetProcAddress@8:NEAR EXTERN LoadLibraryA@4:NEAR EXTERN FreeLibrary@4:NEAR EXTERN ExitProcess@4:NEAR EXTERN MessageBoxA@16:NEAR includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib ELSE ; TASM includelib c:\tasm32\lib\import32.lib EXTERN GetProcAddress:NEAR EXTERN LoadLibraryA:NEAR EXTERN FreeLibrary:NEAR EXTERN ExitProcess:NEAR EXTERN MessageBoxA:NEAR GetProcAddress@8 = GetProcAddress LoadLibraryA@4 = LoadLibraryA FreeLibrary@4 = FreeLibrary ExitProcess@4 = ExitProcess MessageBoxA@16 = MessageBoxA ENDIF ;---------------------------------------------- ; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' TXT DB 'Ошибка динамической библиотеки',0 MS DB 'Сообщение',0 LIBR DB 'DLL2.DLL',0 HLIB DD ? MS1 DB 'Сообщение из библиотеки',0 TEXT DB 'Строка содержится в основном модуле',0 IFDEF MASM NAMEPROC DB '_DLLP1@0',0 ELSE NAMEPROC DB 'DLLP1',0 ENDIF _DATA ENDS

; сегмент кода

_TEXT SEGMENT DWORD PUBLIC USE32 'CODE' ; [EBP+10H] ; резервный параметр ; [EBP+0CH] ; причина вызова ; [EBP+8] ; идентификатор DLL-модуля START: ; загрузить библиотеку PUSH OFFSET LIBR CALL LoadLibraryA@4 CMP EAX,0 JE _ERR MOV HLIB,EAX ; получить адрес PUSH OFFSET NAMEPROC PUSH HLIB CALL GetProcAddress@8 CMP EAX,0 JNE YES_NAME ; сообщение об ошибке _ERR: PUSH 0 PUSH OFFSET MS PUSH OFFSET TXT PUSH 0 CALL MessageBoxA@16 JMP _EXIT YES_NAME: PUSH OFFSET MS1 PUSH OFFSET TEXT CALL EAX PUSH 0 PUSH OFFSET MS PUSH EAX PUSH 0 CALL MessageBoxA@16 ; закрыть библиотеку PUSH HLIB CALL FreeLibrary@4 ; библиотека автоматически закрывается также ; при выходе из программы ; выход _EXIT: PUSH 0 CALL ExitProcess@4 _TEXT ENDS END START



Теперь пришла пора вплотную заняться потоками



II

Теперь пришла пора вплотную заняться потоками. Вначале я намерен решить задачу из предыдущей главы (Рисунок 3.1.2) при помощи потока.

Поток может быть создан при помощи функции CreateThread. Рассмотрим параметры этой функции.

1-й параметр. Указатель на структуру атрибутов доступа. Имеет значение только для Windows NT. Обычно полагается NULL. 2-й параметр. Размер стека потока. Если параметр равен нулю, то берется размер стека по умолчанию, равный размеру стека родительского потока. 3-й параметр. Указатель на потоковую функцию, с вызова которой начинается исполнение потока. 4-й параметр. Параметр для потоковой функции. 5-й параметр. Флаг, определяющий состояние потока. Если флаг равен 0, то выполнение потока начинается немедленно. Если значение флага потока равно CREATE_SUSPENDED (4H), то поток находится в состояние ожидания и запускается по выполнению функции ResumeThread. 6-й параметр. Указатель на переменную, куда будет помещен дескриптор потока.

Как уже было сказано, выполнение потока начинается с потоковой функции. Окончание работы этой функции приводит к естественному окончанию работы потока. Поток также может закончить свою работу, выполнив функцию ExitThread с указанием кода выхода. Наконец, порождающий поток может закончить работу порожденного потока при помощи функции TerminateThread. В нашем примере на Рисунок 3.2.2 запускаемый процесс не может сам закончить свою работу и прекращает свою работу вместе с приложением по команде TerminateThread. Надо сказать, что такое завершение, вообще говоря, является аварийным и не рекомендуется к обычному употреблению. Связано это с тем, что при таком завершении не выполняется никаких действий по освобождению занятых ресурсов (блоки памяти, открытые файлы и т.п.). Поэтому стройте свои приложения так, что бы поток завершался по выходу из потоковой процедуры. Таким образом, в некотором смысле приведенный ниже пример является демонстрацией того, чего не рекомендуется делать.

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




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

// файл thread.rc // определение констант #define WS_SYSMENU 0x00080000L // элементы на окне должны быть изначально видимы #define WS_VISIBLE 0x10000000L // бордюр вокруг элемента #define WS_BORDER 0x00800000L // при помощи TAB можно по очереди активизировать элементы #define WS_TABSTOP 0x00010000L // текст в окне редактирования прижат к левому краю #define ES_LEFT 0x000OL // стиль всех элементов на окне #define WS_CHILD 0x40000000L // запрещается ввод с клавиатуры #define ES_READONLY 0x0800L // стиль - кнопка #define BS_PUSHBUTTON 0x00000000L // центрировать текст на кнопке #define BS_CENTER 0x00000300L

#define DS_3DLOOK 0x0004L

// определение диалогового окна DIAL1 DIALOG 0, 0, 240, 100 STYLE WS_SYSMENU | DS_3DLOOK CAPTION "Пример использования потока" FONT 8, "Arial" { // окно редактирования, идентификатор 1 CONTROL "", 1, "edit", ES_LEFT | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP | ES_READONLY, 100, 5, 130, 12 // кнопка, идентификатор 2 CONTROL "Выход", 2, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 180, 76, 50, 14 }

; файл thread.inc ; константы ; сообщение приходит при закрытии окна WM_CLOSE equ 10h ; сообщение приходит при создании окна WM_INITDIALOG equ 110h ; сообщение приходит при событии с элементом на окне WM_COMMAND equ 111h ; сообщение посылки текста элементу WM_SETTEXT equ 0Ch

; прототипы внешних процедур IFDEF MASM ; для MASM EXTERN SendMessageA@16:NEAR EXTERN GetDlgItem@8:NEAR EXTERN Sleep@4:NEAR EXTERN TerminateThread@8:NEAR EXTERN CreateThread@24:NEAR EXTERN wsprintfA:NEAR EXTERN GetLocalTime@4:NEAR EXTERN ExitProcess@4:NEAR EXTERN GetModuleHandleA@4:NEAR EXTERN DialogBoxParamA@20:NEAR EXTERN EndDialog@8:NEAR ELSE ; для TASM EXTERN SendMessageA:NEAR EXTERN GetDlgItem:NEAR EXTERN Sleep:NEAR EXTERN TerminateThread:NEAR EXTERN CreateThread:NEAR EXTERN _wsprintfA:NEAR EXTERN GetLocalTime:NEAR EXTERN ExitProcess:NEAR EXTERN GetModuleHandleA:NEAR EXTERN DialogBoxParamA:NEAR EXTERN EndDialog:NEAR SendMessageA@16 = SendMessageA GetDlgItem@8 = GetDlgItem Sleep@4 = Sleep TerminateThread@8 = TerminateThread CreateThread@24 = CreateThread wsprintfA = _wsprintfA GetLocalTime@4 = GetLocalTime ExitProcess@4 = ExitProcess GetModuleHandleA@4 = GetModuleHandleA DialogBoxParamA@20 = DialogBoxParamA EndDialog@8 = EndDialog ENDIF ; структуры ; структура сообщения MSGSTRUCT STRUC MSHWND DD ? MSMESSAGE DD ? MSWPARAM DD ? MSLPARAM DD ? MSTIME DD ? MSPT DD ? MSGSTRUCT ENDS



; структура данных дата- время DAT STRUC year DW ? month DW ? dayweek DW ? day DW ? hour DW ? min DW ? sec DW ? msec DW ? DAT ENDS

; файл thread.asm .386P ; плоская модель .MODEL FLAT, stdcall include thread.inc ; директивы компоновщику для подключения библиотек IFDEF MASM ; для компоновщика LINK.EXE includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib includelib c:\masm32\lib\gdi32.lib ELSE ; для компоновщика TLINK32.EXE includelib c:\tasm32\lib\import32.lib ENDIF ;----------------------------------------------------

; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' MSG MSGSTRUCT <?> HINST DD 0 ; дескриптор приложения PA DB "DIAL1",0 TIM DB "Дата %u/%u/%u Время %u:%u:%u",0 STRCOPY DB 50 DUP (?) DATA DAT <0> HTHR DD ? _DATA ENDS

; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; получить дескриптор приложения PUSH 0 CALL GetModuleHandleA@4 MOV [HINST], EAX ; создать диалоговое окно PUSH 0 PUSH OFFSET WNDPROC PUSH 0 PUSH OFFSET PA PUSH [HINST] CALL DialogBoxParamA@20 CMP EAX,-1 JNE KOL ; сообщение об ошибке KOL: ;------------------------------------ PUSH 0 CALL ExitProcess@4 ;------------------------------------

; процедура окна ; расположение параметров в стеке ; [EBP+014Н] ; LPARAM ; [EBP+10Н] ; WAPARAM ; [EBP+0CH] ; MES ; [EBP+8] ; HWND WNDPROC PROC PUSH EBP MOV EBP,ESP PUSH EBX PUSH ESI PUSH EDI ;------------------- CMP DWORD PTR [EBP+0CH],WM_CLOSE JNE L1 L3: ; здесь реакция на закрытие окна ; удалить поток PUSH 0 PUSH HTHR CALL TerminateThread@8 ; закрыть диалог PUSH 0 PUSH DWORD PTR [EBP+08H] CALL EndDialog@8 JMP FINISH L1: CMP DWORD PTR [EBP+0CH],WM_INITDIALOG JNE L2 ; здесь начальная инициализация ; получить дескриптор окна редактирования PUSH 1 PUSH DWORD PTR [EBP+08H] CALL GetDlgItem@8 ; создать поток PUSH OFFSET HTHR ; сюда дескриптор потока PUSH 0 PUSH EAX ; параметр PUSH OFFSET GETTIME ; адрес процедуры PUSH 0 PUSH 0 CALL CreateThread@24 JMP FINISH L2: CMP DWORD PTR [EBP+0CH],WM_COMMAND JNE FINISH ; кнопка выхода? CMP WORD PTR [EBP+10H],2 JE L3 FINISH: POP EDI POP ESI POP EBX POP EBP MOV EAX,0 RET 16 WNDPROC ENDP

; потоковая функция ; [EBP+8] параметр=дескриптор окна редактирования GETTIME PROC PUSH EBP MOV EBP,ESP L0: ; задержка в 1 секунду PUSH 1000 CALL Sleep@4 ; получить локальное время PUSH OFFSET DATA CALL GetLocalTime@4 ; получить строку для вывода даты и времени MOVZX EAX,DATA.sec PUSH EAX MOVZX EAX,DATA.min PUSH EAX MOVZX EAX,DATA.hour PUSH EAX MOVZX EAX,DATA.year PUSH EAX MOVZX EAX,DATA.month PUSH EAX MOVZX EAX,DATA.day PUSH EAX PUSH OFFSET TIM PUSH OFFSET STRCOPY CALL wsprintfA ; отправить строку в окно редактирования PUSH OFFSET STRCOPY PUSH 0 PUSH WM_SETTEXT PUSH DWORD PTR [EBP+08H] CALL SendMessageA@16 JMP L0 ; бесконечный цикл POP EBP RET 4 GETTIME ENDP _TEXT ENDS END START


Управление памятью



Управление памятью.

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

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

Обычно значение флага принимают равным константе GMEM_FIXED, которая равна нулю. Это означает, что блок памяти неперемещаем. Неперемещаемость следует понимать в том смысле, что не будет меняться виртуальный адрес блока, тогда как физическая память, куда проецируется данный блок, может, разумеется, меняться системой. Комбинация данного флага с флагом GMEM_ZEROINIT приводит к автоматическому заполнению выделенного блока нулями, что часто бывает весьма удобно. Изменить размер выделенного блока можно при помощи функции GlobalReAlloc. Первым аргументом данной функции является указатель на изменяемый блок, второй аргумент - размер нового блока, третий аргумент - флаг. Заметим, что данная функция может изменить свойства блока памяти, т.е., например, сделать его перемещаемым.

Обратимся теперь снова к флагам функции GlobalAlloc. Дело в том, что если ваша программа интенсивно работает с памятью, т.е. многократно выделяет и освобождает память, память может оказаться фрагментированной. Действительно - Вы же запрещаете перемещать блоки. В этом случае можно использовать флаг GMEM_MOVEABLE. Выделив блок, Вы можете в любой момент зафиксировать его при помощи функции GlobalLock, после этого спокойно работая с ним. С помощью функции GlobalUnlock можно в любой момент снять фиксацию, т.е. разрешить системе упорядочивать блоки. Надо иметь в виду, что при использовании флага GMEM_MOVEABLE возвращается не адрес, а дескриптор. Но как раз аргументом функции GlobalLock и является дескриптор. Сама же функция GlobalLock возвращает адрес.




Возможен и еще более экзотический подход с использованием флага GMEM_DISCARDABLE. Этот флаг используется совместно с GMEM_MOVEABLE. В этом случае блок может быть удален из памяти системой, если только вы его предварительно не зафиксировали. Если блок был удален системой, то функция GlobalLock возвратит 0, и Вам придется снова выделять блок и загружать, если необходимо, данные.

Для удаления блока памяти используется функция GlobalFree. Причем в случае выделения фиксированного блока памяти, аргументом функции является адрес блока памяти, а в случае перемещаемого блока памяти - дескриптор. Для освобождения удаляемого блока памяти используйте функцию GlobalDiscard.

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

MEM STRUC dwLength DW ? dwMemoryLoad DW ? dwTotalPhys DW ? dwAvailPhys DW ? dwTotalPageFile DW ? dwAvailPageFile DW ? dwTotalVirtual DW ? dwAvailVirtual DW ? MEM ENDS

dwLength - размер структуры в байтах.

dwMemoryLoad - процент использованной памяти.

dwTotalPhys - полный объем физической памяти в байтах.

dwAvailPhys - объем доступной физической памяти в байтах.

dwTotalPageFile - количество сохраненных байт физической памяти на диске.

dwAvailPageFile - количество доступных байт памяти, сохраненных на диске.

dwTotalVirtual - объем виртуальной памяти.

dwAvailVirtual - объем доступной виртуальной памяти.

Ниже на Рисунок 3.6.4 показано простейшее применение функции GlobalAlloc.

; файл MEM.ASM .386P ; плоская модель .MODEL FLAT, stdcall

; константы ; для вывода в консоль STD_OUTPUT_HANDLE equ -11 GENERIC_READ equ 80000000h OPEN_EXISTING equ 3 IFDEF MASM ; MASM ; прототипы внешних процедур EXTERN GlobalFree@4:NEAR EXTERN GlobalAlloc@8:NEAR EXTERN GetFileSize@8:NEAR EXTERN CloseHandle@4:NEAR EXTERN CreateFileA@28:NEAR EXTERN ReadFile@20:NEAR EXTERN GetStdHandle@4:NEAR EXTERN WriteConsoleA@20:NEAR EXTERN ExitProcess@4:NEAR EXTERN GetCommandLineA@0:NEAR ; директивы компоновщику для подключения библиотек includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib ELSE ; TASM LOCALS ; прототипы внешних процедур EXTERN GlobalFree:NEAR EXTERN GlobalAlloc:NEAR EXTERN GetFileSize:NEAR EXTERN CloseHandle:NEAR EXTERN CreateFileA:NEAR EXTERN ReadFile:NEAR EXTERN GetStdHandle:NEAR EXTERN WriteConsoleA:NEAR EXTERN ExitProcess:NEAR EXTERN GetCommandLineA:NEAR



GlobalFree@4 = GlobalFree GlobalAlloc@8 = GlobalAlloc GetFileSize@8 = GetFileSize CloseHandle@4 = CloseHandle CreateFileA@28 = CreateFileA ReadFile@20 = ReadFile GetStdHandle@4 = GetStdHandle WriteConsoleA@20 = WriteConsoleA ExitProcess@4 = ExitProcess GetCommandLineA@0 = GetCommandLineA ; директивы компоновщику для подключения библиотек includelib c:\tasm32\lib\import32.lib ENDIF ;------------------------------------------------- ; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' LENS DWORD ? HANDL DWORD ? ; дескриптор консоли HF DWORD ? ; дескриптор файла SIZEH DWORD ? ; старшая часть длины файла SIZEL DWORD ? ; младшая часть длины файла GH DWORD ? ; указатель на блок памяти NUMB DWORD ? BUF DB 10 DUP (0) _DATA ENDS

; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; получить HANDLE вывода PUSH STD_OUTPUT_HANDLE CALL GetStdHandle@4 MOV HANDL,EAX ; получить количество параметров CALL NUMPAR CMP EAX,2 JB _EXIT ;------------------------------------------------- ; получить параметр номером EDI MOV EDI,2 LEA EBX,BUF CALL GETPAR ; теперь работаем с файлом ; открыть только для чтения PUSH 0 PUSH 0 PUSH OPEN_EXISTING PUSH 0 PUSH 0 PUSH GENERIC_READ PUSH OFFSET BUF CALL CreateFileA@28 CMP EAX,-1 JE _EXIT ; запомнить дескриптор файла MOV HF,EAX ; определить размер файла PUSH OFFSET SIZEH PUSH EAX CALL GetFileSize@8 ; запомнить размер, предполагаем, что размер не превосходит 4 Гб MOV SIZEL,EAX ; запросить память для считывания туда файла PUSH EAX PUSH 0 CALL GlobalAlloc@8 CMP EAX,0 JE _CLOSE ; запомнить адрес выделенного блока MOV GH,EAX ; читать файл в выделенную память PUSH 0 PUSH OFFSET NUMB PUSH SIZEL PUSH GH PUSH HF CALL ReadFile@20 CMP EAX,0 JE _FREE ; вывести прочитанное PUSH 0 PUSH OFFSET LENS PUSH SIZEL PUSH GH PUSH HANDL CALL WriteConsoleA@20 _FREE: ; освободить память PUSH GH CALL GlobalFree@4 ; закрыть файлы _CLOSE: PUSH HF CALL CloseHandle@4 _EXIT: ; конец работы программы PUSH 0 CALL ExitProcess@4 ;--------------------------------------------- ; область процедур ; процедура определения количества параметров в строке ; определить количество параметров (->EAX) NUMPAR PROC CALL GetCommandLineA@0 MOV ESI,EAX ; указатель на строку XOR ECX,ECX ; счетчик MOV EDX,1 ; признак @@L1: CMP BYTE PTR [ESI],0 JE @@L4 CMP BYTE PTR [ESI],32 JE @@L3 ADD ECX,EDX ; номер параметра MOV EDX,0 JMP @@L2 @@L3: OR EDX,1 @@L2: INC ESI JMP @@L1 @@L4: MOV EAX,ECX RET NUMPAR ENDP

; получить параметр из командной строки ; EBX - указывает на буфер, куда будет помещен параметр ; в буфер помещается строка с нулем на конце ; EDI - номер параметра GETPAR PROC CALL GetCommandLineA@0 MOV ESI,EAX ; указатель на строку XOR ECX,ECX ; счетчик MOV EDX,1 ; признак @@L1: CMP BYTE PTR [ESI],0 JE @@L4 CMP BYTE PTR [ESI],32 JE @@L3 ADD ECX,EDX ; номер параметра MOV EDX,0 JMP @@L2 @@L3: OR EDX,1 @@L2: CMP ECX,EDI JNE @@L5 MOV AL,BYTE PTR [ESI] MOV BYTE PTR [EBX],AL INC EBX @@L5: INC ESI JMP @@L1 @@L4: MOV BYTE PTR [EBX],0 RET GETPAR ENDP _TEXT ENDS END START


В данном разделе мы рассмотрим весьма интересный вопрос о всплывающих подсказках



III

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

1. Прежде всего заметим, что всплывающая подсказка - это всего лишь окно с определенными свойствами. Вот эти свойства: DS_3DLOOK, WS_POPUP, WS_VISIBLE, WS_BORDER. В принципе можно экспериментировать - добавлять или удалять свойства. Но без одного свойства Вы никак не обойдетесь - это WS_POPUP. Собственно POPUP можно перевести как поплавок. Кроме того, определение всплывающего окна в файле ресурсов не должно содержать опции CAPTION.

2. Появление подсказки не должно менять ситуацию в диалоговом окне. Это значит - вызов подсказки должен быть немодальным, при помощи функции CreateDialogIndirect. Кроме того, следует предусмотреть переустановку фокуса на диалоговое окно. Для этого достаточно в нужном месте (см. Рисунок 3.1.3) вызвать функцию SetFocus.

3. Итак, подсказка - это диалоговое окно, и, следовательно, оно должно иметь свою функцию. Что должна содержать эта функция? По крайней мере, обработку трех событий: WM_INITDIALOG, WM_PA1NT, WM_TIMER. По получении сообщения WM_INITDIALOG следует определить размер и положение подсказки. Кроме того, если мы предполагаем, что подсказка должна спустя некоторое время исчезать, следует установить таймер. По получении сообщения WM_PAINT следует вывести в окно подсказки текст. Если определять размер окна подсказки точно по строке выводимого текста, то цвет фона подсказки будет полностью определяться цветом выводимого текста. Наконец по приходе сообщения WM_TIMER мы закрываем подсказку.

4. С самой подсказкой более или менее ясно. Определимся теперь, как и где будет вызываться эта подсказка. Мне более импонирует такой подход: в основном диалоговом окне определяем таймер, в функции которого и будет проверяться положение курсора. В зависимости от этого положения и будет вызываться или удаляться подсказка. В функции таймера должно предусмотреть:






Проверку положения курсора. Если курсор оказался на данном элементе, то вызывать подсказку. При этом желательно, чтобы подсказка появлялась бы с некоторой задержкой. Последнее можно обеспечить введением счетчика - вызывать подсказку, если счетчик превысил некоторое значение. Необходимо обеспечить удаление подсказки, если курсор покидает данный элемент.

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

// файл HINT.RC // определение констант #define WS_SYSMENU 0x00080000L // элементы на окне должны быть изначально видимы #define WS_VISIBLE 0x10000000L // бордюр вокруг элемента #define WS_BORDER 0x00800000L // при помощи TAB можно по очереди активизировать элементы #define WS_TABSTOP 0x00010000L // текст в окне редактирования прижат к левому краю #define ES_LEFT 0x0000L // стиль всех элементов на окне #define WS_CHILD 0x40000000L // стиль - кнопка #define BS_PUSHBUTTON 0x00000000L // центрировать текст на кнопке #define BS_CENTER 0x00000300L // тип окна - "поплавок" #define WS_POPUP 0x80000000L // стиль - диалоговое окно Windows 95 #define DS_3DLOOK 0x0004L

// определение диалогового окна DIAL1 DIALOG 0, 0, 240, 100 STYLE WS_SYSMENU | DS_3DLOOK CAPTION "Окно с всплывающими подсказками" FONT 8, "Arial" { // окно редактирования, идентификатор 1 CONTROL "", 1, "edit", ES_LEFT | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP , 100, 5, 130, 12 // кнопка, идентификатор 2 CONTROL "Выход", 2, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 180, 76, 50, 14 }

// диалоговое окно подсказки HINTW DIALOG 0, 0, 240, 8 STYLE DS_3DLOOK | WS_POPUP | WS_VISIBLE | WS_BORDER FONT 8, "MS Sans Serif" { }

; файл HINT.INC ;константы ;цвет фона окна подсказки RED = 255 GREEN = 255 BLUE = 150 RGBB equ (RED or (GREEN shl 8)) or (BLUE shl 16)



; цвет текста окна подсказки RED = 20 GREEN = 20 BLUE = 20 RGBT equ (RED or (GREEN shl 8)) or (BLUE shl 16)

;сообщение приходит при закрытии окна WM_CLOSE equ 10h WM_INITDIALOG equ 110h WM_COMMAND equ 111h WM_TIMER equ 113h WM_SETTEXT equ 0Ch WM_COMMAND equ 111h WM_PAINT equ 0Fh

;прототипы внешних процедур IFDEF MASM EXTERN CreateDialogParamA@20:NEAR EXTERN SetFocus@4:NEAR EXTERN lstrcpyA@8:NEAR EXTERN DestroyWindow@4:NEAR EXTERN lstrlenA@4:NEAR EXTERN GetDlgItem@8:NEAR EXTERN GetCursorPos@4:NEAR EXTERN TextOutA@20:NEAR EXTERN SetBkColor@8:NEAR EXTERN SetTextColor@8:NEAR EXTERN BeginPaint@8:NEAR EXTERN EndPaint@8:NEAR EXTERN GetTextExtentPoint32A@16:NEAR EXTERN MoveWindow@24:NEAR EXTERN GetWindowRect@8:NEAR EXTERN ReleaseDC@8:NEAR EXTERN GetDC@4:NEAR EXTERN SendDlgItemMessageA@20:NEAR EXTERN ExitProcess@4:NEAR EXTERN GetModuleHandleA@4:NEAR EXTERN DialogBoxParamA@20:NEAR EXTERN EndDialog@8:NEAR EXTERN SetTimer@16:NEAR EXTERN KillTimer@8:NEAR ELSE EXTERN CreateDialogParamA:NEAR EXTERN SetFocus:NEAR EXTERN lstrcpyA:NEAR EXTERN DestroyWindow:NEAR EXTERN lstrlenA:NEAR EXTERN GetDlgItem:NEAR EXTERN GetCursorPos:NEAR EXTERN TextOutA:NEAR EXTERN SetBkColor:NEAR EXTERN SetTextColor:NEAR EXTERN BeginPaint:NEAR EXTERN EndPaint:NEAR EXTERN GetTextExtentPoint32A:NEAR EXTERN MoveWindow:NEAR EXTERN GetWindowRect:NEAR EXTERN ReleaseDC:NEAR EXTERN GetDC:NEAR EXTERN SendDlgItemMessageA:NEAR EXTERN ExitProcess:NEAR EXTERN GetModuleHandleA:NEAR EXTERN DialogBoxParamA:NEAR EXTERN EndDialog:NEAR EXTERN SetTimer:NEAR EXTERN KillTimer:NEAR CreateDialogParamA@20 = CreateDialogParamA SetFocus@4 = SetFocus lstrcpyA@8 = IstrcpyA DestroyWindow@4 = DestroyWindow lstrlenA@4 = IstrlenA GetDlgItem@8 = GetDlgItem GetCursorPos@4 = GetCursorPos TextOutA@20 = TextOutA SetBkColor@8 = SetBkColor SetTextColor@8 = SetTextColor BeginPaint@8 = BeginPaint EndPaint@8 = EndPaint GetTextExtentPoint32A@16 = GetTextExtentPoint32A MoveWindow@24 = MoveWindow GetWindowRect@8 = GetWindowRect ReleaseDC@8 = ReleaseDC GetDC@4 = GetDC SendDlgItemMessageA@20=SendDlgItemMessageA ExitProcess@4 = ExitProcess GetModuleHandleA@4 = GetModuleHandleA DialogBoxParamA@20 = DialogBoxParamA EndDialog@8 = EndDialog SetTimer@16 = SetTimer KillTimer@8 = KillTimer ENDIF



; структуры ; структура сообщения MSGSTRUCT STRUC MSHWND DD ? MSMESSAGE DD ? MSWPARAM DD ? MSLPARAM DD ? MSTIME DD ? MSPT DD ? MSGSTRUCT ENDS

; структура размера окна RECT STRUC L DD ? T DD ? R DD ? B DD ? RECT ENDS

;структура размер SIZ STRUC X DD ? Y DD ? SIZ ENDS

; структура для BeginPaint PAINTSTR STRUC hdc DWORD 0 fErase DWORD 0 left DWORD 0 top DWORD 0 right DWORD 0 bottom DWORD 0 fRes DWORD 0 fIncUp DWORD 0 Reserv DB 32 dup (0) PAINTSTR ENDS

; структура для получения позиции курсора POINT STRUC X DD ? Y DD ? POINT ENDS

; файл HINT.ASM .386P ; плоская модель .MODEL FLAT, stdcall include hint.inc ; директивы компоновщику для подключения библиотек IFDEF MASM ; для компоновщика LINK.EXE includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib includelib c:\masm32\lib\gdi32.lib ELSE ; для компоновщика TLINK32.EXE includelib c:\tasm32\lib\import32.lib ENDIF ;------------------------------------------------- ; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' MSG MSGSTRUCT <?> HINST DD 0 ; дескриптор приложения PA DB "DIAL1",0 HIN DB "HINTW",0 XX DD ? YY DD ? ;------------------------------- R1 RECT <?> R2 RECT <?> S SIZ <?> PS PAINTSTR <?> PT POINT <?> ; дескрипторы окон-подсказок, для первого и второго элемента H1 DD 0 H2 DD 0 ; строка-подсказка HINTS DB 60 DUP (?) ; перечень подсказок HINT1 DB "Редактирование строки",0 HINT2 DB "Кнопка выхода",0 ; для временного хранения контекста устройства DC DD ? ; счетчик P1 DD ? _DATA ENDS

; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; получить дескриптор приложения PUSH 0 CALL GetModuleHandleA@4 MOV [HINST], EAX ;---------------------------- PUSH 0 PUSH OFFSET WNDPROC PUSH 0 PUSH OFFSET PA PUSH [HINST] CALL DialogBoxParamA@20 CMP EAX,-1 JNE KOL KOL: ;----------------------------- PUSH 0 CALL ExitProcess@4 ;-----------------------------

; процедура окна ; расположение параметров в стеке ; [EBP+014Н] ; LPARAM ; [EBP+10Н] ; WAPARAM ; [EBP+0CH] ; MES ; [EBP+8] ; HWND WNDPROC PROC PUSH EBP MOV EBP,ESP PUSH EBX PUSH ESI PUSH EDI ;------------------------------ CMP DWORD PTR [EBP+0CH],WM_CLOSE JNE L1 ; здесь реакция на закрытие окна ; удалить таймер L4: PUSH 2 ; идентификатор таймера PUSH DWORD PTR [EBP+08H] CALL KillTimer@8 ; закрыть диалог PUSH 0 PUSH DWORD PTR [EBP+08H] CALL EndDialog@8 JMP FINISH L1: CMP DWORD PTR [EBP+0CH], WM_INITDIALOG JNE L2 ; здесь начальная инициализация ; установить таймер PUSH OFFSET TIMPROC PUSH 500 ; интервал 0.5 с. PUSH 2 ; идентификатор таймера PUSH DWORD PTR [EBP+08H] CALL SetTimer@16 JMP FINISH L2: CMP DWORD PTR [EBP+0CH],WM_COMMAND JNE L3 ; кнопка выхода? CMP WORD PTR [EBP+10H],2 JNE L3 JMP L4 L3: FINISH: POP EDI POP ESI POP EBX POP EBP MOV EAX,0 RET 16 WNDPROC ENDP



;--------------------------------------------- ; процедура таймера ; расположение параметров в стеке ; [EBP+014Н] ; LPARAM - промежуток запуска Windows ; [EBP+10Н] ; WAPARAM - идентификатор таймера ; [EBP+0CH] ; WM_TIMER ; [EBP+8] ; HWND TIMPROC PROC PUSH EBP MOV EBP,ESP ; получить положение курсора PUSH OFFSET PT CALL GetCursorPos@4 ; запомнить координаты MOV EAX,PT.X MOV XX,EAX MOV EAX,PT.Y MOV YY,EAX ; получить положение элементов ; окно редактирования PUSH 1 PUSH DWORD PTR [EBP+08H] CALL GetDlgItem@8 PUSH OFFSET R1 PUSH EAX CALL GetWindowRect@8 ; кнопка выхода PUSH 2 PUSH DWORD PTR [EBP+08H] CALL GetDlgItem@8 PUSH OFFSET R2 PUSH EAX CALL GetWindowRect@8 ; увеличить счетчик INC P1 MOV ECX,XX MOV EDX,YY ; проверка условий .IF H1==0 && P1>5 .IF EDX<=R1.B && EDX>=R1.T && ECX>=R1.L && ECX<=R1.R ; подготовить строку PUSH OFFSET HINT1 PUSH OFFSET HINTS CALL lstrcpyA@8 ; создать диалоговое окно - подсказку PUSH 0 PUSH OFFSET HINT PUSH DWORD PTR [EBP+08H] PUSH OFFSET HIN PUSH [HINST] CALL CreateDialogParamA@20 MOV H1,EAX ; установить фокус PUSH DWORD PTR [EBP+08H] CALL SetFocus@4 ; обнулить счетчик MOV P1,0 JMP _END .ENDIF .ENDIF .IF H1!=0 .IF (EDX>R1.B || EDX<R1.T) || (ECX<R1.L || ECX>R1.R) ; удаление подсказки в связи с перемещением курсора PUSH H1 CALL DestroyWindow@4 MOV H1,0 JMP _END .ENDIF .ENDIF .IF H2==0 && P1>5 .IF EDX<=R2.B && EDX>=R2.T && ECX>=R2.L && ECX<=R2.R ; подготовить строку PUSH OFFSET HINT2 PUSH OFFSET HINTS CALL lstrcpyA@8 ; создать диалоговое окно - подсказку PUSH 0 PUSH OFFSET HINT PUSH DWORD PTR [EBP+08H] PUSH OFFSET HIN PUSH [HINST] CALL CreateDialogParamA@20 MOV H2,EAX ; установить фокус PUSH DWORD PTR [EBP+08H] CALL SetFocus@4 ; обнулить счетчик MOV P1,0 JMP _END .ENDIF .ENDIF .IF H2!=0 .IF (EDX>R2.B || EDX<R2.T) || (ECX<R2.L || ECX>R2.R) ;удаление подсказки в связи с перемещением курсора PUSH H2 CALL DestroyWindow@4 MOV H2,0 JMP _END .ENDIF .ENDIF ; восстановить стек _END: POP EBP RET 16 TIMPROC ENDP



; процедура окна всплывающей подсказки HINT PROC PUSH EBP MOV EBP,ESP CMP DWORD PTR [EBP+0CH],WM_INITDIALOG JNE NO_INIT ; инициализация ; получить контекст PUSH DWORD PTR [EBP+08H] CALL GetDC@4 MOV DC,EAX ; получить длину строки PUSH OFFSET HINTS CALL lstrlenA@4 ; получить длину и ширину строки PUSH OFFSET S PUSH EAX PUSH OFFSET HINTS PUSH DC CALL GetTextExtentPoint32A@16 ; установить положение и размер окна-подсказки PUSH 0 PUSH S.Y ADD S.X,2 PUSH S.X SUB YY,20 PUSH YY ADD XX,10 PUSH XX PUSH DWORD PTR [EBP+08H] CALL MoveWindow@24 ; закрыть контекст PUSH DC PUSH DWORD PTR [EBP+08H] CALL ReleaseDC@8 ; установить таймер PUSH 0 PUSH 6000 ; интервал 6 с. PUSH 3 ; идентификатор таймера PUSH DWORD PTR [EBP+08H] CALL SetTimer@16 JMP FIN NO_INIT: CMP DWORD PTR [EBP+0CH],WM_PAINT JNE NO_PAINT ; перерисовка окна ; получить контекст PUSH OFFSET PS PUSH DWORD PTR [EBP+08H] CALL BeginPaint@8 MOV DC,EAX ; установить цвета фона и текста подсказки PUSH RGBB PUSH EAX CALL SetBkColor@8 PUSH RGBT PUSH DC CALL SetTextColor@8 ; вывести текст PUSH OFFSET HINTS CALL lstrlenA@4 PUSH EAX PUSH OFFSET HINTS PUSH 0 PUSH 0 PUSH DC CALL TextOutA@20 ; закрыть контекст PUSH OFFSET PS PUSH DWORD PTR [EBP+08H] CALL EndPaint@8 JMP FIN NO_PAINT: CMP DWORD PTR [EBP+0CH],WM_TIMER JNE FIN ; обработка события таймера ; удалить таймер и удалить диалоговое окно ; подсказка удаляется в связи с истечением срока 6 с. PUSH 3 PUSH DWORD PTR [EBP+08H] CALL KillTimer@8 PUSH DWORD PTR [EBP+08H] CALL DestroyWindow@4 FIN: POP EBP RET 16 HINT ENDP _TEXT ENDS END START


В данном разделе рассматривается простой модуль на языке ассемблера



I

В данном разделе рассматривается простой модуль на языке ассемблера, содержащий процедуру, копирующую одну строку в другую. Мы подсоединяем этот модуль к программам, написанным на языке Си и Паскаль, с использованием трех трансляторов: Borland C++ 5.02, Visual C++ 6.0, Delphi 5.0.



В Есть ли дополнительные средства упрощающие файловую обработку?



В. Есть ли дополнительные средства, упрощающие файловую обработку?

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

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

Работу с файлами, отображаемыми в память, производят по следующему алгоритму:

Открыть (или создать) файл с помощью обычной функции CreateFile. Функция, как известно, возвращает дескриптор открытого файла. Отобразить файл45 с помощью функции CreateFileMapping. Именно эта функция определяет размер отображения. Эта функция, как и предыдущая, возвращает дескриптор, только не обычного, а отображенного файла. Скопировать файл (или его часть) в созданную область при помощи функции MapViewOfFile. Эта функция возвращает указатель (вот оно!) на начало области, где будет расположен файл. После этого руки у Вас развязаны, и Вы можете делать с отображенным файлом все, что Вам заблагорассудится. При желании можно записать область памяти в файл при помощи функции FlushViewOfFile. Разумеется, сбрасываться на диск будет та область памяти, которую мы заказали при помощи функции CreateFileMapping. Записывать на диск можно, разумеется, и с помощью обычной функции WriteFile. Перед тем как закрывать отображенный файл, следует вначале сделать указатель недействительным. Это делается с помощью функции UnmapViewOfFile. Закрыть следует оба дескриптора. Вначале дескриптор, возвращенный функцией CreateFileMapping, а затем дескриптор, созданный функцией CreateFile.




Как видите, алгоритм работы с отображаемыми файлами весьма прост. Рассмотрим теперь новые для Вас функции.

Функция CreateFileMapping. Возвращает дескриптор отображаемого файла.

1-й параметр. Дескриптор открытого файла. 2-й параметр. Атрибут доступа, обычно полагают равным нулю. 3-й параметр. Может принимать одно из следующих значений, и должен быть совместим с режимом разделения файла: PAGE_READONLY, PAGE_WRITECOPY, PAGE_READWRITE. Как Вы понимаете, этот атрибут определяет защиту отображаемого файла и не должен противоречить атрибуту файла, открытого с помощью CreateFile. 4-й параметр. Старшая часть (32 бита) размера отображаемого файла. 5-й параметр. Младшая часть размера отображаемого файла (как Вы понимаете, размер может и не совпадать с размером файла). Если оба параметра равны нулю, то размер полагается равным размеру открытого файла (1-й параметр). 6-й параметр. Имя отображаемого файла. Необходимо только в том случае, если предполагается, что отображаемый файл будет использоваться несколькими процессами. В этом случае повторный вызов функции CreateFileMapping другими процессами с тем же именем приведет не к созданию нового отображаемого файла, а к возвращению уже созданного дескриптора.

Функция MapViewOfFile. Возвращает указатель на область памяти, где помещается файл или его часть.

1-й параметр. Дескриптор, возвращенный функцией CreateFileMapping. 2-й параметр. Определяет операцию, которую мы будем делать. Например, FILE_MAP_READ означает - только чтение, a FILE_MAP_WRITE - чтение и запись. 3-й параметр. Старшая часть (32-байта) смещения в файле, откуда начинается копирование в память. 4-й параметр. Младшая часть смещения в файле, откуда начинается копирование. 5-й параметр. Определяет количество копируемых байт. Если вы хотите скопировать весь файл, то положите три последних параметра равными 0.

Функция FlushViewOfFile.

1-й параметр. Указывает на область, записываемую в файл. 2-й параметр. Определяет количество записываемых байт.

Функция UnmapViewOfFile.

1-й параметр. Дескриптор отображаемого файла.

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

45 Возможно, несколько правильнее сказать, что функция создает объект, под названием отображаемый файл.


В Имеет ли операционная система



В. Имеет ли операционная система Windows средства, упрощающие операции над группами файлов и каталогами?

Да, имеется функция SHFileOperation, которая умеет выполнять копирование, перенос, переименование или удаление файловых объектов (т.е. файлов и каталогов, в том числе и вложенных). Данная функция имеет всего один параметр - указатель на структуру, которая и определяет, какую операцию следует произвести, над чем и как. Вот эта структура.

SH STRUCT hwnd DWORD ? wFunc DWORD ? pFrom DWORD ? pTo DWORD ? fFlags DWORD ? fAnyOperationsAborted DWORD ? hNameMappings DWORD ? lpszProgressTitle DWORD ? SH ENDS

Рассмотрим значение этих полей.

hwnd - дескриптор окна, куда будет выводиться статус операции. wFunc - код операции. Может принимать следующие значения: FO_COPY, FO_DELETE, FO_MOVE, FO_RENAME. Смысл этих значений, я думаю, Вам понятен. pFrom - название файла, каталога или группы файлов или каталогов, над которыми будет производиться операция. Если несколько объектов, то имена отделяются символами с кодом 0. Можно выделять списки, которые отделяются друг от друга двумя нулевыми символами. pTo - имя или группа имен - результат операции, например копирование. fFlags - флаг, определяет характер операции. Может являться комбинацией следующих констант:

FOF_ALLOWUNDO - сохранить, если возможно, информацию для возвращения в исходное состояние. FOF_CONFIRMMOUSE - данное значение не реализовано. FOF_FILESONLY - выполнять только над файлами, если определен шаблон. FOF_MULTIDESTFILES - указывает, что pTo содержит несколько результирующих файлов или каталогов. Например, можно копировать сразу в несколько каталогов. Если pFrom состоит из нескольких файлов, то каждый файл будет копироваться в свой каталог. FOF_NOCONFIRMATION - отвечать утвердительно на все запросы. FOF_NOCONFIRMMKDIR- не подтверждать создание каталога, если это требуется. FOF_RENAMEONCOLLISION - давать файлам новые имена, если файлы с такими именами уже существуют. FOF_SILENT - не показывать окно-статус. FOF_SIMPLEPROGRESS - показывать окно-статус, но не показывать имена файлов. FOF_WANTMAPPINGHANDLE - заполнять отображаемый файл (см. ниже).

fAnyOperationsAborted - переменная, по которой после операции можно определить, была ли прервана операция (<>0) или нет (0). hNameMappings - дескриптор отображаемого в памяти файла, содержащего массив, состоящий из новых и старых имен файлов, участвующих в операции. lpszProgressTitle - указывает на строку-заголовок для диалогового окна-статуса.

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



В этом разделе используется другой тип вызова быстрый



II

В этом разделе используется другой тип вызова - быстрый, или регистровый. В соответствии с таблицей в начале главы, этот тип вызова предполагает, что три первых параметра будут передаваться в регистрах (EAX,EDX,ECX), а остальные в стеке, как и в случае соглашения stdcall. При этом если стек был задействован, освобождение его возлагается на вызываемую процедуру. Есть еще один нюанс. В случае быстрого вызова транслятор Си добавляет к имени значок @ спереди, что мы естественно учитываем в ассемблерном модуле.

#include <windows.h> #include <stdio.h> // файл ADDC.cpp // объявляется внешняя функция сложения четырех целых чисел extern "C" _fastcall ADDD(DWORD, DWORD, DWORD, DWORD); void main() { DWORD a,b,c,d; a=1; b=2; c=3; d=4; printf("%lu\n",(DWORD *)ADDD(a,b,c,d)); ExitProcess(0); }

; файл add.asm .386P .MODEL FLAT, stdcall PUBLIC @ADDD ; плоская модель _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' ; процедура возвращает сумму четырех параметров ; передача параметров регистровая ; первые три параметра в регистрах EAX,EDX,ECX ; четвертый параметр в стеке, т.е. [ЕРВ+08Н] @ADDD PROC PUSH EBP MOV EBP,ESP ADD EAX,ECX ADD EAX,EDX ADD EAX,DWORD PTR [EBP+08H] POP EBP RET 4 @ADDD ENDP _TEXT ENDS END



В Как отправить данные на печатающее устройство?



В. Как отправить данные на печатающее устройство?

Вопрос весьма обширен, и за подробностями отсылаю читателей к книге [12]. Здесь же приведу общий алгоритм, который используется при печати в Windows. Вот основные положения, которые позволят Вам быстро разобраться в сути проблемы.

Печать очень схожа с выводом на экран. При этом используются те же функции вывода, например TextOut или Ellipse. Как и в случае с экраном, для этого необходимо знать контекст принтера. Контекст принтера создается, а не получается, как в случае с экраном. Для этого используется функция CreateDC, у которой второй параметр - это указатель на строку, содержащую имя принтера. Все остальные три параметра, как правило, равны NULL. Функция PrintDlg позволяет выбрать название принтера в диалоге. При этом она возвращает тот контекст принтера. Если Вы все же решили использовать функцию CreateDC, Вам крайне необходимо узнать имена принтеров, которые есть в системе. Для этого Вам как раз подойдет функция EnumPrinters. Это весьма мощное средство может определить не только локальные, но и сетевые принтеры. Начало печати документа осуществляется функцией StartDoc. Конец печати - EndDoc. Эти две функции обрамляют блок, осуществляющий печать. В пределах этого блока можно выделять также страницы при помощи функций StartPage и EndPage. По окончании печати следует удалить контекст при помощи функции DeleteDC.



В Как сделать так чтобы при минимизации окна значок его помещался бы на системную панель?



В. Как сделать так, чтобы при минимизации окна значок его помещался бы на системную панель?

Эта проблема решается с использованием системной функции Shell_NotifyIcon. Причем решение это столь просто и прозрачно, что приходится удивляться тому, что большинство для этих целей привлекают какие-то библиотеки. Вот параметры этой функции:

1-й параметр. Действие, которое необходимо произвести:

NIM_ADD equ 0h ; добавить иконку на системную панель    NIM_MODIFY equ 1h ; удалить иконку    NIM_DELETE equ 2h ; модифицировать иконку

2-й параметр. Указатель на структуру, где содержится информация, необходимая для реализации указанного действия.

NOTI_ICON STRUC cbSize DWORD ? hWnd DWORD ? uID DWORD ? uFlags DWORD ? uCallbackMessage DWORD ? hIcon DWORD ? szTip DB 64 DUP (?) NOTI_ICON ENDS

cbSize - размер структуры.

hWnd — дескриптор окна, куда будет посылаться сообщение (см. ниже)

uID - идентификатор иконки

uFlags - комбинация следующих флагов

   NIF_MESSAGE equ 1h; использовать поле hIcon

   NIF_ICON equ 2h; использовать поле uCallbackMessage

   NIF_TIP equ 4h; использовать поле szTip

uCallbackMessage - сообщение, которое приходит на окно, определяемое дескриптором hWnd, в случае возникновения некого события вблизи иконки на системной панели. Значение сообщения должно быть больше, чем 1024. При приходе этого сообщения WPARAM содержит идентификатор иконки, a LPARAM - событие, т.е. то, что произошло с иконкой.

hIcon - дескриптор иконки.

szTip - текст всплывающей подсказки.

Давайте рассмотрим, как работает весь механизм. В случае минимизации окна на функцию окна приходит сообщение WM_SIZE. Причем wParam должен содержать значение SIZE_MINIMIZED. Вот тогда то и следует воспользоваться функцией Shell_NotifyIcon, которая поместит иконку на системную панель. Любые же события с мышью, когда ее курсор находится на иконке, вызовут приход на функцию окна сообщения uCallbackMessage, которое, естественно, мы сами и определи. При этом младшее слово wParam будет содержать идентификатор иконки, а по младшему слову lParam можно определить, что же приключилось (см. ниже). Ниже (Рисунок 3.5.1) Вы увидите, как все это работает.




// файл tray.rc // определение констант #define WS_SYSMENU 0x00080000L #define WS_MINIMIZEBOX 0x00020000L #define WS_MAXIMIZEBOX 0x0001OOOOL #define DS_3DLOOK 0x0004L

// идентификаторы #define IDI_ICON1 1

// определили иконку IDI_ICON1 ICON "ico1.ico"

// определение диалогового окна DIAL1 DIALOG 0, 0, 250, 110 STYLE WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | DS_3DLOOK CAPTION "Поместить иконку на системную панель" FONT 8, "Arial" { }

; файл tray.inc ; константы NIM_ADD equ 0h ; добавить иконку на системную панель NIM_MODIFY equ 1h ; удалить иконку NIM_DELETE equ 2h ; модифицировать иконку NIF_MESSAGE equ 1h ; использовать поле hIcon NIF_ICON equ 2h ; использовать поле uCallbackMessage NIF_TIP equ 4h ; использовать поле szTip FLAG equ NIF_MESSAGE or NIF_ICON or NIF_TIP

SIZE_MINIMIZED equ 1h SW_HIDE equ 0 SW_SHOWNORMAL equ 1

; сообщение приходит при закрытии окна WM_CLOSE equ 10h WM_INITDIALOG equ 110h WM_SIZE equ 5h WM_LBUTTONDBLCLK equ 203h WM_LBUTTONDOWN equ 201h

; прототипы внешних процедур IFDEF MASM EXTERN ShowWindow@8:NEAR EXTERN LoadIconA@8:NEAR EXTERN lstrcpy@8:NEAR EXTERN Shell_NotifyIconA@8:NEAR EXTERN ExitProcess@4:NEAR EXTERN GetModuleHandleA@4:NEAR EXTERN DialogBoxParamA@20:NEAR EXTERN EndDialog@8:NEAR EXTERN SendDlgItemMessageA@20:NEAR ELSE EXTERN ShowWindow:NEAR EXTERN LoadIconA:NEAR EXTERN lstrcpy:NEAR EXTERN Shell_NotifyIconA:NEAR EXTERN ExitProcess:NEAR EXTERN GetModuleHandleA:NEAR EXTERN DialogBoxParamA:NEAR EXTERN EndDialog:NEAR EXTERN SendDlgItemMessageA:NEAR ShowWindow@8 = ShowWindow LoadIconA@8 = LoadIconA lstrcpy@8 = lstrcpy Shell_NotifyIconA@8 = Shell_NotifyIconA ExitProcess@4 = ExitProcess GetModuleHandleA@4 = GetModuleHandleA DialogBoxParamA@20 = DialogBoxParamA EndDialog@8 = EndDialog SendDlgItemMessageA@20 = SendDlgItemMessageA ENDIF

; структуры ; структура сообщения MSGSTRUCT STRUC MSHWND DD ? MSMESSAGE DD ? MSWPARAM DD ? MSLPARAM DD ? MSTIME DD ? MSPT DD ? MSGSTRUCT ENDS

; структура для функции Shell_NotifyIcon NOTI_ICON STRUC cbSize DWORD ? hWnd DWORD ? uID DWORD ? uFlags DWORD ? uCallbackMessage DWORD ? hIcon DWORD ? szTip DB 64 DUP (?) NOTI_ICON ENDS



; файл tray.asm .386P ; плоская модель .MODEL FLAT, stdcall include tray.inc ; директивы компоновщику для подключения библиотек IFDEF MASM includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib includelib c:\masm32\lib\shell32.lib ELSE includelib c:\tasm32\lib\import32.lib ENDIF ;————————-----------------------------------------

; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' MSG MSGSTRUCT <?> HINST DD 0 ; дескриптор приложения PA DB "DIAL1",0 NOTI NOTI_ICON <0> TIP DB "Пример использования функции" DB "Shell_NotifyIcon",0 _DATA ENDS

;сегмент кода

_TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; получить дескриптор приложения PUSH 0 CALL GetModuleHandleA@4 MOV [HINST], EAX ;-------------------------------------- PUSH 0 PUSH OFFSET WNDPROC PUSH 0 PUSH OFFSET PA PUSH [HINST] CALL DialogBoxParamA@20 CMP EAX,-1 JNE KOL ; сообщение об ошибке KOL: ;-------------------------------------- PUSH 0 CALL ExitProcess@4 ;--------------------------------------

; процедура окна ; расположение параметров в стеке ; [EBP+014Н] ; LPARAM ; [EBP+10Н] ; WAPARAM ; [EBP+0CH] ; MES ; [EBP+8] ; HWND WNDPROC PROC PUSH EBP MOV EBP,ESP PUSH EBX PUSH ESI PUSH EDI ;--------------- CMP DWORD PTR [EBP+0CH],WM_CLOSE JNE L1 PUSH 0 PUSH DWORD PTR [EBP+08H] CALL EndDialog@8 JMP FINISH L1: CMP DWORD PTR [EBP+0CH],WM_INITDIALOG JNE L2 JMP FINISH L2: CMP DWORD PTR [EBP+0CH],WM_SIZE JNE L3 CMP DWORD PTR [EBP+10H],SIZE_MINIMIZED JNE L3 ; здесь работа по установке иконки на системную панель MOV NOTI.cbSize,88 MOV EAX,DWORD PTR [EBP+8H] MOV NOTI.hWnd,EAX MOV NOTI.uFlags,FLAG MOV NOTI.uID,12 ; идентификатор иконки MOV NOTI.uCallbackMessage,2000 ; сообщение ; загрузить иконку из ресурсов PUSH 1 PUSH [HINST] CALL LoadIconA@8 ; скопировать текст всплывающего сообщения MOV NOTI.hIcon,EAX PUSH OFFSET TIP PUSH OFFSET NOTI.szTip CALL lstrcpy@8 ; поместить иконку PUSH OFFSET NOTI PUSH NIM_ADD CALL Shell_NotifyIconA@8 ; спрятать минимизированное окно PUSH SW_HIDE PUSH DWORD PTR [EBP+08H] CALL ShowWindow@8 JMP FINISH L3: ; сообщение от иконки на системной панели? CMP DWORD PTR [EBP+0CH],2000 JNE FINISH ; идентификатор нашей иконки? CMP WORD PTR [EBP+10H],12 JNE FINISH ; что произошло? двойной щелчок? CMP WORD PTR [EBP+14H],WM_LBUTTONDBLCLK JNE FINISH ; заполнить структуру MOV NOTI.cbSize,88 MOV EAX,DWORD PTR [EBP+8H] MOV NOTI.hWnd,EAX MOV NOTI.uFlags,NIF_ICON MOV NOTI.uID,12 ; идентификатор иконки MOV NOTI.uCallbackMessage,1000 ; сообщение ; удалить иконку PUSH OFFSET NOTI PUSH NIM_DELETE CALL Shell_NotifyIconA@8 ; восстановить окно PUSH SW_SHOWNORMAL PUSH DWORD PTR [EBP+08H] CALL ShowWindow@8 FINISH: MOV EAX,0 POP EDI POP ESI POP EBX POP EBP RET 16 WNDPROC ENDP _TEXT ENDS END START


В Может ли приложение узнать какие программы в настоящее время запущены?



В. Может ли приложение узнать, какие программы в настоящее время запущены?

Да. В основу метода положено использование функции EnumWindows (т.е. пересчитать окна). Вот параметры этой функции.

1-й параметр. Адрес процедуры, которая будет вызываться автоматически, если будет найдено окно. 2-й параметр. Произвольное значение, которое будет передаваться в процедуру.

Сама вызываемая процедура получает два параметра: дескриптор найденного окна и определенный выше параметр. По известному дескриптору с помощью функции GetWindowThreadProcessId можно определить уникальный идентификатор процесса или потока, который владеет данным окном. Имея же идентификатор, мы можем, в частности, удалить данный процесс из памяти с помощью функции TerminateProcess (впрочем, будьте осторожнее).

Ниже на Рисунок 3.5.4 представлен пример программы, выдающей список работающих процессов. Обратите внимание на использование функции GetWindowText, которая определяет текст заголовка окна. Не пугайтесь, что в списке будут представлены и те процессы, окна которых мы не видим. Окна, как известно, могут быть скрытыми. Заметьте, что в список попадают и консольные приложения. Программа определяет также уникальные идентификаторы процессов.

// файл proc.rc // определение констант

#define WS_SYSMENU 0x00080000L #define WS_MINIMIZEBOX 0x00020000L #define WS_VISIBLE 0x10000000L #define WS_TABSTOP 0x00010000L #define WS_VSCROLL 0x00200000L #define WS_HSCROLL 0x00100000L #define DS_3DLOOK 0x0004L #define LBS_NOTIFY 0x000lL #define LBS_SORT 0x0002L

// идентификаторы #define LIST1 101

// определение диалогового окна DIAL1 DIALOG 0, 0, 220, 110 STYLE WS_SYSMENU | WS_MINIMIZEBOX | DS_3DLOOK CAPTION "Поиск процессов" FONT 8, "Arial" { CONTROL "ListBox1", LIST1, "listbox", WS_VISIBLE | WS_TABSTOP | WS_VSCROLL | WS_HSCROLL | LBS_NOTIFY, 16, 16, 190, 75 }

; файл proc.inc ; константы ; значения, возвращаемые функцией GetDriveType ; значения 0 и 1 можно считать признаком отсутствия устройства




DRIVE_REMOVABLE equ 2 ; накопитель на гибком диске DRIVE_FIXED equ 3 ; устройство жесткого диска DRIVE_REMOTE equ 4 ; сетевой диск DRIVE_CDROM equ 5 ; накопитель на лазерном диске DRIVE_RAMDISK equ 6 ; электронный диск

; сообщение приходит при закрытии окна WM_CLOSE equ 10h WM_INITDIALOG equ 110h WM_COMMAND equ 111h LB_ADDSTRING equ 180h LB_RESETCONTENT equ 184h WM_LBUTTONDOWN equ 201h

; прототипы внешних процедур IFDEF MASM EXTERN wsprintfA:NEAR EXTERN GetWindowThreadProcessId@8:NEAR EXTERN GetWindowTextA@12:NEAR EXTERN EnumWindows@8:NEAR EXTERN lstrcat@8:NEAR EXTERN ExitProcess@4:NEAR EXTERN GetModuleHandleA@4:NEAR EXTERN DialogBoxParamA@20:NEAR EXTERN EndDialog@8:NEAR EXTERN SendDlgItemMessageA@20:NEAR ELSE EXTERN _wsprintfA:NEAR EXTERN GetWindowThreadProcessId:NEAR EXTERN GetWindowTextA@12:NEAR EXTERN EnumWindows:NEAR EXTERN lstrcat:NEAR EXTERN ExitProcess:NEAR EXTERN GetModuleHandleA:NEAR EXTERN DialogBoxParamA:NEAR EXTERN EndDialog:NEAR EXTERN SendDlgItemMessageA:NEAR

wsprintfA = _wsprintfA GetWindowThreadProcessId@8 = GetWindowThreadProcessId GetWindowTextA@12 = GetWindowTextA EnumWindows@8 = EnumWindows lstrcat@8 = lstrcat ExitProcess@4 = ExitProcess GetModuleHandleA@4 = GetModuleHandleA DialogBoxParamA@20 = DialogBoxParamA EndDialog@8 = EndDialog SendDlgItemMessageA@20 = SendDlgItemMessageA ENDIF

; структуры ; структура сообщения MSGSTRUCT STRUC MSHWND DD ? MSMESSAGE DD ? MSWPARAM DD ? MSLPARAM DD ? MSTIME DD ? MSPT DD ? MSGSTRUCT ENDS

; файл proc.asm .386P ; плоская модель .MODEL FLAT, stdcall include proc.inc ; директивы компоновщику для подключения библиотек IFDEF MASM includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib ELSE includelib c:\tasm32\lib\import32.lib ENDIF ;------------------------------------------------- ; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' MSG MSGSTRUCT <?> HINST DD 0 ; дескриптор приложения PA DB "DIAL1",0 BUFER DB 200 DUP (0) BUF DB 20 DUP (0) FORM DB ";%lu",0 IDP DD ? HWN DD ? _DATA ENDS



; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; получить дескриптор приложения PUSH 0 CALL GetModuleHandleA@4 MOV [HINST], EAX ;--------------------------------------- PUSH 0 PUSH OFFSET WNDPROC PUSH 0 PUSH OFFSET PA PUSH [HINST] CALL DialogBoxParamA@20 CMP EAX,-1 JNE KOL ; сообщение об ошибке KOL: ;--------------------------------------- PUSH 0 CALL ExitProcess@4 ;---------------------------------------

; процедура окна ; расположение параметров в стеке ; [EBP+014Н] ;LPARAM ; [EBP+10Н] ;WAPARAM ; [EBP+0CH] ;MES ; [EBP+8] ;HWND WNDPROC PROC PUSH EBP MOV EBP,ESP PUSH EBX PUSH ESI PUSH EDI ;------------------------- CMP DWORD PTR [EBP+0CH],WM_CLOSE JNE L1 PUSH 0 PUSH DWORD PTR [EBP+08H] CALL EndDialog@8 JMP FINISH L1: CMP DWORD PTR [EBP+0CH],WM_INITDIALOG JNE FINISH ; запомним дескриптор окна MOV EAX,DWORD PTR [EBP+08H] MOV HWN,EAX ; вызвать функцию EnumWindows PUSH 1 ; неиспользуемый параметр PUSH OFFSET PENUM CALL EnumWindows@8 FINISH: MOV EAX,0 POP EDI POP ESI POP EBX POP EBP RET 16 WNDPROC ENDP

; процедура обратного вызова, вызываемая при поиске окон ; [EBP+0CH] ; параметр ; [EBP+8] ; дескриптор окна PENUM PROC PUSH EBP MOV EBP,ESP ; получить заголовок окна PUSH 200 PUSH OFFSET BUFER PUSH DWORD PTR [EBP+8] CALL GetWindowTextA@12 ; получить идентификатор процесса или потока, ; владеющего окном PUSH OFFSET IDP PUSH DWORD PTR [EBP+8] CALL GetWindowThreadProcessId@8 ; сформировать строку для списка PUSH OFFSET IDP PUSH OFFSET FORM PUSH OFFSET BUF CALL wsprintfA ADD ESP,12 PUSH OFFSET BUF PUSH OFFSET BUFER CALL lstrcat@8 ; добавить в список PUSH OFFSET BUFER PUSH 0 PUSH LB_ADDSTRING PUSH 101 PUSH [HWN] CALL SendDlgItemMessageA@20 POP EBP MOV EAX,1 RET 8 PENUM ENDP _TEXT ENDS END START


В Можно ли контролировать ввод информации в окне редактирования?



В.Можно ли контролировать ввод информации в окне редактирования?

Да, в главе 2.4 мы видели, как можно это корректно делать при помощи горячих клавиш. Однако с помощью горячих клавиш легко блокировать приход в окно редактирования некоторых символов. Речь же в некоторых случаях может идти о том, чтобы преобразовывать вводимую информацию, что называется, "налету". Таким механизмом может быть использование подклассов. Для демонстрации его мы возьмем программу на Рисунок 1.3.2, видоизменим ее так, чтобы можно было контролировать ввод информации.

На самом деле в этом механизме нет ничего нового. Еще в операционной системе MS DOS можно было перехватывать прерывание и встраивать туда свою процедуру. В результате при вызове данного прерывания вызывалась вначале Ваша процедура, а потом уже та, которая была установлена ранее. Впрочем, можно было поступать и по-другому: вначале вызывается процедура, существовавшая ранее, а потом, в последнюю очередь, вызывается Ваша процедура (см. [1]). Поскольку перехватывать прерывание можно было многократно, в результате могла образоваться целая цепочка процедур, выполняющихся одна за другой. Мне уже приходилось сравнивать вызов процедур окна с вызовом прерываний. Это очень похоже, не правда ли? С точки зрения объектного программирования это является ни чем иным как созданием класса-родителя.

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

1-й параметр. Дескриптор окна, которое было создано текущим процессом. 2-й параметр. Величина, определяющая, какой атрибут следует изменить. Вообще говоря, это всего лишь смещение в некоторой области памяти. Нас будет интересовать только величина, определяющая адрес процедуры окна. Эта величина определяется константой DWL_DLGPROC = 4 для диалогового окна и GWL_WNDPROC = -4 для обычного окна. Отсюда, кстати, следует, что данное действо можно производить не только над простыми, но и над диалоговыми окнами. 3-й параметр. Новое значение атрибута окна.




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

Вопрос следующий: как вызвать старую процедуру окна. Обычная команда CALL не подходит. Нужно использовать специальную функцию CallWindowProc. Параметры этой функции следующие:

1-й параметр. Адрес вызываемой процедуры. 2-й параметр. Дескриптор окна. 3-й параметр. Код сообщения. 4-й параметр. Параметр WPARAM. 5-й параметр. Параметр LPARAM.

; файл edit.inc ; константы WM_CHAR equ 102h WM_SETFOCUS equ 7h ; сообщение приходит при закрытии окна WM_DESTROY equ 2 ; сообщение приходит при создании окна WM_CREATE equ 1 ; сообщение, если что-то происходит с элементами на окне WM_COMMAND equ 111h ; сообщение, позволяющее получить строку WM_GETTEXT equ 0Dh ; константа для функции SetWindowLong GWL_WNDPROC equ -4 ; свойства окна CS_VREDRAW equ 1h CS_HREDRAW equ 2h CS_GLOBALCLASS equ 4000h WS_TABSTOP equ 10000h WS_SYSMENU equ 80000h WS_OVERLAPPEDWINDOW equ 0 + WS_TABSTOP + WS_SYSMENU STYLE equ CS_HREDRAW + CS_VREDRAW + CS_GLOBALCLASS

;CS_HREDRAW equ 2h BS_DEFPUSHBUTTON equ 1h WS_VISIBLE equ 10000000h WS_CHILD equ 40000000h WS_BORDER equ 800000h STYLBTN equ WS_CHILD + BS_DEFPUSHBUTTON + WS_VISIBLE + WS_TABSTOP STYLEDT equ WS_CHILD + WS_VISIBLE + WS_BORDER + WS_TABSTOP ; идентификатор стандартной иконки IDI_APPLICATION equ 32512 ; идентификатор курсора IDC_ARROW equ 32512 ; режим показа окна - нормальный SW_SHOWNORMAL equ 1 ; прототипы внешних процедур IFDEF MASM EXTERN CallWindowProcA@20:NEAR EXTERN SetWindowLongA@12:NEAR EXTERN SetFocus@4:NEAR EXTERN SendMessageA@16:NEAR EXTERN MessageBoxA@16:NEAR EXTERN CreateWindowExA@48:NEAR EXTERN DefWindowProcA@16:NEAR EXTERN DispatchMessageA@4:NEAR EXTERN ExitProcess@4:NEAR EXTERN GetMessageA@16:NEAR EXTERN GetModuleHandleA@4:NEAR EXTERN LoadCursorA@8:NEAR EXTERN LoadIconA@8:NEAR EXTERN PostQuitMessage@4:NEAR EXTERN RegisterClassA@4:NEAR EXTERN ShowWindow@8:NEAR EXTERN TranslateMessage@4:NEAR EXTERN UpdateWindow@4:NEAR ELSE EXTERN CallWindowProcA:NEAR EXTERN SetWindowLongA:NEAR EXTERN SetFocus:NEAR EXTERN SendMessageA:NEAR EXTERN MessageBoxA:NEAR EXTERN CreateWindowExA:NEAR EXTERN DefWindowProcA:NEAR EXTERN DispatchMessageA:NEAR EXTERN ExitProcess:NEAR EXTERN GetMessageA:NEAR EXTERN GetModuleHandleA:NEAR EXTERN LoadCursorA:NEAR EXTERN LoadIconA:NEAR EXTERN PostQuitMessage:NEAR EXTERN RegisterClassA:NEAR EXTERN ShowWindow:NEAR EXTERN TranslateMessage:NEAR EXTERN UpdateWindow:NEAR CallWindowProcA@20 = CallWindowProcA SetWindowLongA@12 = SetWindowLongA SetFocus@4 = SetFocus SendMessageA@16 = SendMessageA MessageBoxA@16 = MessageBoxA CreateWindowExA@48 = CreateWindowExA DefWindowProcA@16 = DefWindowProcA DispatchMessageA@4 = DispatchMessageA ExitProcess@4 = ExitProcess GetMessageA@16 = GetMessageA GetModuleHandleA@4 = GetModuleHandleA LoadCursorA@8 = LoadCursorA LoadIconA@8 = LoadIconA PostQuitMessage@4 = PostQuitMessage RegisterClassA@4 = RegisterClassA ShowWindow@8 = ShowWindow TranslateMessage@4 = TranslateMessage UpdateWindow@4 = UpdateWindow ENDIF



; структуры ; структура сообщения MSGSTRUCT STRUC MSHWND DD ? MSMESSAGE DD ? MSWPARAM DD ? MSLPARAM DD ? MSTIME DD ? MSPT DD ? MSGSTRUCT ENDS

;----структура класса окон WNDCLASS STRUC CLSSTYLE DD ? CLWNDPROC DD ? CLSCBCLSEX DD ? CLSCBWNDEX DD ? CLSHINST DD ? CLSHICON DD ? CLSHCURSOR DD ? CLBKGROUND DD ? CLMENNAME DD ? CLNAME DD ? WNDCLASS ENDS

; файл edit.asm .386P ; плоская модель .MODEL FLAT, stdcall include edit.inc ; директивы компоновщику для подключения библиотек IFDEF MASM includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib ELSE includelib c:\tasm32\lib\import32.lib ENDIF

;------------------------------------------------- ; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' NEWHWND DD 0 MSG MSGSTRUCT <?> WC WNDCLASS <?> HINST DD 0 ; дескриптор приложения TITLENAME DB 'Контроль окна редактирования',0 CLASSNAME DB 'CLASS32',0 CPBUT DB 'Выход',0 ; выход CPEDT DB ' ',0 CLSBUTN DB 'BUTTON',0 CLSEDIT DB 'EDIT',0 HWNDBTN DWORD 0 HWNDEDT DWORD 0 CAP BYTE 'Сообщение',0 MES BYTE 'Конец работы программы',0 TEXT DB 100 DUP (0) OLDWND DD 0 CHAR DD ? _DATA ENDS

; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; получить дескриптор приложения PUSH 0 CALL GetModuleHandleA@4 MOV [HINST], EAX REG_CLASS: ; заполнить структуру окна ; стиль MOV [WC.CLSSTYLE],STYLE ; процедура обработки сообщений MOV [WC.CLWNDPROC], OFFSET WNDPROC MOV [WC.CLSCBCLSEX],0 MOV [WC.CLSCBWNDEX],0 MOV EAX, [HINST] MOV [WC.CLSHINST], EAX ;----------иконка окна PUSH IDI_APPLICATION PUSH 0 CALL LoadIconA@8 MOV [WC.CLSHICON], EAX ;----------курсор окна PUSH IDC_ARROW PUSH 0 CALL LoadCursorA@8 MOV [WC.CLSHCURSOR], EAX ;—————————— MOV [WC.CLBKGROUND], 17 ; цвет окна MOV DWORD PTR [WC.CLMENNAME],0 MOV DWORD PTR [WC.CLNAME],OFFSET CLASSNAME PUSH OFFSET WC CALL RegisterClassA@4 ; создать окно зарегистрированного класса PUSH 0 PUSH [HINST] PUSH 0 PUSH 0 PUSH 150 ; DY - высота окна PUSH 400 ; DX - ширина окна PUSH 100 ; Y - координата левого верхнего угла PUSH 100 ; X - координата левого верхнего угла PUSH WS_OVERLAPPEDWINDOW PUSH OFFSET TITLENAME ; имя окна PUSH OFFSET CLASSNAME ; имя класса PUSH 0 CALL CreateWindowExA@48 ; проверка на ошибку CMP EAX,0 JZ _ERR MOV [NEWHWND], EAX ; дескриптор окна ;----------------------------------------- PUSH SW_SHOWNORMAL PUSH [NEWHWND] CALL ShowWindow@8 ; показать созданное окно ;----------------------------------------- PUSH [NEWHWND] CALL UpdateWindow@4 ; команда перерисовать видимую ; часть окна, сообщение WM_PAINT ; петля обработки сообщений MSG_LOOP: PUSH 0 PUSH 0 PUSH 0 PUSH OFFSET MSG CALL GetMessageA@16 CMP AX, 0 JE END_LOOP PUSH OFFSET MSG CALL TranslateMessage@4 PUSH OFFSET MSG CALL DispatchMessageA@4 JMP MSG_LOOP END_LOOP: ; выход из программы (закрыть процесс) PUSH [MSG.MSWPARAM] CALL ExitProcess@4 _ERR: JMP END_LOOP ;----------------------------------------- ; процедура окна ; расположение параметров в стеке ; [EBP+014Н] ; LPARAM ; [EBP+10H] ; WAPARAM ; [EBP+0CH] ; MES ; [EBP+8] ; HWND WNDPROC PROC PUSH EBP MOV EBP,ESP PUSH EBX PUSH ESI PUSH EDI CMP DWORD PTR [EBP+0CH],WM_DESTROY JE WMDESTROY CMP DWORD PTR [EBP+0CH],WM_CREATE JE WMCREATE CMP DWORD PTR [EBP+0CH],WM_COMMAND JE WMCOMMND JMP DEFWNDPROC WMCOMMND: MOV EAX,HWNDBTN CMP DWORD PTR [EBP+14H],EAX JNE NODESTROY JMP WMDESTROY NODESTROY: MOV EAX, 0 JMP FINISH WMCREATE: ; создать окно-кнопку PUSH 0 PUSH [HINST] PUSH 0 PUSH DWORD PTR [EBP+08H] PUSH 20 ; DY PUSH 60 ; DX PUSH 10 ; Y PUSH 10 ; X PUSH STYLBTN PUSH OFFSET CPBUT ; имя окна PUSH OFFSET CLSBUTN ; имя класса PUSH 0 CALL CreateWindowExA@48 MOV HWNDBTN,EAX ; запомнить дескриптор кнопки ; создать окно редактирования PUSH 0 PUSH [HINST] PUSH 0 PUSH DWORD PTR [EBP+08H] PUSH 20 ; DY PUSH 350; DX PUSH 50 ; Y PUSH 10 ; X PUSH STYLEDT PUSH OFFSET CPEDT ; имя окна PUSH OFFSET CLSEDIT ; имя класса PUSH 0 CALL CreateWindowExA@48 MOV HWNDEDT,EAX ; установить фокус на окне редактирования PUSH HWNDEDT CALL SetFocus@4 ; установить свою собственную процедуру обработки PUSH OFFSET WNDEDIT PUSH GWL_WNDPROC PUSH [HWNDEDT] CALL SetWindowLongA@12 MOV OLDWND,EAX MOV EAX, 0 JMP FINISH DEFWNDPROC: PUSH DWORD PTR [EBP+14H] PUSH DWORD PTR [EBP+10H] PUSH DWORD PTR [EBP+0CH] PUSH DWORD PTR [EBP+08H] CALL DefWindowProcA@16 JMP FINISH WMDESTROY: ; получить отредактированную строку PUSH OFFSET TEXT PUSH 150 PUSH WM_GETTEXT PUSH HWNDEDT CALL SendMessageA@16 ; показать эту строку PUSH 0 PUSH OFFSET CAP PUSH OFFSET TEXT PUSH DWORD PTR [EBP+08H] ; дескриптор окна CALL MessageBoxA@16 ; на выход PUSH 0 CALL PostQuitMessage@4 ; сообщение WM_QUIT MOV EAX, 0 FINISH: POP EDI POP ESI POP EBX POP EBP RET 16 WNDPROC ENDP ;----------------------------------------------- ; новая процедура обработки сообщений окну редактирования WNDEDIT PROC PUSH EBP MOV EBP,ESP MOV EAX,DWORD PTR [EBP+10H] MOV CHAR,EAX CMP DWORD PTR [EBP+0CH],WM_CHAR JNE _OLD ; проверка вводимого символа CMP AL,13 JNE _OLD ; послать сообщение о закрытии основного окна PUSH 0 PUSH 0 PUSH WM_DESTROY PUSH [NEWHWND] CALL SendMessageA@16 _OLD: ; вызвать старую процедуру PUSH DWORD PTR [EBP+014H] PUSH DWORD PTR [EBP+10H] PUSH DWORD PTR [EBP+0CH] PUSH DWORD PTR [EBP+8H] PUSH [OLDWND] CALL CallWindowProcA@20 FIN: POP EBP RET 16 WNDEDIT ENDP _TEXT ENDS END START


В Можно ли не допустить многократный запуск одного и того же приложения?



В. Можно ли не допустить многократный запуск одного и того же приложения?

Да. Наиболее часто употребляемым для этого средством является создание объекта Mutex. Этот объект как раз и предназначен для того, чтобы координировать разные процессы. Создается данный объект при помощи функции CreateMutex. Рассмотрим параметры этой функции.

1-й параметр. Указатель на структуру, определяющую атрибут доступа. Обычно NULL (0). 2-й параметр. Флаг. В случае ненулевого значения процесс требует немедленного владения объектом (!). 3-й параметр. Указатель на имя объекта.

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

К тому же результату можно прийти, используя семафор или файл, отображаемый в память. В данном случае все достаточно тривиально.

Еще один подход основан на разделяемой памяти. Определим область разделяемой памяти и там - переменную. При запуске приложение проверяет значение переменной, и если она равна нулю, то засылает туда единицу. Если переменная уже равна единице, то - выход (или действия, предусмотренные в этом случае).

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



В прикладном программировании часто возникает вопрос определения сетевых устройств



I

В прикладном программировании часто возникает вопрос определения сетевых устройств. В принципе, вопрос можно поставить более широко: как определить тип того или иного устройства. Тот, кто программировал в MS DOS, помнит, что там правильное определение типа устройства было не простой задачей. Операционная система Windows и здесь облегчает нам задачу. Здесь имеется очень полезная функция GetDriveType, единственным аргументом которой является строка корневого каталога искомого устройства, например "А:\" или "D:\". По возвращаемому функцией значению мы и определяем тип устройства (см. файл driv.inc на Рисунок 3.4.1). Результат работы программы представлен на Рисунок 3.4.2.

// файл driv.rc // определение констант

#define WS_SYSMENU 0x00080000L #define WS_MINIMIZEBOX 0x00020000L #define WS_MAXIMIZEBOX 0x00010000L #define WS_VISIBLE 0x10000000L #define WS_TABSTOP 0x00010000L #define WS_VSCROLL 0x00200000L #define DS_3DLOOK 0x0004L #define LBS_NOTIFY 0x0001L #define LBS_SORT 0x0002L #define LBS_WANTKEYBOARDINPUT 0x0400L

// идентификаторы #define LIST1 101

//определение диалогового окна DIAL1 DIALOG 0, 0, 180, 110 STYLE WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | DS_3DLOOK CAPTION "Определение типов устройств" FONT 8, "Arial" { CONTROL "ListBox1",LIST1,"listbox", WS_VISIBLE | WS_TABSTOP | WS_VSCROLL | LBS_NOTIFY | LBS_WANTKEYBOARDINPUT, 16, 16, 100, 75 }

; файл driv.inc ; константы ; значения, возвращаемые функцией GetDriveType ; значения 0 и 1 можно считать признаком отсутствия устройства

DRIVE_REMOVABLE equ 2 ; накопитель на гибком диске DRIVE_FIXED equ 3 ; устройство жесткого диска DRIVE_REMOTE equ 4 ; сетевой диск DRIVE_CDROM equ 5 ; накопитель на лазерном диске DRIVE_RAMDISK equ 6 ; электронный диск

; сообщение приходит при закрытии окна WM_CLOSE equ 10h WM_INITDIALOG equ 110h WM_COMMAND equ 111h LB_ADDSTRING equ 180h LB_RESETCONTENT equ 184h WM_LBUTTONDOWN equ 201h

; прототипы внешних процедур IFDEF MASM EXTERN lstrcpy@8:NEAR EXTERN lstrcat@8:NEAR EXTERN GetDriveTypeA@4:NEAR EXTERN ExitProcess@4:NEAR EXTERN GetModuleHandleA@4:NEAR EXTERN DialogBoxParamA@20:NEAR EXTERN EndDialog@8:NEAR EXTERN SendDlgItemMessageA@20:NEAR ELSE EXTERN lstrcpy:NEAR EXTERN lstrcat:NEAR EXTERN GetDriveTypeA:NEAR EXTERN ExitProcess:NEAR EXTERN GetModuleHandleA:NEAR EXTERN DialogBoxParamA:NEAR EXTERN EndDialog:NEAR EXTERN SendDlgItemMessageA:NEAR lstrcpy@8 = lstrcpy lstrcat@8 = lstrcat GetDriveTypeA@4 = GetDriveTypeA ExitProcess@4 = ExitProcess GetModuleHandleA@4 = GetModuleHandleA DialogBoxParamA@20 = DialogBoxParamA EndDialog@8 = EndDialog SendDlgItemMessageA@20 = SendDlgItemMessageA ENDIF




; структуры ; структура сообщения MSGSTRUCT STRUC MSHWND DD ? MSMESSAGE DD ? MSWPARAM DD ? MSLPARAM DD ? MSTIME DD ? MSPT DD ? MSGSTRUCT ENDS

; файл driv.asm .386P ; плоская модель .MODEL FLAT, stdcall include driv.inc ; директивы компоновщику для подключения библиотек IFDEF MASM includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib ELSE includelib c:\tasm32\lib\import32.lib ENDIF ;-------------------------------------------------

; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' PRIZ DB 0 MSG MSGSTRUCT <?> HINST DD 0 ; дескриптор приложения PA DB "DIAL1",0 ROO DB "?:\", 0 BUFER DB 40 DUP (0) TYP0 DB " Нет устройства",0 TYP1 DB " Нет устройства",0 TYP2 DB " Гибкий диск",0 TYP3 DB " Жесткий диск",0 TYP4 DB " Сетевой диск",0 TYP5 DB " Лазерный диск",0 TYP6 DB " Электронный диск",0 INDEX DD OFFSET TYP0 DD OFFSET TYP1 DD OFFSET TYP2 DD OFFSET TYP3 DD OFFSET TYP4 DD OFFSET TYP5 DD OFFSET TYP6 _DATA ENDS

; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; получить дескриптор приложения PUSH 0 CALL GetModuleHandleA@4 MOV [HINST], EAX ;-------------------------------------- PUSH 0 PUSH OFFSET WNDPROC PUSH 0 PUSH OFFSET PA PUSH [HINST] CALL DialogBoxParamA@20 CMP EAX,-1 JNE KOL ; сообщение об ошибке KOL: ;-------------------------------------- PUSH 0 CALL ExitProcess@4 ;-------------------------------------- ; процедура окна ; расположение параметров в стеке ; [BP+014Н] ; LPARAM ; [BP+10Н] ; WAPARAM ; [BP+0CН] ; MES ; [BP+8] ; HWND WNDPROC PROC PUSH EBP MOV EBP,ESP PUSH EBX PUSH ESI PUSH EDI ;-------------------------------------- CMP DWORD PTR [EBP+0CH],WM_CLOSE JNE L1 PUSH 0 PUSH DWORD PTR [EBP+08H] CALL EndDialog@8 JMP FINISH L1: CMP DWORD PTR [EBP+0CH],WM_INITDIALOG JNE L2 L4: ; здесь анализ устройств и заполнение списка MOV ECX,65 L00: PUSH ECX MOV ROO, CL ; определить тип устройства PUSH OFFSET ROO CALL GetDriveTypeA@4 ; полный список CMP PRIZ,0 JZ _ALL CMP EAX,2 JB L3 _ALL: ; получить индекс SHL EAX,2 PUSH EAX ; создать строку для списка PUSH OFFSET ROO PUSH OFFSET BUFER CALL lstrcpy@8 POP EBX PUSH INDEX[EBX] PUSH OFFSET BUFER CALL lstrcat@8 ; отправить строку в список PUSH OFFSET BUFER PUSH 0 PUSH LB_ADDSTRING PUSH 101 PUSH DWORD PTR [EBP+08H] CALL SendDlgItemMessageA@20 L3: ; проверить, не достигнута ли граница цикла POP ECX INC ECX CMP ECX,91 JNE L00 JMP FINISH L2: CMP DWORD PTR [EBP+0CH],WM_LBUTTONDOWN JNE FINISH PUSH 0 PUSH 0 PUSH LB_RESETCONTENT PUSH 101 PUSH DWORD PTR [EBP+08H] CALL SendDlgItemMessageA@20 CMP PRIZ,0 JE YES_0 MOV PRIZ,0 JMP L4 YES_0: MOV PRIZ,1 JMP L4 FINISH: MOV EAX,0 POP EDI POP ESI POP EBX POP EBP RET 16 WNDPROC ENDP _TEXT ENDS END START


В Возможны ли какиелибо способы взаимодействия и обмен информацией между запущенными приложениями?



В. Возможны ли какие-либо способы взаимодействия и обмен информацией между запущенными приложениями?

Мы уже говорили о различных способах синхронизации, о разделяемой памяти. Есть еще один интересный подход, реализованный в Windows, - это анонимные каналы (pipes)46. Этот подход наиболее эффективен для обмена информацией с консольным процессом, порождаемым данным процессом. Представьте себе, что Вам необходимо, чтобы запускаемый Вами из приложения консольный процесс (например, какой-нибудь строковый компилятор) выводил информацию не в консоль, а в окно редактирования основного процесса. Пример такого приложения представлен на Рисунок 3.5.3.

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

// файл pipe.rc // определение констант #define WS_SYSMENU 0x00080000L #define WS_VISIBLE 0x10000000L #define WS_TABSTOP 0x00010000L #define DS_3DLOOK 0x0004L #define ES_LEFT 0x0000L #define WS_CHILD 0x40000000L #define WS_BORDER 0x00800000L #define ES_MULTILINE 0x0004L #define WS_VSCROLL 0x00200000L #define WS_HSCROLL 0x00100000L

MENUP MENU { POPUP "&Запуск программы" { MENUITEM "&Запустить", 200 MENUITEM "Выход из &программы", 300 } }

// определение диалогового окна DIAL1 DIALOG 0, 0, 200, 140 STYLE WS_SYSMENU | DS_3DLOOK CAPTION "Пример использования PIPE" FONT 8, "Arial" { CONTROL "", 101, "edit", ES_LEFT | ES_MULTILINE | WS_VISIBLE | WS_BORDER | WS_VSCROLL | WS_HSCROLL, 24, 20, 128, 70 }

; файл pipe.inc ; константы

SW_HIDE equ 0 SW_SHOWNORMAL equ 1 STARTF_USESHOWWINDOW equ 1h STARTF_USESTDHANDLES equ 100h STARTF_ADD = STARTF_USESHOWWINDOW + STARTF_USESTDHANDLES

; сообщение приходит при закрытии окна WM_CLOSE equ 10h WM_INITDIALOG equ 110h WM_COMMAND equ 111h EM_REPLACESEL equ 0C2h




; прототипы внешних процедур IFDEF MASM EXTERN ReadFile@20:NEAR EXTERN CloseHandle@4:NEAR EXTERN CreatePipe@16:NEAR EXTERN SetMenu@8:NEAR EXTERN LoadMenuA@8:NEAR EXTERN CreateProcessA@40:NEAR EXTERN ExitProcess@4:NEAR EXTERN GetModuleHandleA@4:NEAR EXTERN DialogBoxParamA@20:NEAR EXTERN EndDialog@8:NEAR EXTERN SendDlgItemMessageA@20:NEAR ELSE EXTERN ReadFile:NEAR EXTERN CloseHandle:NEAR EXTERN CreatePipe:NEAR EXTERN TerminateProcess:NEAR EXTERN WaitForSingieObject:NEAR EXTERN SetMenu:NEAR EXTERN LoadMenuA:NEAR EXTERN CreateProcessA:NEAR EXTERN ExitProcess:NEAR EXTERN GetModuleHandleA:NEAR EXTERN DialogBoxParamA:NEAR EXTERN EndDialog:NEAR EXTERN SendDlgItemMessageA:NEAR

ReadFile@20 = ReadFile CloseHandle@4 = CloseHandle CreatePipe@16 = CreatePipe TerminateProcess@8 = TerminateProcess WaitForSingleObject@8 = WaitForSingleObject SetMenu@8 = SetMenu LoadMenuA@8 = LoadMenuA CreateProcessA@40 = CreateProcessA ExitProcess@4 = ExitProcess GetModuleHandleA@4 = GetModuleHandleA DialogBoxParamA@20 = DialogBoxParamA EndDialog@8 = EndDialog SendDlgItemMessageA@20 = SendDlgItemMessageA ENDIF

; структуры ; структура сообщения MSGSTRUCT STRUC MSHWND DD ? MSMESSAGE DD ? MSWPARAM DD ? MSLPARAM DD ? MSTIME DD ? MSPT DD ? MSGSTRUCT ENDS

; структура для CreateProcess STARTUP STRUC cb DD 0 lpReserved DD 0 lpDesktop DD 0 lpTitle DD 0 dwX DD 0 dwY DD 0 dwXSize DD 0 dwYSize DD 0 dwXCountChars DD 0 dwYCountChars DD 0 dwFillAttribute DD 0 dwFlags DD 0 wShowWindow DW 0 cbReserved2 DW 0 lpReserved2 DD 0 hStdInput DD 0 hStdOutput DD 0 hStdError DD 0 STARTUP ENDS

; структура - информация о процессе PROCINF STRUC hProcess DD ? hThread DD ? Idproc DD ? idThr DD ? PROCINF ENDS

; файл pipe.asm .386P ; плоская модель .MODEL FLAT, stdcall include pipe.inc ; директивы компоновщику для подключения библиотек IFDEF MASM includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib ELSE includelib c:\tasm32\lib\import32.lib ENDIF ;---------------------------------------- ; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' STRUP STARTUP <?> INF PROCINF <?> MSG MSGSTRUCT <?> HINST DD 0 ; дескриптор приложения PA DB "DIAL1",0 CMD DB "c:\tasm32\bin\tlink32.exe",0 PMENU DB "MENUP",0 HW DD ? HR DD ? BUFER DB 3000 DUP (0) BYT DD ? _DATA ENDS



; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; получить дескриптор приложения PUSH 0 CALL GetModuleHandleA@4 MOV [HINST], EAX ;------------------------------------------- PUSH 0 PUSH OFFSET WNDPROC PUSH 0 PUSH OFFSET PA PUSH [HINST] CALL DialogBoxParamA@20 CMP EAX,-1 JNE KOL ; сообщение об ошибке KOL: ;--------------------------------- PUSH 0 CALL ExitProcess@4 ;--------------------------------- ; процедура окна ; расположение параметров в стеке ; [EBP+014Н] ; LPARAM ; [EBP+10Н] ; WAPARAM ; [EBP+0CH] ; MES ; [EBP+8] ; HWND WNDPROC PROC PUSH EBP MOV EBP,ESP PUSH EBX PUSH ESI PUSH EDI ;------------------------ CMP DWORD PTR [EBP+0CH],WM_CLOSE JNE L1 L3: PUSH 0 PUSH DWORD PTR [EBP+08H] CALL EndDialog@8 JMP FINISH L1: CMP DWORD PTR [EBP+0CH],WM_INITDIALOG JNE L2 ; загрузить меню PUSH OFFSET PMENU PUSH [HINST] CALL LoadMenuA@8 ; установить меню PUSH EAX PUSH DWORD PTR [EBP+08H] CALL SetMenu@8 JMP FINISH L2: CMP DWORD PTR [EBP+0CH],WM_COMMAND JNE FINISH CMP WORD PTR [EBP+10H],300 JE L3 CMP WORD PTR [EBP+10H],200 JNE FINISH ; здесь запуск ; в начале PIPE PUSH 0 PUSH 0 PUSH OFFSET HW PUSH OFFSET HR CALL CreatePipe@16 MOV EAX,HW ; здесь запуск консольного приложения MOV STRUP.cb,68 MOV STRUP.lpReserved,0 MOV STRUP.lpDesktop,0 MOV STRUP.lpTitle,0 MOV STRUP.dwFlags,STARTF_ADD MOV STRUP.cbReserved2,0 MOV STRUP.lpReserved2,0 MOV STRUP.wShowWindow,SW_HIDE ; окно процесса невидимо MOV STRUP.hStdOutput,EAX MOV STRUP.hStdError,EAX ;---------------------------------------------------------- PUSH OFFSET INF PUSH OFFSET STRUP PUSH 0 PUSH 0 PUSH 0 PUSH 1 ; наследует дескрипторы PUSH 0 PUSH 0 PUSH OFFSET CMD PUSH 0 CALL CreateProcessA@40 ; здесь чтение информации PUSH 0 PUSH OFFSET BYT PUSH 3000 PUSH OFFSET BUFER PUSH HR CALL ReadFile@20 PUSH OFFSET BUFER PUSH 0 PUSH EM_REPLACESEL PUSH 101 PUSH DWORD PTR [EBP+08H] CALL SendDlgItemMessageA@20 ; закрыть HANDLE на запись PUSH HW CALL CloseHandle@4 ; закрыть HANDLE на чтение PUSH HR CALL CloseHandle@4 FINISH: MOV EAX,0 POP EDI POP ESI POP EBX POP EBP RET 16 WNDPROC ENDP _TEXT ENDS END START


Visual C++



Visual C++ 6.0

Текст на языке C++ не изменится, а вот ассемблерный текст придется изменить. Дело здесь в том, что транслятор Си автоматически добавит в конец вызываемой функции @8. В этом случае пришлось отказаться от нашей обычной детализации программы и положится на транслятор MASM. Для этого потребовалось явно указать параметры при объявлении процедуры. Как только это было сделано, транслятор взял часть работы на себя, кроме того, к имени COPYSTR он, естественно, добавил @8. Лишний раз подчеркну, что при таком объявлении не нужно явно устанавливать регистр EBP и освобождать стек - за нас все делает транслятор.

; файл proc.asm .386P .MODEL FLAT, stdcall PUBLIC COPYSTR ; плоская модель _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' ; процедура копирования одной строки в другую ; строка, куда копировать [EBP+08H] ; строка, что копировать [EBP+0CH] ; не учитывает длину строки, куда производится копирование ; явное указывание параметров COPYSTR PROC str1:DWORD, str2:DWORD MOV ESI,str2 ; DWORD PTR [EBP+0CH] MOV EDI,str1 ; DWORD PTR [EBP+08H] L1: MOV AL,BYTE PTR [ESI] MOV BYTE PTR [EDI],AL CMP AL,0 JE L2 INC ESI INC EDI JMP L1 L2: MOV EAX,DWORD PTR [EBP+08H] RET COPYSTR ENDP _TEXT ENDS END



Вызов динамической библиотеки Явное связывание



Рисунок 3.3.3. Вызов динамической библиотеки. Явное связывание.

Трансляция программы на Рисунок 3.3.3 ничем не отличается от трансляции обычных программ.

MASM32.

ml /c /coff /DMASM dllex.asm link /subsystem:windows dllex.obj

TASM32.

tasm32 /ml dllex.asm tlink32 -aa dllex.obj



Вызов динамической библиотеки Неявное связывание



Рисунок 3.3.4. Вызов динамической библиотеки. Неявное связывание.

У читателя, скорее всего, возникнет вопрос, откуда появляется библиотека DLLP1.LIB. Здесь все достаточно просто. Транслятор MASM создает библиотеку автоматически, а в трансляторе TASM есть утилита IMPLIB, которая создает статическую библиотеку непосредственно из динамической библиотеки.

Трансляция программы на Рисунок 3.3.4.

MASM32.

ml /c /coff /DMASM dllex.asm link /subsystem:windows dllex.obj

TASM32.

tasm32 /ml dllex.asm implib dll1.lib dll1.dll tlink32 -aa dllex.obj

Ранее было сказано, что возможным механизмом связывания является определения адреса процедуры через порядковый номер. Изложим схему того, как это можно сделать. Сначала Вы должны сопоставить процедуре, которая будет вызываться из динамической библиотеки, некое двухбайтное число. Это делается посредством строки в DEF-файле: EXPORTS DLLP1@1. Здесь процедуре DLLP1 сопоставляется номер 1. DEF-файлы мы уже использовали при трансляции в TASM. Точно также их можно использовать и в MASM (ключ /DEF:имя для программы link). После этого производится трансляция, и динамическая библиотека готова. Теперь при вызове функции GetProcAddress вторым параметром следует указать порядковый номер, точнее двойное слово, младшее слово которого есть порядковый номер, а старшее слово равно нулю. И все будет работать точно также, как и раньше. Лично я не вижу особой необходимости использовать такой подход.



Вызываемая ассемблерная процедура



III

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

#include <windows.h> #include <stdio.h> // объявляется внешняя функция extern "C" _stdcall DIAL1(); void main() { DIAL1(); ExitProcess(0); }



Взаимоисключения



Взаимоисключения.

Мы не упомянули еще один способ синхронизации, отложив его подробное описание на следующие главы. Здесь же отметим, что этот способ называется "взаимоисключением" или мьютексом (Mutex). Данный способ синхронизации не удобен для работы с потоками, он более пригоден для процессов. Данный объект создается при помощи функции CreateMutex. Все процессы, пытающиеся создать уже созданный объект, получают дескриптор уже существующего, созданного другим процессом объекта "взаимоисключение". Особенность данного объекта прежде всего в том, что им может владеть только один процесс. В документации фирмы Microsoft рекомендуется использовать данный объект для определения, запущено уже данное приложение или нет. Но об этом речь пойдет ниже.

41 К слову сказать, в однозадачной операционной системе MS DOS проблема совместного функционирования резидентных программ стояла весьма остро. Не смотря на то, что программисты, их писавшие, добивались весьма больших успехов, все же одновременная работа нескольких резидентных программ часто приводила к весьма заметным конфликтам.



Здесь рассматривается пример простейшего калькулятора



IV

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

// calcc.срр #include <windows.h> #include <stdio.h>

// вызов главной ассемолернои процедуры extern "С" _stdcall MAIN1(); // сложить extern "С" _stdcall void sum(char *, char *, char *); // вычесть extern "C" _stdcall void su(char *, char *, char *); // умножить extern "C" _stdcall void mu(char *, char *, char *); // разделить extern "C" _stdcall void dii(char *, char *, char *);

int WINAPI WinMain (HINSTANCE hThisInst, HINSTANCE hPrevInst, LPSTR lpszArgs, int nWinMode) { MAIN1(); return 0; }

extern "C" _stdcall void sum(char * s1, char * s2, char * s) { float f1,f2,f; f1=atof(s1); f2=atof(s2); f=f1+f2; sprintf(s,"%f",f); strcat(s," +"); return; }

extern "C" _stdcall void su(char * s1, char * s2, char * s) { float f1,f2,f; f1=atof(s1); f2=atof(s2); f=f1-f2; sprintf(s,"%f",f); strcat(s," -"); return; }

extern "C" _stdcall void mu(char * s1, char * s2, char * s) { float f1,f2,f; f1=atof(s1); f2=atof(s2); f=f1*f2; sprintf(s,"%f",f); strcat(s," *"); return; }

extern "C" __stdcall void dii(char * s1, char * s2, char * s) { float f1,f2,f; f1=atof(s1); f2=atof(s2); if(f2!=0) { f=f1/f2; sprintf(s,"%f",f); strcat(s," /"); } else strcpy(s,"Ошибка деления"); return; }









Pascal (конвенция языка Паскаль) Слева направо Процедура Нет
Register (быстрый или регистровый вызов) Слева направо Процедура Задействованы три регистра (EAX,EDX,ECX), далее стек
Cdecl (конвенция С) Справа налево Вызывающая программа Нет
Stdcall (стандартный вызов) Справа налево Процедура Нет