asm32.info
Keep it simple — code in asm

Глава 4. Улеснения

Отново пишем програма (по лесният начин)

В тази глава ще се върнем отново на първата програма, която написахме и ще видим как можем да я напишем буквално само на няколко реда. В края на краищата това е наистина една много малка програмка и защо трябва да правим нещата трудно, когато можем да ги правим лесно.

1. Макроси - съвсем малко теория...

Макросите са един изключително мощен инструмент при писане на асемблер.

За да разберем как работи цялата работа, още отначало трябва да възприемем правилен подход към макросите. И той е: "Макросите са просто начин да вмъкваме автоматично текст, в сорса на програмата."

Нищо повече, просто там където трябва да напишем много еднообразни редове текст, макросите го правят само с един ред. По време на компилацията, първата работа на асемблера е да замести името на макроса с текста, който макроса генерира. След това се компилира именно сорса с вече заместените макроси. Ето и един елементарен пример, за да ви стане ясно:

  ; дефиниция на макрос.    
  macro myname arg1 {    
    db 'My name is ', arg1, '.', 0    
  }    
    
  ; използване на макрос.    
    myname 'John Found'    
    myname 'Петър Петров'    

Този сорс ще се компилира на две стъпки - първо компилаторът ще замести навсякъде макросът "myname" с текста от дефиницията на този макрос. Забележете, че е възможно предаването на параметри към макроса, които се заместват в текста който трябва да се генерира.

Резултатът след заместването ще бъде следният сорс:

      db 'My name is ', 'John Found', '.', 0    
      db 'My name is ', 'Петър Петров', '.', 0    

След това се компилира именно този текст. Разбира се, с използването на макроси може да се правят много по-сложни неща, отколкото даденият пример, но винаги става въпрос за генериране на текст, който иначе ще е досадно да се пише на ръка.

Използването на макроси, ако се прави с мярка и разум, може да увеличи четимостта на сорса, да освободи програмиста от писането на повтарящ се еднообразен код, както и да автоматизира дейности, които са стандартни и не представляват интерес за решаването на проблема.

И като повечето мощни инструменти, ако не се използват както трябва, макросите могат да направят от сорса на програмата една голяма, неразбираема каша... Всичко е въпрос на баланс и точна преценка, която постепенно ще научим ако искаме да пишем висококачествен код.

Засега ще отложим писането на макроси и запознаването с по-сложните страни на техният синтаксис за след няколко глави.

Предназначението на тази глава е да покаже как с използването на стандартната макро библиотека на FASM може да се улесни писането на програми за Windows.

2. Първо пишем, после обясняваме

Стартирайте FASMW и напишете, или копирайте следният сорс в редактора:

format PE GUI    
include 'win32a.inc'    
     invoke  MessageBox, 0, _message, _caption, \    
                           MB_OK or MB_ICONEXCLAMATION    
     invoke  ExitProcess, 0    

_caption db 'Win32 Assembly Programming',0    
_message db 'I feel the power.',0    

data import    
     library kernel32, 'kernel32.dll', \    
             user32,   'user32.dll'    
     include 'api\kernel32.inc'    
     include 'api\user32.inc'    
end data    

Запишете файла под някакво име (например Hello2.asm) и компилирайте (ctrl+F9). Ако всичко е наред, можете да стартирате с F9 или от менюто.

Сега да сравним този сорс с текста на първата ни програма (виж Глава2). Виждате, че текста е станал значително по-къс. Какво е новото:

Директивата 'include' - синтаксисът на тази директива е следният:

 include 'име на файл'    

'име на файл' е произволно име на текстов файл, което може да съдържа и променливи от вида %променлива%. Стойностите на тези променливи се задават във файла fasmw.ini и служат за задаване на път към библиотеки, които трябва да са достъпни отвсякъде, но предварително не е ясно къде се намират при различните потребители. Например при мене, тези библиотеки са на едно място, а при вас сигурно са на друго, но това не пречи да се компилират сорсовете без редактиране, ако правилно сме настроили FASMW (виж Глава 0).

Когато компилаторът срещне директивата include в сорса, той прочита файла, указан като параметър и го вмъква на мястото на директивата.

