Browse info для ассемблера
Всякий сколько-нибудь значительный программный проект содержит сотни, а то и тысячи идентификаторов. Как у программистов хватает фантазии их придумывать - это отдельная тема для исследования, пока что ждущая своего Фрейда, или Павлова, или Кастанеду, или, на худой конец, Маркеса.
Тут сразу следует напомнить о настоящих ассемблерщиках, которые не признают даже структурных директив MASM 6.1+. Ко всем прочим бедам они подбрасывают своему серому веществу еще и задачку придумывать имена для прорвы меток. Не заботясь о своем здоровье, надо сказать. Потому что, как известно, ничто так не разрушает здоровье, как работа, связанная с необходимостью принятия микрорешений. Позволим себе напомнить вам общеизвестный факт, что уровень преступности, суицида, алкоголизма, сексуальных извращений и политической активности среди сортировщиков куриных яиц примерно в восемь раз выше, чем среди трактористов!
А ведь сортировщики куриных яиц еще не сталкиваются с такой проблемой, как глобальность идентификаторов в пределах модуля! Спасибо любимой Microsoft за то, что хоть метки стали локальны в пределах процедуры. Впрочем, специально для садомазохистов разработчики предусмотрели директиву OPTION NOSCOPED - кайфуйте, ребята.
Каждый крутится как может. Шаблонно мыслящие конформисты идут на поводу у Чарльза Симони и украшают свои исходники венгерской тавтологией вроде lpSomeTable. Аристократические отпрыски Йеля и Итона, сложив губки гузкой, выстукивают холенымы ногтями имя функции: PleaseGiveMeAnErrorCode. Раздолбанные хакеры, вытерев рукавом пиво с клавиатуры, без раздумий используют получившуюся комбинацию: p9ijhbgfd, и в течение следующей минуты навешивают на нее как минимум сорок строк кода. Стиль русских программистов старшего поколения, заставших время, когда их называли советскими программистами, особый, и не меняется уже много лет: vodka362, kolbasa220. Изредка попадающиеся в этой экологически нездоровой среде женщины широко используют свои специфические ассоциации: carnation, champagne, alwaysultraplus. Педанты же как всегда педантичны: proc_for_scan_table_of_strings_00000001.
А вот откуда берется файл .bsc - разобраться надо, потому что без понимания этого процесса запустить Browse Info в ассемблерном проекте вряд ли удастся.
Процесс создания базы данных .bsc, как и следует ожидать, происходит на обоих этапах построения проекта - и на этапе компиляции, и на этапе компоновки.
Компилятор (будь то cl.exe для C++ или ml.exe для MASM) создает промежуточный файл базы данных, содержащий, естественно, только информацию об идентификаторах данной единицы компиляции - cpp-файла или asm-файла. Этот промежуточный файл имеет расширение .sbr и имя, по умолчанию совпадающее с именем компилируемого файла. Размещается он там же, где и создаваемый объектный файл - в папке Debug. Для того, чтобы компилятор озаботился созданием sbr-файла, в его командную строку следует включить опцию /FR или /Fr.
Опции отличаются друг от друга тем, что первая из них включает в sbr-файл информацию обо всех идентификаторах (в том числе локальных), а вторая - только об объявленных внешними и публичными. Выбор - за программистом.
Опции предоставляют возможность изменить местоположение и имя создаваемого sbr-файла, как, например: /FR"..\Support Files\browseinfo.sbr"
Поскольку компиляция файлов C/C++ поддерживается MS Developer Studio, то для того, чтобы заставить компилятор создавать sbr-файл, следует запустить диалог Project Settings, открыть вкладку C/C++, выбрать в списке Category пункт Listing Files, и поставить галочку в чекбоксе Generate browse info. Если нужно заменить местоположение или имя sbr-файла, следует воспользоваться полем Intermediate browse info file destination. А замена опции /FR на опцию /Fr производится галочкой в чекбоксе Exclude local variables from browse info.
Для ассемблерных файлов опцию в нужной форме следует прямо указать в командной строке на вкладке Custom Build при настройке MS Developer Studio.
На этапе компоновки проекта из промежуточных sbr-файлов, созданных компилятором для каждого из модулей, собирается один на весь проект bsc-файл. Правда, слова "на этапе компоновки" отнюдь не означают, что этим занимается сам компоновщик. Для сборки bsc-файла существует специальная утилита - bscmake.exe, находящаяся в папке исполняемых файлов Vc\bin MS Developer Studio.
Включить вызов утилиты bscmake.exe можно с помощью чекбокса Build browse info file, находящегося на вкладке Browse Info диалога Project Settings. Там же можно при необходимости поменять местоположение и имя bsc-файла, подавить выдачу баннерного текста и задать другие опции командной строки утилиты.
Учитывая обширность заголовочных файлов API, следует ожидать, что удовольствие от применения Browse Info хорошо оплачено ресурсами компьютера. Так оно и есть. Sbr-файл, образованный из чистого windows.h, весит почти восемьсот кило. А образованный из него bsc-файл - в два с лишним раза больше. При включенном Browse Info соответственно увеличивается время компиляции и компоновки проекта. Это обстоятельство могло бы превратить жизнь программиста в сущий ад, если бы не добрые дяди из Microsoft, предусмотревшие инкрементную обработку этих файлов. Суть ее в том, что сборка bsc-файла выполняется каждый раз не заново, а только с учетом реально произошедших изменений в проекте. Определить же, какие именно изменения произошли, утилита bscmake.exe умудряется очень просто. Всякий раз после очередной сборки она усекает до 0 размер участвовавших в сборке sbr-файлов. Таким образов в следующем сеансе сборки примут участие только те sbr-файлы, которые с прошлого раза прошли реальную перекомпиляцию.
Ну и, наконец, самое интересное. Как настроить Browse Info для работы с ассемблерным проектом. Специфика организации рабочей среды такова, что имеет смысл рассматривать два варианта настройки:
рабочая среда (workspace) содержит один проект рабочая среда содержит несколько проектов
Вариант с одним проектом:
Создайте и настройте рабочий проект так, как указано в статье "ms devstudio - среда разработки asm"
В диалоге Project Settings на вкладке Custom Build в командную строку компилятора включите опцию:
/FR"Debug\$(InputName).sbr"Это необходимо сделать для всех asm-файлов, для которых вы хотели бы иметь включенным средство Browser Info.
Как видно, sbr-файлы будут помещаться в папку Debug с именем, соответствующим имени asm-файла. Эта папка выбрана нами потому, что по умолчанию в нее же помещаются sbr-файлы модулей, написанных на C/C++. Учтите, что в начале работы с проектом она не существует, поэтому попытка компиляции даст ошибку. Создайте ее вручную. |
создайте файл brinfo.cpp и включите его в проект содержимое файла brinfo. cpp должно представлять собой единственную строку:
#include <windows.h>
включите для этого файла опцию Browser Info как для обычного C++-файла откомпилируйте его
В диалоге Project Settings на вкладке Browse Info:
установите чекбокс Build browse info file
установите чекбокс Suppress startup banner
дополните содержимое поля Project options следующим текстом:
Debug\*.sbr Таким образом вы дадите утилите bscmake.exe команду включить в сборку все sbr-файлы, содержащиеся в папке Debug
Выполните компиляцию всех модулей проекта, а затем его компоновку. Browser Info готово к работе.
Следует учесть одно неприятное обстоятельство. Рабочая среда определяет необходимость запуска утилиты bscmake.exe по факту выполнения компиляции хотя бы одного файла C/C++. Ассемблерные же файлы, будучи компилируемы посредством Custom Build, к сожалению, такой команды рабочей среде не дают. Поэтому все изменения в составе идентификаторов в ассемблерных файлах остаются втуне до тех пор, пока не будет перекомпилирован хотя бы один cpp-файл проекта и после этого не выполнена его компоновка. В чисто ассемблерных проектах придется вручную вызывать компиляцию файла brinfo.cpp.
В этой беде мог бы помочь вынос вызова утилиты bscmake.exe на этап Post-build step. Однако, этот фокус не проходит: дело в том, что однажды активизировавшись, Browse Info открывает bsc-файл и держит его в дальнейшем в открытом состоянии, запрещая таким образом запись в него всем внешним программам. Этот запрет отменяется только на этапе компоновки проекта, но не на этапе Post-build step.
Вариант с несколькими проектами базируется на тех же идеях, что и вариант с одним проектом, но с учетом некоторых коррекций:
прежде всего следует определиться, имеется ли необходимость иметь единое для всех проектов пространство идентификаторов (не в смысле программирования, а в смысле просмотра их с помощью Browse Info), или у каждого проекта должно быть свое пространство, или проекты можно разбить на группы, с отдельным пространством для каждой группы для каждого пространства следует определить одну папку, в которой должны содержаться все sbr-файлы модулей, относящихся к этому пространству для каждого пространства следует использовать отдельный файл brinfo.spp
Инструкция программиста mycall
Приложение MyCall - это пользовательский интерфейс Remote Access Service для Windows 95/98. Приложение разработано как учебно-экспериментальная задача, целью которой является демонстрация отличий в реализации приложений на ассемблере и на C++. См. статью Зачем он нужен, этот ассемблер?.
Исходный текст реализации на C++ состоит из файлов:
main.cpp - основной файл main.h - файл заголовков mycall.rc - файл описания ресурсов icon1.ico - иконка
Полный комплект файлов, необходимых для компиляции приложения, содержится в zip-файле mycallcb.zip (13192 байта)
Исходный текст реализации на ассемблере состоит из файлов:
main.asm - основной файл main.inc - файл заголовков @struct.inc - файл структурных макросов windows.inc - файл заголовков win32 mycall.rc - файл описания ресурсов icon1.ico - иконка
Полный комплект файлов, необходимых для компиляции приложения, содержится в zip-файле mycallab.zip (15913 байта)
компиляция приложения
Компиляция реализации на C++:
Создайте в MS Developer Studio новый проект приложения win32 ("Win32 Application") с именем mycall Распакуйте файл mycallcb.zip, поместив извлеченные из него файлы в папку проекта Подключите к проекту файлы main.cpp и mycall.rc Установите в качестве активной конфигурации для построения проекта "Win32 Release" Измените установки проекта для сборщика (Settings/Link):
удалите из списка подключаемых библиотек все, кроме kernel32.lib и user32.lib добавьте в список rasapi32.lib отключите библиотеки по умолчанию (флажок "Ignore all default libraries") установите в качестве точки входа функцию WinMain (поле "Entry-point symbol" в категории "Output")
Выполните построение проекта
Компиляция реализации на ассемблере:
Создайте в MS Developer Studio новый проект приложения win32 ("Win32 Application") с именем mycall Распакуйте файл mycallab.zip, поместив извлеченные из него файлы в папку проекта Подключите к проекту файлы main.asm и mycall.rc. (Учтите, что файл main.asm становится виден в окне файлового диалога MS Developer Studio только в режиме "Все файлы") Установите в качестве активной конфигурации для построения проекта "Win32 Release" Измените установки для компиляции файла main.asm (Settings/Custom Build):
Это, в свою очередь, диктует особенности программных решений. Был сознательно выбран хаотический стиль программирования, не использующий практически никаких характерных для C++ объектно-ориентированных средств организации программ. (В результате проект, например, изобилует глобальными переменными, что в общем случае следовало бы считать очень плохим стилем.) Также практически не использовались и специфические приемы ассемблерного программирования.
Но зато, положив рядом два листинга - на C++ и на ассемблере - желающие имеют теперь возможность как угодно глубоко разобраться в отличиях реализаций. Может быть полезно также сравнить дизассемблированный код приложения на C++ с реализацией на ассемблере, что позволяет сделать встроенный отладчик MS Developer Studio.
Кстати, о стиле. Для таких маленьких приложений, вполне возможно, хаотический стиль программирования выглядит более предпочтительным, чем объектно-ориентированный, как с точки зрения читабельности программы, так и с точки зрения минимизации накладных расходов: времени на разработку, объема исходного текста и результирующего объема кода.
Работа приложения начинается с передачи управления функции WinMain. По своему построению это обычная для win32 функция, выполняющая следующие задачи:
создание главного окна подготовку рабочих данных запуск цикла обработки сообщений завершение работы
Некоторым отличием от общепринятых норм построения WinMain является то, что в качестве главного окна приложения используется не обычное окно, а окно диалога, описание которого содержится в файле описания ресурсов. Впрочем, это частый прием для небольших утилитоподобных приложений вроде MyCall.
Кроме того, в функции WinMain выполняется проверка запуска второго экземпляра приложения. Для этого применяется специальный способ, основанный на обмене регистрируемым глобальным сообщением.
Все внешние данные приложения хранятся в двух файлах: mycall.txt и mycall.ini. Они оба загружаются соответствующими функциями, вызываемыми из WinMain в начале работы приложения.
Файл mycall.ini содержит данные о позиции окна приложения на рабочем столе и о состоянии выбора списков соединений, телефонов и логинов на момент завершения предыдущего сеанса работы приложения. Это удобная возможность, позволяющая пользователю сохранять привычную ему рабочую среду.
Файл mycall.ini загружается функцией load_ini однократно в начале работы приложения. На основании содержащихся в нем данных устанавливаются значения соответствующих переменных. Если файл mycall.ini не существует, как это бывает, например, при первом запуске приложения, то в переменных остаются значения по умолчанию.
Файл mycall.ini формируется и записывается функцией load_ini непосредственно перед завершением работы приложения. Функция вызывается оконной процедурой superprocedure при обработке ею сообщения WM_DESTROY.
Файл mycall.txt содержит данные, задаваемые пользователем для соединения с провайдером: имена соединений, номера телефонов, логины и пароли.
Он загружается функцией load_data однократно в начале работы приложения. В последующем содержимое файла хранится в специальном буфере, который сканируется всякий раз, когда требуется изменить содержимое соответствующих списков. Это делает функция change_con.
Приложение MyCall не изменяет содержимого файла mycall.txt и не сохраняет его. Отредактировать файл может только пользователь с помощью какого-нибудь текстового редактора. Формат файла должен строго соблюдаться пользователем, так как приложение MyCall не проверяет его при загрузке файла. Ошибки формата не приводят к фатальным последствиям, но данные для работы с провайдерам становятся некорректными.
Общую диспетчеризацию в MyCall, как того и требует архитектура приложений win32, выполняет оконная процедура superprocedure. Она обрабатывает сообщения:
WM_COMMAND - поступающее при изменении позиции списков WM_USER - используемое при мониторинге состояния соединения для принятия решения о необходимости повтора дозвона WM_MOVE - для запоминания позиции окна при его перемещении WM_DESTROY и WM_CLOSE - для выполнения завершающих операций при окончании работы приложения регистрируемое глобальное сообщение my_message - для обеспечения взаимодействия экземпляров приложения
После выбора соединения, телефона и логина, пользователь, нажав кнопку, переводит приложение в режим дозвона. При этом:
блокируются все органы управления приложением, за исключением кнопки, которая начинает служить для прекращения дозвона вызывается функция ras_dial, которая осуществляет дозвон
Функция ras_dial на основании данных, выбранных пользователем, заполняет структуру RASDIALPARAMS и вызывает функцию API RasDial.
Для мониторинга процесса дозвона используется callback-функция ras_dial_func типа RasDialFunc (см. Platform SDK). Эта функция отображает соответствующие сообщения в строке статуса и, в случае неудачи установления соединения, посылает оконной процедуре сообщение WM_USER с параметром "0" для выполнения повторных попыток дозвона.
Использовать механизм сообщений в данном случае необходимо, так как callback-функция вызывается сервисом RAS в отдельной нити, и, следовательно, не синхронизирована с главной нитью приложения.
Если соединение установлено успешно, callback-функция ras_dial_func запускает нить монитора разрыва ras_monitor_func. Такой прием пришлось применить потому, что в сервисе RAS не предусмотрено средств для контроля работающего соединения, и, следовательно, приложение не может никаким иным способом узнать о факте разрыва соединения, например, при потере связи. Нить монитора разрыва каждые 200 мс опрашивает состояние соединения, и при обнаружении его разрыва сообщает об этом основной нити приложения путем посылки сообщения WM_USER с параметром "1", а затем завершается. Получив это сообщение, приложение переходит в режим выбора соединения.
Для прекращения дозвона и принудительного разрыва установленного соединения используется функция ras_hangup. Она вызывает функцию API RasHangUp.
Важным элементом является последующий цикл, опрашивающий состояние соединения. Дело в том, что сервис RAS может иметь задержки реакции на вызов функций API до нескольких секунд. Если в течение времени этой задержки попытаться вызвать, например, функцию RasDial, последствия, как правило, бывают фатальны. Примененный здесь цикл позволяет гарантированно дождаться окончания задержки.
Несмотря на то, что приложение MyCall разрабатывалось как учебно-экспериментальное, оно вполне функционально и может удовлетворить потребности многих пользователей.
Компиляция файлов .asm
Этот материал дополняет статью MS Developer Studio - среда разработки для ASM. Здесь приведен формат командной строки компилятора MASM 6.1+, расшифровка ее опций и комментарии по их применению при работе в среде MS Developer Studio.
Командная строка MASM имеет вид:
ML [/options] filename [/options] filename [/link linkoptions]
Здесь:
/options - необязательный перечень опций (см.таблицу ниже) filename - имена файлов исходных текстов, подлежищих компиляции, разделенные пробелами. В среде MS Developer Studio обычно указывается только одно имя в виде шаблона $(InputPath). /link linkoptions - командная строка компоновщика в случае, если используется опция /Bl. В среде MS Developer Studio обычно не применяется.
опция | назначение | применение для win32 |
/AT | Enable tiny model (.COM file) Создать файл в формате .com (модель tiny) |
Не применяется, так как формат исполняемого файла .com не используется в Win32. |
/Bl<linker> | Use alternate linker Использовать альтернативный компоновщик |
Обычно не применяется, так как возможностей link.exe вполне достаточно. Используется опция /c |
/c | Assemble without linking Только компиляция, без компоновки |
Обязательно для применения в среде MS Developer Studio, чтобы выполнять компоновку отдельным этапом. |
/Cp | Preserve case of user identifiers Сохранение регистра пользовательских идентификаторов |
Применение не обязательно, но возможно для дополнительного контроля синтаксиса. Вызывает ошибку "A2006: undefined symbol" при несовпадении регистра в объявлении идентификатора и обращении к нему. Позволяет избежать ошибок на этапе компоновки в случае, если идентификатор объявлен с неверным регистром. |
/Cu | Map all identifiers to upper case Приведение всех пользовательских идентификаторов к верхнему регистру |
Не применяется, так как компоновка приложений Win32 чувствительна к регистру. |
/Cx | Preserve case in publics, externs Сохранение регистра идентификаторов, объявленных публичными и внешними |
В применении нет необходимости. Регистр идентификаторов имеет смысл на этапе компоновки, но не на этапе компиляции. |
/coff | Generate COFF format object file Создать файл в формате COFF |
Применение обязательно: это стандартный для windows формат объектных и исполняемых файлов. |
/D<name>[=text] | Define text macro Описание текстового макроса |
Применяется по усмотрению программиста. Аналог директив EQU или =. Если текст содержит пробелы, его следует взять в кавычки. Обычно используется в отладочном версии приложения для объявления имени DEBUG. |
/EP | Output preprocessed listing to stdout Вывод листинга препроцессора в stdout |
Обычно применять нет необходимости. Листинг препроцессора представляет собой исходный текст вместе с включаемыми файлами. |
/F<hex> | Set stack size (bytes) Определить размер стека (байт) |
Практически не применяется. То же, что опция /STACK компоновщика link.exe. Обычно используется значение по умолчанию - 1 Мбайт. |
/Fe<file> | Name executable Имя исполняемого файла |
Не применяется, так как с учетом опции /c компилятор не создает исполняемого файла |
/Fl[file] | Generate listing Создание файла листинга |
Обычно не применяется, так как средства MS Developer Studio, как правило, достаточны для работы с текстом приложения. |
/Fm[file] | Generate map Создание map-файла |
Не применяется, так как map-файл создается компоновщиком, а с учетом опции /c компилятор не вызывает компоновщик |
/Fo<file> | Name object file Имя объектного файла |
Обычно не применяется. Позволяет задать obj-файлу имя, отличное от имени asm-файла. |
/FPi | Generate 80x87 emulator encoding Включение кода эмулятора сопроцессора 80x87 |
Начиная с выхода в свет процессора 486 не применяется, так как с тех пор арифметический сопроцессор является неотъемлемой частью современных процессоров. |
/Fr[file] | Generate limited browser info Включить ограниченную информацию броузера |
Применение менее предпочтительно, чем /FR, так как в информацию броузера не включа.тся сведения о локальных идентификаторах. |
/FR[file] | Generate full browser info Включить полную информацию броузера |
Позволяет получать быстрый доступ к любому идентификатору во всем пространстве проекта и заголовочных файлов API win32. См. статью об этом. |
/G<c|d|z> | Use Pascal, C, or Stdcall calls Использовать соглашения вызова Pascal, C или Stdcall |
В применении нет необходимости. Обычно использование соглашений вызова stdcall регламентируется директивой .model в тексте программы. |
/H<number> | Set max external name length Установить максимальную длину внешних имен |
Обычно не применяется. Значение по умолчанию - 31, и его достаточно для работы в среде win32. |
/I<name> | Add include path Добавить путь для inc-файлов |
Не применяется, так как собственных возможностей MS Developer Studio обычно достаточно для определения путей к inc-файлам. Допускается использовать до 10 опций /I. |
/link | <linker options and libraries> Опции командной строки компоновщика и подключаемые библиотеки |
Не применяется, так как компоновка отключена опцией /c. |
/nologo | Suppress copyright message Не показывать баннерный текст компилятора |
Как правило, следует применяеть, так как баннерный текст смысловой нагрузки при разработке проекта не несет. |
/Sa | Maximize source listing Листинг максимального формата |
Применяется редко, так как собственных средств MS Developer Studio обычно достаточно для работы с исходным и компилированным текстом программы, и в выдаче листинга нет необходимости. |
/Sc | Generate timings in listing Включить в листинг синхронизацию |
То же |
/Sf | Generate first pass listing Листинг первого прохода |
То же |
/Sl<width> | Set line width Длина строки листинга, символов: 60...255 или 0. |
То же |
/Sn | Suppress symbol-table listing Не включать в листинг таблицу символов |
То же |
/Sp<length> | Set page length Высота страницы листинга, строк: 10...255 или 0. |
То же |
/Ss<string> | Set subtitle Текст подзаголовков листинга |
То же |
/St<string> | Set title Текст заголовка листига |
То же |
/Sx | List false conditionals Включить в листинг все фрагменты условной компиляции |
То же |
/Ta<file> | Assemble non-.ASM file Компилировать не-.asm файлы |
Обычно не применяется. Служит для компиляции файлов, имя котрых имеет расширение, отличное от .asm. |
/w | Same as /W0 /WX То же, что /W0 /WX |
См. далее. |
/WX | Treat warnings as errors Трактовать предупреждения как ошибки |
Обычно в применении нет необходимости. В случае возникновения предупреждений компиляция завершается неуспешно. |
/W<number> | Set warning level Установить уровень предупреждеинй |
Обычно в применении нет необходимости. Устанавливает перечень событий компиляции, трактуемых как предупреждения. |
/X | Ignore INCLUDE environment path Игнорировать путь, установленный переменной окружения INCLUDE |
Обычно не применяется, так как при работе в среде MS Developer Studio переменная окружения INCLUDE не используется. |
/Zd | Add line number debug info Включить отладочную информацию в виде номеров строк |
Обычно не применяется, так как на этапе отладки более целесообразно использовать опцию /Zi. |
/Zf | Make all symbols public Объявить все имена публичными |
Обычно не применяется. |
/Zi | Add symbolic debug info Включить полную отладочную информацию |
Обязательно применяется на этапе отладки. Формат отладочной информации MASM полностью совместим с используемым встроенным отладчиком MS Developer Studio. |
/Zm | Enable MASM 5.10 compatibility Включить совместимость с MASM 5.10 |
Обычно не применяется. Отключает полезные для прикладного программирования свойства MASM, введенные в версиях 6.1+. |
/Zp[n] | Set structure alignment Установить выравнивание структур |
Может быть использован для установки принятого в win32 выравнивания по умолчанию - на 8 байт. Однако обычно в применении нет необходимости, так как в win32 используется два варианта выравнивания структур - на 4 и на 8 байт, и определять их выравнивание целесообразно непосредственно в описании структуры директивой STRUCT. Возможные значения для этой опции - 1, 2, 4 и 8 (последнее - в версиях MASM 6.13 и выше). |
/Zs | Perform syntax check only Выполнять только проверку синтаксиса |
Обычно не применяется. Подавляет формирование объектного модуля. |
Типовая командная строка для этапа отладки (активен проект Win32 Debug) имеет вид:
ml.exe /c /coff /FR"..\browseinfo\$(InputName).sbr" /nologo /Zi /DDEBUG $(InputPath)
Отключена компоновка, формируется объектный модуль в формате COFF, формируется sbr-файл, содержищий информацию для Browse Info, подавлена выдача баннерной информации, в объектный модуль включена полная отладочная информация, определена константа DEBUG.
Типовая командная строка для чистового этапа (активен проект Win32 Release) имеет вид:
ml.exe /c /coff /nologo $(InputPath)
Отключена компоновка, формируется объектный модуль в формате COFF, подавлена выдача баннерной информации.
Main.asm для mycall (ассемблер)
Это основной файл приложения MyCall на ассемблере. Этот файл в текстовом формате вместе со всеми остальными файлами, необходимыми для компиляции приложения MyCall, содержится в zip-файле mycallab.zip (15913 байт). Имеется также Инструкция программиста.
Для получения комментариев щелкaйте по тексту или пользуйтесь групповым управлением:
if(dhtml){document.write("Все комментарии: [+][-] Открывать: [несколько]");}
;Включаемые файлы:
;@struct.inc - файл структурных макросов
;windows.inc - файл заголовков win32
;main.inc - файл заголовков приложения MyCall
include @struct.inc
include windows.inc
include main.inc
;ГЛАВНАЯ ФУНКЦИЯ ПРИЛОЖЕНИЯ
;///////////////////////////////////////////////////////////// WinMain
.data?
mw_class WNDCLASSEX{}
loop_message MSG{}
win_dim RECT{}
.const
mw_class_name db "MainWindowClass",0
my_message_name db "MyCallMessage",0
.code
WinMain PROC PUBLIC hinst,prev_hinst,command_line,cmd_show
mov dat_buffer,0
mov online,FALSE
;Получение дескриптора экземпляра приложения
;Необходимое действие, так как предполагается компиляция приложения без
;подключения runtime-библиотеки. Подробнее...
invoke GetModuleHandleA,NULL
mov hinst,eax
;Создание главного окна
;Обычное действие, с которого начинаются большинство приложений.
;Единственное отличие в том, что в качестве главного окна в MyCall
;используется окно диалога, описанного в файле ресурсов
;Регистрируется класс главного окна:
mov mw_class.cbSize,sizeof(WNDCLASSEX)
mov mw_class.style,NULL
mov mw_class.lpfnWndProc,offset superprocedure
mov mw_class.cbClsExtra,0
mov mw_class.cbWndExtra,DLGWINDOWEXTRA
mov eax,hinst
mov mw_class.hInstance,eax
invoke LoadIconA,eax,103
mov mw_class.hIcon,eax
mov mw_class.hIconSm,NULL
invoke LoadCursorA,NULL,IDC_ARROW
mov mw_class.hCursor,eax
mov mw_class.hbrBackground,COLOR_WINDOW
mov mw_class.lpszMenuName,NULL
mov mw_class.lpszClassName,offset mw_class_name
@if(ecx==3)
mov ecx,0
@endif
@endif
@if(ecx==0)
@if(ebx==0)
@if(byte ptr[esi]!=0)
@if(byte ptr[esi]!=1)
@push ecx,edx,ebx,esi
invoke SendMessageA,conn_window,CB_ADDSTRING,0,esi
@pop ecx,edx,ebx,esi
inc edx
@if(edx>=MAX_CON)
@break
@endif
@endif
@endif
@endif
@endif
inc ebx
inc esi
@endw
;По данным файла mycall. ini устанавливается прошлая позиция списка conn_window
@if(current_con>=edx)
mov current_con,0
@endif
invoke SendMessageA,conn_window,CB_SETCURSEL,current_con,0
;По состоянию списка conn_window формируются остальные списки
call change_con
;ЦИКЛ ОЖИДАНИЯ СООБЩЕНИЙ И ЗАВЕРШЕНИЕ ПРИЛОЖЕНИЯ
;Неотъемлемый элемент приложений для Windows. В MyCall никаких особенностей не имеет
;Поскольку MyCall собирается без runtime-библиотеки, для завершения работы
;обязательно использовать функцию ExitProcess. Подробнее...
msg_loop:
invoke GetMessageA,offset loop_message,NULL,0,0
@if(!eax)
invoke ExitProcess,loop_message.wParam
@endif
invoke TranslateMessage,offset loop_message
invoke DispatchMessageA,offset loop_message
jmp msg_loop
bad:
invoke fatal,eax
invoke ExitProcess,EXIT_COMMON_ERROR
WinMain ENDP
;ОКОННАЯ ПРОЦЕДУРА
;Стандартный элемент приложений для Windows. В MyCall особенностей не имеет.
;///////////////////////////////////////////////////////////// Оконная процедура
.data?
win_pos RECT{}
.code
superprocedure PROC window_from,message,w_param,l_param
mov eax,message
;Обработка сообщения WM_COMMAND, передаваемого элементами управления диалога
@if(eax==WM_COMMAND)
mov eax,w_param
mov cl,16
shr eax,cl
;Обработка уведомления CBN_SELCHANGE, передаваемого списками при изменении позиции:
;1000: список соединений conn_window. Изменяет содержание phon_window и user_window
;1001: список телефонов phon_window. Устанавливает новый телефон для текущего соединения con_phone[current_con]
;1002: список логинов user_window. Устанавливает новый логин для текущего соединения con_user[current_con]
@if(eax==CBN_SELCHANGE)
mov eax,w_param
and eax,0ffffh
@if(ax==1000)
mov eax,conn_window
and current_con,0ffh
mov esi,offset current_con
@elseif(ax==1001)
mov eax,phon_window
mov esi,offset con_phone
add esi,current_con
@elseif(ax==1002)
mov eax,user_window
mov esi,offset con_user
add esi,current_con
@else
jmp sp_nok
@endif
push esi
invoke SendMessageA,eax,CB_GETCURSEL,0,0
pop esi
@if(eax==CB_ERR)
xor eax,eax
@endif
mov [esi],al
call change_con
jmp sp_ok
@endif
; Обработка уведомления BN_CLICKED, передаваемого кнопкой при клике:
;в состоянии online=TRUE прекращает дозвон (разрывает соединение) и включает списки
;в состоянии online=FALSE отключает списки и начинает дозвон
@if(eax==BN_CLICKED)
mov eax,w_param
and eax,0ffffh
@if(eax==1003)
@if(online)
invoke EnableWindow,butt_window,FALSE
call ras_hangup
invoke EnableWindow,butt_window,TRUE
push FALSE
call disable_controls
@else
push TRUE
call disable_controls
call ras_dial
@endif
jmp sp_ok
@endif
@endif
jmp sp_nok
;Обработка сообщения WM_USER, используемого в MyCall нитью монитора разрыва.
;Обнаружив факт разрыва соединения, нить монитора передает WM_USER. Если был коннект,
;то включаются списки, в противном случает выполняется повторный дозвон
@elseif(eax==WM_USER)
call ras_hangup
@if(l_param)
push FALSE
call disable_controls
@else
call ras_dial
@endif
jmp sp_ok
;Обработка сообщения WM_MOVE: запоминание новой позиции окна
;для последующей записи ее в файл mycall.ini
@elseif(eax==WM_MOVE)
invoke GetWindowRect,main_window,offset win_pos
@if(eax)
push win_pos.left
pop main_win_left
push win_pos.top
pop main_win_top
@else
mov eax,l_param
and eax,0ffffh
mov main_win_left,eax
mov eax,l_param
mov cl,16
rcr eax,cl
and eax,0ffffh
mov main_win_top,eax
@endif
jmp sp_ok
;При завершении работы приложения записать файл mycall.ini,
;и освободить память
@elseif(eax==WM_DESTROY)
call save_ini
@if(dat_buffer)
invoke GlobalFree,dat_buffer
@endif
invoke PostQuitMessage,EXIT_NORMAL
jmp sp_ok
; При закрытии главного окна заблокировать кнопку
;и разорвать соединение
@elseif(eax==WM_CLOSE)
invoke EnableWindow,butt_window,FALSE
@if(online)
call ras_hangup
@endif
jmp sp_nok
;Обработка глобального оконного сообщения, используемого для
;взаимодействия экземпляров приложения. Получив это сообщение,
;экземпляр, запущенный первым, сообщает дескриптор своего главного окна.
;Сообщение, полученное вторым, принимает от первого дескриптор его главного окна,
;выводит его на первый план и завершается. Подробнее...
@if(eax==my_message)
mov ebx,w_param
@if(ebx!=main_window)
@if(!l_param)
invoke SendMessageA,HWND_BROADCAST,eax,main_window,1
@else
invoke SetForegroundWindow,w_param
invoke ExitProcess,EXIT_OVERLOADED
@endif
@endif
jmp sp_ok
@endif
;Возврат необработанных сообщений системе
sp_nok:
invoke DefWindowProcA,window_from,message,w_param,l_param
ret
;Завершение оконной процедуры для обработанных сообщений
sp_ok:
xor eax,eax
ret
superprocedure ENDP
;ЗАГРУЗКА ФАЙЛА ИНИЦИАЛИЗАЦИИ
;Файл mycall.ini хранит состояние списков и положение главного окна на момент
;завершения предыдущего сеанса работы приложения
;///////////////////////////////////////////////////////////// Загрузка файла инициализации
.data?
ini_filedd ?
buffer db INI_FILE_LENGTH dup(?)
bytes_readwrite dd ?
.const
ini_file_name db "mycall.ini",0
.code
load_ini PROC
;Инициализация глобальных переменных
mov main_win_left,MAIN_WIN_DEFAULT_LEFT
mov main_win_top,MAIN_WIN_DEFAULT_TOP
mov current_con,0
mov ebx,MAX_CON
@while(ebx)
dec ebx
mov [con_phone+ebx],0
mov [con_user+ebx],0
@endw
;Попытка загрузки файла mycall.ini
;В случае неудачи используются значения по умолчанию
invoke CreateFileA,offset ini_file_name,GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL
@if(eax!=INVALID_HANDLE_VALUE)
;Чтение файла mycall.ini
push eax
invoke ReadFile,eax,offset buffer,INI_FILE_LENGTH,offset bytes_readwrite,NULL
@if(eax)
;Приведение позиции окна к фактическим размерам экрана и запоминание
xor edx,edx
mov dx,[word ptr buffer]
push edx
invoke GetSystemMetrics,SM_CXSCREEN
sub eax,10
pop edx
@if(edx<=eax)
mov main_win_left,edx
@endif
xor edx,edx
mov dx,[word ptr buffer+2]
push edx
invoke GetSystemMetrics,SM_CYSCREEN
sub eax,10
pop edx
@if(edx<=eax)
mov main_win_top,edx
@endif
;Запоминание позиций списков телефонов и логинов
xor edx,edx
mov dl,[byte ptr buffer+4]
mov current_con,edx
xor ecx,ecx
xor ebx,ebx
@while(ecx<MAX_CON)
mov ax,[word ptr buffer+5+ebx]
mov [con_phone+ecx],al
mov [con_user+ecx],ah
add ebx,2
inc ecx
@endw
@endif
pop eax
invoke CloseHandle,eax
@endif
ret
load_ini ENDP
;СОХРАНЕНИЕ ФАЙЛА ИНИЦИАЛИЗАЦИИ mycall.ini
; В файле сохраняются положение главного окна на экране и позиции
;списков на момент завершения работы приложения
;============================================================= Сохранение файла инициализации
save_ini PROC
invoke CreateFileA,offset ini_file_name,GENERIC_WRITE,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL
@if(eax!=INVALID_HANDLE_VALUE)
push eax
mov eax,main_win_left
mov [word ptr buffer],ax
mov eax,main_win_top
mov [word ptr buffer+2],ax
mov eax,current_con
mov [buffer+4],al
xor ecx,ecx
xor ebx,ebx
@while(ecx<MAX_CON)
mov al,[con_phone+ecx]
mov ah,[con_user+ecx]
mov [word ptr buffer+5+ebx],ax
add ebx,2
inc ecx
@endw
pop eax
push eax
invoke WriteFile,eax,offset buffer,INI_FILE_LENGTH,offset bytes_readwrite,NULL
pop eax
invoke CloseHandle,eax
@endif
ret
save_ini ENDP
;ЗАГРУЗКА ФАЙЛА ДАННЫХ mycall.dat
;Файл содержит имена соединений, телефоны и логины
;///////////////////////////////////////////////////////////// Загрузка файла данных
.data?
dat_file dd ?
dat_file_size dd ?
.const
dat_file_name db "mycall.txt",0
.code
load_data PROC
;Открытие файла, подготовка буфера для него и считывание файла в буфер
invoke CreateFileA,offset dat_file_name,GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL
@if(eax==INVALID_HANDLE_VALUE)
mov eax,FATAL_DAT_FILE_OPEN
jmp ld_out
@endif
mov dat_file,eax
invoke GetFileSize,eax,NULL
@if(eax==0ffffffffh)
mov eax,FATAL_DAT_FILE_SIZE
jmp ld_out
@endif
mov dat_file_size,eax
add eax,3
invoke GlobalAlloc,GMEM_FIXED,eax
@if(!eax)
mov eax,FATAL_DAT_BUF_ALLOC
jmp ld_out
@endif
mov dat_buffer,eax
invoke ReadFile,dat_file,eax,dat_file_size,offset bytes_readwrite,NULL
@if(!eax)
mov eax,FATAL_DAT_FILE_READ
jmp ld_out
@endif
;Замена слэшей, CR, LF и пробелов на 0h, добавление в конец 1h
xor ebx,ebx
mov esi,dat_buffer
@while(ebx<dat_file_size)
mov al,byte ptr [esi][ebx]
xor edx,edx
@if(al==0dh)
inc edx
@elseif(al==0ah)
inc edx
@elseif(al==' ')
inc edx
@elseif(al=='/')
inc edx
@endif
@if(edx)
mov byte ptr [esi][ebx],0
@endif
inc ebx
@endw
mov word ptr [esi][ebx],0
add ebx,2
mov byte ptr [esi][ebx],1
xor eax,eax
ld_out:
push eax
invoke CloseHandle,dat_file
pop eax
ret
load_data ENDP
;ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ
;Используются при разборе содержимого буфера данных:
;skip_nz - пропуск всех символов, пока не встретится 00h
;skip_nz2 - пропуск всех символов, пока не встретится 0000h
;///////////////////////////////////////////////////////////// Пропуски
skip_nz PROC
@while(byte ptr[esi]!=0)
inc esi
@endw
nc esi
ret
skip_nz ENDP
;=============================================================
skip_nz2 PROC
@while(word ptr[esi]!=0)
inc esi
@endw
add esi,2
ret
skip_nz2 ENDP
;ИЗМЕНЕНИЕ СОЕДИНЕНИЯ
;Функция вызывается при выборе в списке соединений нового соединения
;///////////////////////////////////////////////////////////// Изменение соединения
change_con PROC
;Установка указателя на запись текущего соединения
xor edx,edx ;sub_string
xor ecx,ecx ;cur_con
mov esi,dat_buffer
@while(byte ptr[esi]!=1)
@if(ecx==current_con)
@break
@endif
@if(word ptr[esi]==0)
inc edx
@if(edx==3)
inc ecx
xor edx,edx
@endif
inc esi
@endif
inc esi
@endw
call skip_nz2
;Формирование списка телефонов для данного соединения и установка его позиции
push esi
invoke SendMessageA,phon_window,CB_RESETCONTENT,0,0
pop esi
xor edx,edx;number
@while(byte ptr[esi]!=0)
@push esi,edx
invoke SendMessageA,phon_window,CB_ADDSTRING,0,esi
@pop esi,edx
inc edx
call skip_nz
@endw
inc esi
mov ebx,current_con
@if(con_phone[ebx]>=dl)
mov byte ptr con_phone[ebx],0
@endif
xor eax,eax
mov al,con_phone[ebx]
invoke SendMessageA,phon_window,CB_SETCURSEL,eax,esi
; Формирование списка логинов для данного соединения и установка его позиции
invoke SendMessageA,user_window,CB_RESETCONTENT,0,0
xor edx,edx
@while(byte ptr[esi]!=0)
@push esi,edx
invoke SendMessageA,user_window,CB_ADDSTRING,0,esi
@pop esi,edx
inc edx
call skip_nz
call skip_nz
@endw
mov ebx,current_con
@if(con_user[ebx]>=dl)
mov byte ptr con_user[ebx],0
@endif
xor eax,eax
mov al,con_user[ebx]
invoke SendMessageA,user_window,CB_SETCURSEL,eax,esi
ret
change_con ENDP
;АВАРИЙНОЕ ЗАВЕРШЕНИЕ ПРИЛОЖЕНИЯ
;Вызывается в случае, когда продолжение работы приложения невозможно
;///////////////////////////////////////////////////////////// Фатальный аборт
.const
fatal_caption db "MyCall Error",0
fatal_txt db "Can't register main window class",0
db "Can't create main window",0
db "Can't open mycall.txt",0
db "Can't get size of mycall.txt",0
db "Can't allocate memory for mycall.txt",0
db "Can't read mycall.txt",0
db "Remote Access Service fatal error",0
.code
fatal PROC fatal_code
mov esi,offset fatal_txt
@while(fatal_code)
@while(byte ptr [esi])
inc esi
@endw
inc esi
dec fatal_code
@endw
invoke MessageBoxA,NULL,esi,offset fatal_caption,MB_OK OR MB_ICONERROR
ret
fatal ENDP
;В(Ы)КЛЮЧЕНИЕ ОРГАНОВ УПРАВЛЕНИЯ
;Меняет надпись на кнопке и активизирует списки в зависимости от того,
;находится приложение в режиме выбора или в режиме соединения
;///////////////////////////////////////////////////////////// Выключение органов управления
.const
ec_txt0 db "Call",0
ec_txt1 db "HangUp",0
.code
disable_controls PROC disable
mov eax,win_height
@if(disable)
add eax,12
@endif
invoke SetWindowPos,main_window,NULL,NULL,NULL,win_width,eax,SWP_NOMOVE OR SWP_NOZORDER
@if(disable)
mov eax,offset ec_txt1
@else
mov eax,offset ec_txt0
@endif
invoke SendMessageA,butt_window,WM_SETTEXT,0,eax
mov eax,disable
xor eax,1h
push eax
invoke EnableWindow,conn_window,eax
pop eax
push eax
invoke EnableWindow,phon_window,eax
pop eax
invoke EnableWindow,user_window,eax
ret
disable_controls ENDP
;ДОЗВОН
;///////////////////////////////////////////////////////////// Дозвон
.data?
ras_dial_params RASDIALPARAMS{}
.code
ras_dial PROC
;Подготовка структуры RASDIALPARAMS
mov ras_dial_params.dwSize,sizeof(RASDIALPARAMS)
mov ras_dial_params.szCallbackNumber,0
mov ras_dial_params.szDomain,0
mov esi,dat_buffer
xor ebx,ebx
@while(ebx!=current_con)
xor ecx,ecx
@while(ecx<3)
call skip_nz2
inc ecx
@endw
inc ebx
@endw
mov ras_dial_params.szEntryName,0
push esi
invoke lstrcpy,offset ras_dial_params.szEntryName,esi
pop esi
call skip_nz
mov ras_dial_params.szPhoneNumber,0
push esi
invoke lstrcpy,offset ras_dial_params.szPhoneNumber,esi
pop esi
call skip_nz
dec esi
@while(byte ptr[esi]==0)
inc esi
@endw
xor ecx,ecx
mov ebx,current_con
@while(con_phone[ebx]!=cl)
call skip_nz
inc ecx
@endw
push esi
invoke lstrcat,offset ras_dial_params.szPhoneNumber,esi
pop esi
call skip_nz2
xor ecx,ecx
mov ebx,current_con
@while(con_user[ebx]!=cl)
call skip_nz
call skip_nz
inc ecx
@endw
mov ras_dial_params.szUserName,0
push esi
invoke lstrcat,offset ras_dial_params.szUserName,esi
pop esi
call skip_nz
mov ras_dial_params.szPassword,0
push esi
invoke lstrcat,offset ras_dial_params.szPassword,esi
pop esi
;Дозвон
mov ras_conn,0
invoke RasDialA,0,0,offset ras_dial_params,0,ras_dial_func,offset ras_conn
@if(eax)
call ras_hangup
invoke fatal,FATAL_RAS
invoke ExitProcess,EXIT_RAS_ERROR
@else
mov online,TRUE
@endif
ret
ras_dial ENDP
;ФУНКЦИЯ КОНТРОЛЯ СОСТОЯНИЯ СОЕДИНЕНИЯ
;============================================================= RAS Callback function
.const
ras_state_text db "OpenPort",0
db "PortOpened",0
db "ConnectDevice",0
db "DeviceConnected",0
db "AllDevicesConnected",0
db "Authenticate",0
db "AuthNotify",0
db "AuthRetry",0
db "AuthCallback",0
db "AuthChangePassword",0
db "AuthProject",0
db "AuthLinkSpeed",0
db "AuthAck",0
db "ReAuthenticate",0
db "Authenticated",0
db "PrepareForCallback",0
db "WaitForModemReset",0
db "WaitForCallback",0
db "Projected",0
db "StartAuthentication",0
db "CallbackComplete",0
db "LogonNetwork",0
db "SubEntryConnected",0
db "SubEntryDisconnected",0
db "Interactive",0
db "RetryAuthentication",0
db "CallbackSetByCaller",0
db "PasswordExpired",0
db "Connected",0
db "Disconnected",0
db "Unknown state",0
.data?
ras_monitor_id dd ?
.code
ras_dial_func PROC PUBLIC type_of_event,rasconnstate,ras_error
push esi
@if(online)
;Показ состояния соединения в строке статуса
mov edx,rasconnstate
@if(edx>=RASCS_Connected)
sub edx,RASCS_Connected-28
@else
@if(edx>=RASCS_Interactive)
sub edx,RASCS_Interactive-24
@endif
@endif
@if(edx>30)
mov edx,30
@endif
push edx
mov esi,offset ras_state_text
@while(edx)
call skip_nz
dec edx
@endw
invoke SendMessageA,stat_window,WM_SETTEXT,0,esi
pop edx
;Если соединение установлено - запуск нити контроля
@if(rasconnstate==RASCS_Connected)
invoke CreateThread,NULL,0,offset ras_monitor_func,0,0,offset ras_monitor_id
; Если соединение не установлено - передача сообщения WM_USER для
;повтора дозвона
@elseif(rasconnstate==RASCS_Disconnected)
push edx
invoke PostMessageA,main_window,WM_USER,0,0
pop edx
@endif
@endif
pop esi
ret
ras_dial_func ENDP
;НИТЬ МОНИТОРА РАЗРЫВА
; Каждые 200 мс опрашивает состояние установленного соединения.
;При обнаружении факта разъединения посылает сообщение WM_USER
;и завершается
;============================================================= Нить монитора разрыва
.data?
ras_connect_status RASCONNSTATUS{}
.code
ras_monitor_func PROC PUBLIC param
rm_loop:
@if(online)
mov ras_connect_status.dwSize,sizeof(RASCONNSTATUS)
invoke RasGetConnectStatusA,ras_conn,offset ras_connect_status
@if(!eax)
@if(ras_connect_status.rasconnstate!=RASCS_Disconnected)
invoke Sleep,200
jmp rm_loop
@endif
@endif
@endif
invoke PostMessageA,main_window,WM_USER,0,1
ret
ras_monitor_func ENDP
;ПОЛОЖИТЬ ТРУБКУ
;Если происходит дозвон, или соединение установлено,
;дает команду на разрыв и ожидает, когда RAS ее выполнит
;///////////////////////////////////////////////////////////// Положить трубку
ras_hangup PROC
mov online,FALSE
@if(ras_conn)
invoke RasHangUpA,ras_conn
rh_loop:
mov ras_connect_status.dwSize,sizeof(RASCONNSTATUS)
invoke RasGetConnectStatusA,ras_conn,offset ras_connect_status
@if(eax!=ERROR_INVALID_HANDLE)
invoke Sleep,0
jmp rh_loop
@endif
@endif
mov ras_conn,0
invoke Sleep,1000
ret
ras_hangup ENDP
br>
;#############################################################
end
Main.cpp для mycall (c++)
Это основной файл приложения MyCall на C++. Этот файл в текстовом формате вместе со всеми остальными файлами, необходимыми для компиляции приложения MyCall, содержится в zip-файле mycallcb.zip (13192 байта). Имеется также Инструкция программиста.
Для получения комментариев щелкaйте по тексту или пользуйтесь групповым управлением:
if(dhtml){document.write("Все комментарии: [+][-] Открывать: [несколько]");}
//Включение заголовочных файлов
//Здесь windows.h и ras.h - стандартные из пакета MS Visual C++,
//а main.h - заголовочный файл приложения MyCall
#include "windows.h"
#include "ras.h"
#include "main.h"
//ГЛАВНАЯ ФУНКЦИЯ ПРИЛОЖЕНИЯ
/////////////////////////////////////////////////////////////// WinMain
int WINAPI WinMain(HINSTANCE hinst,HINSTANCE prev_hinst,LPSTR command_line,int cmd_show){
//Получение дескриптора экземпляра приложения
//Необходимое действие, так как предполагается компиляция приложения без
//подключения runtime-библиотеки. Подробнее...
hinst=GetModuleHandle(NULL);
//Создание главного окна
//Обычное действие, с которого начинаются большинство приложений.
//Единственное отличие в том, что в качестве главного окна в MyCall
//используется окно диалога, описанного в файле ресурсов
//Регистрируется класс главного окна:
WNDCLASSEX mw_class;
mw_class.cbSize=sizeof(WNDCLASSEX);
mw_class.style=NULL;
mw_class.lpfnWndProc=superprocedure;
mw_class.cbClsExtra=0;
mw_class.cbWndExtra=DLGWINDOWEXTRA;
mw_class.hInstance=hinst;
mw_class.hIcon=LoadIcon(hinst,MAKEINTRESOURCE(103));
mw_class.hIconSm=NULL;
mw_class.hCursor=LoadCursor(NULL,IDC_ARROW);
mw_class.hbrBackground=(HBRUSH)COLOR_WINDOW;
mw_class.lpszMenuName=NULL;
mw_class.lpszClassName="MainWindowClass";
if(!RegisterClassEx(&mw_class)){fatal(FATAL_MAIN_CLASS_REG);return EXIT_COMMON_ERROR;}
//Главное окно создается:
if(!(main_window=CreateDialog(hinst,MAKEINTRESOURCE(101),NULL,NULL))){fatal(FATAL_MAIN_CLASS_CREATE);return EXIT_COMMON_ERROR;}
// По состоянию списка conn_window формируются остальные списки
change_con();//
//ЦИКЛ ОЖИДАНИЯ СООБЩЕНИЙ
//Неотъемлемый элемент приложений для Windows. В MyCall никаких особенностей не имеет
MSG loop_message;
while (GetMessage(&loop_message,NULL,0,0)){TranslateMessage(&loop_message);DispatchMessage(&loop_message);}
//ЗАВЕРШЕНИЕ РАБОТЫ ПРИЛОЖЕНИЯ
//Поскольку MyCall собирается без runtime-библиотеки, для завершения работы
//обязательно использовать функцию ExitProcess, при этом оператор return оказывается
//недостижимым, но необходим по требованиям синтаксиса. Подробнее...
ExitProcess(loop_message.wParam);
return 0;
}
//ОКОННАЯ ПРОЦЕДУРА
//Стандартный элемент приложений для Windows. В MyCall особенностей не имеет.
/////////////////////////////////////////////////////////////// Оконная процедура
LRESULT CALLBACK superprocedure(HWND window_from,UINT message,WPARAM w_param,LPARAM l_param){
//Разбор и обработка оконных сообщений
switch(message){
//Обработка сообщения WM_COMMAND, передаваемого элементами управления диалога
case WM_COMMAND:
//Обработка уведомления CBN_SELCHANGE, передаваемого списками при изменении позиции:
//1000: список соединений conn_window. Изменяет содержание phon_window и user_window
//1001: список телефонов phon_window. Устанавливает новый телефон для текущего соединения con_phone[current_con]
//1002: список логинов user_window. Устанавливает новый логин для текущего соединения con_user[current_con]
if(HIWORD(w_param)==CBN_SELCHANGE){
switch (LOWORD(w_param)){
case 1000:
current_con=SendMessage(conn_window,CB_GETCURSEL,0,0);
if(current_con==CB_ERR){current_con=0;}
change_con();
return 0;
case 1001:
con_phone[current_con]=SendMessage(phon_window,CB_GETCURSEL,0,0);
if(con_phone[current_con]==CB_ERR){con_phone[current_con]=0;}
return 0;
case 1002:
con_user[current_con]=SendMessage(user_window,CB_GETCURSEL,0,0);
if(con_user[current_con]==CB_ERR){con_user[current_con]=0;}
return 0;
default:
break;
}
return 0;
}
// Обработка уведомления BN_CLICKED, передаваемого кнопкой при клике:
//в состоянии online=TRUE прекращает дозвон (разрывает соединение) и включает списки
//в состоянии online=FALSE отключает списки и начинает дозвон
if((HIWORD(w_param)==BN_CLICKED)&&(LOWORD(w_param))==1003){
if(online){
EnableWindow(butt_window,FALSE);
ras_hangup();
EnableWindow(butt_window,TRUE);
disable_controls(FALSE);
}else{
disable_controls(TRUE);
ras_dial();
}
return 0;
}
break;
//Обработка сообщения WM_USER, используемого в MyCall нитью монитора разрыва.
//Обнаружив факт разрыва соединения, нить монитора передает WM_USER. Если был коннект,
//то включаются списки, в противном случает выполняется повторный дозвон
case WM_USER:
ras_hangup();
l_param?disable_controls(FALSE):ras_dial();
return 0;
//Обработка сообщения WM_MOVE: запоминание новой позиции окна
//для последующей записи ее в файл mycall.ini
case WM_MOVE:
RECT win_pos;
if(GetWindowRect(main_window,&win_pos)){
main_win_left=win_pos.left;
main_win_top=win_pos.top;
/div>
}else{
main_win_left=(int)LOWORD(l_param);
main_win_top=(int)HIWORD(l_param);
}
return 0;
//При завершении работы приложения записать файл mycall.ini,
//и освободить память
case WM_DESTROY:
save_ini();
if(dat_buffer){GlobalFree(dat_buffer);}
PostQuitMessage(EXIT_NORMAL);
return 0;
//При закрытии главного окна заблокировать кнопку
//и разорвать соединение
case WM_CLOSE:
EnableWindow(butt_window,FALSE);
if(online){ras_hangup();}
break;
}
//Обработка глобального оконного сообщения, используемого для
//взаимодействия экземпляров приложения. Получив это сообщение,
//экземпляр, запущенный первым, сообщает дескриптор своего главного окна.
//Сообщение, полученное вторым, принимает от первого дескриптор его главного окна,
//выводит его на первый план и завершается. Подробнее...
if(message==my_message){
if((HWND)w_param!=main_window){
if(l_param==0){
SendMessage(HWND_BROADCAST,my_message,(WPARAM)main_window,1);
}else{
SetForegroundWindow((HWND)w_param);
ExitProcess(EXIT_OVERLOADED);
}
}
return 0;
}
//Возврат необработанных сообщений системе
return DefWindowProc(window_from,message,w_param,l_param);
}
//ЗАГРУЗКА ФАЙЛА ИНИЦИАЛИЗАЦИИ
//Файл mycall.ini хранит состояние списков и положение главного окна на момент
//завершения предыдущего сеанса работы приложения
/////////////////////////////////////////////////////////////// Загрузка файла инициализации
void load_ini(){
//Инициализация глобальных переменных
main_win_left=MAIN_WIN_DEFAULT_LEFT;
main_win_top=MAIN_WIN_DEFAULT_TOP;
current_con=0;
for(int i=0;i<MAX_CON;i++){
con_phone[i]=0;
con_user[i]=0;
}
//Попытка загрузки файла mycall.ini
//В случае неудачи используются значения по умолчанию
HANDLE ini_file=CreateFile(INI_FILE,GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if(ini_file!=INVALID_HANDLE_VALUE){
//Чтение файла mycall.ini
BYTE buffer[INI_FILE_LENGTH];
DWORD bytes_read;
if(ReadFile(ini_file,buffer,INI_FILE_LENGTH,&bytes_read,NULL)){
//Приведение позиции окна к фактическим размерам экрана и запоминание
main_win_left=(((INT)buffer[1])<<8)|((INT)buffer[0]);
if(main_win_left>=GetSystemMetrics(SM_CXSCREEN)-10){main_win_left=MAIN_WIN_DEFAULT_LEFT;}
main_win_top=(((INT)buffer[3])<<8)|((INT)buffer[2]);
if(main_win_top>=GetSystemMetrics(SM_CYSCREEN)-10){main_win_top=MAIN_WIN_DEFAULT_TOP;}
//Запоминание позиций списков телефонов и логинов
current_con=(INT)buffer[4];
for(int i=0;i<MAX_CON;i++){
con_phone[i]=(INT)buffer[(i<<1)+5];
con_user[i]=(INT)buffer[(i<<1)+6];
}
}
CloseHandle(ini_file);
}
}
//СОХРАНЕНИЕ ФАЙЛА ИНИЦИАЛИЗАЦИИ mycall.ini
//В файле сохраняются положение главного окна на экране и позиции
//списков на момент завершения работы приложения
/////////////////////////////////////////////////////////////// Сохранение файла инициализации
void save_ini(){
HANDLE ini_file=CreateFile(INI_FILE,GENERIC_WRITE,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
if(ini_file!=INVALID_HANDLE_VALUE){
BYTE buffer[INI_FILE_LENGTH];
buffer[0]=(BYTE)main_win_left;
buffer[1]=(BYTE)(main_win_left>>8);
buffer[2]=(BYTE)main_win_top;
buffer[3]=(BYTE)(main_win_top>>8);
buffer[4]=(BYTE)current_con;
for(int i=0;i<MAX_CON;i++){
buffer[(i<<1)+5]=(BYTE)con_phone[i];
buffer[(i<<1)+6]=(BYTE)con_user[i];
}
DWORD bytes_written;
WriteFile(ini_file,buffer,INI_FILE_LENGTH,&bytes_written,NULL);
CloseHandle(ini_file);
}
}
//ЗАГРУЗКА ФАЙЛА ДАННЫХ mycall.dat
// Файл содержит имена соединений, телефоны и логины
/////////////////////////////////////////////////////////////// Загрузка файла данных
BOOL load_data(){
//Открытие файла, подготовка буфера для него и считывание файла в буфер
HANDLE dat_file=CreateFile(DAT_FILE,GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if(dat_file==INVALID_HANDLE_VALUE){fatal(FATAL_DAT_FILE_OPEN);return FALSE;}
DWORD dat_file_size=GetFileSize(dat_file,NULL);
if(dat_file_size==0xffffffff){CloseHandle(dat_file);fatal(FATAL_DAT_FILE_SIZE);return FALSE;}
dat_buffer=(LPBYTE)GlobalAlloc(GMEM_FIXED,dat_file_size+3);
if(dat_buffer==NULL){CloseHandle(dat_file);fatal(FATAL_DAT_BUF_ALLOC);return FALSE;}
DWORD bytes_read;
if(!ReadFile(dat_file,dat_buffer,dat_file_size,&bytes_read,NULL)){CloseHandle(dat_file);fatal(FATAL_DAT_FILE_READ);return FALSE;}
CloseHandle(dat_file);
//Замена слэшей, CR, LF и пробелов на 0h, добавление в конец 1h
ptr=dat_buffer;
for(DWORD i=0;i<dat_file_size;i++){
if((*ptr==0xd)||(*ptr==0xa)||(*ptr=='/')||(*ptr==' ')){*ptr=0;}
ptr++;}
dat_buffer[i]=0;dat_buffer[i+1]=0;dat_buffer[i+2]=1;
return TRUE;
}
//ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ
//Используются при разборе содержимого буфера данных:
//skip_nz - пропуск всех символов, пока не встретится 00h
//skip_nz2 - пропуск всех символов, пока не встретится 0000h
/////////////////////////////////////////////////////////////// Пропуски
VOID skip_nz(){
while(*ptr!=0){ptr++;}
ptr++;
}
VOID skip_nz2(){
while(*(LPWORD)ptr!=0){ptr++;}
ptr+=2;
}
//ИЗМЕНЕНИЕ СОЕДИНЕНИЯ
//Функция вызывается при выборе в списке соединений нового соединения
/////////////////////////////////////////////////////////////// Изменение соединения
VOID change_con(){
// Установка указателя на запись текущего соединения
INT sub_string=0;
INT cur_con=0;
ptr=dat_buffer;
while(*ptr!=1){
if(cur_con==current_con){break;}
if(*(LPWORD)ptr==0){
if(++sub_string==3){cur_con++;sub_string=0;}
ptr++;
}
ptr++;
}
skip_nz2();
//Формирование списка телефонов для данного соединения и установка его позиции
SendMessage(phon_window,CB_RESETCONTENT,0,0);
int number=0;
while(*ptr!=0){
SendMessage(phon_window,CB_ADDSTRING,0,(LPARAM)ptr);
number++;
skip_nz();
}
ptr++;
if(con_phone[current_con]>=number){con_phone[current_con]=0;}
SendMessage(phon_window,CB_SETCURSEL,(WPARAM)con_phone[current_con],0);
//Формирование списка логинов для данного соединения и установка его позиции
SendMessage(user_window,CB_RESETCONTENT,0,0);
number=0;
while(*ptr!=0){
SendMessage(user_window,CB_ADDSTRING,0,(LPARAM)ptr);
number++;
skip_nz();
skip_nz();
}
if(con_user[current_con]>=number){con_user[current_con]=0;}
SendMessage(user_window,CB_SETCURSEL,(WPARAM)con_user[current_con],0);
}
//АВАРИЙНОЕ ЗАВЕРШЕНИЕ ПРИЛОЖЕНИЯ
//Вызывается в случае, когда продолжение работы приложения невозможно
/////////////////////////////////////////////////////////////// Фатальный аборт
void fatal(int fatal_code){
LPCSTR fatal_text[]={
"Can't register main window class",
"Can't create main window",
"Can't open mycall.txt",
"Can't get size of mycall.txt",
"Can't allocate memory for mycall.txt",
"Can't read mycall.txt",
"Remote Access Service fatal error"};
MessageBox(NULL,fatal_text[fatal_code],"MyCall Error",MB_OK|MB_ICONERROR);
}
//В(Ы)КЛЮЧЕНИЕ ОРГАНОВ УПРАВЛЕНИЯ
//Меняет надпись на кнопке и активизирует списки в зависимости от того,
//находится приложение в режиме выбора или в режиме соединения
/////////////////////////////////////////////////////////////// В(ы)ключение органов управления
VOID disable_controls(BOOL disable){
SetWindowPos(main_window,NULL,NULL,NULL,win_width,win_height+(disable?12:0),SWP_NOMOVE|SWP_NOZORDER);
SendMessage(butt_window,WM_SETTEXT,0,(LPARAM)(disable?"HangUp":"Call"));
EnableWindow(conn_window,!disable);
EnableWindow(phon_window,!disable);
EnableWindow(user_window,!disable);
}
//ДОЗВОН
/////////////////////////////////////////////////////////////// Дозвон
VOID ras_dial(){
//Подготовка структуры RASDIALPARAMS
RASDIALPARAMS ras_dial_params;
ras_dial_params.dwSize=sizeof(RASDIALPARAMS);
ras_dial_params.szCallbackNumber[0]=0;
ras_dial_params.szDomain[0]=0;
ptr=dat_buffer;
for(int j=0;j!=current_con;j++){for(int k=0;k<3;k++){skip_nz2();}}
ras_dial_params.szEntryName[0]=0;
lstrcpy(ras_dial_params.szEntryName,(LPSTR)ptr);
skip_nz();
ras_dial_params.szPhoneNumber[0]=0;
lstrcpy(ras_dial_params.szPhoneNumber,(LPSTR)ptr);
skip_nz();ptr--;
while(*ptr==0){ptr++;}
for(j=0;j!=con_phone[current_con];j++){skip_nz();}
lstrcat(ras_dial_params.szPhoneNumber,(LPSTR)ptr);
skip_nz2();
for(j=0;j!=con_user[current_con];j++){skip_nz();skip_nz();}
ras_dial_params.szUserName[0]=0;
lstrcpy(ras_dial_params.szUserName,(LPSTR)ptr);
skip_nz();
ras_dial_params.szPassword[0]=0;
lstrcpy(ras_dial_params.szPassword,(LPSTR)ptr);
//Дозвон
ras_conn=NULL;
if(RasDial(0,0,&ras_dial_params,0,ras_dial_func,&ras_conn)){
ras_hangup();
fatal(FATAL_RAS);
ExitProcess(EXIT_RAS_ERROR);
}else{
online=TRUE;
}
}
//ФУНКЦИЯ КОНТРОЛЯ СОСТОЯНИЯ СОЕДИНЕНИЯ
/////////////////////////////////////////////////////////////// Контроль состояния соединения
VOID WINAPI ras_dial_func(UINT msg,RASCONNSTATE rasconnstate,DWORD error){
LPCSTR ras_state_text[]={
"OpenPort",
"PortOpened",
"ConnectDevice",
"DeviceConnected",
"AllDevicesConnected",
"Authenticate",
"AuthNotify",
"AuthRetry",
"AuthCallback",
"AuthChangePassword",
"AuthProject",
"AuthLinkSpeed",
"AuthAck",
"ReAuthenticate",
"Authenticated",
"PrepareForCallback",
"WaitForModemReset",
"WaitForCallback",
"Projected",
"StartAuthentication",
"CallbackComplete",
"LogonNetwork",
"SubEntryConnected",
"SubEntryDisconnected",
"Interactive",
"RetryAuthentication",
"CallbackSetByCaller",
"PasswordExpired",
"Connected",
"Disconnected",
"Unknown state"};
if(online){
//Показ состояния соединения в строке статуса
UINT ras_message_num=rasconnstate;
if(ras_message_num>=RASCS_Connected){ras_message_num-=RASCS_Connected-28;}
else{if(ras_message_num>=RASCS_Interactive){ras_message_num-=RASCS_Interactive-24;}}
if(ras_message_num>30){ras_message_num=30;}
SendMessage(stat_window,WM_SETTEXT,0,(LPARAM)ras_state_text[ras_message_num]);
//Если соединение установлено - запуск нити контроля
if(rasconnstate==RASCS_Connected){
CreateThread(NULL,0,ras_monitor_func,0,0,&ras_monitor_id);
//Если соединение не установлено - передача сообщения WM_USER для
//повтора дозвона
}else{
if(rasconnstate==RASCS_Disconnected){
PostMessage(main_window,WM_USER,0,0);
}
}
}
}
//НИТЬ МОНИТОРА РАЗРЫВА
// Каждые 200 мс опрашивает состояние установленного соединения.
//При обнаружении факта разъединения посылает сообщение WM_USER
//и завершается
//============================================================= Нить монитора разрыва
DWORD WINAPI ras_monitor_func(LPVOID param){
RASCONNSTATUS ras_connect_status;
while(online){
ras_connect_status.dwSize=sizeof(RASCONNSTATUS);
if(RasGetConnectStatus(ras_conn,&ras_connect_status)){break;}
if(ras_connect_status.rasconnstate==RASCS_Disconnected){break;}
Sleep(200);
}
PostMessage(main_window,WM_USER,0,1);
return 0;
}
//ПОЛОЖИТЬ ТРУБКУ
//Если происходит дозвон, или соединение установлено,
//дает команду на разрыв и ожидает, когда RAS ее выполнит
/////////////////////////////////////////////////////////////// Положить трубку
VOID ras_hangup(){
RASCONNSTATUS ras_connect_status;
online=FALSE;
if(ras_conn){
RasHangUp(ras_conn);
while(TRUE){
ras_connect_status.dwSize=sizeof(RASCONNSTATUS);
if(RasGetConnectStatus(ras_conn,&ras_connect_status)==ERROR_INVALID_HANDLE){break;}
Sleep(0);
}
ras_conn=0;
Sleep(1000);
}
}
Main.h для mycall (c++)
Это файл заголовков для приложения MyCall на C++. Этот файл в текстовом формате вместе со всеми остальными файлами, необходимыми для компиляции приложения MyCall, содержится в zip-файле mycallcb.zip (13192 байта). Имеется также Инструкция программиста.
Для получения комментариев щелкaйте по тексту или пользуйтесь групповым управлением:
if(dhtml){document.write("Все комментарии: [+][-] Открывать: [несколько]");}
//Положение главного окна на экране по умолчанию (когда mycall.ini недоступен)
#define MAIN_WIN_DEFAULT_LEFT 100
#define MAIN_WIN_DEFAULT_TOP 100
//Максимальное количество позиций в списках
#define MAX_CON 16
//Размер файла mycall.ini
#define INI_FILE_LENGTH 4+1+MAX_CON*2
//Коды выхода приложения:
//EXIT_NORMAL: нормальное завершение работы
//EXIT_COMMON_ERROR: фатальная ошибка
//EXIT_OVERLOADED: экземпляр приложения уже существует
//EXIT_RAS_ERROR: ошибка Remote Access Service
#define EXIT_NORMAL 0
#define EXIT_COMMON_ERROR 1
#define EXIT_OVERLOADED 2
#define EXIT_RAS_ERROR 3
//Коды фатальных ошибок
#define FATAL_MAIN_CLASS_REG 0
#define FATAL_MAIN_CLASS_CREATE 1
#define FATAL_DAT_FILE_OPEN 2
#define FATAL_DAT_FILE_SIZE 3
#define FATAL_DAT_BUF_ALLOC 4
#define FATAL_DAT_FILE_READ 5
#define FATAL_RAS 6
//Имена используемых файлов
#define INI_FILE "mycall.ini"
#define DAT_FILE "mycall.txt"
//Объявления функций
LRESULT CALLBACK superprocedure (HWND,UINT,WPARAM,LPARAM);
VOID load_ini();
VOID save_ini();
BOOL load_data();
VOID change_con();
VOID fatal(INT);
VOID disable_controls(BOOL);
VOID ras_dial();
VOID ras_hangup();
VOID ras_control();
VOID WINAPI ras_dial_func(UINT,RASCONNSTATE,DWORD);
DWORD WINAPI ras_monitor_func(LPVOID);
//Дескрипторы окон и элементов управления
HWND main_window;
HWND conn_window;
HWND phon_window;
HWND user_window;
HWND butt_window;
HWND stat_window;
//Положение и размеры главного окна
INT main_win_left;
INT main_win_top;
LONG win_width;
LONG win_height;
//Текущее соединение
INT current_con;
//Массивы телефонов и логинов для текущего соещинения
INT con_phone[MAX_CON];
INT con_user[MAX_CON];
//Дескриптор установленного соединения
HRASCONN ras_conn;
//Идентификатор нити монитора разрыва
DWORD ras_monitor_id;
//Код регистрируемого глобального оконного сообщения "MyCallMessage"
UINT my_message;
//Флаг состояния приложения:
//FALSE: выбор соединения пользователем
//TRUE: выполняется дозвон или поддерживается коннект
BOOL online=FALSE;
//Буфер для mycall.txt
LPBYTE dat_buffer=0;
//Указатель внутри этого буфера
LPBYTE ptr;
Main.inc для mycall (ассемблер)
Это файл заголовков для приложения MyCall на ассемблере. Этот файл в текстовом формате вместе со всеми остальными файлами, необходимыми для компиляции приложения MyCall, содержится в zip-файле mycallab.zip (15913 байт). Имеется также Инструкция программиста.
Для получения комментариев щелкaйте по тексту или пользуйтесь групповым управлением:
if(dhtml){document.write("Все комментарии: [+][-] Открывать: [несколько]");}
;КОНСТАНТЫ
//Положение главного окна на экране по умолчанию (когда mycall.ini недоступен)
MAIN_WIN_DEFAULT_LEFT=100
MAIN_WIN_DEFAULT_TOP=100
//Максимальное количество позиций в списках
MAX_CON=16
//Размер файла mycall.ini
INI_FILE_LENGTH=4+1+MAX_CON*2
//Коды выхода приложения:
//EXIT_NORMAL: нормальное завершение работы
//EXIT_COMMON_ERROR: фатальная ошибка
//EXIT_OVERLOADED: экземпляр приложения уже существует
//EXIT_RAS_ERROR: ошибка Remote Access Service
EXIT_NORMAL=0
EXIT_COMMON_ERROR=1
EXIT_OVERLOADED=2
EXIT_RAS_ERROR=3
//Коды фатальных ошибок
FATAL_MAIN_CLASS_REG=0
FATAL_MAIN_CLASS_CREATE=1
FATAL_DAT_FILE_OPEN=2
FATAL_DAT_FILE_SIZE=3
FATAL_DAT_BUF_ALLOC=4
FATAL_DAT_FILE_READ=5
FATAL_RAS=6
;ПРОТОТИПЫ ПОЛЬЗОВАТЕЛЬСКИХ ПРОЦЕДУР
;Необходимы в случаях, когда процедура вызывается оператором INVOKE,
;или когда ее адрес передается системной функции API в качестве параметра
fatal PROTO :DWORD
ras_dial_func PROTO :DWORD,:DWORD,:DWORD
ras_monitor_func PROTO :DWORD
;ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ
//Дескрипторы окон и элементов управления
main_window dd ?
conn_window dd ?
phon_window dd ?
user_window dd ?
butt_window dd ?
stat_window dd ?
//Положение и размеры главного окна
main_win_left dd ?
main_win_top dd ?
win_width dd ?
win_height dd ?
//Текущее соединение
current_con dd ?
//Массивы телефонов и логинов для текущего соещинения
con_phone db MAX_CON dup(?)
con_user db MAX_CON dup(?)
//Дескриптор установленного соединения
ras_conn dd ?
//Код регистрируемого глобального оконного сообщения "MyCallMessage"
my_message dd ?
//Флаг состояния приложения:
//FALSE: выбор соединения пользователем
//TRUE: выполняется дозвон или поддерживается коннект
online dd ?
//Буфер для mycall.txt
dat_buffer dd ?
Минимальная stub-программа
"Stub" в переводе с английского - "пень, обломок, огрызок". Это в литературном переводе. В нелитературный перевод углубляться не будем. Скажем только, что это то самое, что показывает компьютер человеку, попытавшемуся запустить из-под DOS приложение, написанное для Windows. Обычно он показывает сообщение: "This program cannot be run in DOS mode". А вы что подумали?
В связи с понятием "stub-программа" обычно возникают три вопроса:
Как она работает? Как сделать собственную stub-программу? Как уменьшить ее до размера, не раздражающего настоящего ассемблерщика?
Как она работает? Чтобы ответить на этот вопрос, следует заглянуть во внутренности формата PE-файла - стандартного исполняемого модуля Windows. Там мы обнаружим следующее:
PE-файл содержит в себе две программы, склеенные друг вслед за дружкой. Первая из них и есть stub-программа в обычном для MS-DOS exe-формате. Вторая - приложение для Windows. stub-программа, как и положено в MS-DOS, начинается в файле с нулевого смещения и первые два байта представляют собой знаменитые инициалы MZ, коими увековечил себя кто-то из корифеев Microsoft (не могу навскидку вспомнить. Кто знает - напишите) (16.05.01 написали. Михаил Орлов сообщил, что это был Mark Zbikovski. Спасибо им обоим.) Можно сказать, вырезал свое имя на каждом пне. С этого имени начинается стандартный заголовок exe-файла MS-DOS. windows-программа начинается с некоторого смещения, кратного параграфу (16 байт) и зависящему от размера stub-программы. Первые два байта windows-программы - это инициалы какого-то другого мужика со странным именем Portable Executable. Далее следует заголовок PE-файла и все остальные его элементы.
PE-формат - не единственный формат исполняемых модулей, который понимает Windows. Но мы не станем здесь рассматривать другие форматы, так как применительно к теме статьи это не имеет смысла.
Глупая DOS загружает exe-файл, обнаруживает в его начале привычный для себя заголовок и исполняет stub-программу, ничего не зная о наличии в файле еще чего-то, кроме слов "а пошел ты туда-то и туда-то".
Подозреваем, что не все компоновщики поддерживают эту опцию. Однако link.exe из состава MS Developer Studio, конечно же, поддерживает.
Написанную stub-программу используйте на этапе компоновки своего Windows-приложения. Для этого в командную строку компоновщика добавьте опцию /STUB:"filename.exe", где filename.exe, как вы можете догадаться - это имя вашей stub-программы, при необходимости с путем. Обнаружив эту опцию, компоновщик заменит стандартную stub-программу на вашу.
Здесь настоящего ассемблерщика подстерегает серьезное разочарование. Проблема в следующем. Обычно при компоновке exe-программ для DOS в исполняемом файле резервируется довольно большое место под таблицу перемещения. Например, в порядке вещей, если таблица перемещения вольготно располагается между смещениями 1ch (конец заголовка) и 200h (начало сегмента кода). Даже если ваша stub-программа не содержит ни одного фрагмента, подлежащего перемещению, все равно 486 байт чистых нулей будет бессмысленно вбацано в нежное девственное тело вашего Windows-приложения. Задачка, стоит ли избегать такого варварского разбазаривания дискового пространства и как это сделать, предлагается для самостоятельного решения. Кое-какие идеи читайте далее.
Минимальная stub-программа. Заглянув внутрь исполняемого модуля какого-нибудь Windows-приложения, вы обнаружите, что стандартная stub-программа чаще всего занимает 128 байт. Можно ли уменьшить этот размер и до какой величины? Отвечаем: можно, до 64 байт.
Возьмите любой редактор бинарных файлов и создайте с его помощью вот такой файл:
000000 4D 5A 00 00 01 00 00 00 02 00 00 00 FF FF 00 00
000010 40 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00
000020 B4 4C CD 21 00 00 00 00 00 00 00 00 00 00 00 00
000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Присвойте ему имя, например, stub.exe и используйте в качестве stub-программы. А если лень набивать самому - можете скачать его отсюда.
Вот расшифровка содержимого файла stub.exe с комментариями:
смещение | значение | назначение | комментарий |
+0 | 5A4D | Подпись exe-файла ('MZ') | |
+2 | 0000 | Длина последней неполной страницы образа, байт | Игнорируется операционной системой |
+4 | 0001 | Длина образа, страниц (страница = 512 байт) | Программа занимает менее одной страницы |
+6 | 0000 | Число элементов в таблице перемещения | В этой программе перемещаемых элементов нет |
+8 | 0002 | Размер exe-заголовка, параграфов (параграф = 16 байт) | Указывается размер базовой части заголовка. С учетом остальных значений параметров в данном случае означает, что исполняемый код в файле начинается со смещения 20h, и стартовый адрес находится в начале исполняемого кода |
+0ah | 0000 | Минимум требуемой памяти за концом программы (параграфов) | В данном случае смысла не имеет |
+0ch | FFFF | Максимум требуемой памяти за концом программы (параграфов) | Традиционно отводится вся доступная память |
+0eh | 0000 | Сегментное смещение сегмента стека (для установки регистра ss) | |
+10h | 0040 | Значение регистра sp (указателя стека) при запуске | В этой программе стек не имеет значения |
+12h | 0000 | Контрольная сумма исполняемого модуля | Не используется |
+14h | 0000 | Значение регистра ip (указателя команд) при запуске | Стартовая точка совпадает с началом кодового сегмента |
+16h | 0000 | Cегментное смещение кодового сегмента (для установки регистра cs) | |
+18h | 0040 | Cмещение в файле 1-го элемента перемещения | В этой программе ни одного элемента перемещения нет, а исполняемый код находится внутри заголовка, поэтому данное значение совпадает с концом программы |
+1ah | 0000 | Номер оверлея | Здесь не используется |
+1eh | 0000 0000 |
Резерв - 4 байта | |
+20h | 4CB4 21CD |
Исполняемый код: mov ah,4ch int 21h |
В данном случае программа просто возвращает управление операционной системе. Максимальный размер исполняемого кода для данных значений заголовка - 28 байт (смещения 20h...3bh). Обратите внимание, что исполняемый код с целью экономии размера программы находится в области, отведенной под exe-заголовок! |
+3ch | 0000 0000 |
Зарезервированное двойное слово | Используется компоновщиком Windows-приложения для размещения смещения PE-заголовка |
Вероятно, уменьшить размер stub- программы до величины, меньшей 64 байт, невозможно. А вот интересный вопрос: возможно ли вообще изъять stub-программу из модуля? Не спешите отвечать "нет"! 16-битные компоновщики от Microsoft имели в синтаксисе def-файла конструкцию вида STUB NONE, которая, согласно документации, предназначалась для использования при сборке dll-библиотек. В 32-битных компоновщиках эта возможность пропала. А жаль...
Вот уж в самом деле, "никогда не говори "никогда"! 24 июля 2001 года, в день падения 114-летнего рекорда жары в Москве, пришло письмо:
На вашем сайте assembler.ru в статье "Минимальная stub-программа" автором упомянуто, что создать stub короче 40h байт, вероятнее всего, невозможно. Привожу пример stub'а длиной в 20h байт: 00000000 4D 5A 00 00 01 00 00 00 01 00 00 00 FF FF 00 00 MZ.............. 00000010 B4 4C CD 21 00 00 00 00 40 00 00 00 00 00 00 00 .L.!....@....... В отличие от примера, приведенного в статье, размер заголовка уменьшен до одного параграфа. В терминах статьи это означает, что stub-код располагается по смещению 10h от начала файла. Очевидно, после такого "сокращения" области заголовка от 10h и выше продолжают использоваться. Но в данном случае это неважно, так как на месте 4CB4h находится initial SP, а на месте 21CDh - checksum. Ни то, ни другое роли не играют. К сожалению, stub-код ограничен в 4 байта, потому что уже следующий word - initial IP. Далее, в файле со stub'ом в 20h байт смещение 3Ch от начала файла приходится на смещение 1Ch PE-заголовка. Здесь расположен dword, который, по-видимому, ни на что не влияет, так что можно спокойно ставить туда 00000020h: 00000020 50 45 00 00 4C 01 07 00 21 B1 97 36 00 00 00 00 PE..L...!..6.... 00000030 00 00 00 00 E0 00 0E 01 0B 01 04 14 20 00 00 00 ............ ... Такую штуку вряд ли поддерживают компоновщики (мне от них этого добиться не удалось), я лично правил EXE вручную. Зато работает. Напоследок - пример тоже рабочего stub'а длиной в 18h байт - меньше, по-моему, некуда: 00000000 4D 5A 00 00 01 00 00 00 00 00 00 00 B0 21 CD 29 MZ...........!.) 00000010 B4 4C CD 21 0C 00 00 00 50 45 00 00 4C 01 07 00 .L.!....PE..L... 00000020 21 B1 97 36 00 00 00 00 00 00 00 00 E0 00 0E 01 !..6............ 00000030 0B 01 04 14 20 00 00 00 00 2E 00 00 18 00 00 00 .... ........... Из-под DOS он выводит восклицательный знак (21h). Ну как, размер достоин настоящего ассемблерщика? автор - Grief (soul_inspector@chat.ru) |
Действительно, ни отнять, ни прибавить: только в голову настоящего ассемблерщика могут придти трюки, позволяющие так, чтобы помягче сказать, использовать формат исполняемого файла. Еще раз, вот они:
Когда делается попытка запустить программу из-под DOS, загрузчик, определив по сигнатуре "MZ", что это исполняемый файл, считывает по смещению 08h размер exe-заголовка, а по смещению 14h стартовое значение регистра ip, чтобы узнать, где находится исполняемый код. В предложенном Grief'ом варианте stub'а длиной 20h он таким образом выясняет, что передавать управление следует на смещение 10h (просто выход в DOS посредством функции 4Ch), а в варианте длиной 18h - на смещение 0Ch (здесь перед выходом успевает еще напечататься восклицательный знак). И неважно, что занятые исполняемым кодом байты на самом деле являются полями exe-заголовка "Максимум требуемой памяти за концом программы", "Сегментное смещение сегмента стека", "Значение регистра sp" и "Контрольная сумма исполняемого модуля". Все эти поля в данном случае не влияют работу программы. Если же программа запускается из-под Windows,то загрузчик, опять же разобравшись с назначением файла, первым делом считывает двойное слово по смещению 03ch, чтобы узнать, где находится PE-заголовок. Так вот, здесь это смещение оказывается внутри самого PE-заголовка! В варианте "18h" оно попадает на поле, содержащее суммарный размер секций неинициализированных данных в образе приложения, а в варианте "20h" - на поле, содержащее размер секций кода. Использование этих полей в современных нам версиях Windows неочевидно. По одним источникам - они используются для первичного отведения памяти под приложение. По другим - не используются вообще. Во всяком случае, практическая проверка показывает, что приложения с таким stub'ом работают нормально. (Что будет дальше - пожалуй, неизвестно даже Microsoft'у.)
Конечно же, заставить какой-нибудь компоновщик создать исполняемый файл с предлагаемыми stub'ами не удастся. Такие трюки придется делать вручную, с помощью какого-нибудь бинарного редактора: переместить образ приложения внутри файла на нужное смещение, отредактировать exe-заголовок, PE-заголовок, заголовки секций - ну, в общем, кто знает формат - тот справится. (А лучше доверить эту рутину какой-нибудь маленькой программуле.)
Ну и напоследок, опровергнув свои же слова что "...меньше некуда...", Grief прислал вообще шедевр - вполне работающий stub длиной всего 0Ch!!! Разобраться, как он устроен, вы сможете самостоятельно, руководствуясь уже достаточно ясной идеей: в заголовках полным-полно никому не нужных полей, которые мы можем использовать для своих целей. Итак:
00000000 4D 5A 00 00 01 00 00 00 01 00 00 00 50 45 00 00 MZ..........PE..
00000010 4C 01 07 00 08 00 00 00 B0 21 CD 29 B4 4C CD 21 L........!.).L.!
00000020 E0 00 0E 01 0B 01 04 14 20 00 00 00 00 2E 00 00 ........ .......
00000030 18 00 00 00 00 E0 00 00 00 10 00 00 0C 00 00 00 ................
Минимальное приложение
Вопрос о минимальной программе для того или иного языка программирования и операционной среды обычно возникает в следующих случаях:
когда начинающий программист дочитывает до конца первую главу учебника и до него доходит, что большинство известных ему программ умеют нечто большее, чем тупо приветствовать мир. Пытливый ум новичка подсказывает ему, что если встречаются программы, умеющие больше этого, то, значит, где-то есть и программы, умеющие меньше когда начинающий программист робко, опасаясь быть проигнорированным, замодерированным или посланным, забрасывает вопрос в какую-нибудь конференцию. Последующие события, как правило, приводят его в шок, надолго отбивая охоту приступать к прочтению второй главы. Со всех концов бескрайней саванны мгновенно слетаются и сбегаются бесчисленные гуру, возникает потасовка, из облака пыли доносятся рык, ржание, визги и предсмертные хрипы, разлетаются перья и клоки шерсти. Великие считают и пересчитывают строчки в исходниках, потом буквочки в них же, а потом и байтики в екзешниках. Вопли стихают только после того, когда кто-нибудь предлагает подсчитывать биты, установленные в 1, и все вдруг понимают, почему двери в психушках открываются исключительно вовнутрь. Вывалив языки и тяжко дыша, братия расползается по своим кельям зализывать раны и готовиться к грядущим битвам. Посередине вытоптанной поляны, одинокий, жалкий и забытый, остывает трупик несчастного новичка когда новичок, отчаявшись познать истину самостоятельно или испить ее из ладоней великих, записывается на компьютерные курсы, и, с трудом досидев на первом занятии до сакраментального "У кого есть вопросы?", тянет руку и получает в ответ краткое изложение всей программы обучения. Затем, сверившись со списком группы и перезвонив в кассу, преподаватель также дает обещание, что к концу курса студент сам сможет элементарно ответить на этот простейший вопрос.
Между тем вопрос о минимальной программе - совсем не простейший, и представляет отнюдь не академический интерес. Минимальная программа, очевидно, решает две задачи: (1)стартует и (2)завершается в конкретной рабочей среде. И то, и другое она должна делать корректно, с тем, чтобы:
В результате компиляции и сборки этого приложения получается исполняемый файл размером 1536 байт. Из них собственно код занимает 10 байт. Все остальное - на совести создателей формата PE-файла. Приложение безропотно запускается и не задумываясь завершает свою работу, возвращая код выхода.
Что здесь что и зачем?
.386 - директива ассемблера, определяющая набор команд процессора, который может быть использован в программе. Для приложений win32 необходимо использовать эту директиву или выше, в зависимости от того, собираетесь ли вы использовать возможности, предоставляемые процессорами последующих поколений .model flat,stdcall - сегментная директива ассемблера, определяющая сегментную модель приложения как плоскую, использующую соглашения о вызове процедур, принятые в win32. Именно такая сегментная модель должна всегда использоваться при написании приложений для win32 ExitProcess PROTO :DWORD - прототип функции API, выполняющей завершение приложения. (Поскольку сервис этой функции предоставляется dll-библиотекой kernel32.dll, то при сборке приложения хорошо бы не забыть подключить библиотеку импорта kernel32.lib) .code - сегментная директива ассемблера, определяющая начало сегмента кода WinMain PROC PUBLIC hinst,prev_hinst,command_line,cmd_show - начало тела стартовой процедуры. Следует обратить внимание на наличие директивы видимости (visibility) PUBLIC, которая позволит сборщику сделать процедуру доступной для операционной системы, дабы та смогла передать управление приложению. Параметры процедуры обсудим чуть ниже, правда, новость будет не очень приятной. ;... - здесь должна быть всякая прочая мелочь, которую вы хотели бы заставить делать ваше приложение invoke ExitProcess,0 - собственно вызов функции завершения приложения. Здесь код выхода 0, но он, естественно, может быть любым в пределах 32-разрядного целого. В полноценных приложениях нормой считается определение кода выхода в цикле обработки сообщений главного окна. WinMain ENDP - всякое тело должно иметь конец. Это он и есть. Правда, в силу наличия предыдущей строки, он в данной программе не достижим, но это и не требуется: здесь мы просто исполняем необходимую формальность. Кстати, настоящим ассемблерщикам должно быть приятно, что, опустив ненужную в связи с этим команду ret, мы сэкономили аж 4 байта кода! end - конец модуля.
Замечание 1. По поводу необходимости применения функции ExitProcess. Именно с ее помощью, а не посредством команды ret, как могли бы ожидать знатоки C/C++, должно завершаться приложение win32, написанное на ассемблере.
Если залезть внутрь runtime-библиотеки C/C++, то мы увидим, что именно так завершает работу приложения уже упоминавшаяся функция _WinMainCRTStartup. Но поскольку подключать runtime-библиотеку C/C++ к ассемблерной программе как-то нелогично (хотя и вполне возможно), мы должны вызвать ExitProcess "вручную". Только эта функция корректно завершает работу приложения, в частности, оповещая использовавшиеся приложением библиотеки DLL о необходимости декрементировать их счетчики занятости и, при обнулении последних, выгрузиться из памяти.
Замечание 2. А вот и обещанная плохая новость про параметры стартовой процедуры. Дело в том, что установкой их значений занимается - кто бы вы думали? - опять же _WinMainCRTStartup! И следовательно, в ассемблерных программах, где ее нет, при входе в WinMain параметры содержат:
hinst - не дескриптор текущего экземпляра приложения, а количество акций Microsoft у Билла Гейтса prev_hinst - не 0, а прогнозируемое значение NASDAQ после выхода на мировой рынок российской ОС BrokenWindows command_line - не указатель на командную строку, а традиционный народный российский указатель направления cmd_show - не предлагаемый начальный вид окна, а то, что обещал показать мировому сообществу Никита Сергеевич Хрущов (и даже, помнится, начал раздеваться)
Так что, воленс-ноленс, придется получать эту ценную информацию самостоятельно. Как говорится, клик хере.
Замечание 3. И все-таки приведенный текст приложения - не самый минимальный. Можно сделать его еще меньше, для чего следует убрать все параметры функции WinMain. Поскольку приложение собирается без runtime-библиотеки C/C++, это вполне допустимо, так как больше не с чем выполнять ее связывание. В результате объем исполняемого файла останется тем же, а вот объем исполняемого кода в нем (образа приложения) сократится аж до 7 байт. Про это стоит почитать еще.
Замечание 4. В этой статье рассматривается минимизация размера кода программы, но отнюдь не exe-файла, ее содержащего. Как известно, исполняемые файлы Windows обычно имеют так называемый PE-формат, и манипуляции с этим форматом дают некоторые возможности для сокращения размера файла. Например, можно вместо стандартной stub-программы использовать более компактную, собственной разработки.
Ms devstudio - среда разработки asm
Настоящие ассемблерщики - народ неприхотливый, хорошая находка для рачительной хозяйки. Едят, как правило, мало, и не особо разбираясь, что (одного моего друга жена, большая любительница животных, накормила как-то ради эксперимента педигрипалом. Очень хвалил). Место в помещении почти не занимают: не более двух кв.м вместе с компьютером. Линяют не чаще раза в год. В спячку, правда, не впадают, зато гон непродолжительный и тихий. В совокупности представляют собой прекрасный вторичный рынок для компьютеров 386 серии. Основой операционной среды предпочитают видеть кастрированный по самое ядро Norton Commander 5.0. Трансляцию запускают из командной строки (самые волевые с трудом заставляют себя написать batch-файл). Отладчик при разработке собственных приложений вообще не используют.
Поэтому Microsoft Developer Studio для них - как стадион "Уэмбли" для единственной выжившей после зимовки коровы симментальской породы из колхоза "Привет коммунизму": гораздо больше, чем нужно. И на такие его (MS DevStudio, а не "Уэмбли") мелкие недостатки, как отсутствие поддержки ассемблера, пенять не приходится.
Впрочем, к делу. Первый шаг - это подготовка MS DevStudio для работы с MASM:
Инсталлируйте, если не сделали этого до сих пор, MS Developer Studio в варианте Visual C++. Никаких особенностей здесь нет - все должно быть так же, как для программирования на C++ Найдите где-нибудь MASM 6.11с или более поздний. Чистый 6.11 (без "c") лучше не использовать: есть у него кое-какие проблемы. (В частности, выравнивает структуры максимум на 4, а в win32 часто требуется выравнивать на 8.) Сейчас щедрый американский дядюшка позволяет пользоваться MASM бесплатно, а совсем недавно приходилось покупать его за хорошие зеленые деньги. Есть он, например, в составе MSDN. Или его можно скачать из Сети (только не с ftp.microsoft.com. Рыбные места вы сможете найти в разделе ссылок нашего сайта). По минимуму из всего пакета потребуется один лишь модуль ml.exe размером около 400 Кбайт. Поместите ml.exe в папку исполняемых модулей пакета MS DevStudio (по умолчанию - C:\Program Files\DevStudio\VC\BIN).
Ваш пакет MS Developer Studio готов к разработке приложений win32 на MASM.
Любознательный читатель спросит: почему бы не использовать для разработки приложений inline-ассемблер, являющийся, как известно, неотъемлемым атрибутом C++. Вообще-то ничего невозможного на свете нет. Можно и отверткой в ухе ковыряться, только не очень глубоко. Но надо помнить, что inline-ассемблер предназначен для улучшения языка C++, а не для разработки самостоятельных приложений. Поэтому из него исключены большинство директив и, самое обидное, все макросредства, сильно помогающие облегчить процесс программирования.
Следующий шаг - это подготовка включаемых файлов:
Получите файл includes.zip
Распакуйте его и поместите файлы @struct.inc и windows.inc в папку включаемых файлов пакета MS DevStudio (по умолчанию - C:\Program Files\DevStudio\VC\INCLUDE).
Файл @struct.inc - это авторская отсебятина, которой в принципе можно и не пользоваться, а можно и наоборот, добавить в него что-нибудь свое, полезное. Что и для чего в этом файле, вы можете прочитать здесь.
С файлом windows.inc все гораздо сложнее. Это файл заголовков API, ассемблерный аналог той тучи h-файлов, которые содержатся в пакете Visual C++ и начинаются с файла windows.h (и, возможно, не только). Поскольку Microsoft официально не поставляет файл windows.inc (в отличие от windows.h), у ассемблерного программиста в связи с этим возникает большая-пребольшая проблема. Ее можно попытаться решить несколькими путями:
транслировать windows.inc из windows.h с помощью специальной программы-переводчика H2INC, поставляемой в составе пакета MASM 6.11+. Сразу скажем, что следует смело отправить эту надежду в мусоропровод. Продравшись сквозь частокол багов H2INC, вы обнаружите, что ее интеллекта хватает лишь на трансляцию тривиальнейших заголовочных файлов, к которым windows.h, безусловно, не относится. Вы утонете в бездне сообщений о некорректном переводе, и ваш сизифов труд по их обходу будет вознагражден получением совершенно негодного результата найти windows.inc (вариант - win.inc) в Интернете. Это гораздо более реально. Существует некоторое количество чудаков (возможно, это дети миллионеров, которым наплевать на необходимость зарабатывать на жизнь), которые тратят свое время на формирование и актуализацию этого файла и раздают его всем желающим. Некоторых из них вы найдете в разделе ссылок. Есть мнение, что пользоваться такими файлами настоятельно не рекомендуется. Представьте себе ситуацию к концу второго месяца возни этого доброго человека с каким-нибудь пятым по уровню вложенности заголовочным файлом в пирамиде windows.h. Возьмет, да и перепутает с устатку порядок описания членов какой-нибудь структуры. Или забудет указать ее выравнивание относительно умалчиваемого. Хорошо еще, если соответствующая функция API при использовании ее вами будет вываливаться с фатальной ошибкой вроде нарушения общей защиты. А может ведь и не вываливаться. И будете вы разбираться, почему глючит ваше приложение, дольше, чем добрый дядя переводил заголовочный файл описывать структуры, объявлять прототипы и делать все прочие действия, которые делает windows.h, внутри собственного проекта. Это еще более реальный вариант. Для этого вам потребуется оперативный доступ к содержимому windows.h из своего проекта (см.ниже). Каждый раз, как у вас возникнет необходимость обратиться к какой-нибудь функции API, вы должны будете найти в windows.h все, что касается ее вызова, и включить это в свой проект, предварительно переведя вручную на язык ассемблера. При наличии небольшого навыка такой перевод выполняется довольно быстро и проблем не вызывает делать все то же самое, но не в рамках текущего проекта, а в едином файле windows.inc, чтобы не повторять эти действия в последующих проектах. Со временем необходимость править windows.inc будет возникать у вас все реже и реже
Именно последний вариант и предлагается в качестве основного. Файл windows.inc, который мы вам предложили выше, взят из состава приложения MyCall. В нем только самая малая часть из того, что должно быть, и есть кое-что лишнее (RAS, например, в Visual C++ имеет собственный заголовочный файл). Но для старта этого достаточно.
Не забудьте только включить свой windows.inc в процедуру архивирования (надеемся, таковая у вас имеется). Будет обидно начинать все сначала после какой-нибудь очередной переинсталляции системы.
Ну вот, а теперь пора создать новый проект:
Запустите MS DevStudio Выберите в меню: File/New
В появившемся диалоге выберите вкладку Projects
В списке типов проектов выберите Win32 Application
В поле Location укажите каталог, в который предполагаете поместить папку проекта В поле Project Name укажите желаемое имя проекта. Учтите, что по умолчанию это имя будет присвоено исполняемому модулю и, кроме того, папке, в которой будут находиться файлы проекта на этапе его разработки. (Поле Location автоматически дополняется именем папки проекта.) По остальным элементам диалога убедитесь, что DevStudio собирается создавать для вашего проекта новое рабочее пространство (Create new workspace) для платформы Win32 Нажмите кнопку OK
Новый проект создан.
В рамках вновь созданного проекта первым делом необходимо создать файл с исходным текстом:
Нажмите кнопку New Text File или создайте текстовый файл посредством диалога, вызываемого через меню File/New
Сохраните вновь созданный файл командой меню File/Save As. При этом дайте файлу желаемое имя с расширением .asm, например, main.asm. В принципе, расширение тоже может быть любым, но лучше не отходить от общепринятых стандартов Подключите файл к проекту. Для этого, например, можно в окне Workspace на вкладке FileView щелкнуть правой кнопкой мыши по заголовку пока пустого списка файлов проекта и в появившемся контекстном меню выбрать пункт Add Files to Project. В файловом диалоге, чтобы увидеть наш новый файл, придется переключиться в режим Все Файлы.
А теперь самое важное. Файл когда-нибудь придется компилировать. Подготовиться к этому следует уже сейчас:
В окне Workspace на вкладке FileView щелкните правой кнопкой мыши по имени файла В появившемся контекстном меню выберите пункт Settings. Появится диалог Project Settings в режиме установок для файла В диалоге выберите вкладку Custom Build
В списке Settings for выберите пункт Win32 Debug
В текстовом поле Build command(s) введите новую команду:
ml.exe /c /coff /nologo /Zi /DDEBUG $(InputPath)
В текстовом поле Output file(s) введите текст:
.\$(InputName).obj
В списке Settings for выберите пункт Win32 Release
В поле Build command(s) введите новую команду:
ml.exe /c /coff /nologo $(InputPath)
В поле Output file(s) введите текст:
.\$(InputName).obj
Файл готов к компиляции с помощью модуля ml.exe. Обратите внимание, что пункты 5 и 6 касаются компиляции asm-файла для отладочного варианта проекта, и получаемый в этом случае объектный файл содержит отладочную информацию. Пункты же 8 и 9 касаются окончательного варианта проекта, готового к дистрибуции.
Наконец, наш файл должен иметь какое-нибудь корректное содержимое. Допустим, мы разрабатываем минимальное приложение. Учитывая, что кое-что из него уже имеется в файле @struct.inc, его содержимое может быть, например, таким:
include @struct.inc include c:\program files\devstudio\vc\include\windows.inc .code WinMain PROC PUBLIC hinst,prev_hinst,command_line,cmd_show invoke ExitProcess,0 WinMain ENDP end |
Обратите внимание, что имя включаемого файла windows.inc указано с полным путем. Это не случайно. В процессе разработки приложения вам придется очень часто обращаться к этому файлу, добавляя в него заголовочную информацию. В MS DevStudio это сделать очень просто, причем несколькими способами. Например, можно выделить имя файла и, щелкнув по нему правой кнопкой, выбрать пункт контекстного меню Open Document. Но удобнее все-таки подключить файл windows.inc к проекту "на постоянно", воспользовавшись правой кнопкой мыши в окне Workspace. Если вы удачно попадете по имени проекта или по какой-нибудь папке, вам станет доступна опция Add files to Project (Folder)... С ее помощью-то вы и подключите файл к проекту, и в дальнейшем простым двойным кликом сможете вызывать его на редактирование.
Теперь перейдем к сборке проекта:
В окне Workspace на вкладке FileView щелкните правой кнопкой мыши по заголовку списка файлов (он же имя проекта) В появившемся контекстном меню выберите пункт Settings. Появится диалог Project Settings в режиме установок для проекта В диалоге выберите вкладку Link
В списке Settings for выберите пункт Win32 Debug
В списке Category выберите пункт General
В поле Output file name вы можете при желании указать имя создаваемого исполняемого модуля, если вас не устраивает имя по умолчанию Установите флажок Ignore all default libraries. Таким образом вы откажетесь от подключения runtime-библиотеки C/C++ и сэкономите несколько десятков килобайт кода. Подробнее см. здесь и здесь. В списке Category выберите пункт Output
В поле Entry-point symbol укажите WinMain (в данном случае) - имя функции, являющейся точкой входа для проекта В списке Settings for выберите пункт Win32 Release
Проделайте все то же самое по п.п.6...9
Все, проект готов к сборке. Нажмите кнопку Build (F7) и убедитесь, что сборка проходит нормально. Можете также попробовать запустить полученный исполняемый модуль проекта и убедиться, что ничего не происходит: он выполняется и завершается слишком быстро и без каких-либо видимых признаков.
Выше мы обещали обеспечить удобный доступ к файлам заголовков API, чтобы можно было пользоваться ими как справочником при подготовке файла windows.inc. Для этого нам потребуется активизировать встроенное средство MS DevStudio под названием Browse Info. Работает оно очень просто. Достаточно щелкнуть по интересующему нас имени в исходном тексте программы правой клавишей мыши и выбрать пункт контекстного меню Go To Definition Of, как моментально загрузится заголовочный файл Visual C++, в котором это имя описано. В проектах на C/C++ активизация Browse Info происходит автоматически, при первой попытке выполнить указанное действие. А вот в проектах на ассемблере, к сожалению, этого нет. Но ничего страшного. Просто нужно немножко повозиться.
И наконец, маленькое замечание о зависимостях (Dependencies). Не забывайте о них. Например, файл main.asm, очевидно, зависит от windows.inc. И поскольку последний будет очень часто подвергаться изменениям, в том числе и вне текущего проекта, имеет прямой смысл эту зависимость описать. При этом в каждом случае, когда происходит изменение файла windows.inc, будет автоматически вызываться перекомпиляция файла main.asm. В MS DevStudio зависимости описываются очень просто (хотя и не очень удобно: приходится вводить имя файла вручную), гораздо проще, чем в заковыристых исторических make-файлах. Снова вызовите диалог Project Settings для файла main.asm. Найдите на вкладке Custom Build кнопку Dependencies и в появившеся диалоге укажите полное имя файла windows.inc.
Все, MS Developer Studio готово для ассемблерного программирования. Можно предъявить много всяких претензий по сервису и оформлению, но главное - это работает.
Mycall.rc для mycall
Это файл описания ресурсов для приложения MyCall. Этот файл вместе со всеми остальными файлами, необходимыми для компиляции приложения MyCall, содержится в zip-файлах mycallcb.zip (C++, 13192 байта) и в mycallab.zip (ассемблер, 15913 байт). Имеется также Инструкция программиста.
Для получения комментариев щелкaйте по тексту или пользуйтесь групповым управлением:
if(dhtml){document.write("Все комментарии: [+][-] Открывать: [несколько]");}
//ВКЛЮЧАЕМЫЙ ФАЙЛ С ОПРЕДЕЛЕНИЯМИ КОНСТАНТ
//(cтандартный файл заголовков win32 из пакета MS Developer Studio)
#include "windows.h"
//ОБЪЯВЛЕНИЕ ИМЕН ИДЕНТИФИКАТОРОВ РЕСУРСОВ
//Обычно выносится в файл заголовков
//(по умолчанию в MS Developer Studio - resource.h)
//Содержится здесь по причине крайней простоты приложения.
#define IDD_DIALOG1 101
#define IDI_ICON1 103
#define IDC_COMBO1 1000
#define IDC_COMBO2 1001
#define IDC_COMBO3 1002
#define IDC_BUTTON1 1003
#define IDC_EDIT1 1004
//ОПИСАНИЕ ДИАЛОГА
//Следует обратить внимание на наличие определения CLASS.
//Обычно его применение не рекомендуется, так как оно перегружает стандартную
//обработку диалога и передает поток диалоговых сообщений процедуре окна,
//для которого зарегистрирован указанный класс. Однако в нашем случае, когда диалог
//используется в качестве главного окна приложения, это нормально.
IDD_DIALOG1 DIALOG DISCARDABLE 100,100,217,18
STYLE WS_CAPTION|WS_SYSMENU
CAPTION "MyCall"
CLASS "MainWindowClass"
FONT 8,"MS Sans Serif"
BEGIN
COMBOBOX IDC_COMBO1,2,2,43,120,CBS_DROPDOWNLIST|WS_VSCROLL
COMBOBOX IDC_COMBO2,48,2,58,120,CBS_DROPDOWNLIST|WS_VSCROLL
OMBOBOX IDC_COMBO3,108,2,68,120,CBS_DROPDOWNLIST|WS_VSCROLL
PUSHBUTTON "Call",IDC_BUTTON1,179,2,35,13
EDITTEXT IDC_EDIT1,2,17,215,12,ES_READONLY|NOT WS_BORDER
END
//ОПИСАНИЕ ИКОНКИ
IDI_ICON1 ICON DISCARDABLE "icon1.ico"
Обсуждение статьи "Зачем он нужен, этот ассемблер?"
Читайте также:
статью "Зачем он нужен, этот ассемблер?" дополнение Геннадия Майко
Нам приходят письма, в которых читатели излагают свои соображения, сомнения и возражения по теме, обсуждаемой в статье Зачем он нужен, этот ассемблер? Последнее мы получили совсем недавно от MemoBreaker'а. Автор излагает в нем мнение, во многом совпадающее с мнением других людей, чьи письма мы получали ранее и обсуждали в приватных беседах по е-мэйлу. И поскольку эта точка зрения изложена в письме MemoBreaker'а наиболее полно, мы с удовольствием и благодарностью публикуем его с разрешения автора:
Hello assembler.ru, Вот прочитал я вашу статью: "зачем он нужен, этот ассемблер?". И возникло у меня несколько соображений по этому поводу... Сразу отмечу, что сам я начинающий ассемблерщик (на PC, а вообще АСМ я знаю еще с Z80). Я не согласен с некоторыми утверждениями этой статьи: 0) Насчет ООП - все правильно, нормально в нем разобраться мне еще не удалось, хотя я надеюсь изучить С++. Далеко не каждый из знакомых мне С++ программистов смог нормально рассказать что такое полиморфизм, инкапсуляция и т.д. 1) Включать в список файлов, которые необходимо создавать самому, windows.inc нет смысла. Так как если особо не извращаться и писать на ЧИСТОМ MASM'е, то данный и другие файлы с описанием API уже есть в стандартной поставке. Писать не очень большие проги на масме гораздо удобнее и, по-моему, быстрее, нежели использовать монструозную Visual Studio. 2) Насчет размера файлов асма и С++ все в корне не верно! Если вы что-то сравниваете, то необходимо провести несколько сравнений: во-первых минимальное приложение на асме, выводящее только MessageBox, занимает, если не ошибаюсь полтора килобайта (меньше по-моему Винда не позволяет); во-вторых минимальное приложение, имеющее функцию WinMain, занимает 4608 байт(включая иконку, без оной - 3584); в-третьих нужно сравнивать и более серьезные проекты, причем, чем больше функций у программы, тем больше выигрыш по размеру при программировании на ассемблере. В результате если написать WordPad на ассемблере и на С++, то ассемблерная прога будет в несколько раз меньше, нежели СИшная. 3) Неудобство ассемблера заключается в том, что иногда простые ошибки очень сложно отловить, так, например, было с моей (и The Byte Reaper'а тоже) программой UIN2IP. Суть в том, что в MASM32 изначально были допущены ошибки во встроенной подпрограмме (из MASM32.lib) конвертации ASCII to DWORD, есть в масме и еще пара ошибочек, но менее существенных. В отношении ошибок С++ тоже есть в чем упрекнуть, бывали случаи, когда С++ сильно ругался на куски кода, которые ошибки (по крайней мере в теории) не имели. Хуже всего, когда на код ругается линкер - понять в чем ошибка становится гораздо сложнее. 4) Есть еще несколько применений ассемблера: - на нем удобно писать такие вещи как VxD драйвера (не знаю возможно ли это на С++) - ассемблер используется в критичных к скорости выполнения программах (можно, конечно, и INLINE, но как сами говорили "можно и отверткой в ухе...") Только не надо говорить, что современные процессоры позволяют добиваться приемлемых скоростей и на С++, представьте, что вы пишете движок для рендеринга, но не используя ускоритель. В данном случае С++ отстанет от асма процентов на 50 точно (если писать под конкретный процессор на ассемблере). Другой пример, более реальный, - вы пишете программу, проводящую большое количество вычислений (тот же подбор пароля), вот здесь-то скорость и пригодится как нельзя лучше! Ну вот в принципе и все претензии. Надеюсь что они будут рассмотрены и учтены :) -- Best regards, MemoBreaker P.S. Кстати, по моему личному мнению лучшее место для ассемблера в современной индустрии программо-строения - различные .DLL , в них исчезают некоторые недостатки асма и проявляются достоинства: - почти всегда отсутствует интерфейс, следовательно над ним не придется мучиться. - необходима высокая скорость работы, для чего асм подходит лучше всего (пример: сжатие MP3 или MPEG; пользователь как всегда хочет побыстрее!) - также возможна оптимизация под различные MMX, 3D-NOW!, SSE и т.д. |
MemoBreaker в работает с пакетом MASM32 и считает его лучшим вариантом ассемблера для PC (в письме, говоря об ассемблере, он имеет в виду именно этот пакет). В качестве основы для рабочей среды он предпочитает не Visual Studio, а:
MASM32 Win32api.hlp Aditor v2.x (текстовый редактор) парочку утилит SoftIce
В аргументах MemoBreaker'а есть многое, с чем можно согласиться. Например, с тем, что MASM32 - действительно идеальное средство для разработки ассемблерных приложений под Windows. И входящий в его состав файл windows.inc, который весит сейчас уже больше 800 Кбайт - действительно освобождает программиста от необходимости создавать этого монстра самому, потому что сделан достаточно хорошо, со знанием специфики и, что самое главное, постоянно обновляется. И компилятор ml.exe, который входит в пакет - всегда самой последней версии. И это при всем при том, что Microsoft не имеет к MASM32 никакого отношения (ну разве что компилятор родом оттуда). Потому что MASM32 - это детище независимых разработчиков во главе с Iсzelion'ом.
Вы всегда можете найти самую свежую версию пакета MASM32 на его официальном сайте. Кстати, дизайн сайта достаточно красноречиво говорит о дворянском происхождении MASM32, что, впрочем, ничуть не умаляет уважения к титаническому труду создателей пакета.
Но некоторые положения статьи, которые вызывают возражения у наших корреспондентов, хотелось бы пояснить.
Собираясь ставить эксперимент по сравнению ассемблерной и сишной версий MyCall, мы изначально хотели наглядно показать преимущества ассемблерной программы. У нас совсем не было планов показывать, что никаких радикальных преимуществ нет, а вот очевидные недостатки (в первую очередь - большая трудоемкость) - имеются. Мало того, мы ведь вложили в этот эксперимент несколько больше, чем вы, дорогие читатели, в прочтение нашей статьи. И поэтому вы должны представить себе наше разочарование конечными результатами.
Проведенный эксперимент совершенно не претендует на роль истины в последней инстанции. MemoBreaker предлагает провести несколько сравнений: от минимального приложения до большого проекта, и только тогда делать выводы. Наверное, такой масштабный эксперимент дал бы много свежей пищи для размышлений. К сожалению, ничего подобного мы нигде не встречали ранее, да и вряд ли встретим когда-либо. По нашим наблюдениям, то, что сделали мы с MyCall - единственная реальная попытка подтвердить (или опровергнуть?) на практике так часто выдвигаемый тезис о прекрасной приспособленности ассемблера для создания приложений для Windows.
И если абстрагироваться от оптимистических оценок, и опираться только на сухие результаты этого единственного эксперимента, то мы вынуждены повторить неутешительный вывод, уже сделанный в оригинале статьи. Нет никаких причин разрабатывать приложения для Windows на ассемблере, за исключением одной-единственной: личного предпочтения разработчика.
Другое дело, следует ли считать эту причину существенной. По нашему мнению - да, стоит. Потому что мы глубоко убеждены, что склонность к программированию на ассемблере - это не следствие образования или опыта работы, а в первую очередь следствие типа мышления данного конкретного индивидуума. И этот тезис мы повторяли и будем повторять на страницах сайта. Начиная с шуточного психологического теста и кончая этими вполне серьезными рассуждениями.
Современное программирование базируется на абстракции, причем чем дальше - тем больше. Это естественный путь, проложенный от табуляторов первых ламповых ЭВМ до той далекой точки за горизонтом, где машинные языки в конечном итоге сольются с высшим проявлением абстракции - нормальным человеческим языком.
К сожалению, дар абстракции дан людям не в равной степени. Наверное, в этом проявляется рудиментарное наследие каменного века, когда одни индивидуумы абстрактно загоняли мамонтов в ловчие ямы, а другие вполне конкретно мочили несчастных сверху булыжниками и дубинами. В результате у первых больше развилось интуитивное правое полушарие головного мозга, а у вторых - рациональное левое. И теперь, по причине отсутствия мамонтов, первые занялись программированием на C++, а вторые - на ассемблере.
Еще раз сформулируем нашу позицию касательно размера приложения, выработанную в результате эксперимента с MyCall и на основании собственного опыта разработки приложений для Windows:
Разработка небольших приложений на ассемблере не дает ощутимых преимуществ по объему кода по сравнению с C++, а вернее, C, потому что в таких приложениях объектно-ориентированные возможности C++ обычно неприменимы. Пример тому - MyCall. Что касается больших приложений, то однозначно выявить преимущество ассемблера перед C++ по объему кода практически не представляется возможным. В таких приложениях существенную роль играют диктуемые языком принципы организации программ. Ассемблер предлагает программисту хаотический стиль, и приходится применять усилия и специальные приемы (см., например, статью Лептонный стиль программирования) для преодоления этого недостатка языка. C++, напротив, стимулирует формирование у программиста объектно-ориентированного стиля, и лишь неспособность или нежелание воспринять его приводит к отказу от этого стиля в пользу хаотического. А по-разному организованные программы становятся практически несравнимыми, даже если решают одну и ту же задачу. Еще более неочевидным становится гипотетическое преимущество ассемблера в объеме кода, если вспомнить, что приложения для Windows далеко не полностью состоят собственно из кода. Более того, в массе случаев код занимает в них меньшую часть. А гораздо больший объем занимают разнообразные данные: ресурсы, структуры, таблицы и прочее, прочее, прочее. Понятно, что данные в своем большинстве инвариантны к языку, и, следовательно, их наличие маскирует преимущество ассемблера в компактности кода. Плюс к сказанному: современные компиляторы C++ (в частности, Visual C++), при установленном флажке "Оптимизация по объему" дают практически оптимальный код, лишь в малой степени уступающий коду, написанному вручную на ассемблере. Более того, простая небрежность, лень или неполное знание ассемблерщика способны легко разрушить его призрачное преимущество. А вот глупому компилятору C++ такие человеческие слабости незнакомы - он оптимизирует код всегда.
Похожие аргументы мы высказываем и при рассмотрении преимуществ ассемблера в быстродействии:
Многозадачная операционная среда, каковой является Windows, распределяет ресурсы производительности компьютера отнюдь не по признаку того, на каком языке написано приложение. Вытесняющая многозадачность, не спрашивая желания нашего приложения, выделяет ему тот временной ресурс, который считает нужным, а остальное время отдает другим потокам, маскируя то преимущество в быстродействии, которое мог бы дать ассемблерный код. Развитый сервис API, освободивший ассемблерщика от огромного объема рутинной работы, играет в данном случае злую шутку: вызовы API обрабатываются с одной и той же скоростью вне зависимости от того, на каком языке написано приложение, и в силу этого также стирается преимущество в скорости ассемблерного кода. Такой же эффект производит и система сообщений, без которой не обходится ни одно уважающее себя оконное приложение. Ее обслуживает очень приличный кусок кода, локализованного вне приложения, и отнимающего у приложения свою немалую долю ресурса производительности. В случае разработки приложений с активным использованием графики большая часть гигантской работы по ее обслуживанию все больше ложится на "железо" - 2d и 3d ускорители, которым также безразлично, на чем написано наше приложение. Наконец, современные компиляторы C++ при установленном флажке "Оптимизация по времени" генерируют практически оптимальный код, который дает максимальную скорость так же независимо от настроения и знаний программиста, как и минимальный размер в случае оптимизации по размеру.
В подтверждение этих тезисов приведем письмо, полученное от Dmitry S. Bobrik (спасибо ему!) буквально во время написания этой статьи:
Hello! Можете себе представить - компиленый Сишный код работает БЫСТРЕЕ написаного на Асме! Единственное разумное объяснение - оптимизатор MSVC пишет код учитывая особенности архитектуры процессоров Пентиум (ну и выше...). А на Асме все то ручками, да ручками... обидно даже :-/ wbr, Dmitry http://bcsoft.da.ru http://home.tula.net/frazez |
Дмитрий сообщил, что впервые об этом эффекте он прочитал в мэйл-листе sp-forth@egroups.com
И несколько замечаний в заключение:
Это не похороны ассемблера, хотя бы потому, что:
ему по-прежнему есть и будет место в разработке драйверов устройств ему по-прежнему есть и будет место в приложениях, монополизирующих ресурсы системы:
в играх в обработке потоков данных, в том числе в реальном времени в расчетах и т.д.
вы будете смеяться, но Windows - совсем не единственная операционная система
Это не похороны ассемблера, даже несмотря на пришествие технологии .NET, славной такой компактностью исходного кода, которая не снилась никакому ассемблеру. Это не похороны ассемблера хотя бы потому, что его хоронили уже бессчетное количество раз. А мы программировали, программируем и будем на нем программировать.
Ошибки при вызове функций api
Date: | 17-24 ноября 1999 | ||
Newsgroup: | microsoft.public.masm | ||
Subject: | How to call win32 API? (Как вызвать win32 API?) | ||
Участвуют: | Min Xaphiosis Randall Hyde |
minwang@hotmail.com void_s@ihug.com.au rhyde@shoe-size.com |
Organization: The Internet Group Ltd |
if(dhtml){document.write(" Все сообщения: [+][-] Открывать: [несколько]");}
[+] MinРабочая среда: VC6.0/MASM6.11/NT Server 4.0 .386 .MODEL flat, stdcall PUBLIC _start .DATA .CODE _start: INVOKE MessageBox, NULL, "ok", "test", MB_OK END _start Получаю сообщение: error A2004: symbol type conflict Также пробую MessageBoxA/MessageBoxW, бесполезно. Есть идеи?
Regards, Min [+] XaphiosisHehehehhhehehhehheheh ;)
Мне бы очень понравился такой синтаксис, но вы переоцениваете возможности ассемблера... если только вы не определили макрос MessageBox, вы не можете вызывать его с текстом в кавычках! И даже если вы сделали нечто подобное, вы не включили в строки завершающий ноль.
так что следовало бы поступить так: INVOKE MessageBox, NULL, addr msg_ok, addr msg_test, MB_OK Попробуйте.. Я гарантирую, что это будет работать...
Sincerely X. [+] MinСпасибо за ответ. Но оно по-прежнему не работает :( .386 .MODEL flat, stdcall include win.inc PUBLIC _start .DATA ALIGN DWORD msg_ok BYTE "ok",0 msg_test BYTE "test",0 .CODE _start: INVOKE MessageBox, NULL, addr msg_ok, addr msg_test, MB_OK END _start Сообщение об ошибке: "error A2004: symbol type conflict"
Regards, Min [+] XaphiosisAlright... Я быстренько откомпилировал ваш код на своей системе... Напоминаю, если вы забыли: OPTION CASEMAP:NONE включает чувствительность к регистру, иначе win.inc не работает
кроме того, после MessageBox, мне кажется, вы должны вызвать ExitProcess (правда, это не влияет на компиляцию)
Наконец, я думаю, следует проверить возможность того, что ваш win.inc дефектен (довольно сомнительно)
В любом случае, вот код, который я использовал: Попробуйте и убедитесь, что он работает ;) .386 .MODEL FLAT, STDCALL OPTION CASEMAP:NONE
include <my equivalent of win.inc = various files> PUBLIC _start
. DATA ALIGN DWORD msg_ok BYTE "ok",0 msg_test BYTE "test",0
.CODE _start: INVOKE MessageBox, NULL, addr msg_ok, addr msg_test, MB_OK call ExitProcess ;EXIT POINT END _start Sincerely, X. [+] MinThank you very much.
Оказывается, MASM611 содержит include-файл для win3.1! Поэтому я скачал пакет MASM32 - и все заработало.
Regards, Min [+] Randall HydeЕсли вам действительно нравится такой синтаксис, переходите на HLA (High Level Assembler). Он позволит вам писать код, подобный следующему: MessageBox( MB_OK, "test", "ok", NULL ); (параметры перечислены в обратном порядке, потому что HLA использует соглашения вызова Паскаля, а не C/C++. Вы можете использовать макрос для устранения этой проблемы, если она для вас существенна).
HLA будет автоматически размещать строки в памяти "только для чтения" и предоставлять вам адреса таких строк. Вы можете найти HLA на http://webster.cs.ucr.edu
Randy Hyde
Точка зрения assembler.ru изложена в статье Вызов функций API.
Параметры функции WinMain
Приложения для win32, написанные на ассемблере и собранные без runtime-библиотеки (как это сделать - см. статьи минимальное приложение и ms devstudio - среда разработки asm), работают в среде, несколько отличающейся от той, которая привычна программистам C/C++ и выше. Все дело в том, что runtime-библиотека в значительной мере занимается подготовкой этой среды. Вернее, не сама библиотека (это, конечно, не ее задача), а входящая в нее функция _WinMainCRTStartup. В частности, эта функция устанавливает значения четырех параметров функции WinMain, которую программист воспринимает как стартовую функцию приложения:
WinMain PROC PUBLIC hInstance,hPrevInstance,lpCmdLine,nCmdShow
Здесь:
hInstance - дескриптор текущего экземпляра данного приложения. Это значение используется в дальнейшем для взаимодействия с элементами рабочей среды, связанными с данным приложением: ресурсами, окнами, изображениями и многими другими. Как правило, надобность в знании этого дескриптора возникает почти сразу после запуска экземпляра приложения и сохраняется на протяжении всего срока его жизни. Этот дескриптор локален в рамках данного экземпляра приложения и не имеет смысла для других приложений и других экземпляров этого же приложения. hPrevInstance - должен был бы быть дескриптор предыдущего экземпляра данного приложения. Однако в win32 этот параметр не используется и всегда равен 0. Часто бывает нужно знать, существуют ли в момент запуска приложения другие его экземпляры, и в зависимости от их существования предпринимать какие-либо действия. (Например, отменить запуск нового экземпляра и вывести окно уже существующего экземпляра на передний план.) Так вот, hPrevInstance после гибели win16 стал для этого совершенно бесполезен. (См. статью взаимодействие экземпляров приложения.) lpCmdLine - указатель на командную строку. Сегодня большинство обычных пользователей практически не обращают внимания на архаичные возможности управления приложениями, предоставляемые командной строкой. Однако некоторые программисты все еще используют ее (в том числе и те, которые писали Проводник). Не факт, что командная строка вам понадобится, но знать, как получить к ней доступ - полезно. nCmdShow - параметр, описывающий, в каком виде рекомендуется создать главное окно приложения: скрытое, свернутое, нормальное, развернутое и т.д. Полезен в случаях, когда вызывающий процесс должен определять вид окна вызываемого процесса. Тогда nCmdShow следует использовать при первом вызове функции ShowWindow для главного окна приложения. Если же вы пишете самостоятельное приложение, то вполне допустимо проигнорировать этот параметр и показывать окно в том состоянии, которое вам больше нравится.
И вот все эти ценные и не очень параметры программисту на ассемблере недоступны. Сборка приложения без подключения runtime-библиотеки (например libc.lib) экономит от одного до нескольких десятков килобайт. Но тогда функция _WinMainCRTStartup не существует, а функция WinMain объявляется при сборке как точка входа. И следовательно, значения ее параметров неопределенны. Как же их получить?
Далее использован синтаксис вызова системных функций для masm6.1+
Дескриптор экземпляра приложения узнать очень просто:
invoke GetModuleHandleA,NULL
mov hInstance,eax
Возвращаемое функцией GetModuleHandleA в eax значение - и есть искомый дескриптор. Будучи настоящими ассемблерщиками, мы, как видите, сохраняем его в hInstance, чтобы не пропадали даром целых четыре байта памяти. Между прочим, этот дескриптор фактически представляет собой базовый адрес памяти для размещения образа приложения (image base в терминах формата PE-файла). Для самостоятельных приложений по умолчанию он обычно равен 400000h.
Следует обратить внимание на наличие суффикса A в имени функции, говорящего о том, что мы работаем с кодировкой ANSI. Если вы предпочитаете Unicode, то суффикс должен быть W. Это, так сказать, "ручной" выбор типа кодировки приложения на этапе компиляции. Поскольку функций API, имеющих две версии, большинство, то, в принципе, может иметь смысл автоматизация этого процесса, как это сделано, например, для C++ в MS Developer Studio. Ввести некую настроечную константу и, в зависимости от нее, подключать ту или иную версию функции.
Командную строку следует получать так:
invoke GetCommandLineA
mov lpCmdLine,eax
Возвращаемое в eax значение указывает на буфер в памяти, содержащий текст командной строки с завершающим нулем. Результат, правда, несколько отличается от того, который можно было бы получить из lpCmdLine, будь у нас runtime-библиотека. Функция GetCommandLineA возвращает командную строку, включая полный путь к исполняемому модулю приложения. Путь взят в кавычки, что необходимо, поскольку он может содержать пробелы. Например, командная строка может иметь такой вид:
"D:\myapp\Little Joke.exe" c: /format
Между прочим, lpCmdLine, буде оно нам доступно, содержало бы адрес в этом же самом буфере памяти, следующий за пробелом, которым завершается путь к исполняемому модулю.
Параметр показа главного окна приложения (если он вам действительно нужен) получить несколько труднее. Для этого необходимо воспользоваться фрагментом кода:
.data?
startup_info STARTUPINFO{}
.code
...
invoke GetStartupInfoA,offset startup_info
xor eax,eax
mov ax,startup_info.wShowWindow
mov nCmdShow,eax
...
Уникальная для каждого процесса структура STARTUPINFO заполняется вызывающим процессом, например, Проводником, перед запуском нашего приложения, при этом в нее заносится в том числе и значение параметра показа окна в виде члена wShowWindow, имеющего размер слова. С помощью этого кода мы считываем структуру STARTUPINFO, извлекаем из нее wShowWindow, приводим размер к двойному слову и записываем полученное значение в nCmdShow.
Таким образом, мы восстановили все, что потеряли, когда отказались от сборки ассемблерного приложения с runtime-библиотекой. Вернее, почти все, потому что runtime-библиотека делает чуть-чуть больше, чем установка параметров функции WinMain. Смайл.
И еще одно замечание. Раз уж мы выполняем сборку приложения без runtime-библиотеки, то ничто более не предъявляет к нашей пусковой функции каких-либо требований по ее именованию или числу аргументов. Мы можем назвать ее, например, my_main_function и лишить ее аргументов насовсем (если, допустим, они нам не понадобятся, или мы будем сохранять их значения как-нибудь по-другому), либо оставить только те, которые нам необходимы. Например:
my_main_function PROC PUBLIC inst_handle,command_string
Главное - объявить эту функцию точкой входа приложения с помощью опции /entry:"my_main_function" сборщика link.exe.
Шаблон оконного приложения
Каждый сайт, хоть немного причастный к программированию для Windows, считает своим долгом поместить на своих страницах шаблон оконного приложения. Assembler.ru в этом отношении ничем не хуже остальных. Почему бы и нет? Это настоящее, живое, работающее приложение, чего ж его стыдиться? Многим из нас его хватит, чтобы сказать себе с удовлетворением: "Достаточно, я в себя поверил", и вернуться к любимому третьему видеорежиму. Остальным же, возможно, оно понадобится еще много-много раз для того, чтобы начать с него новый проект, и, потратив полмесяца на его развитие, выяснить, что нужно было начинать с чего-то другого. Итак:
.386 .Model flat,stdcall
include windows.inc ;заголовочный файл API;///////////////////////////////////////////////////////////// windows.inc GetModuleHandleA PROTO :DWORD ;прототипы функций API LoadIconA PROTO :DWORD,:DWORD LoadCursorA PROTO :DWORD,:DWORD RegisterClassExA PROTO :DWORD CreateWindowExA PROTO :DWORD,:DWORD,:DWORD,:DWORD,\ :DWORD,:DWORD,:DWORD,:DWORD,\ :DWORD,:DWORD,:DWORD,:DWORD ShowWindow PROTO :DWORD,:DWORD GetMessageA PROTO :DWORD,:DWORD,:DWORD,:DWORD TranslateMessage PROTO :DWORD DispatchMessageA PROTO :DWORD ExitProcess PROTO :DWORD PostQuitMessage PROTO :DWORD DefWindowProcA PROTO :DWORD,:DWORD,:DWORD,:DWORD
WNDCLASSEXA STRUCT 8 ;структура класса окна cbSize dd ? style dd ? lpfnWndProc dd ? cbClsExtra dd ? cbWndExtra dd ? hInstance dd ? hIcon dd ? hCursor dd ? hbrBackground dd ? lpszMenuName dd ? lpszClassName dd ? hIconSm dd ? WNDCLASSEXA ENDS
POINT STRUCT 8 x dd ? y dd ? POINT ENDS
MSG STRUCT 8 ;структура сообщения hwnd dd ? message dd ? wParam dd ? lParam dd ? time dd ?
pt POINT{} MSG ENDS
NULL =0 ;разные константы IDI_APPLICATION =32512 IDC_ARROW =32512 COLOR_WINDOWFRAME =6 SW_SHOW =5 EXIT_ERROR =0ffffffffh WM_DESTROY =2
CS_VREDRAW =0001h CS_HREDRAW =0002h
WS_OVERLAPPED =00000000h ;константы стилей окна WS_CAPTION =00C00000h WS_SYSMENU =00080000h WS_THICKFRAME =00040000h WS_MINIMIZEBOX =00020000h WS_MAXIMIZEBOX =00010000h
WS_OVERLAPPEDWINDOW= WS_OVERLAPPED OR\ WS_CAPTION OR\ WS_SYSMENU OR\ WS_THICKFRAME OR\ WS_MINIMIZEBOX OR\ WS_MAXIMIZEBOX if(dhtml){document.all["include"].style.display="block";document.all["windows"].style.display="none";}
;///////////////////////////////////////////////////////////// WinMain .const app_name db "WinApp Template",0 ;имя приложения class_name db "WinApp Template Class",0 ;имя класса окна .data? win_class WNDCLASSEXA{} win_handle dd ? loop_message MSG{} .code ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::: WinMain PROC PUBLIC hinst,prev_hinst,command_line,cmd_show ;......................................................... регистрация класса окна mov win_class.cbSize,sizeof(WNDCLASSEXA) mov win_class.style,CS_HREDRAW OR CS_VREDRAW mov win_class.lpfnWndProc,offset win_procedure mov win_class.cbClsExtra,0 mov win_class.cbWndExtra,0 invoke GetModuleHandleA,NULL mov win_class.hInstance,eax invoke LoadIconA,NULL,IDI_APPLICATION mov win_class.hIcon,eax mov win_class.hIconSm,eax invoke LoadCursorA,NULL,IDC_ARROW mov win_class.hCursor,NULL mov win_class.hbrBackground,COLOR_WINDOWFRAME mov win_class.lpszMenuName,NULL mov win_class.lpszClassName,offset class_name invoke RegisterClassExA,offset win_class .if(!eax) jmp abort .endif ;......................................................... создание окна invoke CreateWindowExA,NULL,\ offset class_name,\ offset app_name,\ WS_OVERLAPPEDWINDOW,\ 100,100,300,200,\ NULL,\ NULL,\ win_class.hInstance,\ NULL .if(!eax) jmp abort .endif mov win_handle,eax ;он еще пригодится, только не сегодня ;......................................................... вывод окна на экран invoke ShowWindow,eax,SW_SHOW ;......................................................... инициализация ;здесь можно вставить все необходимые действия для первичной инициализации ;приложения (например, загрузку и разбор файла настроек) ;......................................................... цикл опроса очереди сообщений msg_loop: invoke GetMessageA,offset loop_message,NULL,0,0 .if(!eax) invoke ExitProcess,loop_message.wParam ;успешное завершение приложения .endif invoke TranslateMessage,offset loop_message invoke DispatchMessageA,offset loop_message jmp msg_loop ;......................................................... аварийное завершение abort: invoke ExitProcess,EXIT_ERROR WinMain ENDP
;///////////////////////////////////////////////////////////// Оконная процедура win_procedure PROC window_from,message,w_param,l_param .if(message==WM_DESTROY) invoke PostQuitMessage,0 ; .elseif(message==...) ;обработка остальных необходимых сообщений ; ... ; .elseif(message==...) ; ... ; .elseif(message==...) ; ... .else ;сообщение не обработано invoke DefWindowProcA,window_from,message,w_param,l_param ret .endif xor eax,eax ;сообщение обработано ret win_procedure ENDP
;############################################################# end
Вот, собственно, и все приложение. В нем реализованы фундаментальные концепции общего прикладного програмирования для Windows:
работа начинается с вынужденного объектно-ориентированного действия - громоздкой регистрации класса окна с помощью функции RegisterClassExA. Вы должны сделать это, даже если собираетесь создать всего одно окно с такими свойствами, потому что так повелел когда-то Уильям Гейтс Второй, известный в народе как просто Билл. Впрочем, вы можете избежать этой неприятной процедуры, если воспользуетесь при создании окна одним из предопределенных классов - BUTTON, COMBOBOX , EDIT , LISTBOX , MDICLIENT , SCROLLBAR , STATIC. С помощью, например, класса BUTTON вы можете сделать большую-пребольшую кнопку и нажимать ее день и ночь до умопомрачения, ожидая, пока из floppy-дисковода выскочит банан. А класс STATIC очень удобен для эмуляции забора: написанное на таком окне емкое трехбуквенное слово всегда напомнит вам о необходимости вернуться к повседневным заботам. если регистрация класса окна прошла успешно, можно создать само окно с помощью функции CreateWindowExA. Полученный при ее возврате в регистре eax дескриптор окна следует сохранить до лучших времен. Он потребуется очень часто для выполнения манипуляций с окном. созданное окно полностью функционально за исключением одной мелочи: его не видно. Может, и в самом деле его не стоит показывать, чтобы не позориться. А иногда это действительно не нужно, например, если использовать главное окно для общей диспетчеризации приложения, а для общения с пользователем создавать другие окна. Но если вам действительно нужно показать окно, то это делает функция ShowWindow следующий этап - ваш. Делайте что хотите. Можете пойти попить кофейку, а можете чего-нибудь попрограммировать. Но помните: пока еще в приложении обработка сообщений не идет, поэтому оно мертво. Свежесозданное окно торчит недвижимо на экране как символ стабильности внутриполитических процессов, и ничто не оставляет на нем следов, как на зубах Кристины Пугачевой/Орбакайте/Пресняковой/Орбакайте. и только цикл опроса очереди сообщений оживит ваше окно, и кнопочки в его заголовке начнут нажиматься, вызывая разные полезные эффекты, и границы его станут послушны движениям придавленной слева мыши, и много еще чего разного можно будет сделать с этим окном. потому что в вашем распоряжении теперь - оконная процедура win_procedure. Она принимает все и всяческие сообщения, адресуемые вашему окну всеми, кому не лень, и делает в ответ на них все, что вы пожелаете. В данном случае вы пожелали отправлять все эти сообщения обратно в систему, чтобы она сама делала с окном то, что повелел когда-то Билл. Единственное сообщение, на которое реагирует ваша оконная процедура - это WM_DESTROY, которое означает, что вам надоело любоваться окном, и вы нажали Alt+F4, или щелкнули по кнопочке с крестиком, или выбрали Close в системном меню. Да и поделом ему.
Если вы соберетесь откомпилировать приложение, вы можете сделать это с помощью MS Developer Studio, воспользовавшись нашими рекомендациями.
Только не забудьте удалить из исходного текста либо строку include windows.inc, либо содержимое файла windows.inc, если он у вас имеется в готовом виде.
Старт и завершение приложений
Поэт утверждал, что-де "все начинается с любви". Поэтам вообще свойственно сказануть что-нибудь этакое не подумав, получить гонорар - и в ресторан, а вы потом разбирайтесь, чего он имел ввиду. Например, предположим, имеется пара гомо сапиенс противоположных полов, выращенная до репродуктивного возраста в условиях, гарантированно исключающих ознакомление с процессом размножения двуполых животных вообще и вида гомо сапиенс в частности. Вопрос: сможет ли указанная пара, руководствуясь исключительно собственными наблюдениями разницы физиологического строения своих организмов и инстинктом, реализовать репродуктивную функцию? Низшие существа, как-то: хомячки - могут, проверено. А мы, цари природы, сможем? Разум не помешает? Ой, что-то сомнительно. Похоже, в процессе эволюции мы прикупили себе одно средство выживания - разум, заплатив за него другим - способностью существовать вне информационного поля социума.
Впрочем, что касается приложений win32, то здесь поэт точно был неправ. В этом случае все начинается с того, что вызывающая программа (например, Проводник) подготавливает и вызывает функцию API CreateProcess.
Ту же задачу, в принципе, решают и устаревшие функции WinExec и LoadModule. На самом деле на сегодняшний день они представляют собой всего лишь реализации той же функции CreateProcess.
Еще одно замечание, несколько офф-топик. Поскольку вызываемый процесс совершенно независим от вызывающего, то функция CreateProcess возвращает управление вызывающему процессу обычно до того, как вызываемый процесс закончит свою инициализацию. Если вы хотите, чтобы вызывающий процесс взаимодействовал с вызываемым, следует использовать функцию WaitForInputIdle для того, чтобы подождать завершения инициализации.
Функция CreateProcess:
подготавливает для вновь создаваемого процесса рабочую среду (контекст, context), в которую входят: виртуальное адресное пространство, исполняемый код и данные, набор дескрипторов объектов, набор переменных окружения, базовый приоритет, минимальный и максимальный рабочие наборы страниц памяти в процессе подготовки контекста загружает PE-файл приложения. (Большинство из перечисленных выше элементов контекста определяются содержимым этого файла.) создает в рамках контекста процесса первичный поток (нить, thread) приложения и запускает его исполнение через точку входа приложения. Обычно точкой входа служит скрытая от прикладного программиста функция _WinMainCRTStartup, содержащаяся в runtime-библиотеке C/C++, подключаемой к приложению на этапе его сборки. В приложениях, написанных на ассемблере, без подключения runtime-библиотеки, точкой входа объявляется функция, подготовленная пользователем, обычно WinMain. (Подробнее...)
Функция _WinMainCRTStartup, если таковая имеется, подготавливает рабочую среду для runtime-библиотеки, а также значения параметров для функции WinMain, после чего передает управление функции WinMain.
И, наконец, функция WinMain делает все, что придумает прикладной программист.
Несколько пояснений про элементы контекста. Поскольку assembler.ru не ставит своей целью продублировать всю имеющуюся в природе документацию на Windows, то эти пояснения будут самыми общими.
виртуальное адресное пространство (virtual address space) - это набор адресов памяти от 0 до 0ffffffffh, доступных данному процессу. Каждый процесс имеет свое собственное виртуальное адресное пространство, одно для всех потоков процесса. Физически в каждый момент времени в оперативной памяти присутствует только небольшая часть из 4 Гбайт, доступных процессу, как правило, только то, что ему необходимо для работы. Для этого используется механизм страничной организации. Виртуальное адресное пространство разбито на страницы по 4 Кбайта. Страницы по мере необходимости загружаются в оперативную память или удаляются из нее. Это происходит автоматически, прозрачно для прикладного программиста исполняемый код и данные (executable code and data) - это совокупность кода и данных, загружаемых из соответствующих секций PE-файла приложения. Одни и те же фрагменты кода могут одновременно использоваться несколькими нитями процесса дескрипторы объектов (object handles) - это уникальный для данного процесса набор 32-битных идентификаторов, обеспечивающих обращение к самым разнообразным объектам рабочей среды: файлам, элементам графического интерфейса, процессам, потокам и пр. переменные окружения (environment variables) - это пришедший из DOS и UNIX набор текстовых пар имя=значение. Каждый процесс может иметь собственное окружение, либо пользоваться окружением вызвавшего его процесса базовый приоритет (base priority) - это значение в интервале от 0 до 31, определяющее приоритет каждого потока в многозадачной среде. В зависимости от базового приоритета распределяется процессорное время между всеми активными в данный момент потоками. Базовый приоритет формируется из класса приоритета, который присваивается каждому процессу и уровня приоритета, присваиваемого каждому потоку внутри процесса минимальный рабочий набор (minimum working set) - минимально необходимый для существования активного процесса набор страниц памяти максимальный рабочий набор (maximum working set) - наибольший набор страниц памяти, который может потребоваться активному процессу единовременно
В ОС Windows существует множество способов закончить работу приложения. В MSDN сказано, что это произойдет, если:
любой из потоков приложения вызовет функцию ExitProcess первичный поток приложения завершится командой ret Завершение первичного потока вызовом функции ExitThread в случае наличия других потоков не приведет к завершению приложения.
завершится (любым способом) последний из потоков приложения любой из потоков приложения вызовет функцию TerminateProcess консольное приложение получит от пользователя сигнал CTRL+C (CTRI+BREAK) завершится работа системы (shut down) или работа пользователя в системе (log off).
Надо сказать, что этот список не окончателен. Николай Критский (nkritsky@mail.ru) подсказал еще и такой экзотический, но вполне работоспособный вариант: функцией SetErrorMode отключить выдачу системных сообщений об ошибках, а затем искусственно создать такую ошибку (например, выполнив деление на 0). Возникшая при этом исключительная ситуация приведет к завершению работы приложения.
На самом деле перечисленное разнообразие - только кажущееся, так как некоторые из указанных вариантов - это всего лишь скрытый вызов все той же функции ExitProcess. Это касается и ret в первичном потоке, и CTRL+C для консольного приложения, и даже варианта Николая Критского. Фактически в этих вариантах управление передается в существующие по умолчанию runtime-модуль, процедуру поддержки консоли или обработчик исключительной ситуации, которые и вызовут функцию ExitProcess.
Рекомендуется завершать работу приложения вызовом функции ExitProcess. При этом гарантируется, что система будет освобождена от последствий работы приложения, а именно:
Все занятые приложением dll-библиотеки будут освобождены, то есть для каждой из них будет вызвана входная функция со параметрами отключения. При этом внутренние счетчики занятости библиотек будут декрементированы, и те библиотеки, счетчики которых достигнут 0, будут выгружены из системы. Будут закрыты все дескрипторы объектов, существовавшие в приложении Будет завершена работа всех потоков приложения Объект приложения перейдет в состояние "установлен", так что если кто-то в системе ждал завершения работы нашего приложения, сразу узнает об этом Все объекты потоков приложения также перейдут в состояние "установлен" Статус завершения процесса изменится со значения STILL_ACTIVE на значение, переданное функции ExitProcess
Упомянутая выше функция TerminateProcess делает все то же самое, за исключением освобождения dll-библиотек, и именно поэтому не может быть рекомендована как регулярное средство завершения работы. Ее следует использовать только в каких-то специальных или чрезвычайных случаях.
В заключение необходимо заметить, что отнюдь не все ресурсы, занятые приложением, освобождаются автоматически при завершении его работы. Поэтому хороший стиль программирования предполагает, что программист самостоятельно, не надеясь на систему, должен освобождать занятые им ресурсы по мере исчерпания надобности в них или перед завершением работы приложения.
См. также статьи минимальное приложение и параметры функции WinMain.
@Struct.inc для mycall (ассемблер)
Это файл структурных макросов для приложения MyCall на ассемблере. Этот файл в текстовом формате вместе со всеми остальными файлами, необходимыми для компиляции приложения MyCall, содержится в zip-файле mycallab.zip (15913 байт). Имеется также Инструкция программиста.
Для получения комментариев щелкaйте по тексту или пользуйтесь групповым управлением:
if(dhtml){document.write("Все комментарии: [+][-] Открывать: [несколько]");}
;Этот прием совместно с завершиющим ENDIF позволяет предотвратить
;ошибочное многократное включение файла @struct.inc в проект
IFNDEF @@struct_inc
@@struct_inc=1;
;Замена штатного написания структурных операторов MASM с лидирующей точкой
;на написание с лидирующим @. Повышает читаемость листингов:
;- зритильно выравнивает левый край записи, так как плотность символа @ сравнима
;с плотностью букв, чего не скажешь о точке
;- позволяет легче обнаруживать взглядом структурные операторы, так как символ @
;хорошо заметен в потоке текста
@if equ <.if>
@while equ <.while>
@break equ <.break>
@endif equ <.endif>
@endw equ <.endw>
@else equ <.else>
@elseif equ <.elseif>
@repeat equ <.repeat>
;Пара макросов, позволяющих сохранять в стеке произвольное количество регистров.
;Это очень удобно. Пара команд процессора pusha/popa, сохраняющая все регистры разом,
;применима далеко не всегда, так как восстанавливает все регистры, в том числе и те,
;содержимое которых следовало бы оставить без восстановления. А применять цепочки
;команд push и pop некрасиво - и так ассембленый листинг вытянут в длину.
;Предлагаемые макросы позволяют записывать сохраняемые регистры в строку.
;Причем порядок регистров в обоих макросах одинаков, например: @push eax,ecx,esi и
;@pop eax,ecx,esi. Это позволяет при наборе формировать команду @pop путем
;копирования строки команды @push с последующей заменой слова @push на @pop. Таким
;образом существенно снижается количество труднообнаружимых ошибок, вроде
;перепутывания порядка сохранения/восстановления регистров.
@push MACRO p0,p1,p2,p3,p4,p5,p6,p7,p8,p9,pa,pb,pc,pd,pe,pf
FOR param,<p0,p1,p2,p3,p4,p5,p6,p7,p8,p9,pa,pb,pc,pd,pe,pf>
IFNB <param>
push param
ENDIF
ENDM
ENDM
@pop MACRO p0,p1,p2,p3,p4,p5,p6,p7,p8,p9,pa,pb,pc,pd,pe,pf
FOR param,<pf,pe,pd,pc,pb,pa,p9,p8,p7,p6,p5,p4,p3,p2,p1,p0>
IFNB <param>
pop param
ENDIF
ENDM
ENDM
; Стандартный набор директив для прикладного программирования под win32.
;Вынесен сюда, чтобы не засорять основной листинг.
;ВНИМАНИЕ! Файл @struct.inc должен включаться первым!
.386
.Model flat,stdcall
;См. примечание к первому оператору в этом файле.
ENDIF
Варианты реализации макросов @push/@pop в версии Андрея Бордачева - в статье Макросы First и Second.
Вызов функций api
В этой статье речь идет именно о вызове системных функций API win32, а не об организации процедур в ассемблере вообще. Обсуждаемая здесь тема, следовательно, существенно уже, так как многие возможности языка при вызове функций API не применяются. Например, здесь нет необходимости обсуждать описание процедур, так как функции API написаны добрыми людьми из Misrosoft, и нам самим их писать не придется. Также не придется обсуждать вызов функций с переменным числом параметров и кое-что еще.
Вызов системных функций API win32 из программы на ассемблере подчиняется набору соглашений stdcall, ведущему свою родословную в части именования функций - от языка C, а в части передачи аргументов - от языка Pascal. С точки зрения прикладного программиста и с учетом специфики Windows и MASM эти соглашения заключаются в следующем:
регистр символов в имени функции не имеет значения. Например, функции с именами ILoveYou и iloveyou - это одна и та же функция. Здесь мы наблюдаем отличие от реализации MS Visual C++, где компилятор строго отслеживает совпадение регистра в именах используемых программистом функций с прототипами, содержащимися в системных заголовочных файлах. Сборщику же, как видим, регистр символов безразличен аргументы передаются вызываемой функции через стек. Если аргумент укладывается в 32-битное значение и не подлежит модификации вызываемой функцией, он обычно записывается в стек непосредственно. В остальных случаях программист должен разместить значение аргумента в памяти, а в стек записать 32-битный указатель на него. Таким образом, все передаваемые функции API параметры представляются 32-битными величинами, и количество байт, занимаемых в стеке для передачи аргументов, кратно четырем вызывающая программа загружает аргументы в стек последовательно, начиная с последнего, указанного в описании функции, и кончая первым. После загрузки всех аргументов программа вызывает функцию командой call за возвращение стека в исходное состояние после возврата из функции API отвечает сама эта вызываемая функция. Программисту заботиться о восстановлении указателя стека esp нет необходимости вызываемая функция API гарантированно сохраняет регистры общего назначения ebp, esi, edi. Регистр eax, как правило, содержит возвращаемое значение. Состояние остальных регистров после возврата из функции API следует считать неопределенным. (Полный набор соглашений stdcall регламентирует также сохранение системных регистров ds и ss. Однако для flat-модели памяти, используемой в win32, эти регистры значения не имеют.)
На системном уровне этот набор соглашений добавляется вот еще чем. Компилятор при формировании из исходного текста объектного файла добавляет к началу имени функции символ подчеркивания, а к концу - выражение вида @n, где n - десятичное число, равное количеству байт, занятому в стеке под аргументы функции. Так формируется технологическое имя, позволяющее осуществить связывание не только по имени вызываемой функции, но и по количеству ее аргументов. Благодаря ему при сборке обнаруживаются ошибки программиста в случае, когда он задал для вызываемой функции неправильное число аргументов. Конечно, этому сервису далеко до строгого контроля типов C++, но от огромного количества трудноустранимых ошибок он все-таки оберегает.
Соблюдение перечисленных соглашений обеспечивается компилятором автоматически. Для этого необходимо включить в начало исходного файла комбинацию директив:
.386
model flat,stdcall
Директиву .386 можно заменить на более высокую в зависимости от того, на какой процессор вы рассчитываете. Директива model определяет сегментную модель приложения. Для всех приложений win32 она должна иметь именно такой вид, как показано здесь.
Прежде чем переходить к рассмотрению примеров, следует обсудить еще одно обстоятельство. Оно связано с тем, что Windows, благодаря прозорливости ее создателей, поддерживает два типа кодировки текстовых символов - архаичную восьмибитную ANSI и перспективную шестнадцатибитную Unicode. Из-за этого программистам Microsoft пришлось для всех функций API, так или иначе работающих с текстовыми символами, писать по два программных варианта. Тот, который работает с кодировкой ANSI, имеет суффикс A, например, CreateFileA. Тот, который работает с кодировкой Unicode, имеет суффикс W, например, CreateFileW.
Обычно приложение ориентируется на работу только с одной из этих двух кодировок. Выбор производится программистом на этапе компиляции путем указания значения для специальной настроечной константы. В MS Visual C++, например, это константа UNICODE. Если она определена, то вызов функции CreateFile в тексте программы при компиляции незаметно для программиста преобразуется в вызов функции CreateFileW, в противном случае - в CreateFileA.
В программах на ассемблере, конечно, можно реализовать тот же механизм. А можно, руководствуясь девизом "handmade forever", делать это вручную.
Основываясь на вышесказанном, приведем пример вызова какой-нибудь функции API. Допустим, это будет уже упоминавшаяся функция CreateFile. Вот ее описание для C++, как оно дано в Platform SDK:
HANDLE CreateFile(
LPCTSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDistribution,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile
);
Фрагмент программы для кодировки ANSI, открывающий для чтения уже существующий файл, может выглядеть, например, так:
.data file_name DB "MyFile.dat",0 .code ... push NULL push FILE_ATTRIBUTE_NORMAL push OPEN_EXISTING push NULL push 0 push GENERIC_READ push offset file_name call CreateFileA ... |
;имя открываемого файла ;hTemplateFile ;dwFlagsAndAttributes ;dwCreationDistribution ;lpSecurityAttributes ;dwShareMode ;dwDesiredAccess ;lpFileName |
CreateFileA PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
.data
file_name DB "MyFile.dat",0
.code
...
invoke CreateFileA,offset file_name,GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL
...
Оператор invoke - это совсем небольшая и довольно очевидная доработка, которая, вместе с некоторыми другими, мгновенно превратила ассемблер из машинного языка в нормальный язык программирования.
Взаимодействие экземпляров приложения
Вспоминается одна смешная история. Во времена Word 6.0, работая как-то в одной солидной организации, трепетно относившейся к своим коммерческим тайнам, был приглашен возмущенным соседом по комнате к его компьютеру. На экране красовалось нечто вроде: "Вы не можете открыть этот очень конфиденциальный документ, так как его уже открыл пользователь имярек". Имяреком был назван начальник отдела эксплуатации, сидевший в другом конце здания. Параноидальное сознание сразу стало выстраивать сложную схему шпионажа через локальную сеть, который развернул этот самый начальник. Другая часть мозга, пользуясь невытесняющей многозадачностью, принялась прикидывать размер вознаграждения за его поимку. Короче, помчались разбираться. Через пять минут половина отдела эксплуатации во главе с недоумевающим начальником толклась около компьютера и выдвигала идеи. В итоге оказалось, что никакого шпионажа нет, а ...
... а во всем виноваты Word 6.0, невнимательный пользователь и пиратство. Пиратство виновато потому, что так тогда делали все. Word 6.0 инсталлировался с дискет, которые добрый начальник отдела эксплуатации по очереди раздавал всем желающим, так что под его именем рожала документы вся организация. Невнимательный пользователь виноват потому, что редактировал-редактировал документ в одном экземпляре Word'а, а потом взял, да и попытался открыть его из файл-менеджера еще раз. А Word виноват потому, что уж так он тогда обеспечивал взаимодействие нескольких экземпляров приложения (сейчас гораздо лучше).
Больше всех жалко начальника отдела эксплуатации, которому пришлось прилично понервничать. Пользователь-то, которому его заложил глупый Word, был сотрудником отдела безопасности!
Многозадачность Windows, являясь безусловным благом, не подлежащим ныне обсуждению и сомнению, является вместе с тем источником многих вызовов, бросаемых программисту. Некоторые из них весьма изощренны и ответ на них требует глубокого понимания принципов организации рабочей среды.
Например, при разработке приложения MyCall пришлось столкнуться с такой проблемой. Как известно, Remote Access Service (RAS), работающий в асинхронном режиме, запускается в отдельном потоке (нити), отличном от основного потока приложения. Также не менее хорошо известно, что для контроля процесса установления соединения в RAS используется callback-функция (например, RasDialFunc), которую работающий RAS вызывает при необходимости сообщить приложению о своем состоянии. Так вот, если у программиста имеется потребность передать из RasDialFunc какое-либо пользовательское сообщение окну приложения, то он может побояться воспользоваться для этой цели функцией SendMessage, полагая, что два потока, одновременно запускающие одну и ту же оконную процедуру в одном контексте - это пролог к катастрофе. Между тем такое действие вполне допустимо и безопасно, так как умная Windows, прежде чем передать управление оконной процедуре, проверяет, в каком потоке создавалось соответствующее окно, и при необходимости переключает потоки. При этом передающий поток замораживается на время обработки сообщения принимающим потоком.
Другие же проблемы многозадачности более тривиальны, даже рутинны. Одна из них встает всякий раз, когда программист начинает работу над новым проектом: что делать в случае попытки запуска приложения, если оно уже запущено?
В самом деле. Сама по себе Windows не ограничивает возможности пользователя запускать какое-либо приложение любое разумное количество раз. Выполнив два двойных щелчка по одной и той же иконке, пользователь может получить два экземпляра одного и того же приложения, и каждый из них будет функционировать в собственном процессе и собственном контексте так, как будто другого не существует. Правда, только до той поры, пока они не попытаются разделить между собой какой-нибудь ресурс (в широком смысле), который разделять невозможно или нельзя. Например, невозможно разделять коммуникационный порт. И нельзя разделять общий файл данных, если оба экземпляра приложения пишут в него противоречащие друг другу данные. Разнообразие возникающих в этом случае эффектов может порадовать самого взыскательного искателя развлечений.
Принятие решения о том, как поступать со вторым и последующими экземплярами приложения, целиком возлагается на программиста. Можно придумать много разных вариантов стратегии поведения приложения, в зависимости от назначения приложения. Среди них такие:
Второй и последующие экземпляры приложения запускаются и функционируют самостоятельно, безотносительно к существованию друг друга Запуск второго экземпляра (и, следовательно, всех последующих) запрещен, пока функционирует первый запущенный экземпляр При попытке запуска второго экземпляра он не запускается, а в работе первого происходят некоторые изменения
Яркий пример третьей стратегии реализован в Word. Если экземпляр приложения уже запущен, и пользователь выполняет двойной щелчок по какому-нибудь файлу .doc в Проводнике, то запускаемый при этом второй экземпляр Word передает реквизиты загружаемого файла первому экземпляру и завершается, а первый экземпляр загружает файл в новое окно документа.
С первой стратегией все просто. При запуске приложения ничего делать не надо - и тогда вы сможете наблюдать на экране столько экземпляров вашего приложения, сколько нужно, чтобы получить моральное удовлетворение. Главное - позаботиться, чтобы экземпляры приложения никогда не пытались разделять ресурсы, которые невозможно или нельзя разделять.
Вторая стратегия несколько сложнее. Очевидно, для ее реализации необходимо, чтобы второй экземпляр приложения каким-то образом узнал о том, что существует первый экземпляр, и завершился, не успев принести какого-нибудь вреда. К сожалению, никакого специального средства для этого не существует.
Поговаривают, что не всегда все было так трагично. Во времена наших предков, когда "Докторская" стоила 2.20, а Билл был еще совсем бедным со своими двумя десятками гигабаксов, заработанных на DOSе, это делалось очень просто. В win16 имел смысл параметр hPrevInstance функции WinMain. Достаточно было убедиться, что он ненулевой - и приложение знало, что оно не одиноко на этом свете. Но когда разрабатывалась win32, кто-то из главных системщиков Microsoft лопухнулся, а потом, когда все всплыло, на специальном совещании было принято решение прикрыть провинившегося и объяснить все произошедшее какими-нибудь благообразными словами, вроде: "Мы так и хотели с самого начала, а hPrevInstance сохранили для совместимости снизу вверх." Так или иначе - но hPrevInstance теперь бесполезен. А следует пользоваться средствами межпроцессного взаимодействия (Interprocess Communications), которые, к чести Microsoft, реализованы в Windows великолепно.
Способов дать знать второму экземпляру о существовании первого можно придумать много. Один из них прямо дается в MSDN в статье WinMain:
hPrevInstance
Identifies the previous instance of the application. For a Win32-based application, this parameter is always NULL. If you need to detect whether another instance already exists, create a named mutex using the CreateMutex function. If the GetLastError function returns ERROR_ALREADY_EXISTS, another instance of your application exists (it created the mutex).
Идентифицирует предыдущий экземпляр приложения. Для Win32-приложений этот параметр всегда равен NULL. Если вам необходимо определить, существует ли уже экземпляр приложения, создайте именованный объект mutex, используя функцию CreateMutex function. Если функция GetLastError вернет ERROR_ALREADY_EXISTS, другой экземпляр вашего приложения существует (так как он уже создал mutex).
Вот этой самой идеей и воспользуемся, слегка модифицировав ее:
.386 .Model flat,stdcall
include windows.inc ;заголовочный файл API;///////////////////////////////////////////////////////////// windows.inc OpenMutexA PROTO :DWORD,:DWORD,:DWORD ;прототипы функций API CreateMutexA PROTO :DWORD,:DWORD,:DWORD ExitProcess PROTO :DWORD
SYNCHRONIZE =00100000h ;константы типов доступа STANDARD_RIGHTS_REQUIRED =000f0000h MUTANT_QUERY_STATE =0001h MUTANT_ALL_ACCESS =STANDARD_RIGHTS_REQUIRED OR SYNCHRONIZE OR MUTANT_QUERY_STATE MUTEX_ALL_ACCESS =MUTANT_ALL_ACCESS
NULL =0 ;разные константы FALSE =0 TRUE =1 if(dhtml){document.all["include"].style.display="block";document.all["windows"].style.display="none";}
;///////////////////////////////////////////////////////////// WinMain EXIT_OK =0 ;коды выхода EXIT_ALREADY_EXIST =1 .const check_inst_mutex_name db "Check Instant Mutex 0001",0 ;имя mutex .data? check_inst_mutex dd ? ;дескриптор mutex .code ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::: WinMain PROC PUBLIC hinst,prev_hinst,command_line,cmd_show ;......................................................... проверка повторного запуска mov check_inst_mutex,0 invoke OpenMutexA,MUTEX_ALL_ACCESS,FALSE,offset check_inst_mutex_name .if(!eax) ;если mutex не существует - создать его и запустить приложение invoke CreateMutexA,NULL,TRUE,offset(check_inst_mutex_name) mov check_inst_mutex,eax .else ;если mutex существует - прервать работу приложения invoke ExitProcess,EXIT_ALREADY_EXIST .endif ;......................................................... тело приложения ;... ;... ;весь остальной текст приложения ;... ;......................................................... завершение работы .if(check_inst_mutex!=0) ;уничтожить объект mutex invoke ReleaseMutex,check_inst_mutex .endif invoke ExitProcess,EXIT_OK WinMain ENDP ;############################################################# end
Как видим, логика работы очень проста. Первым делом пытаемся открыть mutex с заданным именем.
Если вы - автор многих разных приложений, имейте в виду, что одинаковое имя mutex'а позволит запускаться только одному из этих приложений. Если ваше чувство юмора не столь развито, не забывайте модифицировать имя mutex'а в разных приложениях.
Если mutex открыть не удается, значит запускаемый нами экземпляр приложения уникален, и мы смело можем продолжат его работу. Только перед этим создадим mutex, чтобы не дать запускаться последующим экземплярам.
Ну и, конечно, при завершении работы приложения следует не забыть вызвать функцию ReleaseMutex.
В принципе, это не обязательно, так как если не освободить mutex с помощью этой функции, по завершении работы приложения он переводится в состояние WAIT_ABANDONED, которое в данном случае не мешает достижению стоящей перед нами задачи. Однако хороший стиль программирования предполагает явное освобождение занимаемых приложением ресурсов.
А вот Сергей AKA The Byte Reaper (http://neptunix.narod.ru) совершенно справедливо обратил наше внимане на то, что приведенный выше пример реализации второй стратегии сильно перегружен формальностями и реверансами в сторону MSDN. Полностью сохраняет ту же функциональность, но гораздо приятнее взору настоящего ассемблерщика предложенный Сергеем вот какой вариант:
;=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= invoke CreateMutexA,NULL,0,offset(check_inst_mutex_name) invoke GetLastError .if(eax==ERROR_ALREADY_EXISTS) invoke ExitProcess,EXIT_ALREADY_EXIST .endif ;=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Идея проста предельно. Если создаваемый mutex уже присутствует в системе, то функция GetLastError вовращает код ERROR_ALREADY_EXISTS. Кстати, функция CreateMutex возвращает при этом вполне валидный дескриптор существующего mutex'а, но нас это совершенно не должно интересовать. Мы просто заканчиваем работу приложения - и все.
Третья стратегия существенно сложнее в реализации, а гамма возможных решений намного больше. Очевидно, ее основой должно быть не только взаимное оповещение экземплярами приложения о существовании друг друга, но и обмен между ними какой-то информацией. Все зависит от того, какой объем информации потребуется. Если это одно-два двойных слова, то может оказаться достаточным использовать систему сообщений. В других случаях имеет смысл, например, организовать разделяемую память посредством File Mapping.
Пример реализации третьей стратегии, использующий только систему оконных сообщений, можно найти в приложении MyCall. Там используется регистрируемое глобальное сообщение, которое приложение передает в широковещательном режиме в надежде, что уже запущенный экземпляр ответит ему тем же сообщением. Если такой ответ получен, то делается вывод, что один экземпляр приложения уже запущен, и запускаемый экземпляр завершает работу. Но перед этим он извлекает из ответного сообщения дескриптор главного окна работающего экземпляра и выводит это окно на первый план, чтобы удовлетворить потребность пользователя в доступе к приложению.
Здесь мы приведем версию, выполняющую ту же задачу, но несколько отличную в реализации. По сути, она является гибридом из описанной выше и примененной в MyCall: для определения факта повторного запуска приложения используется mutex, а для передачи дескриптора окна работающего приложения вновь запускаемому - глобальное сообщение.
Это полностью работающее приложение. В его основу положен шаблон оконного приложения. Фрагменты, отвечающие за обеспечение уникальности экземпляра приложения и за вывод окна работающего приложения на первый план, выделены бледно-желтым фоном. Содержимое файла windows.inc не приводится (то, что он должен собой представлять, показано в предыдущем примере. Недостающие элементы следует добавить самостоятельно, по аналогии, пользуясь заголовочными файлами для C/C++).
.386 .Model flat,stdcall
include windows.inc ;заголовочный файл API
;///////////////////////////////////////////////////////////// WinMain EXIT_OK =0 ;коды выхода EXIT_ERROR =1 EXIT_ALREADY_EXIST =2
QUESTION_PRIME_HWND =1 ;запрос идентификатора окна ANSWER_PRIME_HWND =2 ;ответ идентификатора окна
.data? win_class WNDCLASSEXA{} ;структура класса главного окна main_window dd ? ;дескриптор главного окна check_inst_mutex dd ? ;дескриптор mutex'а second_instance dd ? ;признак второго экземпляра check_inst_message dd ? ;идентификатор регистрируемого сообщения loop_message MSG{} ;сообщение для цикла обработки .const app_name db "Single Instance Application",0 class_name db "Single Instance Application Class",0 check_inst_mutex_name db "Check Instant Mutex 0001",0 check_inst_message_name db "Check Instant Message 0001",0 .code ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::: WinMain PROC PUBLIC hinst,prev_hinst,command_line,cmd_show ;......................................................... регистрация класса окна mov win_class.cbSize,sizeof(WNDCLASSEXA) mov win_class.style,CS_HREDRAW OR CS_VREDRAW mov win_class.lpfnWndProc,offset win_procedure mov win_class.cbClsExtra,0 mov win_class.cbWndExtra,0 invoke GetModuleHandleA,NULL mov win_class.hInstance,eax invoke LoadIconA,NULL,IDI_APPLICATION mov win_class.hIcon,eax mov win_class.hIconSm,eax invoke LoadCursorA,NULL,IDC_ARROW mov win_class.hCursor,NULL mov win_class.hbrBackground,COLOR_WINDOWFRAME mov win_class.lpszMenuName,NULL mov win_class.lpszClassName,offset class_name invoke RegisterClassExA,offset win_class .if(!eax) jmp abort .endif ;......................................................... создание окна invoke CreateWindowExA,NULL,\ offset class_name,\ offset app_name,\ WS_OVERLAPPEDWINDOW,\ 100,100,300,200,\ NULL,\ NULL,\ win_class.hInstance,\ NULL .if(!eax) jmp abort .endif mov main_window,eax ;.............................................. зарегистрировать глобальное сообщение mov check_inst_message,0 invoke RegisterWindowMessageA,offset check_inst_message_name .if(eax) mov check_inst_message,eax .endif ;......................................................... проверить повторный запуск mov check_inst_mutex,0 invoke OpenMutexA,MUTEX_ALL_ACCESS,FALSE,offset check_inst_mutex_name .if(!eax) mov second_instance,FALSE invoke CreateMutexA,NULL,TRUE,offset check_inst_mutex_name mov check_inst_mutex,eax .else mov second_instance,TRUE .if(check_inst_message) invoke SendMessageA,HWND_BROADCAST,check_inst_message,QUESTION_PRIME_HWND,NULL
.endif invoke ExitProcess,EXIT_ALREADY_EXIST .endif ;......................................................... вывод окна на экран invoke ShowWindow,main_window,SW_SHOW ;......................................................... инициализация ;здесь можно вставить все необходимые действия для первичной инициализации ;приложения (например, загрузку и разбор файла настроек) ;......................................................... цикл опроса очереди сообщений msg_loop: invoke GetMessageA,offset loop_message,NULL,0,0 .if(!eax) ;завершение работы .if(check_inst_mutex!=0) ;освободить mutex invoke ReleaseMutex,check_inst_mutex .endif invoke ExitProcess,loop_message.wParam .endif invoke TranslateMessage,offset loop_message invoke DispatchMessageA,offset loop_message jmp msg_loop abort: invoke ExitProcess,EXIT_ERROR WinMain ENDP
;///////////////////////////////////////////////////////////// Оконная процедура win_procedure PROC hwnd,message,w_param,l_param mov eax,message ;................................................... обработка регистрируемого сообщения .if(eax==check_inst_message) ;если получено зарегистрированное сообщение .if(check_inst_message) .if(second_instance) ;если это второй экземпляр .if(w_param==ANSWER_PRIME_HWND) ;если в сообщении получен дескриптор окна invoke SetForegroundWindow,l_param ;вывести это окно на первый план jmp worked .endif .else ;если это первый экземпляр .if(w_param==QUESTION_PRIME_HWND) ;если запрашивается дескриптор окна mov eax,hwnd ;послать ему дескриптор главного окна .if(eax==main_window) invoke SendMessageA,HWND_BROADCAST,check_inst_message,ANSWER_PRIME_HWND,main_window jmp worked .endif .endif .endif .endif jmp noworked ;........................................................ закрытие приложения .elseif(eax==WM_DESTROY) invoke PostQuitMessage,EXIT_OK jmp worked ;........................................................ обработка остальных сообщений ; .elseif(message==...) ; ... ; .elseif(message==...) ; ... ; .elseif(message==...) ; ... ;........................................................ сообщение не обработано .else noworked: invoke DefWindowProcA,hwnd,message,w_param,l_param ret ;........................................................ сообщение обработано .endif worked: xor eax,eax ret win_procedure ENDP ;############################################################# end
Общая идея такова:
регистрируется класс главного окна и создается само окно с тем, чтобы вновь запускаемое приложение могло принимать и обрабатывать сообщения с помощью оконной процедуры win_procedure регистрируется глобальное сообщение, которое впоследствии должно использоваться для обмена информацией между экземплярами приложения выполняется проверка наличия уже работающего экземпляра приложения с помощью объекта mutex если уже работающий экземпляр приложения существует - ему посылается инициирующее сообщение с параметром QUESTION_PRIME_HWND. Поскольку дескриптор окна этого экземпляра неизвестен, сообщение посылается широковещательно (HWND_BROADCAST).
ответ от уже работающего экземпляра принимает оконная процедура приложения в виде такого же сообщения, но уже с параметром ANSWER_PRIME_HWND. При этом второй параметр сообщения представляет собой дескриптор окна работающего приложения. запускаемый второй экземпляр приложения, пользуясь полученным дескриптором, выводит окно первого экземпляра на первый план с помощью функции SetForegroundWindow и завершает свою работу
если запускаемый экземпляр приложения - первый и единственный, то он продолжает свою работу так, как задумал программист. При этом его оконная процедура всегда готова получить и обработать зарегистрированное сообщение с параметром QUESTION_PRIME_HWND, которое могут послать повторно запускаемые экземпляры. В ответ на такое сообщение работающий экземпляр отвечает таким же сообщением, но с параметром ANSWER_PRIME_HWND, передавая в качестве второго параметра дескриптор своего главного окна.
См. также интересный, эффективный и простой вариант решения рассматриваемой задачи, предложенный Геннадием Майко.
Взаимодействие экземпляров приложения (вариант)
Этот материал предоставлен Геннадием Майко. Он дополняет публикацию "Взаимодействие экземпляров приложения", в которой assembler.ru излагал суть проблемы и рассматривал некоторые способы ее решения. Вариант, предложенный Геннадием, изящен и предельно прост в реализации. Приносим ему свою благодарность. Итак, слово автору: