Что-то я решил молодость вспомнить. Перед тем, как меня понесло в сторону Perl, юниксов и всякой функциональщины, я долгое время игрался с сями, ассемблером и WinAPI. Сейчас все это начинает потихоньку забываться, а жаль, потому что, как мы уже выясняли, знать всякие низкоуровневые вещи полезно .
Предвижу возмущение некоторых читателей сего блога, дескать, что за дела, я не хочу читать про WinAPI, я хочу ФП и юниксы! Если вы оказались одним из них, пожалуйста, пройдите по приведенной выше ссылке и внимательно ознакомьтесь с тамошней заметкой, она вам все объяснит. Хорошая новость для новичков — при помощи серии постов о WinAPI, которую я планирую написать, вы можете начать изучение программирования практически с нуля. Впрочем, прочитать сначала книжку Кернигана и Ритчи , а также иметь представление о том, как язык Си хранит в памяти целые числа и строки, не повредит.
Для написания кода я буду использовать Windows XP, запущенный под Ubuntu Linux в VirtualBox, и среду разработки Visual Studio 2005. Если вы тоже не сидите под Windows, советую выбрать такую же конфигурацию, поскольку более поздние версии Visual Studio и Windows требуют крайне много ресурсов. Обязательно установите гостевые дополнения, это позволит использовать в Windows такое же разрешение экрана, что и в хост-системе. Если даже WinXP для вас тяжеловата, подойдут Windows 2000 и Visual C++ 6.0. Несмотря на то, что я буду использовать не самые свежие Windows и Visual Studio, по идее все должно прекрасно работать и в более поздних их версиях. Кроме того, не должно составить большого труда воспроизвести мои действия в каком-нибудь wxDev-C++ или даже Borland Delphi. Писать будем на Си, возможно, лишь изредка прибегая к некоторым фишкам C++.
Дополнение: Надо отдать должное Microsoft. Самые актуальные на середину 2017-го года Windows 10 и Visual Studio Community довольно шустро работают в условиях ограниченных ресурсов под VirtualBox. Более того, Visual Studio Community доступна для скачивания совершенно бесплатно .
Установить Visual Studio несложно. Будем считать, что с этим вы справились. Давайте создадим новый проект. Запустите Visual Studio и выберите в меню File → New → Project. В появившимся диалоге выберите Visual C++ → Win32 → Win32 Project. В поле Name введите какое-нибудь название проекта, например, First. Жмите OK, Next, Finish. В результате будет создан каркас проекта. Обратите внимание на выпадающий список с конфигурациями проекта по центру экрана. Выберите в нем конфигурацию Release:
Для компиляции программы нажмите F7. Для ее запуска нажмите Ctr+F5. Если вы используете VirtualBox, обратите внимание, что правая клавиша Ctr используется в хоткеях, управляющих виртуальной машиной. Например, сочетание Ctr+F служит для переключения между оконным и полноэкранным режимами. Поэтому, если вы решили использовать VirtualBox, используйте в хоткеях Visual Studio клавишу Ctr, находящуюся слева.
Если теперь перейти в каталог с проектом и найти собранную программу (она находится в подкаталоге с именем release), можно обнаружить, что ее размер составляет 64 Кб. По нынешним меркам это не так уж много, но все-таки что-то многовато для программы, всего лишь рисующей окошко с менюшкой. Мы же собираемся писать на Си и WinAPI, а одним из преимуществ этого подхода является возможность писать очень компактные программки.
Давайте разберемся, как это делается:
- Закройте текущий проект: File → Close Solution;
- Создайте новый проект с именем Project, в визарде поставьте галочку Additional Options → Empty Project;
- Выберите конфигурацию Release;
- Создайте новый файл исходного кода, сказав Project → Add New Item… → Visual C++ → Code → C++ File (.cpp), с именем Main;
- Откройте свойства проекта: Project → Project Properies…;
- Отключите проверку безопасности буфера: C/C++ → Code Generation → Buffer Security Check → No;
- Сообщите, что мы пишем код на Си: С/C++ → Advanced → Compile As → Compile As C Code;
- Игнорируем библиотеки по умолчанию: Linker → Input → Ignore All Default Libraries → Yes;
- В качестве точки входа указываем процедуру main: Linker → Advanced → Entry Point → main;
- Отключаем отладочную информацию: Linker → Debugger → Generate Debug Info → No;
- Отключаем генерацию манифеста: Liner → Manifest File → Generate Manifest → No;
- Жмем Apply, затем OK;
Теперь, если вы напишите в Main.cpp код:
}
… и скомпилируете его, то получите абсолютно ничего не делающий исполняемый файл Project.exe, размером ровно 1 Кб. Программа получилась такой маленькой, потому что мы выкинули из нее систему времени исполнения языка C++, а также отладочную информацию и прочий «мусор». Фактически, это минимальный размер исполняемого файла в Windows, поскольку он должен содержать некоторую служебную информацию (DOS-заглушку, PE-заголовок, таблицу секций) и иметь некоторое выравнивание. Можно ужать его еще чуть-чуть, но этим мы рискуем сломать совместимость с некоторыми версиями Windows.
Чтобы при создании каждого нового проекта не повторять описанные выше шаги, закройте проект и сохраните его в архиве с названием типа project_template.zip. Теперь попробуем на основе этого проекта создать программу, которая что-то делает. Разархивируйте проект, переименуйте его каталог из Project в MsgBox, откройте проект в Visual Studio. Введите следующий код:
void main ( ) {
MessageBox ( 0 , L «Hello!» , L «MsgBox» , 0 ) ;
ExitProcess ( 0 ) ;
}
Заметьте, что VisualStudio может дополнять имена процедур, если при их вводе нажать Ctr+пробел. Для сохранения кода используйте сочетание Ctr+S. При вводе аргументов процедур отображается подсказка.
Если теперь вы запустите программу, то увидите примерно следующее:
Размер exe-шника при этом составил 2 Кб. Можно уменьшить его до 1 Кб, добавив в самом начале кода строку:
Этим мы говорим компилятору объединить несколько секций исполняемого файла в одну. Но мне кажется, что это уже перебор, файл и так получился крохотный. Есть и другие приемы, но нам вполне хватит описанных выше.
Приведенный выше код довольно прост. Заголовочный файл windows.h содержит объявление процедур, структур и констант, необходимых для работы с WinAPI. Процедура ExitProcess завершает текущий процесс с кодом возврата, переданным в качестве аргумента. Процедура MessageBox отображает окно с сообщением. Первый и последний ее аргументы сейчас не важны. Второй по счету аргумент представляет собой строку с сообщением, а третий — заголовок окна.
Префикс L перед открывающей кавычкой означает, что строка хранится в unicode. В действительности, есть две версии процедуры MessageBox — MessageBoxA, работающая с ascii-строками, и MessageBoxW, работающая со строками в unicode. Аналогичная ситуация характерна и для большинства других процедур WinAPI. По умолчанию в свойствах проекта сказано использовать unicode-версии процедур, поэтому во время сборки проекта компилятор подменяет вызов MessageBox на MessageBoxW. На низком уровне ascii-процедуры представляют собой всего лишь обертки над unicode-версиями (для преобразования строк из unicode в ascii и обратно в Windows используются процедуры MultiByteToWideChar и WideCharToMultiByte ). Работая с процедурами, имена которых заканчиваются на «W», мы получаем более быстрые программы.
В заключение хочется отметить, что приведенный код успешно компилируется MinGW и запускается под Wine:
$ wine msgbox.exe
Правда, в результате получается exe-шник, содержащий «мусор», избавление от которого выходит за рамки этого поста.
Собственно, это все, о чем я хотел сегодня рассказать. В качестве домашнего задания можете почитать документацию к MessageBox на MSDN . Попробуйте отобразить окна с различными иконками и кнопками, а также по коду возврата определить, какая из кнопок была нажата.
Как всегда, буду рад ответить на ваши вопросы. И заодно скажите, насколько вообще все это вам интересно?
Дополнение: Пишем простое консольное приложение на чистом WinAPI