7.1. Первая программа

В качестве нашего первого примера посмотрим, насколько проще написать под Windows программу, которая загружает другую программу. В DOS (см. раздел 4.10) нам приходилось изменять распределение памяти, заполнять специальный блок данных ЕРВ и только затем вызывать DOS. Здесь же не только вся процеду­ра сокращается до одного вызова функции, а еще оказывается, что можно точно так же загружать программы, документы, графические и текстовые файлы и даже почтовые и Internet-адреса - все, для чего в реестре Windows записано действие, выполняющееся при попытке открытия.

winurl.asm

Пример програму для Win32.

Запускает установленный по умолчанию браузер на адрес,   указанный в строке ■ URL.

Аналогично можно запускать любую программу, документ и какой угодно файл, для которого определена операция open.

;

include

include kernel32.inc

|ЦЦ1|;   Программирование для Windows 95/NT

URL

start:

.386

 

 

.model

flat

 

.const

 

 

db

"http://www.lionking.org/"cubbi/",0

. code

 

 

 

;   Метка точки входа должна начинатвся с подчеркивания.

xor

ebx,ebx

 

push

ebx

Для исполняемых файлов - способ показа. ■

push

ebx

Рабочая директория.

push '

ebx

Командная строка.

push

offset URL

Имя файла с путем

push

ebx

Операция open или print   (если NULL - open).

push

ebx

Идентификатор окна, которое получит сообщения

call

ShellExecute

ShellExecute    (NULL,NULL, url, NULL, NULL, NULL)

push

ebx "

Код выхода.

call

ExitProcess

ExitProcess(O)

_start

 

 

end

Итак, в этой программе выполняется вызов двух системных функций Win32 -ShellExecute (открыть файл) и ExitProcess (завершить процесс). Чтобы активизи­ровать системную функцию Windows, программа должна поместить в стек все пара­метры от последнего к первому и передать управление дальней CALL. Данные функ­ции сами освобождают стек (завершаясь RET N) и возвращают результат работы в регистре ЕАХ. Такая договоренность о передаче параметров называется STDCALL. С одной стороны, это позволяет вызывать функции с нефиксированным числом параметров, а с другой - вызывающая сторона не должна заботиться об освобож­дении стека. Кроме того, функции Windows сохраняют значение регистров ЕВР, ESI, EDI и ЕВХ, этим мы пользовались в нашем примере - хранили 0 в регистре ЕВХ и применяли 1-байтную команду PUSH ЕВХ вместо 2-байтной PUSH 0.

Прежде чем мы сможем скомпилировать winurl.asm, нужно создать файлы kernel32.inc и shell32.inc, куда директивы, описывающие вызываемые

системные функции.

; kernel32.inc

;   Включаемый файл

с определениями функций из kernel32.dll.

ifdef

else

_TASM_

includelib

endif

import32.1ib

; Имена используемых функций. extrn     ExitProcess:near

;  Истинные имена используемых функций. ехЬгп_шр_£х^Ргосезз@4: бтгй

;   Присваивания для облегчения читаемости кода. ЕхкРгосеээ        еди_шр_ЕхПРгосезБ@4

she!132.inc

Включаемый файл с определениями функций из shell32.dll.

ifdef _TASM_

includelib import32.1ib

; Имена используемых функций, extrn ShellExecuteA:near

; Присваивания для облегчения читаемости кода. ShellExecute       equ ShellExecuteA

else

includelib shell32.1ib

; Истинные имена используемых функций. extrn_imp_Shell£xecuteA@24:dword

;   Присваивания для облегчения читаемости кода.

ShellExecute       equ_imp_ShellExecuteA@24

endif .

Имена всех системных функций Win32 модифицируются так, что перед име­нем функции ставится подчеркивание, а после - знак @ и число байтов, кото­рое занимают параметры, передаваемые ей в стеке: так ExitProcess превращается в _ExitProcess(3>4. Компиляторы с языков высокого уровня часто останавливают­ся на этом и вызывают функции по имени _ExitProcess@4, но реально появляется небольшая процедура-заглушка, которая ничего не делает, а лишь передает управ­ление на такую же метку, но с добавленным_imp_ -_imp_ExitProcess@4.

Во всех наших примерах мы будем обращаться напрямую к_imp_ExitProcess@4.

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

будут использовать их, если в командной строке ассемблера указать /D_TASM_.

Кроме того, все функции, работающие со строками (как, например, ShellExecute), существуют в двух вариантах. Если строка рассматривается в обычном смысле, как набор символов ASCII, к имени функции добавляется A (ShellExecuteA). Дру­гой вариант функции, использующий строки в формате UNICODE (два байта на

" символ), заканчивается буквой U. Во всех наших примерах будут использоваться обычные ASCII-функции, но, если вам потребуется перекомпилировать програм­мы на UNICODE, достаточно поменять А на U во включаемых файлах.

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

Компиляция MASM:

ml /с /coff /Cp winurl.asm

linb winurl.obj /subsystem:windows

(здесь и далее используется 32-битная версия link.exe) Компиляция TASM:

tasm /m /ml /D_TASM_ winurl.asm tlink32 /Tpe /aa /c /x winurl.obIll   Программирование для Windows 95/NT

Компиляция WASM: wasm winurl.asm

wlink file winurl.obj form windows nt op с

Также для компиляции потребуются файлы kernel32.1ib и shell32.1ib в первом и третьем случае и import32.1ib - во втором. Эти файлы входят в дистрибутивы любых средств разработки для Win32 от соответствующих компаний - Microsoft, Watcom (Sybase) и Borland (Inprise), хотя их всегда можно воссоздать из файлов kernel32.dll и sh.ell32.dll, находящихся в директории WINDOWS\SYSTEM.

Иногда вместе с дистрибутивами различных средств разработки для Windows поставляется файл windows.inc, в котором дано макроопределение Invoke или заменена макросом команда call так, что при вызове можно передать список аргу­ментов, первым из которых будет имя вызываемой функции, а затем через запя­тую - все параметры. С использованием данных макроопределений наша про­грамма выглядела бы так:

_start:

xor ebx.ebx

Invoke   ShellExecute, ebx, ebx, offset URL,  ebx,\ .

ebx, ebx

Invoke    ExitProcess, ebx end _start

И этот текст компилируется в точно такой же код, что и у нас, но выполняет­ся вызов промежуточной функции _ExitProcess@4, а не функции _ _imp_ _ExitProcess@4. Использование данной формы записи не позволяет применять

отдельные эффективные приемы оптимизации, которые мы будем приводить в на­ших примерах, - помещение параметров в стек заранее и вызов функции коман­дой JMP. И наконец, файла windows.inc у вас может просто не оказаться, так что будем писать push перед каждым параметром вручную.