Начало программирования в Виндовс

Неутомимым бойцам YouTube, посвящается. А также, всем мальчикам и девочкам, с крыльями и без, возрастом от 7 и до 97 лет.

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

Почему так происходит? Потому, что авторы, большинства известных сегодня программ, логику работы своих творений, в части полного описания интерфейса пользователя, попросту замалчивают и открывать не спешат. То ли из жадности, то ли по недомыслию. Попробуем восполнить этот пробел. Слегка. Чем богаты, тем и рады, как говорится :). Тем более, что лучше один раз увидеть, чем ни разу не услышать.

Итак, вы решили научиться программировать в Виндовс. Не знаю, зачем вам это надо, но попробовать стоит. В мире существует огромное количество литературы по данной теме. Считаю бестактным вам что-то навязывать. Определяйтесь сами.

Взамен - необходимый минимум. Речь о чистом или классическом программировании, т.е. о написании работающих программ для различных полезных целей, например учебных.

Естественно, всё начинается с выбора языка. Отбросив ненужные мудрствования и теоретизирования, предлагаю просто взглянуть на доступные компиляторы. Уверен, что сегодня, начинающему, без особой нужды, не стоит связываться с экзотикой без графического интерфейса, работающей только из командной строки и не позволяющей получить моментальный результат в один-два щелчка мышью. С другой стороны, чтобы просто попробовать, гигабайтные пакеты тоже не нужны. Делаем ставку на простоту. Ну, а самый простой и доступный, сегодня, это Ассемблер. Естественно, это FASM.

Чтобы начать программировать в Виндовс, ничего особенного знать не надо. Всё уже придумано за нас. По крайней мере, на уровне концепций. И первое, чему следует обязательно научиться, это подключению сторонних библиотек.

Этим и займёмся. В Виндовс полно файлов вида any.dll. Например, kernel32.dll. Это winAPI (интерфейс программирования приложений) или динамические библиотеки. В них содержатся тысячи полезных и не очень подпрограмм, доступных к использованию и названных понятными именами. Например: printf, getchar, putchar и т.д. в crtdll.dll.

Чудо сие - *.dll, специально создано для существенного упрощения тяжёлой программерской жизни. Так, как призвано избавить вашу юную и не очень голову от держания в ней множества специфических, а потому совершенно бесполезных, часто меняющихся и редко используемых данных. Поэтому, первостепеннейшая задача, для новичка, получить к этим выше названным подпрограммам-функциям непосредственный доступ. Вот, как это делается в FASM.

;Programm 1.asm FASM

;(c) Tereshkov Oleg Evgenyevich 2014

format PE GUI 4.0

entry start

;//-------------macro-------------//

macro library [label,string]

{ forward

local _label

dd 0,0,0,rva _label,rva label

common

dd 0,0,0,0,0

forward

_label db string,0 }

macro import [label,string]

{ forward

local _label

label dd rva _label

common

dd 0

forward

_label dw 0

db string,0 }

;//------------endmac-------------//

;//-------------code-------------//

section '.code' code readable executable

start:

push 0

push caption

push message

push 0

call [MessageBoxA]

ret

;//-------------idata-------------//

section '.idata' import data readable writeable

library user32, 'user32.dll'

user32:

import MessageBoxA,'MessageBoxA'

;//-------------data-------------//

section '.data' data readable writeable

caption db 'Programm 1',0

message db 'Привет Мир!',0

Копируем в FASM, сохраняем, запускаем, получаем моментальный результат, наслаждаемся, жмём кнопочку "ОК", пробуем разобраться. Это интуитивно понятный рабочий каркас. Объяснять сейчас в точности, как именно он работает, в виду возможного отсутствия (у вас / у меня) всех необходимых базовых знаний, бессмысленно и значит испортить вам всё удовольствие от процесса самостоятельного изучения и удивительных личных открытий. Просто воспользуйтесь этим каркасом, как трафаретом,

;Programm.asm FASM

;(c) Tereshkov Oleg Evgenyevich 2014

format PE GUI 4.0

entry start

;//-------------macro-------------//

macro library [label,string]

{ forward

local _label

dd 0,0,0,rva _label,rva label

common

dd 0,0,0,0,0

forward

_label db string,0 }

macro import [label,string]

{ forward

local _label

label dd rva _label

common

dd 0

forward

_label dw 0

db string,0 }

;//------------endmac-------------//

;//-------------code-------------//

section '.code' code readable executable

start:

;//-------------idata-------------//

section '.idata' import data readable writeable

;//-------------data-------------//

section '.data' data readable writeable

заполняя секции, '.code' = логика, '.idata' = библиотеки, '.data' = данные, по мере роста знаний и мастерства. Кстати, будет лучше, если порядок следования секций оставить именно таким. Если библиотек много, после каждой добавляйте ,\ :

;//-------------idata-------------//

section '.idata' import data readable writeable

library kernel32, 'kernel32.dll',\

crtdll, 'crtdll.dll'

kernel32:

import GetTickCount,'GetTickCount'

crtdll:

import printf,'printf',\

clock, 'clock'

Если в вашей программе нет данных, не создавайте секцию '.data'. Попробуем консольку, по аналогии:

;Programm 2.asm FASM

;(c) Tereshkov Oleg Evgenyevich 2014

format PE console

entry start

;//-------------macro-------------//