Използваният в нашият пример файл: "Win32a.inc" съдържа всички дефиниции на константи, структури за данни и макроси, необходими за програмиране на Windows приложения. В частност вътре са и дефинициите на константите MB_OK и MB_ICONEXCLAMATION, които ни трябват на нас.

Второто, което прави впечатление е начинът по който се извикват функците с използването на макроса "invoke". Този макрос е създаден специално за викане на системни функции на Windows. Първият му параметър е името на импортираната функция, а следващите аргументи са параметрите, които трябва да се предадат на функцията. Кода който се генерира е същият както от примера в Глава 2.

      invoke  MessageBox, 0, _message, _caption, \    
                         MB_OK or MB_ICONEXCLAMATION    

се преобразува в следният сорс:

      push  MB_ICON or MB_ICONEXCLAMATION    
      push  _caption    
      push  _message    
      push  0    
      call  [MessageBox]    

Още веднъж - това е чисто текстово заместване, истинската компилация започва след това!

Следващото което се вижда от сорса е, начинът по който се импортират функциите:

   data import    
       library kernel32, 'kernel32.dll', \    
               user32,   'user32.dll'    
       include 'api\kernel32.inc'    
       include 'api\user32.inc'    
   end data    

В този код се използва макросът "library" с който дефинираме от кои .dll библиотеки желаем да импортираме функции. Този макрос дефинира нужните структури с данни необходими на Windows, без ние да трябва да ги пишем.

Обърнете внимание на синтаксисът, използван тук:

      library kernel32, 'kernel32.dll', \    
              user32,   'user32.dll'    

Първият ред завършва със символа "". Това е синтаксис, който може да се използва навсякъде във FASM и означава, че следващият ред трябва да се закачи за текущият и да се компилира като един ред. Използването на този механизъм позволява да се направи сорса по-четлив и разбираем и да се предпазим от хоризонтално местене на текста при четене.

Двата файла, които се инклудват след дефиницията на библиотеките (за всяка библиотека поотделно) всъшност импортират всички функции от дадената библиотека, посредствум един хитроумен макрос за условно импортиране "import". Този макрос, проверява дали дадената функция се използва някъде в текста на програмата и я импортира само при условие че се използва. Това е много удобен механизъм, който ни дава възможност да използваме произволни функции в програмите си без да се грижим да ги импортираме изрично или да премахваме импортирането, ако повече не ни трябват (да кажем след някакви промени в програмата).

По този начин размера на програмата се запазва винаги възможно най-малък и се освобождава програмиста да пише кода, който върши нещо полезно, а не кода който е спомагателен и така или иначе винаги е един и същ.

И накрая, накратко ще спомена за един още по-кратък начин за писане на програми с използването на по-сложна макро библиотека: "Win32ax.inc". При този начин, донякъде се жертва яснотата и контрола върху програмата, заради един синтаксис, който е почти като при езиците от високо ниво:

  include 'win32ax.inc'    
    
  .code    
  start:    
       invoke  MessageBox, 0, 'I feel the macro power.',    \    
                              'Win32 Assembly Programming', \    
                              MB_OK or MB_ICONEXCLAMATION    
       invoke  ExitProcess, 0    
  .end  start    

Запишете тази програма под име например "Hello3.asm".

Както виждате тук се използват макросите ".code" и ".end", които генерират всички допълнителни данни, необходими за изграждането на Windows приложение. Другата характерна черта е, че като параметри на функции могат да се предават и текст, заграден в кавички - макро библиотеката сама дефинира структурите от данни и присвоява етикети на тези данни.

Лично аз избягвам да използвам тази библиотека, защото донякъде намалява контрола върху генерираният код, като прави нещата по стандартен начин. Освен това смятам, че начинаещите по-трудно ще разберат същността на нещата, когато подробностите са закопани толкова надълбоко в сложни макро библиотеки.

Решението както винаги е точният баланс между лесното писане и четливият код от една страна и яснотата и контрола върху генерираният изпълним код от друга.

Затова в следващите уроци ще използваме библиотеката"win32a.inc".

В следващата глава ще се върнем отново на теорията, за да разберем как работи процесора, погледнато съвсем, съвсем отблизо...

Last modified on: 05.08.2012 04:05:30

Preview

Comments

4. Улеснения
Filename:
Title: