Калашников.ru - Ассемблер? Это просто!.. (Выпуск № 021)

InterReklama Advertising

Ассемблер? Это просто! Учимся программировать

______________________________________

Выпуск N 021 (Оболочка)

Сегодня в рассылке:

Организационные вопросы

Ваши письма

Оболочка

Организационные вопросы Дорогие мои подписчики! Нас уже очень много (более 8200 человек). Народ постоянно подписывается. Естественно, что мне одному становится тяжело вести нашу рассылку. В связи с этим я хотел бы навести небольшой порядок с тем, что у нас происходит, а также хочу попросить помощи у энтузиастов.

С момента прошлого выпуска произошли многие перемены. Прошу вас, дорогие мои, внимательно прочитать весь данный раздел.

Итак, начнем по порядку.

___________

1. Приглашаются помощники:

для перевода текста с формата HTML в формат MS Word;

для перевода текста с формата *.txt (Windows) в формат MS Word (вопросы и ответы экспертов).

Если у вас есть время и возможность, то, пожалуйста, напишите мне. Буду рад рассмотреть ваши предложения. Более подробную информацию можно получить в письме, которое следует направить по адресу Assembler@Kalashnikoff.ru. В теле письма, пожалуйста, укажите, чем вы можете помочь. Буду очень признателен...

___________

2. Требуются ведущие рубрики "Ваши письма".

Необходимо качественно перевести письма подписчиков в формат HTML из текстового файла и оформить это соответствующим образом (как во всех наших выпусках оформлялась данная рубрика). Что я могу предложить? Предоставить вам полное ведение рубрики "Ваши письма" (с моими поправками, если понадобится). Ваше имя, e-mail, URL будут указаны в рассылке. Письма подписчиков будут высылаться вам по почте от моего имени, но без указания электронных адресов подписчиков. Ваша задача: отобрать наиболее интересные для подписчиков письма, исправить по мере необходимости орфографические ошибки, оформить письма в формате HTML и выслать мне готовый вариант до выхода следующего номера рассылки (примерно за 2-5 дней). Вот, собственно, и все!

___________

3. Экспертные группы (следует внимательно прочесть экспертам и подписчикам, задающим вопросы!).

К сожалению, мне пришлось закрыть три экспертные группы в связи с тем, что данным экспертам практически не приходит вопросов. Закрыты следующие группы:

Эксперты по работе с Turbo Debugger;

Эксперты по работе с AFD;

Эксперты по работе с CodeView.

Как показало время, большинство вопросов задается группе "Эксперты по общим вопросам программирования на Ассемблере под DOS". Безусловно, лидирует по количеству отвеченных вопросов эксперт Slava V. Четкие ответы, лаконичное содержание, простой стиль изложения, оперативность ответов - все это делает Slav'у незаменимым помощником для меня и для всех подписчиков. Те из вас, кто получил ответ от Slav'ы, подтвердят мои слова. Как ему это удается - ума не приложу! В связи с этим хочу повторно выразить благодарность Slav'е за оказанную помощь как мне, так и всем вам, дорогие подписчики.

Однако, кто-то из экспертов старается изо всех сил отвечать на ваши вопросы, а кто-то зарегистрировался и за все существование экспертных групп не ответил ни на один вопрос! Это особенно относится к указанной выше группе. Экспертов в данный момент довольно-таки много. Отбирать вручную и искать "сачков" нет времени. Поэтому мне придется поступить не очень хорошо, попросив вас, уважаемые эксперты, пройти т.н. перерегистрацию. Для этого необходимо направить мне пустое письмо до 31 января 2001 года включительно по адресу: Registration@Kalashnikoff.ru, указав в теле письма ваш электронный адрес по которому вы зарегистрированы в экспертной группе. В ответ на ваше письмо придут новые правила работы экспертных групп. Эксперты, от которых я НЕ получу писем до указанной даты, исключаются из числа экспертов. Сурово? Зато на мой взгляд справедливо по отношению к тем подписчикам, которые хотели бы стать экспертами, да группы уже укомплектованы, и мне с горечью в сердце приходится отказывать всем желающим. В последующих выпусках я напишу о том, сколько мест освободилось в той или иной группе. Надеюсь, что со временем мы соберем отличную экспертную группу в помощь вам, дорогие мои читатели.

Подписчики, желающие задать вопросы экспертам, должны ознакомиться с новыми правилами (установленными 15 января 2001 года), которые можно найти на нашем сайте. Если у вас проблемы с Интернетом, то напишите мне письмо по адресу AssmExperts@Kalashnikoff.ru. Данная информация будет выслана вам в течение двух рабочих дней. Подписчикам, получившим или ознакомившимся на сайте с новыми правилами и адресами экспертов после 15.01.2001, волноваться не стоит! Данные правила упорядочивают работу экспертных групп, а также вносят определенные требования для подписчиков задающих вопросы, с тем, чтобы у нас не возник хаос, беспорядок и пр. Я также постарался оградить экспертов от вопросов, которые задают "ленивые" подписчики, т.е. те, кто столкнувшись с "малюпасенькой" проблемкой, сразу же задает вопрос экспертам. Дорогие мои! Так ведь не интересно! Уверяю вас, что пасуя перед сложностями (или обращаясь с мелкой проблемой к помощникам), вы мало чего добьетесь как в изучении Ассемблера, так и во всем остальном. Постарайтесь приложить максимум усилий для решения возникшей проблемы. Если же ничего не выходит, то - пожалуйста. Эксперты с огромным удовольствием ответят на ваш вопрос(ы). По содержимому вопроса ведь сразу видно, думали вы или нет, прежде, чем написали экспертам...

Все вопросы, поступившие в адрес экспертов, но противоречащие новым правилам, игнорируются экспертами!

___________

