Взаимодействие Квик Бейсика с Ассемблером

Источник: basic113

Как и любой другой язык программирования высокого уровня, Квик Бейсик не может предоставить программисту средств для решения нестандартных или критических по скорости выполнения задач. А такие задачи в практике программиста встречаются достаточно часто, начиная от пересылки больших объемов памяти или скоростного обмена данными с видеобуфером и кончая управлением нестандартными устройствами. С другой стороны, разрабатывать большую прикладную программу целиком на ассемблере - это непозволительная трата времени, так как многие составные части профессионально работающих прикладных программ можно с успехом создать на языке высокого уровня. Знание Квик Бейсика и ассемблера, понимание принципов их взаимодействия позволяет соединить в единое целое быстроту разработки программ на Квик Бейсике и мощь ассемблера.

Взаимодействие Квик Бейсика с ассемблером в самом общем виде уже было затронуто автором в [1] и здесь мы попытаемся подробно разобраться в этой актуальной проблеме. В принципе Квик Бейсик может взаимодействовать с любым ассемблером фирмы Microsoft, однако наиболее целесообразно использовать макроассемблер версий от 5.0 и выше, так как он имеет специальный интерфейс, облегчающий взаимодействие с Квик Бейсиком. Поэтому дальнейшее изложение материала мы построим применительно к макроассемблеру MASM 5.0 фирмы Microsoft, а также рассмотрим дополнительные возможности, обеспечиваемые MASM 6.0-6.1.

При взаимодействии с Квик Бейсиком обычно выбирают среднюю модель памяти, что означает: объем кода может занимать до 1 Мб, а данные - до 64 Кб. Таким образом, макроассемблер для адресации кода использует дальние адреса, а для адресации данных - ближние. Отсюда вытекает фундаментальной важности следствие: когда Квик Бейсик передает макроассемблеру управление, в регистре сегмента данных DS содержится адрес сегмента данных Квик Бейсика и в дальнейшем макроассемблер адресуется к своим переменным, используя именно это содержимое регистра DS. Если вы измените содержимое регистра DS, то макроассемблер будет адресоваться неизвестно к чему.

В ассемблерном модуле может содержаться одна или несколько процедур, но те из них, которые вы хотите сделать доступными Квик Бейсику, необходимо объявить как PUBLIC и FAR. Передача параметров осуществляется через стек. Для этого ассемблерная процедура, предварительно запомнив содержимое регистра BP (Base Pointer - указатель базы), должна поместить в BP значение регистра SP (Stack Pointer - указатель стека) и адресоваться к параметрам через регистр BP по следующим правилам. Допустим, что из Квик Бейсика в ассемблерную процедуру передается N целочисленных параметров, тогда к k-ому аргументу в списке параметров вызова вы получаете доступ через [BP]+6+(N-k)*2.

При стандартном вызове ассемблерной процедуры из Квик Бейсика простым упоминанием имени процедуры со списком параметров:

ИМЯ_ПРОЦЕДУРЫ [список_параметров]

последние передаются в виде смещения их адресов относительно сегмента данных Бейсика. Если процедура вызывается функцией CALLS или со словом SEG перед именем параметра, то Квик Бейсик передает дополнительно и сегментную часть адреса параметра и таким образом в стек заносится не одно слово, а два.

Ниже приведены примеры различной передачи переменных в ассемблерные процедуры (прием параметров в процедуре должен соответствовать конкретному типу их передачи):

1) в процедуру ASMPROC передаются смещения адресов переменных Param1 и Param2

ASMPROC Param1, Param2

2) в процедуру ASMPROC передается смещение адреса непосредственного значения 25 и само значение переменной Param2

ASMPROC (25), BYVAL Param2

3) в процедуру ASMPROC передается смещение переменной Param1 и сегментированный адрес переменной Param2 в виде двух слов

ASMPROC Param1, SEG Param2

4) в процедуру ASMPROC все переменные передаются в виде сегментированных адресов

CALLS ASMPROC (Param1, Param2)

По окончании работы ассемблерной процедуры вы должны восстановить исходные значения регистров и сделать дальний возврат по команде RET m (где m = N * 2, а N - число параметров в списке вызова).

