Шаг 14.
Оптимизация с помощью ассемблера.
Написание внешних модулей TASM

    На этом шаге мы рассмотрим написание внешних модулей TASM.

    Следующие программы демонстрируют, как писать внешние ассемблерные модули. Для изучения этого раздела вам необходимо установить Turbo Assembler.

    В листинге 1 вызывается функция Timer() для доступа к счетчику времени в компьютере, который хранится по адресу 0000:046c и регулярно обновляется процедурой BIOS. Однако не спешите компилировать программу. Вы сделаете это позже, после создания вашего модуля Timer().

    Листинг 1. TESTTIME.CPP (проверка функции Timer())

#include <conio.h>
#include "timer.h"
main()
{
 while (! kbhhit())
  { gotoxy(1, wherey());
     cprintf("%d ", Timer());
   }
 getch();   //Ждать нажатия клавиши 
 return 0;
}  

    В программе не делается ничего особенного, это лишь средство для тестирования функции Timer(), которая будет написана на ассемблере. Во второй строке включается заголовочный файл (листинг 2).

    Листинг 2. Timer.h (заголовочный файл для функции Timer())

// timer.h - Заголовочный файл для timer.cpp
  extern "C" {
 long Timer();
}

   Прототипы внешних функций в программах на C++ обычно объявляются внутри директивы extern "C" {} . Это предотвращает процесс так называемого модифицирования имен, согласно которому C++ преобразует имена функций в уникальные строки, которые позволяют стандартным компоновщикам обрабатывать объектно- ориентированный код. Например, функция с именем Timer() , будет иметь имя @Timer$gv. Подобные имена затруднительны для чтения, и, вероятно, лучше пользоваться модифицированными именами внешних функций в ассемблерных модулях. (Компиляторы ANSI C не модифицируют имена функций, так что этот совет касается только программ на C++.)


    Совет. Если вам необходимо воспользоваться модифицированными именами (например, если вы хотите поддержать и использовать функцию, написанную и на ассемблере и на С++), напишите пустую функцию без операторов и скомпилируйте ее с опцией -S для получения ассемблерного файла с модифицированными именами. Воспользуйтесь этим файлом как оболочкой, которую следует наполнить инструкциями вашей настоящей функции.

    Далее следует ассемблерный модуль, в котором находятся выполняемые инструкции для вашей функции Timer(). В листинге 3 приводится реализация функции Timer() с помощью синтаксиса идеального режима TASM.

    Листинг 3. TIMER.ASM (реализация функции Timer() во внешнем ассемблерном модуле)

IDEAL
MODEL small
CODESEG
PUBLIC _Timer
PROC _Timer
  xor ax, ax      ; Задать ax равным 0000
  mov es, ax      ; Задать es равным 0000
  mov di, 0046cH   ; Задать di равным 046с
  mov ax, [WORD PTR es:di]    ;Взять младший байт timer
  mov dx, [WORD PTR es:di+2]  ;Взять старший байт timer
  ret                         ;Вернуться в место вызова
ENDP
END  
Файлы с текстами можно взять здесь.

    Во второй строке демонстрируется одна важная деталь - выбор модели памяти. Вы должны использовать одну и ту же модель памяти во внешнем модуле и в главной программе. Внутри сегмента кода (который начинается ключевым словом CODESEG) объявляется имя внешней функции с помощью директивы PUBLIC. Предварите глобальное имя символом подчеркивания - таково стандартное соглашение глобальных имен в C.

    Напишите функцию как любую другую отдельную ассемблерную процедуру, обычно, между директивами PROC и ENDP. Вы сами ответственны за возврат всех необходимых значений в соответствующих регистрах, предохранение от порчи стека, сохранение и восстановление регистров bp и ds и прочую низкоуровневую работу. В данном случае функция возвращает длинные целые значения в паре регистров ax:dx. Не имеет смысла также сохранять и восстанавливать bp и стековый указатель sp, поскольку в функции не используются параметры, передающиеся через стек, или переменные в стеке.

    Теперь вы владеете всеми необходимыми компонентами для ассемблирования и компиляции примера. С помощью TASM.EXE и автономного компилятора BCC.EXE, доступных по установленным маршрутам, и находясь в каталоге, содержащем файлы TESTTIME.CPP, TIMER.H и TIMER.ASM, введите из командной строки DOS команду
bcc testtime timer.asm.
Сначала компилятор C++ скомпилирует TESTTIME.CPP в TESTTIME.OBJ, затем TASM ассемблирует TIMER.ASM в TIMER.OBJ. Наконец, компоновщик объединит объектные файлы в исполняемом файле TESTTIME.EXE. Запустите программу и нажмите любую клавишу для остановки таймера.

    Для ассемблирования модулей порознь воспользуйтесь опцией TASM /ml для генерации глобальных имен с различением строчных и прописных букв. Например, введите команду tasm /ml timer для ассемблирования TIMER.ASM в TIMER.OBJ, скомпилируйте TESTTIME.CPP командой bcc -c testtime и скомпонуйте модули, введя bcc testtime.obj timer.obj.

    Хотя это и не отражено в приведенных примерах, ассемблерные модули часто нуждаются в экспорте данных так же, как и кода. С помощью директивы DATASEG начните сегмент данных и задайте имя для глобальной переменной:

IDEAL
 MODEL small
 DATASEG           ;Начать сегмент данных
 PUBLIC _MyVar
_MyVar    DW    0  ;Переменная размером в слово
 CODESEG
;В этом месте в модуле располагаются функции
END

    В главном модуле C++ объявите переменную MyVar (без лидирующего символа подчеркивания) с помощью директивы extern:

    Теперь вы можете пользоваться переменной MyVar так же, как при ее определении в модуле C++. Нет необходимости в использовании опции "C" в директиве extern, как это делается с функциями, поскольку имена переменных C++ не модифицирует.

   Для доступа к переменным C++ из внешних ассемблерных модулей следует воспользоваться директивой EXTRN. Например, если объявлена глобальная переменная

   int Global; 

в ассемблерном модуле можно сослаться на переменную Global как на внешнюю WORD_Global следующим образом:

  
  IDEAL
  MODEL small
  DATASEG
  EXTRN_Global:WORD        ;Внешняя переменная размером в слово
  ; Вставьте в этом месте другие объявления данных  
  CODESEG
  ;Вставьте  в этом месте функции модуля
END 

В листинге 4 демонстрируется функция Timer() в альтернативном стиле MASM. Ассемблируйте этот модуль с помощью команды tasm /ml timer.msm, затем скомпилируйте и скомпонуйте приложение так же, как и раньше, с помощью команды bcc testtime timer.obj.

    Листинг 4. TIMER.MSM (демонстрация функции Timer() в синтаксисе MASM)

.MODEL small
.CODE
PUBLIC _Timer
_Timer PROC
xor ax, ax
mov es, ax
mov di, 0046cH
mov ax, WORD PTR es[di]
mov dx,  WORD PTR es[di+2]
ret
_Timer ENDP
END

    На следующем шаге мы рассмотрим использование прерываний.




Предыдущий шаг Содержание Следующий шаг