На этом шаге мы рассмотрим написание внешних модулей 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++.)
Далее следует ассемблерный модуль, в котором находятся выполняемые инструкции для вашей функции 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
На следующем шаге мы рассмотрим использование прерываний.