Начало программирования в Виндовс
Неутомимым бойцам 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