Пишем сверхмалые приложения на C++ & win32API. Секреты кодинга

///////////////////////////////////////////////////////////////////////////////////////
//
// Тема.......Пишем сверхмалые приложения на C++ & win32API. Секреты кодинга.
// Автор......black_c0de
// Группа.....[The N0b0D1eS] //[tN]
// [email protected]
// HTTP.......http://nteam.ru/
// DATE.......02.06.03
//
//
// # Все права принадлежат [The N0b0D1eS]
// # любое распространение возможно только с сохранением копирайтов и т.д. и т.п.
//
//
///////////////////////////////////////////////////////////////////////////////////////

В данной статье я поведаю все секреты написания сверхмалых по размеру приложений под винду. Писать будем, как и полагается, на C++ с использованием win32 API. Как и все кулхацкерские программы - наша программа будет консольной, GUI - в отстой. Лишняя загрузка кода и геморрой с мышью, это не для нас.

*** Содержание ***
------------------

[1] Вступление
[2] Всему свое место. Когда нужно писать консольное приложение, а когда GUI
[3] Зачем извращаться в пользу малого размера программы? Сейчас винты и ОЗУ по пару гиг!
[4] Почему именно C++ и win32API? Проблемы "распухания" кода
[5] Пишем свою точку входа в программу
[6] Используйте только API-шные функции
[7] Узкие места в программе. Ассемблерные вставки

*** Вступление ***
------------------

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

В данной статье я попытаюсь описать проблемы небрежного программирования и соответственного отношения к написанию кода, что приводить к чрезмерному распуханию программ с всеми последствиями в виде нехватки места на винте, активный свопинг, нехватка ОЗУ, и т.п. Конечно, вы можете уверенно возразить мол сейчас такое железо, что лишние сохраненные 10-20 а то и 200! килобайт
это фигня, сейчас размеры винтов измеряются в гигабайтах, размеры ОЗУ в сотнях... НО! Мы же с вами уважающие себя программисты, зачем же так небрежно относиться к своему "ремеслу"? Ведь такое отношение говорит о том что вы не цените конечного пользователя ваших софтин. А сейчас, в эпоху развития и атомного прогресса интернета - всех интернетчиков, которые будут качать вашу софтину с перегруженного FTP 8)) Так давайте делать все на ОТЛИЧНО и вам воздастся! Ж8)


*** Всему свое место. Когда нужно писать консольное приложение, а когда GUI ***
-------------------------------------------------------------------------------

Очень интересный момент, который затрагивает проблемы разработки GUI (graphic user 1nterfacE) Очень часто приходится видеть следующее - программа, которая выполняет определенную задачу, можно сказать что утилита, и имеет GUI с одной кнопкой start и окошком ввода какого-то значения, а вокруг этого всего всякие картинки, рамки... все заключается в передаче программе нескольких значений, а путаницы и возни с мышью и разбором что куда вводить занимают определенное время.

Разработка GUI - довольно таки не простая вещь, оптимальность и качество GUI можно измерить в количестве движений мышью, количеством стрессов, которые вы получили в процесс разбора what's da fUcking buttons?! и количеством кликов мышью, думаю это понятно. Все сталкивались с неудачно спроектированным интерфейсом. Так вот, если вы пишете программу, предназначенную на выполнение узкой задачи, тот же портсканер! - не лепите этот чертов график юзер интерфейс, мать бы его...

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

Так вот, если затронуть тему статьи - GUI - это то, что придает вашей программе пышность и румяность, прям как свежеиспеченные бабушкины пончики 8)) толстые и пышные, а толку с них - не много, весу то мало ;)) так и программы получаются - 100k а толку ну почти никакого... Так что не парьтесь с разработкой GUI, если в этом нет строгой необходимости, напишите простую и понятную консольную тулзу и все у вас будет хорошо, прям как в сказке 8)


** Зачем извращаться в пользу малого размера программы? Сейчас винты и ОЗУ по пару гиг! ***
-------------------------------------------------------------------------------------------

Это мы уже упоминали, раз вы имеете смелость называть себя программистом - не ленитесь и пишите оптимизированные приложения. В добавок к проблеме распухания кода вы имеете проблему потери в производительности. Только не говорите что у вас там какой-то гигагерцовый четвертый пень. Пофиг какой у вас там пень, с каждого по нитке - голому рубашка. Так и тут, почему все так грешат на бедного Билли и его Виндовз? Не будем брать в счет тех, кому просто завидно, я о тех, кто упрекают его операционку в глючности и тормознутости. Хех, народ, а чего вы хотели?! 8))