macro library [label,string]

{ forward

local _label

dd 0,0,0,rva _label,rva label

common

dd 0,0,0,0,0

forward

_label db string,0 }

macro import [label,string]

{ forward

local _label

label dd rva _label

common

dd 0

forward

_label dw 0

db string,0 }

;//------------endmac-------------//

;//-------------code-------------//

section '.code' code readable executable

start:

call [GetCommandLineA]

push eax

call [printf]

add esp, 4

call [getchar]

call [putchar]

ret

;//-------------idata-------------//

section '.idata' import data readable writeable

library kernel32, 'kernel32.dll',\

crtdll, 'crtdll.dll'

kernel32:

import GetCommandLineA,'GetCommandLineA'

crtdll:

import printf,'printf',\

getchar, 'getchar',\

putchar, 'putchar'

Это уже вполне осмысленная и полезная программа. И даже хакерская. Не верите? Больше смекалки! И кроме того, она со всей убедительностью доказывает, что для использования всех прелестей языка Си, Си-компилятор вообще не нужен. Нужна только crtdll.dll.

Самое прекрасное в любом языке то, что его не надо знать полностью и целиком, от начала и до конца или держать в голове весь одновременно. Хороший справочник, по мере необходимости, надёжнее. Чтобы программировать в Винде на Ассемблере годами, достаточно знать всего навсего плюс-минус семь инструкций: push, pop, call, ret, mov, cmp и jmp, с вариациями. Это просто. Согласитесь.

Ещё одно безусловное преимущество чистого Ассемблера, всего два типа данных. Число и ссылка на число. Что, в свою очередь, предполагает полную свободу от прототипов функций. Весьма ценное свойство для начинающих, которое в полной мере осознаётся лишь при последующем переходе к программированию в Си, например. Теперь вы во всеоружии.

Почему именно и чем так хорош FASM? Очень многим. Маленький размер. Простой синтаксис. Своя IDE. Предельно облегчено подключение любых динамических библиотек. Можно написать всё, от "Привет Мир!" до собственной операционки.

Из недостатков - не может шить объектные файлы, не может использовать библиотеки any.lib, сшитые из объектных файлов. Редкий компилятор или дисассемблер выдаёт ассемблерный листинг совместимый с FASM. Требуется знание winAPI. Всё это, непростительные недостатки :).

Пользуясь FASM, новичок может долгое время не подозревать, что данный компилятор способен создавать и объектные файлы, тоже. Как elf, так и coff, которые потом можно с успехом подсовывать стороннему линкеру. Но на этом, вроде бы, очевидном пути слишком много подводных камней и страданий.

За сим, разрешите откланяться. Надеюсь, что статья оказалась полезной. Всегда ваш, я. Все примеры работают прямо из копи-паст. И это есть good. Приступайте. Всевозможные справочники вам в помощь. Желаю успехов :).

(с) Терешков Олег Евгеньевич. Сентябрь 2014.

Дополнение от 24.05.20. Глядя на бесконечное изобилие языков высокого уровня, можно подумать, что ассемблер давно умер. Но это не так. Предположим, ты пишешь библиотеку и хочешь собрать все стринги в одном объектном файле. Даже, используя самый низкоуровневый из всех высокоуровневых - Си, ничего не получится. Все компиляторы Си в этом отношении одинаковы. У меня есть ответ, почему так происходит. Но пока я воздержусь от обсуждения.

Простейшая программа и ассемблер к ней.

#define STR1 ";string_1\r\n"

#define STR2 ";string_2\r\n"

#define STR3 ";string_3\r\n"

#define STR4 ";string_4\r\n"

#define STR5 ";string_5\r\n"

#define STR6 ";string_6\r\n"

extern int printf();

void _start (void){

printf(STR1);

printf(STR2);

printf(STR5);

}

section code

[global _start]

_start:

; Line 8: void _start (void){

L_4:

; Line 9: printf(STR1);

push dword L_1

call printf

pop ecx

; Line 10: printf(STR2);

push dword L_2

call printf

pop ecx

; Line 11: printf(STR5);

push dword L_3

call printf

pop ecx

; Line 12: }

L_5:

ret

section data

section code

section data

section string

L_3:

db ";string_5"

db 0dh

db 0ah

db 00h

L_2:

db ";string_2"

db 0dh

db 0ah

db 00h

L_1:

db ";string_1"

db 0dh

db 0ah

db 00h

section const

section code

[extern printf]

Стрингов у нас 6, а в объектный файл попали только 3. Урежем программу до нельзя.

#define STR1 ";string_1\r\n"

#define STR2 ";string_2\r\n"

#define STR3 ";string_3\r\n"

#define STR4 ";string_4\r\n"

#define STR5 ";string_5\r\n"

#define STR6 ";string_6\r\n"

section code

section data

section code

section data

section const

В объектном файле полный нуль. А теперь берём ассемблер Nasm и ручками, ручками :)

SECTION .data

SECTION .data

STR1:

db ";string_1", 0dh, 0ah, 00h

STR2:

db ";string_2", 0dh, 0ah, 00h

STR3:

db ";string_3", 0dh, 0ah, 00h

STR4:

db ";string_4", 0dh, 0ah, 00h

STR5:

db ";string_5", 0dh, 0ah, 00h

STR6:

db ";string_6", 0dh, 0ah, 00h