При передаче отдельного элемента статического массива ассемблерная процедура получает к нему доступ в виде смещения его адреса относительно сегмента данных Квик Бейсика и тем самым может не только изменить значение этого элемента, но и вычислить расположение других элементов массива. Однако, при передаче отдельного элемента динамического массива Квик Бейсик временно копирует его в сегмент данных и передает в ассемблерную процедуру смещение адреса этой копии относительно сегмента данных; поэтому при таком подходе процедура не имеет доступа к самому элементу динамического массива. При работе ассемблерных процедур с массивами гораздо проще и надежнее непосредственно передавать их смещение и сегментную часть в виде отдельных переменных, как показано ниже.

Попробуем составить ассемблерную процедуру EQUAL, которая осуществляет инициализацию всех элементов целочисленного массива начальным значением типа INTEGER (от -32768 до +32767). Такая операция довольно часто требуется при различных математических расчетах. Можно, разумеется, обойтись средствами Квик Бейсика и присвоить начальное значение элементам массива в цикле или даже с помощью оператора POKE, но на это уходит слишком много времени, тогда как на ассемблере заполнение массива выполняется практически мгновенно.

.model medium,basic  ; установить связь с Квик

; Бейсиком и выбрать среднюю

; модель памяти

.code                                              ; определить сегмент кода в MASM

PUBLIC EQUAL                           ; сделать процедуру EQUAL

; доступной Квик Бейсику

; ------------------ начало процедуры EQUAL ------------------

EQUAL    proc far

push bp                                         ;    сохраняем BP в стеке

mov bp,sp                                     ;    записываем SP в BP

push di                                           ;    сохраняем регистры

push es                                          ;    DI и ES в стеке

mov bx,[bp]+12                             ;    получаем адрес смещения

; массива в BX

mov di,[bx]                                    ; получаем значение смещения

; в DI по адресу из BX

mov bx,[bp]+10                             ; получаем адрес сегмента

; массива в BX

mov es,[bx]                                    ; получаем значение сегмента

; в ES по адресу из BX

mov bx,[bp]+8                               ; получаем pазмеp массива в CX

mov cx,[bx]

mov bx,[bp]+6                               ; получаем начальное значение

mov ax,[bx]                                    ; в AX

cld                                                   ; направление вперед

rep stosw                                       ; заполняем массив по счетчику в CX

; значением из AX

pop es                                            ; восстанавливаем значения

pop di                                             ; регистров из стека

pop bp

ret 8                                                ; дальний возврат в Квик Бейсик

EQUAL    endp

; ------------------ конец процедуры  EQUAL ------------------

END

Оттранслируем программу на ассемблере, получим объектный файл EQUAL.OBJ и преобразуем его в библиотеку формата Quick, для чего запустим компоновщик Квик Бейсика LINK.EXE. Ответы на запросы компоновщика немного различаются в зависимости от версии Квик Бейсика:

Object Modules [.OBJ]: /QU EQUAL                                                                       <ENTER>

Run File [EQUAL.QLB]:                                                                                              <ENTER>

List File [NUL.MAP]:                                                                                                  <ENTER>

Libraries [.LIB]: QBXQLB.LIB                                                                                    <ENTER>

Definitions File [NUL.DEF]:                                                                                       <ENTER>

В предпоследней строке указана библиотека QBXQLB.LIB применительно к Professional Basic 7.0-7.1, в случае Квик Бейсика 4.0 имя библиотеки необходимо заменить на BQLB40.LIB, а для версии 4.5 - на BQLB45.LIB.

Получив библиотечный файл EQUAL.QLB, загружаем Квик Бейсик с паpаметром /L EQUAL и запускаем программу:

DECLARE SUB EQUAL (Offset%, Segment%, Size%, Value%)

DEFINT A-Z

DIM Array(1 TO 32767) AS INTEGER

Size = UBOUND(Array) - LBOUND(Array) + 1

DO

INPUT "Значение для инициализации элементов массива"; Value

IF Value >= -32768 AND Value <= 32767 THEN

OK = -1

ELSE

OK = 0

PRINT "Значение должно быть от -32768 до +32767"

END IF

LOOP UNTIL OK

EQUAL VARPTR(Array(1)), VARSEG(Array(1)), Size, Value

PRINT "Массив инициализирован значением"; Value

При взаимодействии с ассемблером есть еще один очень важный момент, о котором нельзя забывать во избежание неожиданных сюрпризов: в Квик Бейсике отсутствуют беззнаковые типы данных. Например, в результате тех или иных операций ассемблерная процедура может возвратить в программу целое беззнаковое число в диапазоне 0-65535, но Квик Бейсик