Выпишите на бумажку все те тулзы, которые работают у вас в системе и подумайте, какова вероятность того что ВСЕ тулзы разрабатывались и кодировались с расчетом получить оптимальный по быстродействию код? Конечно же вероятность близится к нулю... Мало кто сейчас задумывается на этим, прошли времена асма и 16-и разрядных машин... но зачем брать плохой пример? Они сами себе редиски 8)

Чем меньше ваша софтина - тем меньше вероятность что она будет подкачиваться с винта и юзать виртуальную память, дрючить ваш винт и перезаписывать своп, все эти действия тормозят ВСЮ систему! Еще одна проблема - когда ваша программа взаимодействует с другими, тут в тему одна из философий UNIX - "меньше - лучше". Большие задачи лучше выполняются взаимодействующей системой
маленьких программ, каждая из которых делает хорошо лишь ОДНО задание. Тут можно провести аналогию с методом внедрения объектов OLE от M1cr0$0fT.


*** Почему именно C++ и win32API? Проблемы "распухания" кода ***
----------------------------------------------------------------

Продолжим тему GUI. Если рассматривать среду разработки GUI, будем брать именно среду ВИЗУАЛЬНОЙ разработки, так как написание интерфейса на чистом API или того лучше - на асме, само собой идеальное решение в плане написания сверхмалого по размеру приложения. Так вот, есть три ОСНОВНЫЕ и наиболее часто пользуемые среды визуальной разработки GUI:

- Delphi
- Builder C++
- Visual C++

всякие там висуал басики и прочий отстой не рассматривается ВООБЩЕ!

Итак, Delphi and C++ Builder - детища корпорации Borland, которая на данный момент пыхтит над решениями для разработчиков .NET, чтобы порадовать их своей средой для C#, CLR и т.п... Всем известны библиотеки классов VCL, о да! мастерски все сделано, очень просто использовать данную библиотеку классов и привязывать к ней свой код, это что касается GUI, НО! ничего не бывает просто так, взамен мы платим лишней сотней килобайт! всем кто писал на Builder'е знакомы ситуации, когда ваша тулза отказывается работать на другом компе, где этот самый зверь C++ Builder не установлен. Да, вы вспомнили, 8)) как такое не вспомнить! Это просто ужас 8))

Помню, как я устраивался в одну контору работать, они потребовали одну феньку с GUI написать для примера, ну я взял что было под руками - Builder C++, накатал софтину и пошел к ним, замечу что раньше не было необходимости в переносе своих GUI тулз дальше чем мой комп, да и не увлекался я тогда GUI... так вот, я три раза к ним бегал пока не принес таки рабочую софтину! Те, кто писал на билдере меня поймут - там нужна уйма bpl'ок и еще парочка dll 8)) bpl-ки можно упаковать в exe'шник а dll'ки или рядом в архивчик или писать дистрибутив 8) В результате мы имеем один большой пончик, который нас не прокормит 8)

C Delphi дела обстоят лучше, у нее нет такой проблемы, при построении проекта все необходимое компонуется с исполняемым модулем, но распухание кода аналогичное 8))

Итак, к чем мы пришли? Остается Visual C++, детище Била Гейтса. В данный момент я работаю именно в данной среде разработки и мало сказать что она мне нравится, она мне чертовски нравится! 8) Очень толково все продумано и сделано.

С++ - язык, изначальной целью которого было написание операционных систем, исходя из этого и спроектирован C++. Этим все сказано, с С++ ребята постарались на славу 8) Это идеальный вариант, если вы пишете системные программы, какие-то тулзы, отличные от "вирус Вася", который удаляет все файлы в текущей дире... Потому именно данный язык советуется использовать при
написании ваших программ. Гибкость, удобность и трезвый смысл - все это впечатляет.

win32API - windows 32-bit Application Programming Interfaces, то есть 32-х разрядный интерфейс разработки ПО под виндовс. Это тот инструмент, который дал нам в руки дядя Билли и благословил нас в добрый путь Ж8) win32API предоставляют фантастические возможности в написании программ. Это второй уровень абстракции после ассемблера. То есть совмещена удобность в виде готовых
функций и оптимальность в виде начинки из ассемблерного кода, на котором написаны все эти API.


*** Пишем свою точку входа в программу ***

Точка входа в программу, это то место, откуда процессор начнет выполнение программы. Те, кто кодировал на асме знают о директиве .startup, вот это и есть точка входа. На асме часто приходилось извращаться с переносом стартапа в конец программы при помощи всяких jmp, те кто кодил, знают ;)

Так вот, зачем писать свою точку входа в программу и почему вы раньше не задумывались над этим? Наверное не знали что имеете такую возможность - переопределить это дело. Да, имеете, линкер позволяет 8))

Я всегда использую в качестве точки входа в программу свою ф-цию WinMain, это функция, с которой обычно начинается выполнение программы на C++ под виндовс. Так вот, как переопределить точку входа, заменив своей и сэкономить на этом пару десятков килобайт, которые идут на вызов всяких стандартных либ и прочего хлама:

#pragma comment(linker,"/MERGE:.rdata=.text")
// /MERGE:.rdata=.text - указываем линкеру, что нужно скомбинировать секцию .rdata с .text

#pragma comment(linker,"/FILEALIGN:512 /SECTION:.text,EWRX /IGNORE:4078")
// /FILEALIGN:512 - указываем что следует использовать 512-и байтовое выравнивание и размер
// секции в исполняемом модуле, посмотрите с помощью тулзы DUMPBIN каково значение у программ,
// которые вы писали это этого ;))
// /SECTION:.text,EWRX :
// указываем что у нас есть одна секция .text, в которую мы ранее переместили .rdata
// задаем ей атрибуты EWRX:
// E данный section поддерживает выполнение кода, Executable короче говоря
// R разрешает операции чтения
// W разрешает операции записи
// X используется при написании резидентов и VxD дров ;))
// маркирует секцию как участок памяти

#pragma comment(linker,"/ENTRY:WinMain")
// ну и тут вся ясно - задаем нашу точку входа, о которой говорили выше.

// вот так выглядит WinMain 8)
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR argv, int)
{
// there your code...
};

еще советую в линковать вашу программу с опциями:

/O1 - оптимизация в пользу уменьшения размера кода
/Os - тоже самое...
/OPT:NOWIN98 -отключает оптимизацию под мастдай, это не значит что программа под мастдаем не будет выполнятся а под NT будет, просто вроде как медленнее, но вы этого не заметите, уверяю вас 8))

эти опции поддерживает Visual C++


*** Используйте только API-шные функции ***
-------------------------------------------

Весь предыдущий геммор с точкой входа сводится на нет, если вы в своей программе будете использовать функции из стандартных библиотек, которые мы отрубили задавая свою точку входа. в C++ это все RTL-овские ф-ции, к примеру часто приходится обрабатывать строки, для этого все используют функции str* (strlen, strstr, strtok, strcmp, etc...) так вот, здесь это не прокатит 8)) существуют сепцовые api-шные функции для работы с строками типа char*:

lstrcat
lstrcpm
lstrstr
lstrlen

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

Правда есть спецовая функция, которая вернет нам параметры в виде массива, но там будут строки UNICODE, то есть с 16-байтовыми символами. Если вас это устраивает - вам проще 8))

Также придется отказаться от всеми любимых printf/scanf 8((, очень нелегко пришлось переходить на WriteConsole/ReadConsole 8))

*** Узкие места в программе. Ассемблерные вставки и системное программирование ***
----------------------------------------------------------------------------------

Использование асмовых вставок - дело неблагодарное 8) Или пишите на win32-asm'e или... Это не придаст желаемого результата, разве что написанные целые функции на асме, но это на любителя. Одно, в чем асм нам помогает в паре с С++ - работа с регистрами и флагами процессора, особенно когда нужно сохранить и потом восстановить указатель ip/eip ;))

Также асмовые вставки хороши когда в программе имеются так называемые "узкие места", то есть критически важные операции, их конечно же лучше писать на асме. НО! Был случай, когда удалось найти эквивалентные ф-ции, выполняемые атомарно, то есть исключен вариант что в процессе выполнения функции доставки вашей подружкой пива с магазина к вам домой девушка не донесет пиво и выпьет его 8) Атомарные функции выполняются атомарно, то есть исключен вариант когда функция не успеет выполнить определенные операции и будет прервана системой. Потому они и названы атомарными, от слова АТОМ - то есть НЕДЕЛИМЫЙ.

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

В общем вы уже знаете в какую сторону копать 8) MSDN, распечатанный на туалетной бумаге, вам в руки и удачи в этом нелегком деле - системном программировании и написании воистину красивого и оптимизированного кода! 8)

Примером написания оптимизированного приложения будет релиз tNStealth, который выйдет одновременно с данной статьей, в виде tNStealth-beta-1.0.0, который является ПОЛНОСТЬЮ переписанным вариантом rserv, которую я ранее релизил. Как говориться - почувствуйте разницу, в данной тулзе используется отлаженный механизм приема-посылки данных удаленным серверам и защита от переполнения буфера как локально так и удаленно, не знаю кому придет в голову подобная чушь, ведь это клиентское ПО, но пускай будет, ради интереса прикрутил 8))

В общем юзайте и трепещите! Софтина размером 6k ! И никаких пакеров типа UPX.... ;)

Если возникнут вопросы или сложности, с радостью помогу вам, пишите - [email protected]

//
// all rights fucked (c) 2003 black c0de //tN [ The N0b0D1eS ] :: [ www.nteam.ru ]
//
///////////////////////////////////////////////////////////////////////////////////////