Статьи по Assembler

Параметры функции 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.

Содержание раздела