- как только это число превысит предел для знакового целого типа, равный 32767 - будет трактовать его как отрицательное целое значение (т.е. компилятор считает, что самый старший бит в слове указывает на знак числа). Ярким примером сказанному служит функция Квик Бейсика SADD, которая возвращает целое число, соответствующее смещению адреса строки. Если смещение адреса строки превышает величину 32767, функция возвращает отрицательное значение, которое на самом деле является беззнаковым целым.

Тем не менее, это ограничение Квик Бейсика можно легко обойти. Допустим, что из ассемблерной процедуры возвращается параметр Value как беззнаковое целое:

DEFINT A-Z

DIM Result AS LONG

. . . . .

DEF SEG                                                   ' установим сегмент данных

' Квик Бейсика

POKE VARPTR(Result), PEEK(VARPTR(Value))

POKE VARPTR(Result) + 1, PEEK(VARPTR(Value) + 1)

Таким образом вы получите в переменной Result типа LONG истинное значение Value, возвращенное ассемблерной процедурой.

Теперь мы рассмотрим весьма полезную ассемблерную процедуру для прямого вывода в видеобуфер текстовой строки, которая в Квик Бейсике может иметь длину до 32 Кб. Процедура является аналогом модуля TPRINT.BAS на Бейсике, опубликованного в [2], и рассчитана на взаимодействие только с Professional Basic 7.0-7.1. Чтобы воспользоваться процедурой в своей программе, запишите ее в файл, например с именем DPRINT.ASM, оттранслируйте с помощью MASM 5.0, преобразуйте в библиотечный файл формата Quick по аналогии с описанным ранее принципом и запустите Квик Бейсик командой: QBX/L DPRINT.

DECLARE SUB DPRINT (Size%,Ofs%,Segm%,r1%,c1%,r2%,c2%)

DEFINT A-Z

DIM Text AS STRING

LINE INPUT "Введите строку (1-2000 букв) "; Text

INPUT "Строка, столбец верхнего левого угла"; r1, c1

INPUT "Строка, столбец нижнего правого угла"; r2, c2

CLS

DPRINT LEN(Text), SADD(Text), SSEG(Text), r1, c1, r2, c2

END

; -----------------------------------------------------------------

; исходный текст процедуры DPRINT                                      (с) Ю.Е.Купцевич, 1993

; (процедура рассчитана на взаимодействие только с QBX PDS 7.0-7.1)

; ----------------------------------------------------------------- .model medium,basic

.data

c1                 dw   ?

c2                 dw   ?

f                    dw   ?

Len               dw   ?

multy           db   160

r1                  db   ?

r2                  db   ?

.code

public DPRINT

; ----- пpоцедуpа для вывода стpоки текста в видеобуфеp -----

DPRINT proc far push bp

mov bp,sp

push di

push si

push es

pushf

push ds

; получаем параметры

mov   bx,[bp]+18     ; получим длину текста в Len

mov   ax,[bx]

mov   Len,ax

mov   bx,[bp]+16     ; получим смещение текста в SI

mov   si, [bx]

mov   bx,[bp]+14     ; получим сегмент текста в DX

mov   dx,[bx]

mov   bx,[bp]+12     ; получим r1 (нумеpация стpок экpана 1-25)

mov   ax,[bx]

dec    al             ; r1 - 1

mov   r1,al

mov   bx,[bp]+10     ; получим c1 (нумеpация столбцов 1-80)

mov   ax,[bx]

dec    al             ; c1 - 1

shl     al,1           ; умножим c1 на 2

mov   c1,ax

mov   bx,[bp]+8      ; получим r2

mov   ax,[bx]

dec    al             ; r2 - 1

mov   r2,al

mov   bx,[bp]+6      ; получим c2

mov   ax,[bx]

dec    al             ; c2 - 1

shl     al,1           ; умножим c2 на 2

mov   c2,ax

mov ax,0B800h                        ; сегмент видеобуфеpа в ES

mov es,ax

sub ax,ax                                   ; обнуляем AX

mov bl,r1                                  ; BL - счетчик 1-го цикла

sub cx,cx                                   ; обнуляем счетчик байт в стpоке текста

cld

; в 1-м цикле задаются экранные строки в окне вывода

St1:  mov al,bl

mul multy                                 ; умножаем BL на 160

push ax                                     ; сохpаняем pезультат в стеке

add ax,c1

mov di,ax                                  ; получаем в DI стаpтовое смещение

; в видеобуфеpе

