Особенности использования ассемблерных функций в качестве функций – членов классов
Пусть задан класс:
class STRING{
char *s;
public:
STRING (char * c){
s = new char[strlen(c)+1];
strcpy (s, c);
}
~STRING (){
delete []s;
}
public:
int len ()
};
Пусть функцию определения длины строки необходимо определить на ассемблере. Определим ее сначала на С. Получим файл:
#pragma inline
#include "mystr.h"
int STRING::len(){
int i;
for (i=0; s[i]; i++);
return i;
}
Здесь директива #pragma inline задана с целью формирования ассемблерного кода. В результате трансляции получим файл на ассемблере, основна часть которого приведена ниже:
.386p
ifdef ??version
if ??version GT 500H
.mmx
endif
endif
model flat
ifndef ??version
?debug macro
endm
endif
?debug S "E:\users\lena\INSTITUT\ASM\USKOR\cl.cpp"
?debug T "E:\users\lena\INSTITUT\ASM\USKOR\cl.cpp"
_TEXT segment dword public use32 'CODE'
_TEXT ends
_DATA segment dword public use32 'DATA'
_DATA ends
_BSS segment dword public use32 'BSS'
_BSS ends
$$BSYMS segment byte public use32 'DEBSYM'
$$BSYMS ends
$$BTYPES segment byte public use32 'DEBTYP'
$$BTYPES ends
$$BNAMES segment byte public use32 'DEBNAM'
$$BNAMES ends
$$BROWSE segment byte public use32 'DEBSYM'
$$BROWSE ends
$$BROWFILE segment byte public use32 'DEBSYM'
$$BROWFILE ends
DGROUP group _BSS,_DATA
_TEXT segment dword public use32 'CODE'
@STRING@len$qv segment virtual
@@STRING@len$qv proc near
?live16385@0:
?debug L 4
push ebp
mov ebp,esp
push ecx
?debug L 6
@1:
xor eax,eax
mov dword ptr [ebp-4],eax
ааааааааааа jmpаааааа short @3
@2:
ааааааааааа incаааааа dword ptr [ebp-4]
@3:
ааааааааааа movаааааа edx,dword ptr [ebp+8]
ааааааааааа movаааааа ecx,dword ptr [edx]
ааааааааааа movаааааа eax,dword ptr [ebp-4]
ааааааааааа cmpаааааа byte ptr [ecx+eax],0
ааааааааааа jneаааааа short @2
ааааааааааа ?debug L 7
ааааааааааа movаааааа eax,dword ptr [ebp-4]
ааааааааааа ?debug L 8
@6:
@5:
ааааааааааа popаааааа ecx
ааааааааааа popаааааа ebp
ааааааааааа ret
ааааааааааа ?debug L 0
@@STRING@len$qv endp
Е
¦ЁюрэрышчшЁєхь яюыєўхээvщ Їрщы. Lч Їрщыр тшфэю, ўЄю шь ЇєэъЎшш Ц ўыхэр ЇюЁьшЁєхЄё шч шьхэш ъырёёр, шьхэш ЇєэъЎшш ш Єшяют ярЁрьхЄЁют. ¦Єю шь ыєў°х тёхую тч Є№ шч юс·хъЄэюую Їрщыр тvчvтр¦•хщ яЁюуЁрььv.
-ы юс·хъЄр ъырёёр яхЁхфрхЄё рфЁхё, уфх эрїюфшЄё рфЁхё эрўрыр ¤Єюую юс·хъЄр, Є.х. яхЁтющ тэєЄЁхээхщ яхЁхьхээющ ¤Єюую ъырёёр (ярЁрьхЄЁ ЇєэъЎшш). -ы фюёЄєяр ъ ёрьющ яхЁхьхээющ шёяюы№чєхЄё фтх ъюьрэфv:
movаааааа edx,dword ptr [ebp+8]
movаааааа ecx,dword ptr [edx]; &s
+•х Ёрч юсЁрЄшЄх тэшьрэшх эр эх¤ЇЇхъЄштэюх шёяюы№чютрэшх ыюъры№эющ яхЁхьхээющ тэєЄЁш ъырёёр!
¦Ёюьх яЁштхфхээvї тv°х ъюьрэф шёяюы№чє¦Єё фюяюыэшЄхы№эvх ъюьрэфv. Lэрышч ъюьрэф яЁюуЁрььv яюърчvтрхЄ, ўЄю яЁю•х яхЁхяшёрЄ№ ъюьрэфv юяЁхфхышЄхы ЇєэъЎшш Ц ўыхэр ъырёёр тэєЄЁш ЇєэъЎшш эр чvъх T++.
¦хрышчрЎш ЇєэъЎшш
#include "mystr.h"
int STRING::len(){
а asm {
аааа movааааааа eax, -1
аааа movааааааа ecx, [ebp+8]
аааа movааааааа ecx, [ecx]
аааа for1:
аааа incаа аааааeax
аааа cmpааааааа byte ptr [ecx+eax], 0
аааа jneа for1
а }
}
TЁртэшЄх ¤ЇЇхъЄштэюёЄ№ ъюфр ё рёёхьсыхЁэющ тёЄртъющ ш схч эхх!
Недостатки функций
Для сравнительной оценки процедур и макросов сначала составим главную программу и функцию для простой задачи, а затем определим недостатки и покажем возможности использования макросов вместо процедур. Определим область применения функций и макросов.
Пример . Составить главную программу и функцию для обмена местами двух целых чисел длиной 32 бита.
; Главная программа
IDEAL
p586
model flat
extrn ExitProcess:proc
dataseg
first dd 1
second dd 2
codeseg
begin:
push offset first
push offset second
call swap
call ExitProcess
; Функция
proc swap
arg a:dword, b:dword=s
push ebp
mov ebp, esp
push esi edi eax ebx
mov esi, [a]
mov edi, [b]
mov eax, [esi]
mov ebx, [edi]
mov [esi], ebx
mov [edi], eax
pop ebx eax esi edi
pop ebp
ret s
endp swap
end begin
Анализ программы показывает, что для обращения к функции необходимо:
* передать параметры или их адреса (число команд не меньше числа передаваемых параметров);
* обратиться к функции (занесение адреса возврата и передача управления - сброс конвейера и декодирование команд заново - фактически 2 команды + сброс конвейера, что соответствует потере не менее 4 тактов).
В самой функии необходимо:
* обеспечить доступ к параметрам (2 команды);
* восстановить стек и передать управление вызывающей программе (2 команды + сброс конвейера).
Таким образом, суммарные «накладные расходы» вызова функциии составляют N + 6 + 2 сброса конвейера, где N - число параметров. Дополнительные расходы по одной команде для параметров-результатов, т.к. необходимо использовать косвенные адреса. В данной программе исполняемая часть функции содержит всего 4 команды и использовать функцию с 10 (N = 2) дополнительными командами и 2 сбросами процессора очень плохо как с точки зрения времени выполнения, так и с точки зрения памяти! Лучше вставить требуемые команды для обмена прямо в программу.
Таким образом, если число команд функции невелико, или если функция используется однократно, лучше функцию не применять!
Для обеспечения вставки в требуемое место участка программы с настройкой с учетом списка параметров используются макросы
Простейшие макросы
Простейшие макросы используют директиву EQU для определения макроса. Директива используется для задания обозначений операторов или их частей. Такие макросы называются текстовыми.
Пример. Пусть необходимо в программе использовать много диагностических сообщений, которые содержат общие части, например:
msg1 db ‘Ошибка в записи идентификатора’, 13, 10, 0
msg2 db ‘Ошибка в записи числа’, 13, 10, 0
···
msgN db ‘Ошибка в записи выражения’, 13, 10, 0
Для упрощения записи обозначим общие части сообщений m1(Ошибка в записи) и m2(13, 10, 0). Для этого используются операторы:
m1 EQU < Ошибка в записи >
m2 EQU <13, 10, 0>
Угловые скобки в записи означают, что данная запись рассматривается как единое целое, а не состоит из нескольких элементов.
Тогда для задания msg1, msg2,... msgN требуются операторы:
msg1 db m1, ‘ идентификатора’, m2
msg2 db m1, ‘ числа’, m2
···
msgN db m1, ‘ выражения’, m2
Для проверки правильности составленных макросов составим программу для формирования строк:
IDEAL
p586
model flat
extrn ExitProcess:proc
dataseg
m1 EQU < 'Ошибка в записи' >
m2 EQU <13, 10, 0>
msg1 db m1, ' идентификатора', m2
msg2 db m1, ' числа', m2
msg3 db m1, ' выражения', m2
codeseg
begin:
call ExitProcess
end begin
Для просмотра сформированных строк войдите в режим DUMP и, используя CTRL/G, задайте просмотр области памяти, начиная с msg1. Вы увидите сформированные строки.
Основные определения
Макроопределение - последовательность строк, которая может содержать формальные параметры, причем после замены формальных параметров фактическими получается синтаксически верный участок программы (аналог функции).
Макрокоманда - используется для указания места в программе, куда подставляется группа команд, и фактических параметров. (аналог команды CALL).
Макрорасширение - это макроопределение, которое получается после замены формальных параметров фактическими, т.е. это последовательность команд, которая подставляется в программу.
Макроопределение (м/о)
Общий вид (м/о):
MACRO <Имя> [<Формальный параметр1>[, <Формальный параметр2>[, ...< Формальный параметрN>]]]
Команда1
Команда2
...
КомандаM
ENDM
В режиме MASM <Имя>
задается перед ключевым словом MACRO.
Пример м/о для обмена местами данных:
MACRO swap a,b
push eax, ebx
mov eax, a
mov ebx, b
mov a, ebx
mov b, eax
pop ebx eax
ENDM
М/о может быть записано в любом месте программы до использования
Макрокоманда (м/к)
Общий вид макрокоманды:
<Имя м/о> [<Фактический параметр1>[, < Фактический параметр2>[, ... < Фактический параметрN>]]]
количество формальных параметров, как правило, должно совпадать с числом фактических. Формальных параметров может быть больше, чем фактических (см. ниже).
Пример м/к для м/о SWAP:
SWAP [first], [second]
М/к должна быть записана в том месте программы, где требуется использование м/о.
Макрорасширение (м/р)
Для получения расширения необходимо в м/о подставить фактические команды макрорасширения. Фактические параметры м/к должны быть записаны так, чтобы получились синтаксически правильные команды. Макрорасширение формируется компилятором и подставляется в программу вместо м/к. В листинге программы можно увидеть команды макрорасширения.
Пример. Составить программу для обмена местами данных длиной 32 бита с помощью макросов.
IDEAL
p586
model flat
extrn ExitProcess:proc
dataseg
first dd 1
second dd 2
codeseg
begin:
MACRO SWAP a, b
push eax ebx
mov eax, a
mov ebx, b
mov a, ebx
mov b, eax
pop ebx eax
endm
SWAP [first], [second]
call ExitProcess
end begin
Откомпилируйте программу в режиме формирования листинга (ключ l). По листингу проанализируйте число сформированных команд для функции и макросов. Сделайте выводы по использованию макросов и функций.
Макросы
Макросы позволяют при трансляции программы заменить одну строку другой или сделать более сложные подстановки с учетом передаваемых параметров. Возможно задание условий трансляции и нетрансляции участка программы и даже повторной трансляции одного и того же участка программы
Правила записи параметров
Ранее составлен макрос для обмена местами двойных слов. Если необходимо менять местами слова, будем использовать другой макрос:
MACRO SWAPW a, b
push ax bx
mov ax, a
mov bx, b
mov a, bx
mov b, ax
pop bx ax
endm
Если бы использовались функции, для каждого типа данных обязательно составлять новую функцию. Для макросов эти макроопределения могут быть объединены в одно, для этого необходимо научиться соединять параметры с постоянной частью или другими параметрами. Например, составляется м/о для работы с байтами или словами. В этом случае используются регистры ax или al cоответственно. Для задания этих регистров буква a используется как постоянная часть, а буквы x
или l являются параметрами.
Для объединения параметров с постоянной частью или другими параметрами используется знак &, т.е. запись имеет вид:
Параметр & Постоянная часть;
Постоянная часть & Параметр
Параметр & Параметр,
например a&Reg, где:
a -Постоянная часть;
Reg -Параметр.
Составим м/о для обмена местами данных для целых длиной 2 и 4 байта.
IDEAL
p586
model flat
extrn ExitProcess:proc
dataseg
firstd dd 1
secondd dd 2
firstw dw 1
secondw dw 2
codeseg
begin:
MACRO SWAP a, b, type
push type&ax type&bx
mov type&ax, a
mov type&bx, b
mov a, type&bx
mov b, type&ax
pop type&bx type&ax
endm
SWAP [firstw], [secondw]
SWAP [firstd], [secondd], e
call ExitProcess
end begi
n
А теперь попытаемся составить м/о таким образом, чтобы его можно было использовать и для байтов. В этом случае в качестве регистров можно использовать регистры al
и bl, т.е. параметром является не только первая, но и последняя буква имени регистра:
Программа для этого варианта имеет вид:
IDEAL
p586
model flat
extrn ExitProcess:proc
dataseg
firstd dd 1
secondd dd 2
firstw dw 1
secondw dw 2
firstb db 1
secondb db 2
codeseg
begin:
MACRO SWAP x, y, ft, st
push ft&ax ft&bx
mov ft&a&st, x
mov ft&b&st, y
mov x, ft&b&st
mov y, ft&a&st
pop ft&bx ft&ax
endm
SWAP [firstb], [secondb], ,l
SWAP [firstw], [secondw],,x
SWAP [firstd], [secondd], e,x
call ExitProcess
end begin
Заметим, что в данном случае оставить формальные параметры a, b нельзя, т.к. постоянная часть и параметр совпадают.
Значение фактического параметра вставляется всюду, даже во внуть строки, если используется со знаком &, например
…
MACRO error name
Db ‘Ошибка. Файл &name’
endm
dataseg
msg1 error first.asm
end
Имя макроопределения может совпадать с ключевыми словами, т.е. можно переопределить команды процессора, правда, в этом случае компилятор выдаст диагностическое сообщение о совпадении имен!
Пример замены операции сложения на операцию вычитания:
IDEAL
p586
model flat
extrn ExitProcess:proc
MACRO error name
Msg db'Ошибка. Файл &name'
endm
MACRO add a, b
mov ax, a
sub ax, b
mov a, ax
endm
dataseg
error first.asm
codeseg
begin:
add cx,dx
call ExitProcess
end begin
В этом примере вместо команды add ax,bx используется команда sub ax,bx.
Допускается многократное переопределение макроса в программе. Действует последнее определенное.
Определение внутренних меток
Пусть необходимо в программе вычислять значения максимального элемента из двух заданных. В этом случае макросы предпочтительнее процедур
MACRO MAX x, y, z
push eax
mov eax, x
cmp eax, y
jle m1
mov eax, y
m1:
mov z, eax
pop eax
endm
Пусть в программе необходимо многократно использовать этот макрос. При повторном его использовании будет ошибка в связи с повтором метки. Для обеспечения использования внутренних меток используется директива LOCAL <Метка>, в этом случае компилятор формирует каждый раз уникальную метку. Директива LOCAL должна быть записана сразу после заголовка м/о.
Пример. Составить программу для определения максимального элемента для трех чисел с помощью макросов.
IDEAL
p386
MODEL fLat
extrn ExitProcess:proc
dataseg
x dd 3
y dd 5
z dd 2
u dd ?
codeseg
begin:
MACRO max x, y, z
local m1
push eax
mov eax, x
cmp eax, y
jge m1
mov eax, y
m1:
mov z, eax
endm
max [x], [y], [u]
max [u], [z], [u]
call ExitProcess
end begin
При компиляции программы формируются метки вида ??0000, ??0001 и т.д.
Использование фактических параметров со специальными знаками
Если фактический параметр внутри содержит запятую или пробел, параметр должен быть заключен в скобки <>, например:
IDEAL
p586
model flat
MACRO ERROR msg, number
m&number db '&msg',13,10,'$'
endm
dataseg
error <Ошибка в записи идентификатора>, 1
error <Ошибка в записи числа>, 2
error <Ошибка в записи числа, длина !> 6>, 3
END
Откомпилируйте этот пример с формированием листинга и проанализируйте созданный листинг.
Если среди символов фактического параметра есть символы <>, перед ними ставится знак !, например для предыдущего макроса необходимо сформировать
error <Ошибка в записи идентификатора, длина !>6>, 3
Если знак ! будет пропущен, первый знак > будет рассмотрен как конец параметра.
Если вместо фактического параметра используется выражение, которое должно быть вычислено до подстановки, перед параметром используется знак % например:
MACRO ARRAY name, type, count
name d&type (count) dup(?)
endm
Для использования макроса для выделения памяти под матрицу с данными типа двойное слово размером 25*80 можно записать:
IDEAL
P586
model flat
macro array name, type, count
name d&type (count) dup (?)
endm
dataseg
array matr, d, %25*80
end
Вложенные и рекурсивные макросы
Макроопределение может включать в себя макрокоманды для других макросов или само определение других макросов.
Пример. Пусть необходимо создать макросы для выполнения операций сложения или вычитания, причем операция определяется параметром. Тогда м/о для создания макросов имеет вид:
MACRO CREAT x,y,z,kod
MACRO op&kod x, y, z
push eax
mov eax, x
kod eax, y
mov z, eax
pop eax
endm
endm
Для создания макроса для сложения используется макрокоманда:
creat x, y, z, add
а для вычитания - макрокоманда
creat x, y, z, sub
Рекурсивные макросы будут рассмотрены ниже
Директивы условной трансляции
IF <Выражение>
<Операторы1>
ELSE
<Операторы2>
ENDIF
Операторы1 компилируются, если значение выражения истинно, в противном случае компилируются операторы2. Выражение
считается ложным, если оно равно 0.
В выражениях можно использовать логические операции AND, OR, NOT, знаки отношений: EQ(=), NE (¹), GT(>), GE(³), LT (<), LE (£), а также операции сдвига SHL, SHR.
Есть специальные выражения, позволяющие определить транслируемые команды в зависимости от определенных переменных, передаваемых параметров и т.д.
Список специальных выражений:
ifdef <Переменная>
- Если определена заданная переменная (т.е. для нее есть оператор equ или =);
ifndef <Переменная> - Если не определена заданная переменная;
ifidn <П1>, <П2>
- если совпадают два параметра макрокоманды, или параметр совпадает с некоторой константой;
ifdif <П1>, <П2>
- если не совпадают два параметра макрокоманды, или параметр не совпадает с некоторой константой;
ifb <П1> -
если заданный параметр в макрокоманде пустой (не определен);
ifnb <П1> - если заданный параметр в макрокоманде не пустой (определен);
if1 - если выполняется первый просмотр;
if2 - если выполняется второй просмотр.
Пример. Составить макроопределение для вычисления у = |x| для данных длиной 32 бита.
ideal
p586
model flat
extrn ExitProcess:proc
MACRO _ABS x, y
local m
push eax
mov eax, x
test eax, eax
jns short m
neg eax
m:
mov y, eax
pop eax
endm
codeseg
begin:
mov edx, -5
_ABS edx, ebx
mov edx, 5
_ABS edx, ebx
call ExitProcess
end begin
Имя
_ABS используется, т.к. имя ABS зарезервировано. Макрос работает не верно, если результат в регистре EAX.
Для использования макроса в случае результата в регистре EAX запишем его в виде:
MACRO _ABS x, y
local m
ifidn <y> , <eax>
r equ <ebx>
else
r equ <eax>
endif
push r
mov r, x
test r, r
jns short m
neg r
m:
mov y, r
pop r
endm
Директива условной трансляции позволяет выбрать рабочий регистр.
Формирование сообщений об ошибках
Пусть для исполнения программы требуется, чтобы переменная TEST была определена. Для проверки этого можно использовать операторы:
IFNDEF TEST
ERR «Определи переменную TEST!»
ENDIF
Если не определена переменная TEST, компилятор сообщает USER ERROR и сообщение «Определи переменную TEST!».
Обзор средств условной трансляции
Позволяют транслировать или не транслировать заданный участок программы, транслировать один и тот же участок повторно или формировать ошибочные сообщения при трансляции программы, при выполнении заданных условий
Примеры необходимости использования средств условной трансляции.
1. Составленные до сих пор программы, сохраняющие содержимое регистров, предполагали, что результат не помещается в рабочие регистры. Если это условие не выполняется, макросы работать не будут. Для обеспечения правильной работы необходимо проверять регистр-приемник.
2. Реализуются циклы с известным числом повторений. Для исключения переходов и сбросов конвейера, связанных с переходом, циклы разворачиваются, т.е. выполняется трансляция участка программы заданное число раз
Безусловное повторение
Общий вид директивы:
REPT count
команды
endm
Команды повторяются заданное число раз.
Пример. Пусть необходимо увеличить на 4 содержимое регистра eах, не изменяя кода переноса (бит с).
Rept 4
inc ax
endm
Директива IRP
IRP Параметр, <П1, П2, ...>
Команды
ENDM
Команды формируются столько раз, сколько задано параметров в угловых скобках. Каждый раз вместо параметра, заданного первым подставляется очередной параметр из списка заданных.
Пример. Составитьмакроопределение для поиска максимального для заданных 5 чисел.
ideal
p586
model flat
extrn ExitProcess:proc
MACRO MAX RES, X1, X2, X3, X4, X5
local m
ifidn <RES>, <eax>
reg equ ebx
else
reg equ eax
endif
push reg
mov reg, X1
IRP X, <X2, X3, X4, X5>
local n
cmp reg, X
jge short n
mov reg, X
n:
endm
mov RES, reg
endm
codeseg
begin:
MAX eax, 5, 4, 3, 6, 1
call ExitProcess
end begin
Если значение параметра, который подставляется, состоит из одной буквы, то используется макрос вида IRPC.
Директива IRPC
Общий вид директивы:
IRPC Параметр, Строка
Символы строки подставляются вместо значение параметра.
Пример. Сформировать массив натуральных чисел
ideal
p586
model flat
dataseg
irpc d, 0123456789
c&d db d
endm
end
Для этого примера сформируйте листинг и проверьте полученный сегмент данных.
Директива WHILE
Общий вид директивы WHILE
WHILE (Выражение)
Команды
ENDM
Последний тип макроса используется, если необходимо развернуть цикл с известным числом повторений для исключения команд перехода, а значит возможного сброса конвейера.
Пример. Составить программу сложения чисел заданного массива, не используя команд перехода
ideal
p586
model flat
extrn ExitProcess:proc
dataseg
a dd 1,2,3,4,5
s dd ?
codeseg
begin:
xor eax, eax
xor esi,esi
i=1
while i le 5
add eax, [a + esi]
add esi, 4
i = i + 1
endm
mov [s], eax
call ExitProcess
end begin
Обработка ошибок с помощью функции GetLastError
Общий вид функции:
DWORD GetLastError(VOID)
Функция возвращает код ошибки (32 бита). Список кодов ошибок приведен в файле WinError.h. Здесь же приведен формат кода ошибки:
Биты | 31-30 | 29 | 28 | 27-16 | 15-0 | ||||||
№ поля | 1 | 2 | 3 | 4 | 5 |
1 – Код степени тяжести ошибки (severity code):
00 – успех;
01 – информация;
10 – предупреждение;
11 – ошибка.
2 – Кем определена ошибка (Microsoft - 0, пользователь -1). Благодаря этому биту можно всегда определять коды ошибок, которые гарантированно не совпадут с системными кодами.
3 – Зарезервировано. Должно быть 0.
4 – Код подсистемы, к которой относится ошибка, например,
#define FACILITY_WINDOWS 8
#define FACILITY_STORAGE 3
#define FACILITY_INTERNET 12
#define FACILITY_CERT 11
Определяется MICROSOFT.
5 – Код ошибки 0..65535.
Ниже приведен фрагмент кода для вывода сообщения, соответствующего ошибке, нв языке, принятом по умолчанию для операционной системы. В этом фрагменте используются функции:
· FormatMessage
– для формирования требуемого сообщения по номеру ошибки, который возвращает функция GetLastError. Для этой функции задается адрес буфер, а функция выделяет буфер в куче и записывает сформированное сообщение; тип формируемого сообщения (для нас – сообщение ОС - FORMAT_MESSAGE_FROM_SYSTEM и язык, принятый по умолчанию – макрос MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT);
· MessageBox – для вывода диалога с собщением;
· LocalFree – для освобождения памяти, выделенной для буфера.
LPVOID lpMsgBuf;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf, 0, NULL );
MessageBox( NULL, lpMsgBuf, "GetLastError", MB_OK|MB_ICONINFORMATION);
LocalFree( lpMsgBuf );
Составление пользовательских функций со стандартной обработкой ошибок
При составлении пользовательских функций со стандартной обработкой ошибок необходимо:
1. Сформировать код ошибки, при этом в качестве кода можно выбрать стандартный код (см. файл WINERROR.H). Если среди кодов нет подходящего, то сформировать свой и определить его в своем заголовочном файле.
2. В функции перед возвратом обратитьсяк функции SetLastError. Заголовок функции :
VOID SetLastError (DWORD dwCode),
Где dwCode – код ошибки.
Именно этот код будет возвращен функцией GetLastError
Пример. Пусть необходимо возвратитькод: Недостаточно памяти (ERROR_NOT_ENOUGH_MEMORY)
#include <windows.h>
bool MyFun (){
SetLastError (ERROR_NOT_ENOUGH_MEMORY);
return false;
}
int main(int argc, char* argv[])
{
bool b = MyFun ();
if (!b){
LPVOID lpMsgBuf;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf, 0, NULL );
MessageBox( NULL, (char*)lpMsgBuf, "GetLastError",MB_OK|MB_ICONINFORMATION);
LocalFree( lpMsgBuf );
}
return 0;
}
Приведенный код программы выведен сообщение о недостающей памяти, на языке, принятом по умолчанию для операционной системы.
Обзор средств ввода-вывода
При программировании на ассемблере используются:
· физический ввод-вывод;
· системные вызовы.
Физический ввод-вывод предполагает работу с портами устройств или использование функций BIOS. Системные вызовы - это использование функций операционной системы для организации ввода-вывода. В данном курсе рассматриваются только системные вызовы.
Для системных вызовов используются функции WIN API. В случае ошибок эти функции возвращают значения, которые зависят от типа возвращаемого значения. Код ошибки, по которому можно определить ее причину, определяется с помощью функции WIN API GetLastError, которая должна быть вызвана непосредственно после функции, которая может быть завершена с ошибкой. Для составления безопасных программ рекомендуется после всех функций, которые могут завершаться с ошибкой, делать такую проверку!
Функция MessageBox
int MessageBoxA(
HWND hWnd, // Дескриптор окна (NULL для текущего) LPCTSTR lpText, // Адрес выводимой строки LPCTSTR lpCaption, // Адрес заголовка UINT uType // Стиль. Определяет выводимые кнопки ); Допустимые стили: MB_ABORTRETRYIGNORE (2) 3 кнопки: Abort, Retry, и Ignore. MB_OK (0) : Кнопка OK. MB_OKCANCEL (1) Кнопки: OK и Cancel. MB_RETRYCANCEL (5) Кнопки: Retry и Cancel. MB_YESNO (4) Кнопки : Yes и No. MB_YESNOCANCEL (3) 3 кнопки: Yes, No, иCancel. Значения констант, которые надо задавать вместо приведенных имен при программировании на ассемблере заданы в скобках после определения константы. Их можно посмотреть в файле WINUSER.H Дополнительно, можно задавать иконки для окна и дополнительную информацию (см. файл WIN32.HLP): Возвращаемое значение: Функция возвращает 0, если не достаточно памяти для создания окна. При успешном завершении функция возвращает код нажатой клавиши: IDABORT (3) нажата кнопка ABORT. IDCANCEL (2) нажата кнопка CANCEL или клавиша ESC. IDIGNORE (5) нажата кнопка IGNORE. IDNO (7)нажата кнопка NO. IDOK (1)нажата кнопка OK. IDRETRY(4)нажата кнопка RETRY. IDYES(6)нажата кнопка YES. |
Пример программы для вывода сообщения “Hello, World”
p586
include win.inc
model flat
extrn ExitProcess:proc
extrn MessageBoxA:proc
dataseg
mystr db 'HelloWorld!', 0
tit db 'Message', 0
codeseg
begin:
push 0
push offset tit
push offset mystr
push 0
call MessageBoxA
call ExitProcess
end begin
Функции и константы для работы с консолью
При работе с консолью необходимо:
· определить номера стандартных устройств (см. файл winuser.h);
· определить дескриптор для заданного устройства;
· обратиться к функции ввода – вывода.
Информация о номерах устройств и требуемых функциях приведена ниже.
Номера стандартный устройств
STD_INPUT_HANDLE (DWORD)-10 ввод STD_OUTPUT_HANDLE (DWORD)-11вывод STD_ERROR_HANDLE (DWORD)-12 вывод ошибок Определение дескриптора устройства: HANDLE GetStdHandle( DWORD nStdHandle // номер устройства ); Если ошибка, функция возвращает INVALID_HANDLE_VALUE, которому соответствует значение –1. Ввод данных со стандартного устройства: BOOL ReadConsole( HANDLE hConsoleInput, // дескриптор консоли LPVOID lpBuffer, // адрес буфера, куда читать данные DWORD nNumberOfCharsToRead, // количество читаемых символов LPDWORD lpNumberOfCharsRead, // адрес количества прочитанных символов LPVOID lpReserved // резерв, должен быть NULL ); Функция возвращает true в случае успеха и false в случае ошибки[19] BOOL WriteConsole( HANDLE hConsoleOutput, // дескриптор консоли CONST VOID *lpBuffer, // адрес буфера для записи DWORD nNumberOfCharsToWrite, // количество символов LPDWORD lpNumberOfCharsWritten, // адрес количества символов, которые действительно записаны LPVOID lpReserved // Резерв, должен быть NULL ); |
Пример программы для ввода – вывода строки символов.
ideal
p386
model flat
extrn ExitProcess:proc
extrn GetStdHandle:proc
extrn ReadConsoleA:proc
extrn WriteConsoleA:proc
dataseg
include "win.inc"
din dd 0
dout dd 0
buf db 80 dup (?)
siz dd ?
mystr db 'Input string, please', 13, 10, 0
codeseg
begin:
push STD_INPUT_HANDLE
push str1
push 0
call MessageBoxA
endm
Файл для проверки макроса
Ideal
p586
model flat
extrn ExitProcess:proc
extrn MessageBoxA:proc
include "win.inc"
dataseg
t db 'Information', 0
text db 'Hello, world!', 0
codeseg
begin:
ShowMessage <offset text>, <offset t>, 0
call ExitProcess
end begin
Пример 2. Составить функцию gets для ввода строки с клавиатуры, которая в результате формирует строку с нулевым завершителем. Признак конца вводимой строки- символ <Enter>.
ideal
p586
model flat
include "win.inc"
extrn GetStdHandle:proc
extrn ReadConsoleA: proc
codeseg
proc gets
public gets
arg mystr:dword
local s:dword=r
push ebp
mov ebp, esp
sub esp, r
push ebx ecx
push STD_INPUT_HANDLE
call GetStdHandle; ebp-0; eip-4; ;&str-8
mov ebx, [ebp+8]
push 0
lea ecx, [ebp-4]
push ecx
push 80 ebx eax
call ReadConsoleA
sub [s], 2
add ebx, [s]
mov [byte ptr ebx], 0
mov eax, [ebp+8]
pop ecx ebx
mov esp, ebp
pop ebp
ret 4
endp
end
Главная программа для проверки функции ввода строки имеет вид:
ideal
p386
model flat
extrn ExitProcess:proc
extrn GetStdHandle:proc
extrn ReadConsoleA:proc
extrn WriteConsoleA:proc
extrn gets:proc
dataseg
include "win.inc"
din dd 0
dout dd 0
buf db 80 dup (?)
siz dd ?
mystr db 'Input string, please', 13, 10, 0
codeseg
begin:
push STD_INPUT_HANDLE
call GetStdHandle
mov [din], eax
push STD_OUTPUT_HANDLE
call GetStdHandle
mov [dout], eax
push 0 offset siz 22 offset mystr [dout]
call WriteConsoleA
push offset buf
call gets
call ExitProcess
end begin
Пример 3. Составить функцию для вывода строки с нулевым завершителем на стандартное устройство
Функция:
Ideal
p586
model flat
include "win.inc"
extrn GetStdHandle:proc
extrn ReadConsoleA: proc
extrn WriteConsoleA:proc
dataseg
buffer db 80 dup (?)
codeseg
proc gets
public gets
arg mystr:dword
local s:dword=r
push ebp
mov ebp, esp
sub esp, r
push ebx ecx
push STD_INPUT_HANDLE
call GetStdHandle;
mov ebx, [ebp+8]
push 0
lea ecx, [ebp-4]
push ecx
push 80 ebx eax
call ReadConsoleA
sub [s], 2
add ebx, [s]
mov [byte ptr ebx], 0
mov eax, [ebp+8]
pop ecx ebx
mov esp, ebp
pop ebp
ret 4
endp
proc puts
public puts
arg mystr1:dword
local s1:dword=r1
push ebp
mov ebp, esp
sub esp, r1
push ebx ecx edx esi
push STD_OUTPUT_HANDLE
call GetStdHandle
mov ebx, [ebp+8]; &
; len
mov edx, 0
mov esi, offset buffer
m2:
mov cl, [ebx+edx]
mov [esi+edx], cl
test cl, cl
jz short m1
inc edx
jmp m2
m1:
push 0
lea ecx, [ebp-4]
push ecx
push edx
push offset buffer
push eax
call WriteConsoleA
mov eax, [ebp+8]
pop esi edx ecx ebx
mov esp, ebp
pop ebp
ret 4
endp
end
Главная программа для проверки функции вывода строки имеет вид:
Ideal
p386
model flat
extrn ExitProcess:proc
extrn GetStdHandle:proc
extrn ReadConsoleA:proc
extrn WriteConsoleA:proc
extrn gets:proc
extrn puts:proc
dataseg
include "win.inc"
din dd 0
dout dd 0
buf db 80 dup (?)
siz dd ?
mystr db 'Input string, please', 13, 10, 0
codeseg
begin:
push offset mystr
call puts
push offset buf
call gets
push offset buf
call puts
call ExitProcess
end begin
Функции ввода-вывода для стандартных устройств
Известно, что кодировка символов , принятая по умолчанию, отличается для WINDOWS 95, WINDOWS NT. Тип кодировки задается буквой A в конце функции для однобайтной кодировки и буквой W для 2-х байтной. При создании программ на языке АССЕМБЛЕР необходимо учитывать это различие.
Для программного определения типа установленной системы используется функция GetVersion:
DWORD GetVersion(VOID)
Функция возвращает номер версии и подверсии установленной системы Для всех платформ младшее слово содержит номера версии (младший байт номер версии, старший – номер подверсии в 16 –ой системе счисления) Для различения платформ ОС используется старший бит и младший байт, которые принимают значения, приведенные в табл. 13.1. Таблица 13.1. Типы операционных систем |
Платформа | Старший бит | Младший байт | |||
Windows NT | 0 | 3 или 4 | |||
Windows 95 | 1 | 4 | |||
Win32s, Windows 3.1 | 1 | 3 |
Для Windows NT и Win32s, оставшиеся биты старшего слова задают номер разработчика, для Windows 95 – резервируются.
Пример использования функции на С++:
…
DwVersion = GetVersion();
dwWindowsMajorVersion = (DWORD)(LOBYTE(LOWORD(dwVersion)));
dwWindowsMinorVersion = (DWORD)(HIBYTE(LOWORD(dwVersion)));
if (dwVersion < 0x80000000) // Windows NT
dwBuild = (DWORD)(HIWORD(dwVersion));
else if (dwWindowsMajorVersion < 4) // Win32s
dwBuild = (DWORD)(HIWORD(dwVersion) & ~0x8000);
else // Windows 95 --
dwBuild = 0;
К функциям ввода – вывода для стандартных устройств относятся:
· функция MessageBox для вывода диалогового окна с заданной информацией и заданными кнопками;
· функции для работы с консолью
По умолчанию в качестве стандартных устройств ввода используется клавиатура, а вывода -монитор. Выводимую информацию можно разделить на информацию, соответствующую штатной работе программы, и информацию, соответствующую реакции на ошибки. По умолчанию для обоих типов информации используется монитор. Допускается переназначение стандартных устройств. Ниже приведены наиболее распространенные константы и функции[18].
Ввод/вывод числовых данных
Для ввода числовых данных необходимо:
1. Ввести соответствующую строку.
2. Преобразовать ее в числовой вид.
Для вывода необходимо выполнить действия в обратном порядке.
Преобразование строки в число.
Пусть задана строка вида:
[<Пробелы>] [<>][<Пробелы>] Цифра [Цифра Цифра...]
Преобразовать ее в соответствующее ей число.
Для преобразования последовательности цифр в число можно использовать схему Горнера. Например, пусть необходимо строку символов «375»
преобразовать в число. Представим число в виде:
(3 * 10 + 7) * 10 + 5. Здесь 3, 7, 5 -цифры числа, а 10
- основание системы счисления для исходного числа. Таким образом, алгоритм преобразования:
r=0;
for (i=n-1; i>=0; i--)
r = r*10 +d[i];
Так как в общем виде числа могут быть пробелы, которые надо игнорировать, напишем макрос для игнорирования этих символов. На вход макроса подается адрес символа строки, на выходе получаем адрес ненулевого символа
macro skip address
local l1, l2
ifidn <di>, <address>
reg equ esi
else
reg equ edi
endif
push reg
mov reg, address
l2:
cmp [byte ptr reg], ‘ ’
jne l1
inc reg
jmp l2
l1:
mov address, reg
pop reg
endm
Данный макрос подключим к файлу win.inc
Функции для преобразования строки в число и обратного преобразования представлены ниже.
Преобразование чисел при выводе
До вывода числа оно должно быть преобразовано из внутреннего представления в строковое. Для преобразования можно использовать следующие способы.
1. Число делится на 10 и остаток от деления рассматривается как очередная цифра. Деление продолжается до тех пор, пока не получим нулевое значение. Недостаток способа: цифры числа получаем, начиная с младших, перед выводом необходимо инвертирование строки или формирование строки заданной длины.
2. Число делится на 10000, 1000,... и получаем цифры числа, начиная со старших цифр. Недостаток. Требуется хранение массива или его программное формирование.
Рассмотрим реализацию первого способа. Для хранения 32-битного десятичного числа требуется 10 цифр + знак + нулевой завершитель, т.е. 12 символов. Так как строка может занимать не все 12 символов, оставшиеся символы слева должны быть пробелами.
Функция для преобразования числа в строку. Входное данное-32-битное число. Выходное - строка с нулевым завершителем
Функции для преобразования числа при вводе и выводе и соответствующая главная программа представлены ниже:
Ideal
p586
model flat
extrn ExitProcess:proc
include "win.inc"
dataseg
my db ' 25', 0
val dd ?
codeseg
proc atoi
public atoi
arg value:dword, mystr:dword = p
push ebp
mov ebp, esp
push ebx ecx edx esi
mov esi, [mystr]
skip esi
mov ebx, 1
cmp [byte ptr esi], '-'
je short minus
cmp [byte ptr esi], '+'
je short plus
jmp prod
minus:
inc esi
neg ebx
jmp prod
plus:
inc esi
prod:
skip esi
xor eax, eax; r=0
mov ecx, 10
for:
cmp [byte ptr esi], 0
je exit
mul ecx
test edx, edx
jne error
mov dl, [esi]
sub dl, '0'
add eax, edx
inc esi
jmp for
exit:
mul ebx
mov esi, [value]
mov [esi], eax
xor eax, eax
jmp lend
error:
mov eax, 1
lend:
pop esi edx ecx ebx
pop ebp
ret p
endp atoi
begin:
push offset my offset val
call atoi
call ExitProcess
end begin
Задание для самостоятельной работы. Составить программу для второго алгоритма и сравнить их по вычислительной сложности.
Обзор системных вызовов для организации ввода-вывода
В зависимости от типа устройств, используемых для ввода -вывода, функции делятся на функции ввода-вывода для стандартных устройств (например, клавиатура, дисплей) и функции ввода-вывода для нестандартных устройств (например, диски).
Обзор функций WINDOWS API для работы с файлами
Файл – это объект операционной системы. Это значит, что ОС создает для каждого файла таблицу, куда заносит информацию о файле, включающую в себя имя файла, его местоположение на диске, текущее положение указателя, режимы открытия и т.д.
Для создания записи о файле используется функция CreateFile:
HANDLE CreateFile(
LPCTSTR lpFileName, // Имя файла (с нулевым завершителем)
DWORD dwDesiredAccess, // Режим доступа к файлу (read-write)
DWORD dwShareMode, // Возможность share
LPSECURITY_ATTRIBUTES lpSecurityAttributes, // атрибуты безопасности
DWORD dwCreationDistribution, // Режимы создания
DWORD dwFlagsAndAttributes, // Атрибуты файла
HANDLE hTemplateFile // Дескриптор файла с атрибутами копирования
);
Параметр dwDesiredAccess может принимать значения.
GENERIC_READ Разрешено чтение.
GENERIC_WRITE Разрешена запись. Может задаваться совместно с первым.
(см. файл WINNT.H)
#define GENERIC_READ (0x80000000L)
#define GENERIC_WRITE (0x40000000L)
#define GENERIC_EXECUTE (0x20000000L)
#define GENERIC_ALL (0x10000000L)
DwShareMode
-возможность совместного использования. Если параметр равен 0, файл нельзя совместно использовать.
Допустимые значения параметров:
FILE_SHARE_DELETE Windows NT only: Разрешается только тогда, когда файл удаляется.
FILE_SHARE_READ Разрешается только для чтения.
FILE_SHARE_WRITE Разрешается только для записи.
LpSecurityAttributes – указатель на структуру SECURITY_ATTRIBUTES с целью задания возможности наследования файла процессом – потомком. Если параметр равен NULL, дескриптор файла не может наследоваться.
Windows NT: lpSecurityDescriptor определяет дескриптор безопасности для дескриптора. Если он равен NULL, дескриптор получает параметры безопасности, принятые по умолчанию, если файловая система поддерживает такие атрибуты. Для Windows 95 эта структура игнорируется.
Функция ReadFile читает данные из файла.
BOOL ReadFile(
HANDLE hFile, // Дескриптор файла
LPVOID lpBuffer, // адрес буфера
DWORD nNumberOfBytesToRead, // количество читаемых байтов
LPDWORD lpNumberOfBytesRead, // адрес количества прочитанных байтов
LPOVERLAPPED lpOverlapped // адрес тсруктуры для данных
);
Если возвращается True, а количество прочитанных байтов равно 0, то это прочитан конец файла.
BOOL WriteFile(
HANDLE hFile, // Дескриптор файла
LPCVOID lpBuffer, // адрес буфера
DWORD nNumberOfBytesToWrite, // количество записанных байтов
LPDWORD lpNumberOfBytesWritten, // указатель на количество записанных байтов
LPOVERLAPPED lpOverlapped // Указатель на структуру для асинхронного использования файла
);
Пример 1. Создать файл для записи и записать туда заданную строку.
Ideal
p586
model flat
extrn ExitProcess:proc
extrn CreateFileA:proc
extrn CloseHandle:proc
extrn ReadFile:proc
extrn WriteFile:proc
extrn CloseHandle:proc
extrn MessageBoxA:proc
include "win.inc"
dataseg
my db ' - 25', 0
val dd ?
d1 dd ?
d2 dd ?
n1 db 'a.txt', 0
n2 db 'b.txt', 0
msg db 'Error', 0
s1 dd ?
codeseg
begin:
push 0 0 CREATE_ALWAYS 0 0
push GENERIC_WRITE
push offset n1
call CreateFileA
cmp eax, INVALID_HANDLE_VALUE
jne short m1
push MB_OK offset msg offset n1 0
call MessageBoxA
jmp short m2
m1:
mov [d1], eax
mov eax, 0
for1:
mov bl, [my+eax]
test bl, bl
je short break
inc eax
jmp for1
break:
push 0 offset s1 eax offset my [d1]
call WriteFile
test eax, eax
jne short m4
push MB_OK offset msg offset n1 0
call MessageBoxA
jmp short m2
m4:
push [d1]
call CloseHandle
cmp eax, INVALID_HANDLE_VALUE
jne short m3
push MB_OK offset msg offset n1 0
call MessageBoxA
jmp short m2
m3:
m2:
call ExitProcess
end begin
Пример 2. Составить функцию для копирования файлов с заданными именами.
Ideal
p586
model flat
extrn ExitProcess:proc
extrn CreateFileA:proc
extrn CloseHandle:proc
extrn ReadFile:proc
extrn WriteFile:proc
extrn CloseHandle:proc
extrn MessageBoxA:proc
include "win.inc"
dataseg
my db ' - 25', 0
val dd ?
n1 db 'io6.exe', 0
n2 db 'proba.txt', 0
msg db 'Error', 0
s1 dd ?
codeseg
proc FileCopy
public FileCopy
arg nin:dword, nout:dword
local buffer:byte:512, d1:dword, d2:dword, count:dword=s
push ebp
mov ebp, esp
sub esp, s
push eax
push 0 0 OPEN_EXISTING 0 0 GENERIC_READ [nin]
call CreateFileA
cmp eax, INVALID_HANDLE_VALUE
jne short @@m1
push MB_OK offset msg [nin] 0
call MessageBoxA
jmp @@m3
@@m1:
mov [d1], eax
push 0 0 CREATE_ALWAYS 0 0 GENERIC_WRITE [nout]
call CreateFileA
cmp eax, INVALID_HANDLE_VALUE
jne short @@m2
push MB_OK offset msg [nout] 0
call MessageBoxA
jmp @@m3
@@m2:
mov [d2], eax
for1:
push 0
lea eax, [count]
push eax 512
lea eax, [buffer]
push eax [d1]
call ReadFile
test eax, eax
jne short @@m4
push MB_OK offset msg [nin] 0
call MessageBoxA
jmp short @@m3
@@m4:
mov eax, [count]
test eax, eax
je short break
push 0
lea eax, [count]
push eax [count]
lea eax, [buffer]
push eax [d2]
call WriteFile
test eax, eax
jne short @@m5
push MB_OK offset msg [nout] 0
call MessageBoxA
jmp short @@m3
@@m5:
mov eax, [count]
cmp eax, 512
jb short break
jmp for1
break:
push [d1]
call CloseHandle
push [d2]
call CloseHandle
@@m3:
pop eax
mov esp, ebp
pop ebp
ret 8
endp
begin:
push offset n2 offset n1
call FileCopy
call ExitProcess
end begin
Удаление файлов
BOOL DeleteFile(
LPCTSTR lpFileName // pointer to name of file to delete
);
lpFileName – имя файла – строка с нулевым завершителем
Windows 95: удаляет даже открытый файл. Для предотвращения потери данных файл надо сначала закрыть.
Windows NT: Не удаляет открытых файлов.
Копирование или перемещение файлов
До копирования файл должен быть закрыт или открыт только для чтения. Для этого используются функции CopyFile или CopyFileEx.
Для перемещения файла он должен быть закрыт. Для этого используются функции MoveFile и MoveFileEx, которые копируют файл в новый и удаляют старый.
Копирование существующего файла в новый.
BOOL CopyFile(
LPCTSTR lpExistingFileName, // Имя существующего файла
LPCTSTR lpNewFileName, // Имя результата
BOOL bFailIfExists // Флаги для определения действий для //существующего файла
);
bFailIfExists= TRUE
и новый файл уже существует, то ошибка;
bFailIfExists= FALSE и новый файл уже существует, то файл пересоздается.
Замечание:
Атрибуты безопасности не копируются в результирующий файл.
Файловые атрибуты (FILE_ATTRIBUTE_*) копируются, например, если исходный файл был FILE_ATTRIBUTE_READONLY, то результирующий файл будет тоже только для чтения
Функция MoveFile переименовывает существующий файл или каталог, переносит, если надо с устройства на устройство и, если это каталог, то переносит все вложенные подкаталоги.
BOOL MoveFile(
LPCTSTR lpExistingFileName, // Имя существующего файла или каталога
LPCTSTR lpNewFileName // Новое имя файла или каталога
);
lpExistingFileName – имя существующего файла или каталога;
lpNewFileName – имя нового файла или каталога. Новое имя не должно существовать. Новое имя файла может соответствовать устройству с другой файловой системой. Новый каталог должен быть на том же устройстве.
Рекомендуем выполнить копирование (перемещение) файлов, используя стандартные функции WIN API и функции языка СPP и сравнить их по производительности!
Блокирование участков файлов
Так как ОС разрешает одновременную работу с файлом нескольких приложений, может возникнуть необходимость блокирования участка файла на некоторый промежуток времени.
Функции LockFile и LockFileEx блокируют заданный участок файла. При обращении к заблокированной области для чтения или записи – всегда ошибка..
Функция LockFileEx даже позволяет указать, особые свойства блокирования. (exclusive)
блокирование запрещает полный доступ всем процессам, в том числе и чтение, и запись. (shared) блокирование запрещает только запись. Это позволяет определить в файле область только для чтения.
Для разблоктрования области используются функции UnlockFile
или UnlockFileEx. Приложение должно разблокировать все области файла перед его закрытием
Функция LockFileEx блокирует участок окрытого файла, задавая shared или exclusive доступ.
BOOL LockFileEx(
HANDLE hFile, // Дескриптор файла
DWORD dwFlags, // Флаги
DWORD dwReserved, // Резерв, 0
DWORD nNumberOfBytesToLockLow, // младшие 32 бита длины //области блокирования
DWORD nNumberOfBytesToLockHigh, // старшие 32 бита длины
// области блокирования
LPOVERLAPPED lpOverlapped // Адрес стрктуры с оределением //области блокирования
);
Значения флагов:
LOCKFILE_FAIL_IMMEDIATELY Если это значение установлено и немедленно нельзя заблокировать заданный участок – сразу ошибка, иначе ждет возможности.
LOCKFILE_EXCLUSIVE_LOCK Блокирование типа exclusive. Иначе типа shared.
Структура содержит значение смещения для блокируемого участка (поля Offset, OffsetHigh).
typedef struct _OVERLAPPED { // o
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
HANDLE hEvent;
} OVERLAPPED;
Функция UnlockFileEx разблокирует заблокированную область открытого файла.
BOOL UnlockFileEx(
HANDLE hFile, // Дескриптор
DWORD dwReserved, // Резерв, 0
DWORD nNumberOfBytesToUnlockLow, // Младшие 32 бита
DWORD nNumberOfBytesToUnlockHigh, // Старшие 32-бита
LPOVERLAPPED lpOverlapped // Адрес структуры со смещением
);
Функция FindFirstFile
HANDLE FindFirstFile(
LPCTSTR lpFileName, // Имя образца для поиска
LPWIN32_FIND_DATA lpFindFileData // Указатель на структуру с результатом
);
Windows 95: Образец для поиска может содержать символы (* и ?). Строка должна быть с нулевым завершителем и по длине не превосходить MAX_PATH
символов.
Windows NT: Ограничение на длину строки снимается, т.к. есть возможность использовать широкую версию (W) функции FindFirstFile. Символы "\\?\" говорят о возможности использования пути длиннее MAX_PATH. Она также работает с именами, заданными в UNICODE (UNC). Символы "\\?\" игнорируются как часть каталога. Например, путь "\\?\C:\myworld\private" интерпретируется как "C:\myworld\private", а "\\?\UNC\bill_g_1\hotstuff\coolapps"
интерпретируется как
"\\bill_g_1\hotstuff\coolapps".
LpFindFileData - Структура WIN32_FIND_DATA.
typedef struct _WIN32_FIND_DATA {
DWORD dwFileAttributes; // Атрибуты файла
FILETIME ftCreationTime; // Время создания
FILETIME ftLastAccessTime; // Время последнего доступа
FILETIME ftLastWriteTime; // Время последней записи
DWORD nFileSizeHigh; //Размер файла – старшая часть
DWORD nFileSizeLow; //Размер файла - младшая часть
DWORD dwReserved0; // Резерв
DWORD dwReserved1; // Резерв
TCHAR cFileName[ MAX/_PATH ]; // Имя файла
TCHAR cAlternateFileName[ 14 ];
} WIN32_FIND_DATA;
dwFileAttributes - Значение атрибутов, определяется битами:
FILE_ATTRIBUTE_ARCHIVE, FILE_ATTRIBUTE_COMPRESSED,
FILE_ATTRIBUTE_DIRECTORY , FILE_ATTRIBUTE_HIDDEN
FILE_ATTRIBUTE_NORMAL
FILE_ATTRIBUTE_OFFLINE- данные из фала сразу же недоступны, они могут быть пока в памяти.
FILE_ATTRIBUTE_READONLY,
FILE_ATTRIBUTE_SYSTEM файл является частью ОС или используется только ею.
FILE_ATTRIBUTE_TEMPORARY- временный файл-используется для временного хранения. Приложение должно писать в него, только если это абсолютно необходимо . Большинство данных остается в памяти без сброса их на диск, т.к. файл очень скоро удаляется.
Функция FindNextFile
Используется для продолжения поиска по образцу, определенному функцией FindFirstFile.
BOOL FindNextFile(
HANDLE hFindFile, // Дескриптор для поиска
LPWIN32_FIND_DATA lpFindFileData // Указатель на структуру с //данными о файле
);
Для определения причины ошибки – используется функция GetLastError, которая возвращает ERROR_NO_MORE_FILES, если файлов больше нет.
Функция FindClose
Завершает поиск, начатый функцией FindFirstFile и продолженный FindNextFile.
BOOL FindClose(
HANDLE hFindFile // Дескриптор поиска
);
Пример. Вывести текущий каталог.
Ideal
p586
model flat
extrn ExitProcess:proc
extrn FindFirstFileA:proc
extrn FindNextFileA :proc
extrn FindClose :proc
extrn MessageBoxA:proc
extrn puts:proc
include win.inc
dataseg
FileName db '*.*', 0
FindFileData WIN32_FIND_DATA<>
d1 dd ?
msg db 'Error!!!', 0
tit db '',0
eol db 13,10,0
codeseg
begin:
lea eax, [ FindFileData]
push eax
push offset FileName
call FindFirstFileA
cmp eax, INVALID_HANDLE_VALUE
je short error
mov [d1], eax
lea ebx, [FindFileData.cFileName]
push ebx
call puts
push offset eol
call puts
for1:
lea ebx, [FindFileData]
push ebx
push [d1]
call FindNextFileA
test eax, eax
je short break;
lea ebx, [FindFileData.cFileName]
push ebx
call puts
push offset eol
call puts
jmp for1
break:
push [d1]
call FindClose
jmp short ok
error:
ok:
call ExitProcess
end begin
Файл вставки (win.inc)
MB_OK equ 0
STD_INPUT_HANDLE equ -10
STD_OUTPUT_HANDLE equ 11
STD_ERROR_HANDLE equ -12
CREATE_NEW equ 1
CREATE_ALWAYS equ 2
OPEN_EXISTING equ 3
OPEN_ALWAYS equ 4
TRUNCATE_EXISTING equ 5
GENERIC_READ equ 80000000h
GENERIC_WRITE equ 40000000h
GENERIC_EXECUTE equ 20000000h
GENERIC_ALL equ 10000000h
INVALID_HANDLE_VALUE equ (-1)
ideal
macro ShowMessage str1, tit, button
push button
push tit
push str1
push 0
call MessageBoxA
endm
macro skip address
localааа l1, l2
ifidn <di>, <address>
regаааааа equааааа esi
else
regаааааа equааааа edi
endif
pushаааа reg
movаааа reg, address
l2:
cmpаааа [byte ptr reg], ' '
jneаааааа l1
incаааааа reg
jmpааааа l2
l1:
movаааа address, reg
popааааа reg
endm
MAX_PATHааа equааааа 260
masm
WIN32_FIND_DATA struc
dwFileAttributes ddаааа ?
ftCreationTimeааааааааааа ddааааааа ?,?
ftLastAccessTime ddааа ?,?
ftLastWriteTimeа ddаааа ?,?
nFileSizeHighа ddааааааа ?
nFileSizeLowаа ddааааааа ?
dwResааааааааааааа ddааааааа 0,0
cFileName dbа (MAX_PATH) dup (?)
cAlternateFileName db 14 dup (?)
ends
+сЁрЄшЄх тэшьрэшх, ўЄю фы ЁрсюЄv ёю ёЄЁєъЄєЁющ яхЁхъы¦ўхэ Ёхцшь ЄЁрэёы Ўшш.
Функции для поиска файлов
Поиск можно выполнять по заданной маске с помощью функций FindFirstFile, FindFirstFileEx, FindNextFile, и FindClose. Образец может включать в себя символы ?, *.
Функции FindFirstFile и FindFirstFileEx создают дескрипторы, котрые используются функцией FindNextFile. Все функции возвращают информацию о найденном файле, которая включает в себя имя файла, его размер, атрибуты и время.
Функция FindClose разрушает дескрипторы, созданные функциями FindFirstFile и FindFirstFileEx.
Приложение может также искать каталог(Функция SearchPath)
Создание временных файлов
Для создания временных файлов используется функция GetTempPath, определяющая каталог, в котором создается временный файл
DWORD GetTempPath(
DWORD nBufferLength, // размер (в символах) буфера для имени //каталога
LPTSTR lpBuffer // адрес буфера для каталога
);
Возвращаемое значение
При успешном завершении – количество символов в буфере, куда записан каталог с нулевым завершителем. Если возвращаемое значение больше, чем задано, фактически возвращается требуемое значение.
Ошибка – 0.
Замечание.
Каталог для временных файлов формируется так:
1. Каталог определяется переменной среды TMP.
2. Каталог определяется переменной среды TEMP, если TMP не задана.
3. Текущий каталог, если TMP и TEMP не заданы.
Для формирования имени временного файла используется функция GetTempFileName.
Имя файла является объединением каталога для временного файла и строки – префикса, формируемой из заданного целого в 16-ой системе счисления. Расширение файла всегда TMP.
Заметим, что функция создает имя файла, но не сам файл. Если задан 0 в качестве целого, функция создает уникальное имя файла.
UINT GetTempFileName(
LPCTSTR lpPathName, // Имя каталога для временного файла
LPCTSTR lpPrefixString, // Строка с префиксом
UINT uUnique, // Число, которое используется для создания имени //файла
LPTSTR lpTempFileName // Адрес буфера для имени файла
);
lpPathName – указатель на строку с нулевым завершителем. Строка должна содержать символы в кодировке ANSI. Обычно задается (.), т.е. текущий каталог или результат работы функции GetTempPath. Если этот параметр равен NULL, функция завершается с ошибкой.
lpPrefixString- Указатель на строку с нулевым завершителем. Функция использует первые 3 символа этой строки как префикс имени файла. Символы необходимо задавать в кодировке ANSI.
uUnique – беззнаковое целое, которое функция преобразует в 16-ую строку и использует при создании имени файла. Если uUnique != 0, функция добавляет эту строку к префиксу для формирования имени. В этом случае функция не создает заданный файл и не проверяет его имя на уникальность. Если uUnique = 0, функция формирует 16-ую строку по текущему системному времени. В этом случае формируются новые значения (++) до тех пор, пока не получим уникальное имя файла, и затем она создает файл в заданном каталоге, а потом его закрывает.
lpTempFileName – сюда заносится сформированное имя файла в кодировке ANSI.
Буфер должен быть достаточной длины (MAX_PATH).
Возвращаемое значение
Успех – функция возвращает уникальное число, которое использовалось при формировании имени файла. Если uUnique не равен 0, то возвращается это значение.
При ошибке функция возвращает 0 ( GetLastError)
Таким образом формат имени:
path\preuuuu.TMP
где
path путь;
pre первые 3 символа префикса
uuuu 16-ое значение uUnique
Когда Windows перезагружается, временные файлы, созданные этой функцией, автоматически не удаляются. Для преобразования имени файла из формата ANSI в Windows строку, необходимо использовать функцию CreateFile для создания временного файла.
Дополнительные функции для работы с файлами
Как показано ранее, использование функций WINAPI в программе на ассемблере и С++, существенно не различается, поэтому ниже в качестве примеров используются примеры на С++.
Позиционирование файла
Для установки требуемой позиции в файле используется функция SetFilePointerEx:
BOOL SetFilePointerEx( HANDLE hFile, // handle to file LARGE_INTEGER liDistanceToMove, // bytes to move pointer PLARGE_INTEGER lpNewFilePointer, // new file pointer DWORD dwMoveMethod // starting point);
hFile – дескриптор открытого файла, который создан в режиме GENERIC_READ или GENERIC_WRITE.
liDistanceToMove – задает смещение в файле, на которое необходимо сместиться;
lpNewFilePointer- переменная, куда записывается новое значение указателя;
dwMoveMethod –определяет, относительно чего смещать. Может принимать значения:
FILE_BEGIN – смещать относительно начала файла;
FILE_CURRENT – смещать относительно ьекущего указателя в файле;
FILE_END – смещать относительно конца файла.
Примеры.
Пример 1. Составить функцию для определения размера файла.
Особенности процессоров с MMX технологией с точки зрения программиста
Для процессоров MMX обеспечивается полная совместимость с предыдущими процессорами и операционными системами. Это означает, что программы, написанные для обычного PENTIUM будут нормально работать для PENTIUM MMX.
Добавлен новый тип данных размером 64 бита.
Для данных можно использовать регистры FPU, которые в этом случае обозначаются MM0..MM7, при этом MM0 соответствует ST0, MM1®ST1, ¼ независимо от вершины стека.
Обеспечено параллельное выполнение операций для частей 64 битного данного размером 1 байт (8 частей), 2 байта (4 части), 4 байта (2 части). Такая технология называется SIMD (Single Instruction Multiple Data)- одна команда для множества данных. Это делает более эффективными операции для:
* видео;
* графики;
* обработки зображений;
* обработки аудио информации;
* скоростной синтез и сжатие;
* телефония и т.д.
Используется 8 регистров MMX длиной 64 бита. Эти регистры для pentium, pentium II совмещены с регистрами FPU, поэтому нельзя одновременно использовать команды FPU и команды MMX. Для pentium Ø èñïîëüçóþòñÿ äëÿ îáåèõ öåëåé ðàçíûå ðåãèñòðû.
Используется 2 типа арифметик:
· арифметика с насыщением (saturating) - если результат превосходит предельное значение, он считается равным предельному (цвет не может быть белее белого и чернее черного);
· обычная арифметика с отбрасыванием переносов.
Арифметические команды
Реализованы операции сложения, вычитания и умножения. Операции сложения и вычитания выполняются для 1, 2, 4, 8 байтов. Для команды умножения используется режим умножения 4-х пар знаковых чисел по 16 бит каждое. В результате умножения формируются младшие (команда PMULLW) и старшие 64 бита результата (PMULHW). Для использования этой команды для чисел многократной точности требуется предварительное преобразование числа таким образом, чтобы биты 15 и 31 были всегда 0. Вопросы эффективности умножения длинных чисел с помощью команд MMX требует дополнительного исследования.
Наряду с элементарными арифметическими операциями используется операция умножения со сложением (PMADDWD). Сначала перемножаются соответствующие 16-битные элементы, в результате получаем четыре 32 битных элемента P0, P1, P2, P3 (знаковое умножение), а затем вычисляется два 32-битных элемента R0 = P0 + P1; R1 = P2+P3. Все операции выполняются с учетом знака.
Команды сравнения
Общий вид команды:
PCMP<Условие>, например
PCMPGT - проверка на больше;
PCMPEQ - проверка на равно;
Условие задается как в выражениях.
В результате выполнения команды в первый операнд записывается 1, если условие истинно и 0 в противном случае. Команда, как и остальные команды, может одновременно выполняться для нескольких элементов.
Команды преобразования
Предназначены для изменения длины числа в сторону уменьшения (усечение старших разрядов или насыщения), увеличения (расширения знаковым разрядом), например
PACKSS - упаковка со знаковым насыщением;
PUNPCKH - расширение данного, буква H в конце означает, что формируется старшая часть результатов;
PUNPCKL - тоже для младшей части.
Логические крманды
Все логические команды используются для 64 битного числа (почему?).
Кроме традиционных команд (PAND, POR, PXOR, PNOT ) есть команда PANDN (AND + NOT).
Команды сдвига
Логический сдвиг выполняется для 2, 4 и 8 байтовых данных.
Команды:
PSSL - сдвиг влево;
PSRL - сдвиг вправо.
Арифметический сдвиг выполняется для 2, 4 байтовых данных.
Команда:
PSRA - сдвиг вправо;
Команды пересылки
Используются для пересылки 32-битных и 64 битных данных. Выполняют пересылку между памятью и MMX регистром, а также между MMX регистрами. 32-битный вариант возможно использовать для пересылки между регистром общего назначения и MMX регистром.
MOVD - пересылка 32-битных данных;
MOVQ - пересылка 64 битных данных.
Управление режимами работы процессора
Напоминаем, что одновременное использование команд MMX и команд FPU не допускается для процессоров, в которых команды MMX и FPU физически используют одни и те же регистры[20]. Вначале автоматически инициализируется режим использования FPU. Первая команда, относящаяся к классу команд MMX переключает процессор в режим использования MMX. Для переключения режима MMХ в режим FPU используется команда EMMS. Команда без операндов. В этом случае все регистры с плавающей точкой считаются свободными. Рекомендуется эту команду писать всегда перед выходом из функции, которая использовала команды MMX[21].
Обзор команд для MMX
Команды делятся на:
* команды пересылки;
* арифметические команды;
* команды сравнения;
* команды преобразования;
* логические команды;
* команда для переключения режима MMX - FPU
* команды сдвига.
Общий вид кода команды:
P<код>{U|S}[S]{B|W|D|Q},
где:
P - признак команды для MMX (PACK - упакованный);
<код> - код операции, например, MOV, ADD,...
U|S - Беззнаковое/знаковое
S - с насыщением
B|W|D|Q - типы данных
Примеры команд: PADDSB, PSUBUSW.
Требования к операндам:
Первый операнд (SRC) - MMX регистр или память.
Исключение: Команда PMOVD может содержать регистр общего назначения.
Второй операнд (DEST) - MMX регистр.
Использование команд MMX в приложениях
Для создания мобильных приложений, которые могут исполняться для процессоров, поддерживающих технологию MMX и не поддерживающих ее рекомендуется создавать 2 варианта кода и выбирать оптимальный программным путем.
Для определения возможности использования команд MMX для данного процессора используется команда CPUID, EAX = 1. Если технология MMX поддерживается, бит 23 в регистре EDX установлен в 1. Пример кода для проверки:
…
mov eax, 1
cpuid
test edx, 00800000H
jnz short yes; MMX поддерживается
…
При создании функций. Использующих команды MMX допускается передача параметров через регистры MMX. В настоящее время нет соглашения для передачи параметров через регистры MMX в языке С. Поэтому функции, принимающие параметры в регистрах MMX и соответствующий вызов необходимо писать на ассемблере или делать ассемблерные вставки.
Возврат значения:
* Если значений много - записываются в структуру, а адрес структуры - в регистр EAX;
* В регистре MMX, но тогда в конце функции нельзя использовать команду EMMS, а это плохо!
Рассмотрим более подробно операции процессора при переключении в режим MMX:
* В поле экспоненты всех MMX - регистров записывается 1 во все биты (16 бит);
* В поле тегов записывается 00, что соответствует занятости всех регистров;
* Вершина стека устанавливается на физически первый регистр.
В таблице представлено влияние команд FPU/MMX на регистры
Для того, чтобы частое переключение режимов меньше сказывалось на производительности процессора рекомендуется:
* в функции (цикле) использовать команды одного типа;
* не использовать содержимое регистров, сформированное командами другого типа;
* как только команды MMX не требуются выполнить переключение режима (команда EMMS);
* перед выходом из функции, использующей FPU очищать стек FPU.
Операционные системы, сохраняющие состояние MMX/FPU
Операционные системы могут сохранять:
* полное состояние для FPU;
* сохраняет состояние по специальному запросу;
* сохраняет только частично состояние.
Вначале для всех задач предполагается, что MMX/FPU не требуется CR0.TS=1.
Если встречается команда FPU/MMX, вызывается обработчик int 7 (устройство не доступно), обработчик входит в состав OS, и выполняет:
* выделение памяти для состояния задачи;
* формируется переменная, определяющая текущий режим;
* включается требуемый режим.
Бит CR0.TS устанавливается в 0, что означает, что необходимо сохранять – восстанавливать среду.
Заметим, что само состояние сохраняется - восстанавливается пользователем с помощью команд FSAVE, FRSTOR для обоих режимов.
Таким образом, если переключаемая задача имеет CR0.TS=0, OS определяет установленный режим и состояние регистров, включает требуемый режим, если CR0.TS=1, это означает, что задача пока не использовала команд FPU/MMX, значит восстанавливать нечего.
Особенности использования режима MMX для многозадачных OS
OS по характеру обработки режима MMX можно разделить на 2 класса:
* Cooperative OS - не сохраняет состояние MMX/FPU, поэтому само приложение должно само заботиться о требуемом состоянии.
* Preemptive OS - операционная система при переключении задач сохраняет текущее состояние.
В первом случае приложение может отследить момент переключения задач и сохраняет требуемые данные, если это требуется.
Во втором случае переключение между задачами может произойти в любое время, поэтому проблему сохранения/восстановления должна решать ОS.
Отладка программ с MMX командами
При отладке программ необходимо помнить, что нет режима эмуляции как для FPU. Если установлен режим эмуляции (CR0.EM=1) и выполняется команда MMX, то формируется исключительная ситуация (int 6). Эта же ситуация возникает, если процессор не поддерживает MMX команд, а в программе встретилась эта команда.
ОСОБЕННОСТИ ПРОГРАММИРОВАНИЯ ДЛЯ MMX
Цель этого раздела - не научить программировать в режиме MMX, а дать основные особенности с целью определения необходимости использования этих возможностей процессора.
СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ
1. Зубков С.М. Assembler. Для Dos, Windows и Unix. - М.: ДМК. 1999. 640 с.
2. Юров В.И. “Ассемблер: Учебный курс”. Питер, 1998
3. Рихтер Джефри. «Профессиональное программирование для Windows 2000, NT», Питер, 2000
4. Качко Е.Г. Программирование на ассемблере. Харьков, ХТУРЭ, 1997
5. Методические указания к практическим занятиям и самостоятельной работе по курсу «Системное программирование», // Качко Е.Г., Белецкий Е.В., Мельникова Р.В. Харьков, ХТУРЭ, 1998.
[1]
Настоятельно рекомендуем использовать этот принцип для программ собственной разработки.
[2]
Это плохой стиль написания программы, но он здесь использован для демонстрации команды условного перехода
[3]
Точнее, автору не известны эти обозначения
[4]
Уточнение адреса для страничного режима в данном курсе не рассматривается
[5]
Не огорчайтесь, если материал этого раздела для Вас не совсем понятен. Постепенно вы научитесь правильно использовать все способы адресации
[6]
Вычисление остатка от деления
[7]
Обычные знаки типа <, > не используются, т.к. зарезирвированы для применения в макросах
[8]
Предполагается, что цифры числа задаются в памяти, начиная с младших цифр, т.е. чем младше цифра, тем меньше ее адрес
[9]
Для решения этой задачи можно использовать формулу суммы арифметической прогрессии. Цикл используется для демонстрации методики применения циклов
[10]
Задаются адреса первых обрабатываемых элементов
[11]
ПОЭ – первый обрабатываемый элемент массива
[12]
Формула может быть доказана методом математической индукции.
[13]
Вычислите, в каком году возникнет «проблема 2000 года!»
[14]
Вызов может быть прямой и косвенный. При прямом вызове задается имя процедуры, при косвенном - адрес с адресом процедуры.
[15]
Для С++ Builder метка может быть записана внутри ассемблерного кода
[16]
Оптимизационные способности современных компиляторов настолько высоки, что не имеет смысла применять регистровые переменные
[17]
Здесь и далее в таблице многоточию соответствуют первые буквы типов параметров в порядке их следования в заголовке
[18]
Функции заданы для однобайтной кодировки символов.
[19]
Все функции WINDOWS API, которые возвращают значения типа BOOL, устанавливают его равным TRUE при благополучном завершении функции и FALSE в случае ошибки
[20]
Это ограничение снято для PENTIUM III.
[21]
Если операционная система не восстанавливает этот бит при переключении задач, рекомендуется выполнять переключение сразу после исполнения участка кода с MMX.