4. Обсуждение книги.

Что можно сказать по этому поводу. Поступило ко мне несколько отзывов и не более того. Причем, все были положительные (к моему великому удивлению), что воодушевило меня на продолжение осуществления моих сумасшедших планов. Я так понимаю, что книга, которая будет издана (по крайней мере, я надеюсь на это) нужна многим, что, безусловно, радует.

Тем не менее, нет необходимости создавать отдельный форум (группу обсуждений) для такого пустяка. Если у вас есть что сказать, то оставляйте запись в Гостевой книге или на форуме. Буду рад прочитать ваши рассуждения не только я один, но (надеюсь!) и многие подписчики.

___________

5. Новые поступления на сайте.

На нашем сайте (http://www.Kalashnikoff.ru) теперь можно найти документацию на русском языке по работе с SoftIce. Берите и качайте на здоровье!

Более того, теперь доступно пособие по написанию вирусов на русском языке, причем, в довольно-таки понятном и простом для начинающего программиста изложении. В данном пособии описаны несколько вариантов вирусов (включая резидентные вирусы и вирусы, заражающие *.exe-файлы). Надеюсь, что вы почерпнете из этого файла много действительно полезной и нужной информации, которая поможет вам освоить все тонкости программирования на Ассемблере.

___________

6. Смена внешнего вида сайта.

Ничто не стоит на месте. Все находится в движении, меняется. Вот и сайт наш уже третий раз поменял свой облик (условно - Версия 3.0). Сидел я, мучил свою несчастную, убитую горем "четверку" все выходные. Зато теперь плод моих трудов вы можете оценить лично. Был бы безмерно счастлив, если бы вы оставили запись (ваше мнение) в Гостевой книге.

Конечно, пока что еще не все подразделы сведены к одному дизайну, стилю (например, Гостевая и форум). Но, тем не менее, уже можно сделать однозначные выводы.

Я же, в свою очередь, хотел бы выразить глубокую благодарность за качественный хостинг компании AGAVA и лично Бобу Марлину (BoB MaRLiN) за помощь в создании настоящей Гостевой книги и выделенное место под нее.

Если у вас есть время и желание написать форум и (или) гостевую на нашем сайте, то буду вам безмерно благодарен. Естественно, все ваши права будут соблюдены. Мой провайдер (www.100mb.ru) позволяет создавать свои CGI-скрипты в каталоге cgi-bin. Если вы надумали написать что-либо, то не нужно мне слать письмо с предложениями. Просто сделайте и пришлите (если не сложно). Я все выложу на сайт. Главное условие - чтобы форум и гостевая были одного стиля с сайтом. Очень на вас надеюсь...

Да! Чуть не забыл! Теперь статистику посещений нашего сайта можно найти здесь: http://www.Kalashnikoff.ru/usage. Она обновляется ежедневно. Статистика включает в себя трафик, страны и регионы, жители которых заходят на сайт, посещаемость по часам и многое другое. Зачем это? Да так, вдруг кому интересно будет...

И вот еще: установил на сайте кнопку, которая сигнализирует всем посетителям о том, в Сети ли я сейчас или нет. Мне понравилось это дело... Только что-то "глючит" она иногда...

Ваши письма Дорогие мои подписчики! Ваши письма мы рассмотрим в следующем спецвыпуске, который выйдет до 26 января 2001 года. Дело в том, что писем ваших очень много, и большинство из них будут интересны всем подписчикам рассылки. Только вот байты сейчас не позволяют сделать это... Так что подождите, пожалуйста, спецвыпуск 022.

Оболочка Как вы уже знаете, наша оболочка разбита на несколько файлов. Ничего лучшего, чем разделить эти файлы по разным окошкам, я не придумал. Надеюсь, что вы без труда разберетесь. Как скопировать эти файлы в DOS-формат - я писал в предыдущих выпусках.

Тем не менее, если вам лень копировать каждый файл в DOS-формат, то вы можете перекачать все целиком отсюда: http://www.Kalashnikoff.ru/Assembler/Issues/Enclosures/Sshell21.rar.

Sshell21.asm

; Sshell21.ASM - программа к рассылке № 021 ; (С) Авторские права на файлы-приложения принадлежат подписчикам рассылки ; "Ассемблер? Это просто! Учимся программировать" ; Автор рассылки: ; Калашников Олег Александрович (e-mail: Assembler@Kalashnikoff.ru) ; http://www.Klashnikoff.ru ; --- Ассемблирование (получение *.com файла) --- ;При использовании MASM: ;ML.EXE Sshell21.asm /AT ;При использовании TASM: ;TASM.EXE Sshell21.asm ;TLINK.EXE Sshell20.obj /t/x ;______________________________________________________ .286 ;Будем использовать команды 80286 процессора CSEG segment assume cs:CSEG, ds:CSEG, es:CSEG, ss:CSEG org 100h Start: jmp Begin ; ======= Процедуры ========= ; Головная include main.asm ; Работа с дисплеем include display.asm ; Работа с файлами include files.asm ; Работа с клавиатурой include keyboard.asm ; Сообщения include messages.asm ; Переменные include data.asm ; Начало программы Begin: call Check_video ;проверим видеорежим и текущую страницу mov ah,9 mov dx,offset Mess_about int 21h ;выведем сообщение с приветствием call Main_proc ;вызывем головную процедуру ; Сюда мы попадем толко в том случае, если пользователь решил выйти в DOS int 20h Finish equ $ CSEG ends end Start

Display.asm

; ===== DISPLAY.ASM - процедуры работы с экраном ====== ; === Рисуем рамку заданного размера в центре экрана === Draw_frame proc mov bp,sp ;BP = SP add bp,2 ;Увеличим BP на 2, т.к. первые два байта - возврат из процедуры push es ;Сохраним ES push 0B800h ;ES должен указывать на сегмент 0-ой видеостраницы pop es ;это сработает только на 286+ процессоре!!! ;________________________________________ ; ; Производим вычисления для того, чтобы ; разместить ЛЮБУЮ рамку в цетре экрана. ;________________________________________ mov ax,Height_X ;AX = высота нашей рамки shr al,1 ;делим высоту на 2 путем смещения битов вправо на 1 mov dh,11 ;Середина sub dh,al ;DH (строка) ГОТОВА!!!!! mov ax,Width_Y ;AX = ширина нашей рамки shr al,1 ;делим ее на 2 mov dl,39 ;Середина sub dl,al ;DL (колонка) ГОТОВА!!!!! ;Теперь DH содержит центрированный ряд (строку), ;а DL - колонку относительно размеров рамки (окошка)... ;_________________________________________ ;Сохраним полученный адрес, с которого начинается вывод рамки ;Нужно для того, чтобы выводить сообщения в рамке. mov Num_DX,dx mov ax,Other ;Получим дополнительную информацию test al,1 ;Нулевой бит равен 0? jz No_copyscr ;Если так, то копировать экран не нужно. mov ax,Height_X ;Иначе копируем в область 2 видеостраницы add ax,2 ;+2, т.к. учитываем 'г=¬' и 'L=-' call Copy_scr No_copyscr: call Get_linear ;получаем линейный адрес в видеобуфере из DX, push di ;который и сохраним... mov ax,Attr ;в AH - атрибуты цвета рамки mov al,'г' ;верхний левый угол... stosw ;заносим два байта (атрибут в AH / символ в AL) mov al,'=' ;далее... mov cx,Width_Y ;в CX - ширина рамки rep stosw ;поехали... mov al,'¬' ;завершаем верхний ряд stosw pop di ;восстановим DI + 160 (следующий ряд) add di,160 inc dh ;DH "идет в ногу" с текущим рядом ;нужно для того, чтобы вывести внизу рамки сообщение ;теперь у нас примерно такая ситуация на экране: ;г===========================¬ ;_ ;где _, там у нас DI и DH mov cx,Height_X ;CX - кол-во повторов (высота) Next_lined: push cx ;сохраним счетчик push di ;сохраним DI mov al,'¦' ;вывели этот символ stosw mov al,32 ;32 - пробел (или 20h или ' ') mov cx,Width_Y ;CX = ширина rep stosw ;понеслась... mov al,'¦' ;завершаем ряд... stosw pop di add di,160 ;переводим DI на следующий ряд inc dh ;передвигаем DH на 1 pop cx ;восстановим счетчик loop Next_lined ;следующий ряд... ;теперь у нас примерно такая ситуация на экране: ;г===========================¬ ;¦ ¦ ;¦ ¦ ;¦ ¦ ;_ - тут DI и DH mov al,'L' ;низ рамки... stosw mov al,'=' mov cx,Width_Y rep stosw mov al,'-' stosw ;теперь у нас примерно такая ситуация на экране: ;г===========================¬ ;¦ ¦ ;¦ ¦ ;¦ ¦ ;L===========================- ;Выводим сообщение внизу рамки mov si,Mess_dn ;SI = адрес строки для вывода call Draw_messfr ;Выводим сообщение ВНИЗУ рамки ;Вот зачем нам нужно было постоянно увеличивать DH на 1 ;(чтобы DH "шло в ногу" с DI)! ;теперь у нас примерно такая ситуация на экране: ;г===========================¬ ;¦ ¦ ;¦ ¦ ;¦ ¦ ;L==== Сообщение внизу ======- ;Выводим сообщение вверху рамки mov dx,Num_DX push dx ;Вот нам и адрес верхнего ряда понадобился! mov si,Mess_up ;SI = адрес строки для вывода call Draw_messfr ;Выводим сообщение вверху рамки ;теперь у нас примерно такая ситуация на экране: ;г==== Сообщение вверху =====¬ ;¦ ¦ ;¦ ¦ ;¦ ¦ ;L==== Сообщение внизу ======- pop dx add dx,0101h mov si,Mess_ins ;Адрес сообщения, которое будет внутри рамки or si,si ;Если там 0, то не выводим... jz No_draw mov ah,[si] inc si call Print_string ;Выводим строку... ;теперь у нас примерно такая ситуация на экране: ;г==== Сообщение вверху =====¬ ;¦Сообщение внутри ¦ ;¦ ¦ ;¦ ¦ ;L==== Сообщение внизу ======- No_draw: mov ax,Other ;Получим дополнительную информацию test ax,10b ;Первый бит равен нулю? jz No_upline ;Если так, то на метку No_upline mov dx,Num_dx ;Получим начальное значение рамки (верхний левый угол - г) add dh,2 ;Прибавим 2 для того, чтобы вывести линию (см. ниже) call Get_linear mov ax,Attr mov al,'¦' mov cx,1 stosw mov cx,Width_Y ;в CX - ширина рамки mov al,'-' rep stosw mov al,'¦' stosw ;теперь у нас примерно такая ситуация на экране: ;г==== Сообщение вверху =====¬ ;¦Сообщение внутри ¦ ;¦--------- линия -----------¦ ;¦ ¦ ;¦ ¦ ;L==== Сообщение внизу ======- No_upline: pop es ;восстановим ES ret 14 ;Выходим, очистив стек от переменных в 14 байт (7 слов) ;Посмотрите, в MAIN.ASM как мы вызываем данную процедуру. Draw_frame endp ; --- Вывод сообщениий вверху и внизу рамки --- ;Вспомогательна процедура. Draw_messfr proc or si,si ;SI = 0?.. jz No_drawup ;тогда ничего выводить не надо, выходим mov ah,[si] ;Первый символ строки - атрибут (см. DATA.ASM) inc si ;Следующий байт - начало строки call Count_strmid ;Вычисляем середину строки call Print_string ;Выводим строку на экран No_drawup: ret Draw_messfr endp ; === Вычисляем середину строки === ;Вход: CS:SI - адрес строки ;Выход: DL - середина адреса для вывода строки Count_strmid proc push es ;Сохраним регистры... push di push ax push cs ;ES=CS pop es mov di,si ;DI=SI xor al,al ;AL=0 mov cx,0FFFFh ;сколько символов перебирать (возьмем максимум)... repne scasb ;Ищем 0 в строке ;0 найден! DI указывает на следующий символ за найденным 0 ;SI=начало строки ;DI=конец строки+1 sub di,si ;DI=DI-SI-1 = длина строки dec di shr di,1 ;Делим длину на 2 mov ax,40 ;Делим кол-во символов в строке на 2 = 40 sub ax,di ;AX=40-половина длины строки = нужная колонка mov dl,al ;DL=колонка, с которой следует выводить строку! pop ax ;Восстановим регистры pop di pop es ret Count_strmid endp ; === Вывод стоки на экран === ;Вход: DS:SI - адрес строки для вывода ; DX - координаты для вывода ; AH - атрибуты строки ;Выход: ничего Print_string proc call Get_linear ;Получаем линейный адрес строки Next_symstr: lodsb ;Получаем очередной символ строки or al,al ;Это 0 (конец строки?) jz Stop_outstr ;Да - выходим... stosw ;Иначе заносим в видеобуфер атрибут (AH) и символ (AL) jmp short Next_Symstr ;Следующий символ Stop_outstr: ret Print_string endp ; === Преобразование DH:DL в линейный массив === Get_linear proc push ax ;сохраним все используемые регистры push bx push dx shl dl,1 ;математика: умножаем DL на 2 (DL=DL*2)... mov al,dh ;в AL - ряд, mov bl,160 ;который нужно умножить на 160 mul bl ;умножаем: AL(ряд)*160; результат --- в AX mov di,ax ;результат умножения - в DI xor dh,dh ;аннулируем DH add di,dx ;теперь в DI линейный адрес в видеобуфере. pop dx ;восстанавливаем регистры... pop bx pop ax ret Get_linear endp ; === Проверяем видеорежим монитора и текущую видеостраницу === Check_video proc mov ah,0Fh int 10h cmp al,3 ;Текстовый режим? je Ok_video mov ax,3 int 10h Ok_video: or bh,bh ;Нулевая страница? jz Ok_page mov ax,0500h int 10h Ok_page: ret Check_video endp ; === Сохраним экран === Save_mainscr proc pusha ;Сохраним регистры... push es push ds push 0B800h ;с нулевой страницы pop ds xor si,si ;нулевого символа push 0B900h ;в первую страницу... pop es xor di,di ;нулевой символ... mov cx,2000 ;4000 байт rep movsw ;делаем копию. pop ds ;восстановим регистры. pop es popa ret Save_mainscr endp ; === Восстановим экран === Restore_mainscr proc pusha push es push ds push 0B900h ;с первой видеостраницы pop ds xor si,si push 0B800h ;в нулевую... pop es xor di,di mov cx,2000 rep movsw pop ds ;очень просто и быстро!!! pop es popa ret Restore_mainscr endp ; === Копируем часть экрана === ;Вход: DH - ряд, с которого необходимо начать копирование ; AL - количество рядов для копирования ;Выход: ничего Copy_scr proc pusha ;Как обычно сохраним регистры push es push ds xor dl,dl ;Обнулим DL на всякий случай. Теперь DH = ряд, DL = 0 call Get_linear ;Получим линейный адрес mov bl,160 ;Получим количество байт, котрые нужно копировать mul bl mov cx,ax ;Их - в CX (будем использовать CX как счетчик) mov si,di ;DS:SI - откуда копируем xor di,di ;ES:SI - куда копируем mov Num_copySI,si ;Сохраним полученные значения для восстановления mov Num_copyDI,di mov Num_copyCX,cx push 0B800h ;Настроим сегментные регистры pop ds push 0BA00h pop es rep movsb ;Копируем... pop ds ;Восстановим регистры и выйдем... pop es popa ret ;Теперь есть копия в самом начале 2-ой видеостраницы. Num_copySI dw ? Num_copyDI dw ? Num_copyCX dw ? Copy_scr endp ; === Восстанавливаем часть экрана === ;Вход: ничего (все уже сохранено в переменных &#x0018) ;Выход: ничего Restore_scr proc pusha ;Сохраним регистры push es push ds mov di,Num_copySI ;Получим сохраненные процедурой Copy_scr значения mov si,Num_copyDI mov cx,Num_copyCX push 0BA00h ;Настроим сегментные регистры pop ds push 0B800h pop es rep movsb ;Копируем со 2-ой страницы в 0-ую... pop ds ;Восстановим регистры pop es popa ret Restore_scr endp ; === Прячем курсор, сохранив предварительно его текущую позицию === Hide_cursor proc mov ah,3 ;получаем текущую позицию курсора mov bh,Video_page int 10h mov Pos_cursor,dx ;теперь она в DX (отсчет с нуля!) mov ah,2 ;Установим курсор на первую страницу как на нулевой mov bh,1 ;Нужно для того, если пользователь захочет int 10h ;посмотреть, что там DOS написАла (Ctrl+F5) mov bh,Video_page ;прячем курсор на 0-ой видеостранице mov dx,1900h ;(установим его на 25 строку (19h)) int 10h ret Hide_cursor endp ; === Восстановим курсор === Restore_cursor proc mov ah,2 mov bh,Video_page ;видеостраница mov dx,Pos_cursor ;сохраненная позиция int 10h ;установим (позиционируем) курсор ret Restore_cursor endp

Files.asm

; ==== FILES.ASM - процедуры работы с файлами ==== ; === Читаем файлы в текущем каталоге === Get_files proc call Get_first ;Получаем первый файл jc No_morefiles ;Если нет файлов вообще - на выход cmp word ptr cs:[DTA+1Eh],002Eh ;Первый файл - '.'? je Next_file ;Если так, то ищем следующий файл... call Move_file ;Если нет, то переносим файл в память. Next_file: call Get_other ;Получаем следующий файл... jc No_morefiles ;Файлы закончились - на выход. call Move_file ;Если нет, то переносим файл в память. jmp short Next_file ;Ищем следующий файл... No_morefiles: ret Get_files endp ; === Поиск первого файла в текущем каталоге === Get_first proc ;Атрибуты файла (в CX): ;000001 - только чтение ;000010 - спрятанный ;000100 - системный ;001000 - метка тома ;010000 - подкаталог ;100000 - архивный mov ah,4Eh ;Функция поиска первого файла mov cx,110111b ;С атрибутами: ;архивный (archive), подкаталог (directory), ;системный (system), спрятанный (hidden) ;только чтение (read-only) mov dx,offset All_files ;Маска поиска int 21h ret Get_first endp ; === Поиск следующих файлов === Get_other proc mov ah,4Fh ;Функция поиска следующих файлов mov dx,9Eh ;DX указывает на DTA int 21h ret Get_other endp ; --- Перенос файла из DTA в сегмент файлов --- Move_file proc push es mov si,DTA+1Eh ;SI=DTA+1Eh=имя найденного файла mov di,Current_offset ;Текущее свободное смещение mov es,Seg_files ;ES=сегмент для найденных файлов Next_fbyte: lodsb ;Получаем первый символ имени файла or al,al ;Это нуль (конец имени)? jz End_offile ;Да - на выход stosb ;Нет - сохраним символ в памяти jmp short Next_fbyte ;Следующий символ... End_offile: stosb ;Запишем нуль в память после имени файла mov Current_offset,di ;Сохраним текущее смещение stosb ;Запишем еще один нуль... pop es ret Move_file endp ; === Вывод наденных файлов на экран === Out_files proc mov bl,Number_files ;BL указывает на количество выводимых файлов push ds push es mov ds,Seg_files ;DS=сегмент найденных файлов в памяти push 0B800h pop es xor si,si mov ah,1Bh ;Атрибуты mov dx,0302h ;Начало для вывода файлов Next_fileout: call Print_string ;Выводим очередной файл cmp byte ptr [si],0 ;Следующий байт=0? Значит, это был последный файл... jz Exit_files ;Тогда выходим. add dx,0100h ;Иначе увеличиваем DH на 1 для вывода следующего имени... dec bl ;Уменьшаем количество выводимых файлов на 1 jnz short Next_fileout ;Уже 0? Тогда больше выводить не нужно... Exit_files: pop es pop ds ret Number_files db 20 ;Количество выводимых файлов на экран. Out_files endp

Keyboard.asm

; ==== KEYBOARD.ASM - процедуры работы с клавиатурой ==== ; === Ждем нажатия клавиши === Pause proc mov ah,10h int 16h ret Pause endp

Main.asm

; === MAIN.ASM - Головная процедура === Main_proc proc call Hide_cursor ;прячем курсор call Prepare_all call Save_mainscr ;сохраним содержимое экрана... push 23 ;высота рамки без 'г=¬' и 'L=-' push 78 ;ширина рамки (т.е. на весь экран) без '¦' и '¦' push 1F00h ;цвет - светло-белый на синем фоне push offset Mess_head ;надпись вверху рамки (если 0, то не выводить) push offset Mess_down ;надпись внизу рамки (если 0, то не выводить) push 0 ;сообщение внутри рамки (если 0, то не выводить) push 10b ;Экран не копировать, но вывести верхнюю линию. call Draw_frame ;Рисуем рамку на весь экран ;ИТОГО: в стек заносим 14 байт вместо 20! call Get_files call Out_files Next_key: call Pause ;давим кнопку!!! or al,al jz Ext_code ;Это расширенный ASCII-код клавиши? Да - проверяем его. &#x0019 cmp al,27 ;Нажали ESC? jne Next_key ;Нет - ждем дальше... &#x0018 call Quit_prog ;Удостоверимся у пользователя о выходе из программы. &#x0019 jnc Next_key ;Пользователь подтвердил выход? НЕТ? Тогда на Next_key &#x0018 ;Да, подтвердил (а жаль!). Тогда выходим. call Restore_mainscr ;восстановим содержимое экрана call Restore_cursor ;восстановим позицию курсор ret ;выход! Ext_code: cmp ah,62h ;Нажали Ctrl+F5? je User_screenl ;Да - показываем экран пользователю &#x0019. jmp short Next_key ;Нет - ждем другую клавишу &#x0018 User_screenl: mov ax,0501h ;Показываем пользователю 1 видеостраницу. int 10h call Pause ;Может быть, подождем?.. Клавишу, а не мою маму! mov ax,0500h ;Опять на нулевую устанавливаем. int 10h jmp short Next_key ;Ждем дальше... &#x0018 Main_proc endp ; === Готовим все необходимое для работы === Prepare_all proc call Set_DTA call Prepare_memory ret Prepare_all endp ;--- Ужимаем память --- Prepare_memory proc mov bx,offset Finish shr bx,4 inc bx mov ah,4Ah int 21h mov ah,48h mov bx,1000h int 21h mov Seg_files,ax ret Prepare_memory endp ; --- Установим DTA --- Set_DTA proc mov ah,1Ah mov dx,80h int 21h ret Set_DTA endp ; === Выходим из программы? === Quit_prog proc push 1 ;высота рамки push offset Mess_quitl ;ширина рамки push 4F00h ;цвет - светло-белый на синем фоне push offset Mess_qup ;надпись вверху рамки (если 0, то не выводить) push 0 ;надпись внизу рамки (если 0, то не выводить) push offset Mess_quit ;сообщение внутри рамки push 01b ;Копировать экран, но не выводить верхнюю линию call Draw_frame ;Рисуем рамку call Pause call Restore_scr ;Восстановим сохранунную часть экрана. cmp al,'Y' ;Нажали 'Y' / 'y' / Enter (13)? je Yes_quit ;Да! &#x0019 cmp al,'y' je Yes_quit cmp al,13 je Yes_quit clc ;Ставим меточку, что нажали другую клавишу (сбросим флаг переноса). ret Yes_quit: stc ;Установим флаг переноса (нажали 'Y', значит выходим)... ret Quit_prog endp

Messages.asm

; === MESSAGES.ASM - сообщения, выводимые оболочкой === Mess_about db 0Ah, 0Dh, 'SuperShell - оболочка для DOS, написанная на Ассемблере.',0Ah, 0Dh db 'Рассылка "Ассемблер? Это просто! Учимся программировать", Выпуск № 021',0Ah,0Dh db 'http://www.Kalashnikoff.ru. E-mail: Assembler@Kalashnikoff.ru',0Ah,0Dh,0Ah db '(C) Авторские права на файлы-приложения принадлежат всем подписчикам рассылки.',0Ah, 0Dh, 0Ah db 9,9,'=== Россия, Москва, 2001 год ===',0Ah,0Dh,'$' Mess_head db 1Eh, ' Super Shell, Версия 1.0 ',0 Mess_down db 1Dh, ' Россия, Москва, 2001 ',0 Mess_qup db 4Eh, ' Выход ',0 Mess_quit db 4Bh, ' Вы действительно хотите выйти в DOS (Y/N)?',0 Mess_quitl equ $-Mess_quit

Data.asm

; === DATA.ASM - данные оболочки === ; ==== ПЕРЕМЕННЫЕ ==== Num_attr db ? ;переменная для атрибутов Num_DX dw ? Video_page db 0 ;текущая видеостраница Pos_cursor dw ? ;главная позиция курсора ; --- Данные для процедуры Draw_frame --- Height_X equ [bp+12] ;высота рамки Width_Y equ [bp+10] ;ширина рамки Attr equ [bp+8] ;атрибуты рамки Mess_up equ [bp+6] ;сообщение вверху рамки Mess_dn equ [bp+4] ;сообщение внизу рамки Mess_ins equ [bp+2] ;сообщение внутри рамки Other equ [bp] ;иная конфигурация окна All_files db '*.*',0 DTA equ 80h Seg_files dw ? ; Сегментный адрес буфера файлов Current_offset dw 0 Итак, поехали...

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

Однако, присмотревшись, вы поймете, что наша оболочка проделывает уйму работы. А именно:

_______

1. Ищет первый файл;

2. Если это '.', то см. пункт 4; *

3. Заносит в память имя найденного файла;

4. Ищет следующий файл;

5. Заносит имя найденного файла в память;

6. Файлы закончились?

7. Нет - см. пункт 4.

8. Да - начинает выводить файлы на экран.

9. Выведено уже 20 файлов? Нет - см. пункт 8. Да - см. пункт 10.

10. Ждет нажатия на клавишу.

11. Пользователь нажал ESC? Да - см. пункт 12. Нет - см. пункт 10.

12. Запрашивает пользователя: уверен ли он, что хочет выйти в DOS.

13. Не уверен (т.е. нажал все, что угодно, кроме 'Y' или 'y') - см. пункт 10.

14. Уверен (т.е. нажал 'Y' или 'y') - выходит в DOS.

Вот, собственно, и алгоритм работы нашей оболочки.

* Примечание. В любом подкаталоге первый файл всегда '.' . Точнее, это не файл, а каталог. Если в командной строке набрать 'CD .' , то директория не изменится. Т.е. данный каталог - это просто текущий каталог. Нам его не нужно выводить в оболочке. Команда DIR выводить эту директорию на экран, в отличие от оболочек.

Теперь подробнее...

_______

Новшество первое.

Обратите внимание, как мы вызываем процедуру Draw_frame (MAIN.ASM, Main_proc). На первый взгляд - ничего особенного. Но это только кажется... Раньше мы заносили в стек адреса строк, которые будут выводится вверху и внизу нашей рамки, а также их атрибуты. Теперь мы атрибуты в стек не заносим. Но как же тогда сообщения выводятся в нужном цвете? Обратите внимание, что находится перед строками Mess_head и Mess_down:

Mess_head db 1Eh, ' Super Shell, Версия 1.0 ',0

Mess_down db 1Dh, ' Россия, Москва, 2001 ',0

Вот это и есть как раз нужные нам атрибуты соответствующих строк. Поступая таким образом, мы экономим байты нашей оболочки, более того, мы не нагружаем стек. Отпадает необходимость заносить в стек два слова атрибутов для верхней строки и два слова для нижней. Процедура вывода строк на экран (DISPLAY.ASM, Draw_messfr) перед тем, как выводить строку, занесет в AH первый символ, который и будет являться атрибутом! Это проще!

mov ah,[si] ;Первый символ строки - это атрибут

inc si ;Следующий байт - начало строки

call Count_strmid ;Вычисляем середину строки

call Print_string ;Выводим строку на экран

_______

Новшество второе.

Теперь процедура рисования рамки (DISPLAY.ASM, Draw_frame) выводит еще и линию вверху окошка по нашему требованию. А что мы добавили перед вызовом данной процедуры? Да ничего почти! Просто изменили один бит:

push 10b ;Экран не копировать, но вывести верхнюю линию.

call Draw_frame ;Рисуем рамку на весь экран

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

Видите, как наша процедура совершенствуется на глазах. А это далеко не предел! Еще дальше пойдем - еще больше будет!

Внимание! "А как же нам проверить, включен ли нулевой бит или нет? Скорее всего, оператором CMP здесь не обойтись." Да, действительно, не обойтись. И вот почему.

Представьте, что первый раз мы выводим окошко, предварительно скопировав экран, но не выводим линию вверху. Тогда нулевой бит будет равен 1:

push 1

Следовательно, можно проверить так (в процедуре draw_frame данное слово будет называться Other (иное)):

cmp Other,1

Если флаг нуля устанавливается, т.е. Other = 1, то нужно предварительно скопировать экран в память.

Хорошо. Теперь представим, что нужно вывести окошко с линией вверху, но не копировать экран (т.е. первый бит будет равен единице, а нулевой - 0):

push 10b ;или push 2

...

cmp Other,2

И, наконец, представим, что нужно вывести и линию вверху, и сохранить экран:

push 11b ;или push 3

...

cmp Other,3

Вроде все нормально... Однако, тем не менее, существует проблема. Нулевой бит (т.е. следует ли копировать экран или нет) мы проверяем в начале процедуры Draw_frame (т.е. до того, как вывели окошко), а вывод верхней линии - после того, как экран скопирован (если нужно), и выведено окошко. Теперь подумайте: можно ли с помощью CMP проверить установлен ли бит нулевой, игнорируя (умышленно не замечая) все оставшиеся биты проверяемого слова (Other)? Вдумайтесь в вопрос. Не спешите читать дальше. Правда, подумайте! Возьмите листок бумаги и поэкспериментируйте! Если сами поймете что да как, то будет гораздо проще разобраться с новым оператором...

...

Не сомневаюсь, что вы нашли выход из положения. Например, так:

mov ax,Other ;Перенесем временно в AX значение переменной Other

push ax

and ax,1 ;Аннулируем все биты, кроме нулевого (вспомним логические команды)

cmp ax,1 ;Проверим, равен ли теперь AX 1?

pop ax

mov Other,ax ;Восстановим переменную Other

je Ravno ;Перейдем, если равен

Следует отметить, что операторы MOV и POP флаги (в частности, флаг нуля) не меняют!

Хорошо получилось! Или не очень? Вероятно, есть другие способы? Безусловно есть! Ассемблер такой гибкий язык, что может делать невероятные, нет - поистине невероятные вещи!

Чтобы временно не сохранять регистр или переменную перед проверкой одного-двух-трех... битов, используется оператор TEST:

Название Перевод Применение Процессор TEST приемник, источник Test - тест, проверка Проверка одного и более битов 8086 Примеры:

mov ax,10100001b

test ax,1 ;Проверим на то, равен ли нулевой бит единице.

jnz Ravno ;Переход, если равен

Обратите внимание, что после CMP мы ставим JE (JZ), если хотим перейти на определенную метку в случае, если приемник и источник равны. В команде TEST все идет "шиворот-навыворот". Т.е. JE (JZ) перейдут на метку, если приемник и источник НЕ равны и наоборот: JNE (JNZ) перейдут на метку, если приемник и источник равны. Не надо путать!

Еще пример:

mov cl,100101b

test cl,1000b ;Проверим на то, равен ли третий бит единице

jz Ne_ravno ;Переход, если НЕ равен

Поехали дальше...

Мы остановились на том, что переменная Other будет содержать более одного параметра. Причем эти параметры должны отвечать на вопрос ДА или НЕТ, т.е. РАВНО или НЕ РАВНО. Как, например, в нашей процедуре Draw_frame. Повторю: первый бит отвечает за рисование линии вверху окошка, а нулевой - за копирование участка экрана перед выводом окошка. Вспомните окно подтверждения выхода из оболочки. Экран-то прежде копируется в память. А вдруг пользователь передумает выходить? Нам что тогда, заново рисовать основное окно и перечитывать каталоги? Или проще сохранить часть затираемого экрана, а затем моментально восстановить его? Да что я говорю?! Прошли мы уже это...

Использование битов переменной для подобных целей подходит как никогда лучше. Мы экономим байты. Вместо, скажем, 8 или 16 переменных мы используем всего одну! Красота...

Вот кусок из нашего файла-приложения (этим мы подведем черту с TESTом):

mov ax,Other ;Получим дополнительную информацию

test al,1 ;Нулевой бит равен 0?

jz No_copyscr ;Если так, то копировать экран не нужно.

___________

Основы работы с памятью в DOS.

Сразу отмечу, что пока мы будем работать с основной памятью, т.е. с 640 Кб.

Подробно рассмотреть управление памятью в DOS за один выпуск невозможно. Мы сегодня затронем лишь ключевые, основополагающие моменты.

Как только программа загрузилась, DOS автоматически отводит для нее всю свободную память. Программист может по своему усмотрению урезать блоки памяти, отводить другие (сколько памяти хватит), а также освобождать отведенные участки (блоки) памяти.

Зачем это нужно?

Представьте ситуацию. Мы написали программу, которая, в свою очередь, загружает другую. Но так как вся память изначально выделена только нашей программе, то мы не сможем загрузить никакую программу из нашей. Ведь ей тоже нужно некоторое количество памяти?

Для того, чтобы урезать память, используется функция 4Ah прерывания 21h:

Вход: AH=4Ah

ES=сегмент распределенного блока

BX=размер блока в 16-и байтовых параграфах Выход: JC - ошибка, при этом:

AX-код ошибки Распределенный блок в нашем случае - вся память, отведенная программе, начиная от нулевого смещения сегмента CS и заканчивая последним свободным байтом. Поэтому перед вызовом функции 4Ah следует убедиться в том, что ES указывает на сегмент, куда мы загрузились (см. таблицу выше)!

Вот какая ситуация после загрузки нашей программы в память не будем брать адреса; какая разница?):

1. Системные файлы; --- память занята от 0 до текущего байта

2. Резидентные программы; --- память занята от 0 до текущего байта

3. Наша программа; --- память занята от 0 до текущего байта

4. Метка Finish; --- память занята от 0 до текущего байта

5. Отведенная память нашей программе. --- память занята от 0 до конца.

Получается, что ВСЯ память (640 Кб) занята после того, как программа загрузилась. Наша задача - урезать занятую нашей программой память до метки Finish (см. п. 3). Для чего это нужно - рассмотрим позже.

Обратите внимание, как мы ужимаем существующий блок памяти (MAIN.ASM, Prepare_memory):

mov bx,offset Finish ;BX=последний байт нашей программы

shr bx,4 ;Т.к. BX должен содержать не количество байт, а количество блоков по 16 байт, то мы должны сдвинуть биты вправо на 4

inc bx ;Увеличим BX на один (на всякий случай!)

mov ah,4Ah ;Функция уменьшения/расширения существующего блока памяти

int 21h ;В данном случае урезаем, т.к. наша программа, естественно, меньше отведенного блока памяти после загрузки.

Теперь картина такая:

1. Системные файлы; --- память занята от 0 до текущего байта

2. Резидентные программы; --- память занята от 0 до текущего байта

3. Наша программа; --- память занята от 0 до текущего байта

4. Метка Finish; --- память занята от 0 до текущего байта

5. Память за меткой Finish. --- память свободна до конца 640 Килобайта, начиная с первого байта, следующего за меткой Finish!

Размер памяти примерно такой: 640Кб минус смещение метки Finish.

Теперь отведем кусок памяти размером 1000h 16-и байтовых блоков (параграфов) или 65536 байт:

mov ah,48h

mov bx,1000h

int 21h

mov Seg_files,ax

Функция 48h прерывания 21h - выделить блок памяти:

Вход: AH=48h

BX=размер блока в 16-и байтовых параграфах Выход: JC - ошибка, при этом:

AX-код ошибки;

Иначе: AX=сегмент выделенного блока. Думаю, что объяснять приведенный над таблицей пример не нужно. Все и так понятно. Рассмотрим только, что получилось:

1. Системные файлы; --- память занята от 0 до текущего байта

2. Резидентные программы; --- память занята от 0 до текущего байта

3. Наша программа; --- память занята от 0 до текущего байта

4. Метка Finish; --- память занята от 0 до текущего байта

5. Память за меткой Finish + 65536 байт; --- память занята

6. Память, начиная с адреса Finish + 65537 байт --- свободна

Зачем мы выделили блок памяти размером 64Кб?

В эту память мы будем загружать информацию о найденных файлах в текущем каталоге. А как вы думаете NC показывает вам файлы в окошечке? Элементарно! Просто считывает их к себе в память, а затем с ними работает (точнее, вы работаете). Команда DIR, например, не загружает в память найденные файлы. А зачем? Она нашла файл, вывела информацию на экран и - следующий...

Подписчик (задумчиво): Я тут сейчас вот как бы нечайно подумал... Мне кажется, это так сложно все... Нет, наверное, это не для меня... Пусть другие пишут на Ассемблере, а я лучше на Бейсике... Сдался мне Асм... Калашников этот вечно лишние проблемы накручивает... Спал раньше так хорошо по ночам...

Ну и очень плохо, что вы спешите делать выводы, не попробовав даже написать простейшую программку. На ваш взгляд - это сложно. На самом деле - элементарщина! Не пугайтесь!!! Все самое интересное только начинается! Конечно, сложные и неинтересные вещи. Но это вам пока только кажется, что они нудные, и знать их не надо. На самом деле, в Ассемблере не бывает неинтересных вещей. Это сложно понять сразу. Со временем вы все выучите...

Как записываем в память найденные файлы?

Алгоритм простейший:

1. Ищем первый файл. Нет файлов - см. п. 5;

2. Заносим имя файла в отведенный блок памяти. Дописываем после имени файла нуль;

3. Ищем следующий файл. Нет больше файлов - см. п. 5;

4. Заносим следующий файл в память сразу же за найденным предыдущим. На пункт 3;

5. Заносим еще один нуль после последнего найденного файла для того, чтобы дать понять процедурам нашей оболочки, что это был последний найденный файл.

6. Выводим указанное количество файлов на экран (сколько находится в переменной Number_files) (FILES.ASM, Out_files).

Думаю, что труда разобраться с тонкостями процедур не составит, тем более, что в файле-приложении достаточно комментариев.

Удачного вам изучения!

До скорой встречи!

С уважением,

Автор рассылки: Калашников Олег

URL сайта подписчиков: http://www.Kalashnikoff.ru

E-mail автора: Assembler@Kalashnikoff.ru

ICQ: 68951340

Москва, 2001.

(C) Авторское право принадлежит автору рассылки. Использование материала из рассылки в коммерческих и иных подобных целях, а также публичное публикование без письменного согласия автора влечет ответственность за нарушение авторских прав. [Следующий выпуск] [На главную страницу]

u="u496.71.spylog.com";d=document;nv=navigator;na=nv.appName;p=1; bv=Math.round(parseFloat(nv.appVersion)*100); n=(na.substring(0,2)=="Mi")?0:1;rn=Math.random();z="p="+p+"&rn="+rn;y=""; y+=""; y+=""; y+=""; d.write(y);if(!n) { d.write("

Соседние файлы в папке Выпуски