pop ax                                       ; восстанавливаем AX из стека

add ax,c2

mov f,ax                                    ; получаем в f конечное смещение в

; видеобуфеpе

; во 2-м цикле осуществляется вывод данных на знакоместа в

; текущей экранной строке

St2:  cmp cx,Len                                      ; сpавниваем счетчик с длиной текста

je Ex                                           ; если CX = Len, закончить pаботу

mov ds,dx                                 ; в DS сегмент стpоки текста

movsb                                       ; получаем байт из стpоки текста

; и пеpеносим в видеобуфеp

pop ds                                      ; восстанавливаем DS

push ds                                    ; восстанавливаем стек

inc cx                                         ; увеличиваем счетчик байт в

; стpоке текста

inc di                                         ; символы выводятся в четные позиции

cmp di,f                                     ; сpавниваем DI с конечным смещением

; в видеобуфеpе

jbe St2                                       ; если DI <= f, пpодолжим вложенный

; цикл

; конец 2-го цикла

inc bl                                         ; увеличим счетчик 1-го цикла

cmp bl,r2                                   ; сpавним BL с r2

jbe St1                                       ; если BL <= r2, пpодолжим 1 цикл

; конец 1-го цикла

Ex:                pop ds

popf

pop    es

pop    si

pop    di

pop    bp

ret      14

DPRINT  endp

END

А можно ли написать для Бейсик-программы ассемблерную функцию?

Можно, если вы располагаете макроассемблером версии не ниже 6.0 и

Professional Basic 7.0-7.1. В этом случае нужно воспользоваться расширенными возможностями директивы PROC в 6-ой версии макроассемблера. (Кстати, макроассемблер версий от 6.0 и выше имеет еще более простой интерфейс для взаимодействия с Бейсиком, чем MASM

5.0 и, как вы увидите в дальнейшем, позволяет не беспокоиться о многих вещах.)  Рассмотрим создание целочисленной функции для Professional Basic на схематическом примере. Функция FuncName получает из Бейсик-программы два целочисленных параметра и возвращает их сумму.

.model medium                                           ; устанавливаем среднюю

; модель памяти

; объявляем функцию FuncName с двумя целочисленными параметрами FuncName  PROTO basic, Param1:PTR word, Param2:PTR word

.code

FuncName  PROC basic, Param1:PTR word, Param2:PTR word

mov   bx, word ptr Param1   ; получаем Param1 в AX

mov   ax,[bx]

mov   bx, word ptr Param2   ; получаем Param2 в CX

mov   cx,[bx]

add    ax,cx

ret                                                                 ; функция возвращает значение

; из AX

FuncName  ENDP

END

Ассемблирование исходного текста функции должно осуществляться по команде: ML /C FUNCNAME.ASM (если имя файла совпадает с именем функции).

В Бейсик-программе соответственно следует объявить:

DECLARE FUNCTION FuncName%(Param1%, Param2%)

Обратите внимание на то, что целочисленное значение возвращается функцией из регистра AX. В случае длинных целых чисел (типа LONG) должна быть задействована пара регистров AX:DX. Чтобы функция могла возвращать дополнительные параметры, например, измененное значение Param1, следует записать так:

.model medium

; объявляем функцию FuncName с двумя целочисленными параметрами FuncName  PROTO basic, Param1:PTR word, Param2:PTR word

.data

buf                     dw ?

.code

FuncName PROC basic, Param1:PTR word, Param2:PTR word mov bx, word ptr Param1

mov   buf,bx                ; запомним смещение Param1

mov   ax,[bx]

mov   bx, word ptr Param2

mov   cx,[bx]

add    ax,cx

mov   bx,buf

mov   [bx],ax               ; теперь возвращаем результат

; сложения в Param1

shr ax,1                                                        ; поделим AX на 2

ret                                                                 ; функция возвращает значение

; из AX

FuncName  ENDP

END

В этом примере результат сложения Param1 и Param2 возвращается в

Param1, а сама функция возвращает (Param1 + Param2) \ 2.

Рассмотрев два схематических примера, попробуем создать весьма

необходимую на практике функцию для определения совместимости

видеоадаптера с EGA, его типа (цветной/монохромный) и размера памяти.

К совместимым с EGA адаптерам относятся EGA, VGA, SVGA и др.

Несовместимы - CGA, MCGA, MDA, Hercules.

.model medium

EGA  PROTO basic, Mono:PTR word, Mem:PTR word

.data

buf1 dw ?

