|
|
|||||||||||||||||||||||||||||
|
Взаимодействие Квик Бейсика с АссемблеромИсточник: 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 Ссылки по теме
|
|