buf2 dw ?

.code

; ------- функция для определения совместимости видеосистемы -------

; ------ с EGA, определения объема памяти видеокарты и ее типа -----

EGA  proc basic, Mono:PTR word, Mem:PTR word

mov   si,word ptr Mono         ; получаем адрес Mono

mov   buf1,si

mov   si,word ptr Mem          ; получаем адрес Mem

mov   buf2,si

xor     ax,ax

mov   es,ax

mov   al,es:[463h]

cmp   al,180                   ; если AL=180, то адаптер

je @EgaMono                                                    ; монохромный

mov dx,0                                                              ; цветной адаптер - DX=0

jmp short @MonoSend

@EgaMono:

mov dx,-1                                                            ; монохромный адаптер - DX=-1

@MonoSend:

mov   si,buf1

mov   [si],dx                  ; возвращаем Mono

mov   ah,12h

mov   bl,10h

int      10h

cmp   bl,10h                   ; если BL<>10h, есть EGA-

je @NoEga                                                         ; совместимый BIOS

xor     bh,bh

inc     bl

mov   cl,6

shl     bx,cl

mov   si,buf2

mov   [si],bx                  ; возвращаем Mem

mov   ax,-1                    ; есть EGA - для возврата функцией

jmp    short @ExEga

@NoEga:

mov ax,0                                                              ; нет EGA - для возврата функцией

@ExEga:

ret

EGA  endp

END

Функция EGA взята из основного ядра библиотеки EasyWork-4 для

Professional Basic 7.0-7.1 (библиотека распространяется PS-магазином при редакции журнала "Персональные Программы", а также Ассоциацией Пользователей Microsoft Basic). Функция возвращает TRUE (-1), если видеосистема совместима с EGA и FALSE (0), если - несовместима. В переменной Mono возвращается TRUE (-1), если видеоадаптер монохромный, и FALSE (0), если - цветной; в переменной Mem в случае совместимости с EGA сообщается размер памяти видеоадаптера в Кб.

Кроме описанных способов взаимодействия Квик Бейсика и ассемблера можно воспользоваться функцией ABSOLUTE для выполнения подпрограммы в машинных кодах и, хотя этот метод устарел, в отдельных случаях его применение может быть вполне оправдано. При работе с функцией ABSOLUTE нужно выполнить следующие шаги:

1) написать ассемблерную процедуру по правилам, рассмотренным в самом начале статьи;

2) оттранслировать ее в объектный файл, указав ассемблеру на необходимость создания *.LST файла;

3) просмотреть *.LST файл и выбрать из него фрагмент кода от PUSH BP и MOV BP,SP до RET;

4) загрузить оператором POKE или каким-то другим способом шестнадцатиричные коды в целочисленный массив, допустим, Proc(0 TO

25) и вызвать функцию ABSOLUTE:

DEF SEG = VARSEG(Proc(0))

CALL ABSOLUTE ([список_параметров,] VARPTR(Proc(0)))

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

' ------- процедура для очистки буфера клавиатуры -------

SUB KEYSOFF

' процедура выполняет машинные команды в соответствии с:

' PUSH BP                                                ;&H55

' MOV BP,SP                                            ;&H8B,&HEC

' CLI                                                           ;&HFA                   (запретить прерывания)

' SUB AX,AX                                          ;&H2B,&HC0  (обнулить регистр AX)

' MOV ES,AX                                          ;&H8E,&HC0

' MOV AL,ES:[41AH]                             ;&H26,&HA0,&H1A,&H04

' MOV ES:[41CH],AL                              ;&H26,&HA2,&H1C,&H04

' STI                                                           ;&HFB                   (разрешить прерывания)

' POP BP                                                   ;&H5D

' RET                                                         ;&HCB

DIM KEYS(0 TO 9) AS INTEGER

KEYS(0) = -29867: KEYS(1) = -1300: KEYS(2) = -16341

KEYS(3) = -16242: KEYS(4) = -24538: KEYS(5) = 1050

KEYS(6) = -24026: KEYS(7) = 1052: KEYS(8) = 24059

KEYS(9) = 203

DEF SEG = VARSEG(KEYS(0))

CALL ABSOLUTE (VARPTR(KEYS(0)))

DEF SEG

END SUB

Чтобы использовать эту процедуру в своей программе, объявите ее:

DECLARE SUB KEYSOFF ()

а затем в нужном месте программы вызовите процедуру: KEYSOFF


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=30104