Текст книги "Программирование в Delphi. Трюки и эффекты"
Автор книги: Александр Чиртик
Жанр: Программирование, Компьютеры
сообщить о неприемлемом содержимом
Текущая страница: 14 (всего у книги 24 страниц)
Следующий простой пример (листинг 7.3) демонстрирует способ определения сетевого имени компьютера. Функция ComputerName скрывает «прелести» работы со строковым буфером, который нужно передавать в API-функцию GetComputerName.
Листинг 7.3. Определение сетевого имени компьютера
function ComputerName(): String;
var
buffer: String;
len: Cardinal;
begin
len:= MAX_COMPUTERNAME_LENGTH + 1;
SetLength(buffer, len);
if GetComputerName(PAnsiChar(buffer), len) <> False then
ComputerName:= Copy(buffer, 1, len)
else
ComputerName:= '';
end;
Имя пользователяОпределить имя пользователя, от имени которого запущена программа (а точнее – вызывающий функцию поток), можно с использованием функции, код которой представлен в листинге 7.4.
Листинг 7.4. Определение имени пользователя
function UserName(): String;
var
buffer: String;
len: Cardinal;
begin
len:= 100; //Увы, но константы UNLEN в модуле Windows найти
//не удалось
//Буфера такой длины должно хватить
SetLength(buffer, len);
if GetUserName(PAnsiChar(buffer), len)
<> False then
UserName:= Copy(buffer, 1, len)
else
UserNameee:= '';
end;
Чаще всего приведенная в листинге 7.4 функция определяет пользователя, выполнившего вход в систему, но если приложение запущено от имени другого пользователя (например, User при вошедшем пользователе Admin), то, соответственно, определяется имя пользователя User.
Состояние системы питания компьютераСледующий пример может заинтересовать обладателей компьютеров с резервным источником питания (батарея в ноутбуке или источник бесперебойного питания).
Для определения состояния системы питания компьютера используется API-функция GetSystemPowerStatus. Она заполняет структуру TSystemPowerStatus и в случае успеха возвращает ненулевое значение. Упомянутая структура имеет следующие поля:
TSystemPowerStatus = packed record
ACLineStatus: Byte; //Подключение к сети переменного тока
BatteryFlag: Byte; //Состояние батареи (уровень заряда
//и прочее)
BatteryLifePercent: Byte; //Оставшийся ресурс батареи (в %)
Reserved1: Byte;
BatteryLifeTime: DWORD; //Оставшееся время (в сек.) работы батареи
BatteryFullLifeTime: DWORD; //Полное время (в сек.) работы батареи
end;
Если значения полей BatteryLifePercent, BatteryLifeTime, BatteryFullLifeTime предельно ясны, то извлечение информации из полей ACLineStatus и BatteryFlag можно посмотреть в листинге 7.5.
Листинг 7.5. Определение состояния системы питания
procedure TForm1.LoadPowerStatus();
var
batFlags: String;
status: TSystemPowerStatus;
prof_info: THWProfileInfo;
begin
lvwPowerStatus.Clear;
//Получаем информацию о состоянии питания
ZeroMemory(Addr(status), SizeOf(status));
GetSystemPowerStatus(status);
//Заполняем список информацией о состоянии питания
//..подключение к сети
case status.ACLineStatus of
0: AddParam('Подключение к сети', 'Отключен');
1: AddParam('Подключение к сети', 'Подключен');
else AddParam('Подключение к сети', 'Неизвестно');
end;
//..заряд батареи (битовая маска)
if statu s.BatteryFlag and 1 <> 0 then batFlags:= 'Высокий';
if statu s.BatteryFlag and 2 <> 0 then batFlags:= batFlags + ' Низкий';
if status.BatteryFlag and 4 <> 0 then
batFlags:= batFlags + 'Критический';
if status.BatteryFlag and 8 <> 0 then
batFlags:= batFlags + '(Идет тзарядка)';
if status.BatteryFlag and 128 <> 0 then
batFlags:= batFlags + 'Батарея не установлена ';
if statu s.BatteryFlag = 255 then batFlags:= batFlags + ' Неизвестно ';
AddParam('Заряд батареи', batFlags);
//..численные характеристики батареи
if status.BatteryLifePercent <> 255 then
AddParam('Остаток заряда батареи ',
IntToStr(Integer(status.BatteryLifePercent)))
else
AddParam(' Остаток заряда батареи', 'Неизвестно');
if status.BatteryLifeTime <> Cardinal(–1) then
AddParam('Время работы батареи (остаток, сек.)',
IntToStr(Integer(status.BatteryLifeTime)))
else
AddParam(' Время работы батареи (остаток, сек .)', 'Неизвестно');
if status.BatteryFullLifeTime <> Cardinal(–1) then
AddParam('Полное время работы батареи, сек.',
IntToStr(Integer(status.BatteryFullLifeTime)))
else
AddParam(' Полное время работы батареи, сек .', 'Неизвестно');
end;
В листинге 7.5 для отображения каждого параметра системы питания вызывается процедура AddParam, добавляющая в элемент управления формы название параметра и его значение. Этим элементом управления может быть, например, компонент ListView. Для рассмотренного примера возможный результат работы процедуры LoadPowerStatus показан на рис. 7.2.
Рис. 7.2. Собранная информация о системе питания
По представленным на рисунке данным можно сделать вывод, что программа испытывалась на компьютере снабженным аккумулятором, который в момент выполнения программы подзаряжался от сети.
Напоследок несколько слов о том, когда рассмотренная программа может реально пригодиться. Пригодится она в случае, если какое-либо ваше приложение оперирует большим объемом важных данных, на сохранение которых требуется длительное время и потеря которых может принести большие неприятности. Тогда при обнаружении разрядки батареи приложение может сохранить (а точнее, длительное время сохранять) данные на диск, например, до тех пор, пока питание вновь не будет включено, а заряд батареи не достигнет требуемого значения.
Состояние памяти компьютераПолучение информации о текущем состоянии памяти компьютера также не представляет серьезной задачи. Недаром эту информацию многие приложения, тот же Блокнот, выводят в окне О программе: заполнить форму чем-то надо, а сведения об объеме памяти кажутся достаточно актуальными.
Получить сведения о состоянии памяти компьютера помогает API-функция GlobalMemoryStatus. Эта функция принимает в качестве параметра структуру TMemoryStatus, заполняет ее поля значениями и в случае успешного выполнения задачи возвращает отличное от нуля число. Объявление структуры TMemoryStatus с комментариями ее полей приведено ниже:
TMemoryStatus = record
dwLength thth: DWORD; //Размер структуры (байт)
dwMemoryLoad: DWORD; //Процент загрузки физической памяти
dwTotalPhys: DWORD; //Полный объем физической памяти
dwAvailPhys: DWORD; //Объем свободной оперативной памяти
dwTotalPageFile: DWORD; //Полный объем файла подкачки
dwAvailPageFile: DWORD; //Объем свободного пространства
//в файле подкачки
dwTotalVirtu al: DWORD; //Полный объем виртуальной памяти
dwAvailVirtu al: DWORD; //Объем свободной виртуальной памяти
end;
Два последних поля структуры TMemoryStatus относятся к приложению, вызывающему функцию GlobalMemoryStatus. Их смысл рассмотрен чуть ниже. Пример использования функции GlobalMemoryStatus приведен в листинге 7.6.
Листинг 7.6. Определение состояния памяти
procedure TForm1.LoadMemoryInfo();
var
memStat: TMemoryStatus;
begin
memStat.dwLength:= SizeOf(memStat);
//Получение информации о загрузке памяти
GlobalMemoryStatus(memStat);
//Заполнение полей формы
//..% использования памяти
pbMemUsage.Position:= memStat.dwMemoryLoad;
lblMemUsage.Caption:= IntToStr(memStat.dwMemoryLoad) + '%';
//..использование оперативной памяти
txtMemTotal.Text:= IntToStr(memStat.dwTotalPhys div 1024);
txtMemAvail.Text:= InttoStr(memStat.dwAvailPhys div 1024);
//..использование файла подкачки
txtPageTotal.Text:= IntToStr(memStat.dwTotalPageFile div 1024);
txtPageAvail.Text:= InttoStr(memStat.dwAvailPageFile div 1024);
//..использование виртуальной памяти
txtVirtualTotal.Text:= IntToStr(memStat.dwTotalVirtual div 1024);
txtVirtualAvail.Text:= InttoStr(memStat.dwAvailVirtual div 1024);
end;
Внешний вид формы, элементы управления которой заполняются значениями в листинге 7.6, показан на рис. 7.3.
Рис. 7.3. Программа для определения состояния памяти компьютера
Напоследок рассмотрим (несколько упрощенно) результаты, выводимые в текстовых полях формы, для тех, кто немного не в курсе, как организовано управление памятью в Windows.
Каждому процессу Windows предоставляет адресное пространство (виртуальное) размером немного меньше 2 Гбайт. В отличие от 16-битных предшественниц, в 32-битных Windows адресные пространства различных процессов являются закрытыми: приложение использует память (а точнее, младшие 2 Гбайт адресного пространства) единолично и не может без дополнительных прав манипулировать данными других процессов. Значения в двух последних полях структуры TMemoryStatus (и нижней группе текстовых полей на форме рис. 7.3) как раз и отражают использование приложением представляемого ему адресного пространства.
Механизм предоставления виртуальной памяти является довольно удобной надстройкой, сглаживающей ограниченность аппаратных ресурсов компьютера. Ограниченный объем оперативной памяти компенсируется использованием места на диске (файла подкачки, страничного файла). В этот файл записываются для временного хранения неиспользуемые страницы памяти (блоки данных по несколько Кбайт), давая возможность помещать другие данные, более нужные приложению, в оперативную память.
Теперь вернемся к форме, показанной на рис. 7.3. Группа текстовых полей Оперативная память показывает полный и свободный объем реально имеющейся на компьютере оперативной памяти (за вычетом памяти, используемой для системных нужд). Использование этого вида памяти иллюстрирует индикатор ProgressBar на форме. Назначение правой группы текстовых полей (Файл подкачки) должно быть также понятно.
Из цифр, выведенных в текстовые поля на форме (рис. 7.3), можно также определить, что общий объем памяти, доступной приложениям (всего было запущено 30 процессов), на испытуемом компьютере составлял около 1,26 Гбайт. Если представить, что память распределялась между всеми процессами одинаково, то получается примерно 43 Мбайт на каждого, не считая памяти, резервируемой для самой Windows.
Системное время
Этот раздел посвящен отнюдь не простому получению текущего времени или даты (благо эти функции можно найти и в библиотеке Borland). Здесь будет рассмотрена несколько более интересная тема – использование системных средств измерения малых промежутков времени.
Все рассмотренные далее способы измерения времени основаны на подсчете количества срабатываний таймера. Для сохранения показаний таймера система поддерживает соответствующие счетчики. Для определения временного интервала регистрируются показания счетчика в начале и конце измеряемого промежутка времени. Затем вычисляется разность между этими показаниями и, если период таймера не соответствует требуемой единице измерения (например, мс), полученная разность делится на частоту таймера.
Определение времени работы операционной системыВ момент запуска Windows запускается и специальный счетчик, отсчитывающий количество миллисекунд, прошедших с момента запуска системы.
Этот системный счетчик можно использовать как для определения времени работы системы, так и для измерения временных интервалов. Для доступа к нему можно использовать API-функцию GetTickCount. Эта функция не имеет параметров и возвращает целочисленное 32-битное значение.
Приведенная в листинге 7.7 функция GetSystemWorkTime демонстрирует использование описываемого счетчика для определения времени работы системы в часах, минутах и секундах.
Листинг 7.7. Определение времени работы системы
function GetSystemWorkTime(): String;
var
ticks: DWORD;
hh, mm, ss: Cardinal;
begin
//Получаем количество миллисекунд с момента старта системы
ticks:= GetTickCount();
//Переводим в секунды
ticks:= ticks div 1000;
//Получаем количество часов, минут, секунд
hh:= ticks div 3600;
Dec(ticks, hh * 3600);
mm:= ticks div 60;
Dec(ticks, mm * 60);
ss:= ticks;
GetSystemWorkTime:= IntToStr(hh) + ':'+
IntToStr(mm) + ':'+ IntToStr(ss);
end;
Из-за относительно малой разрядности значение счетчика обнуляется приблизительно каждые 49,7 суток, что следует учитывать, если планируется измерять длительные интервалы или если измерение времени начинается после длительной работы системы (например, начало измерения выпадает на 50-е сутки за час до обнуления счетчика).
Аппаратный таймерСледующий рассматриваемый способ измерения времени основан на использовании таймера высокого разрешения (высокочастотного). Временной промежуток между срабатываниями этого таймера может быть намного меньше 1 мс, что позволяет производить достаточно точные измерения. Для сохранения количества срабатываний аппаратного таймера используется 64-битный счетчик.
Пример получения значения счетчика аппаратного таймера приведен в листинге 7.8. Частота, возвращаемая функцией hwTimerGetCounter, измеряется в герцах (с-1), то есть означает количество срабатываний таймера в 1 с.
Листинг 7.8. Получение значения счетчика аппаратного таймера
function hwTimerGetCounter(): Int64;
var
freq: Int64;
begin
if QueryPerformanceCounter(freq) <> False then
hwTimerGetCounter:= freq
else
hwTimerGetCount er:= 0; //Ошибка
end;
Чтобы перевести количество срабатываний аппаратного таймера в привычные единицы измерения, нужно определить его частоту. Помочь в этом может функция, приведенная в листинге 7.9.
Листинг 7.9. Определение частоты аппаратного таймера
function hwTimerGetFreq(): Int64;
var
freq: Int64;
begin
if QueryPerformanceFrequency(freq) <> False then
hwTimerGetFreq:= freq
else
hwTimerGetFreq:= 0; //Ошибка
end;
Пусть известна разность между значениями счетчика в начале и конце измерения. Перевести ее в секунды можно следующим образом:
time:= counter div hwTimerGetFreq();
Пример, точнее, результат определения характеристик аппаратного таймера показан на рис. 7.4.
Рис. 7.4. Характеристики аппаратного таймера
Заполнение приведенных на рис. 7.4 текстовых полей выполняется очень просто, а поэтому код, реализующий это, в тексте книги не приводится. При желании вы сможете найти его на диске в папке с названием раздела.
Мультимедиа-таймерРассмотрим еще один способ измерения, основанный на использовании так называемого мультимедиа-таймера. Его использование удобно тем, что появляется возможность указывать точность измерения. Группа API-функций, предназначенная для работы с мультимедиа-таймером, позволяет не только измерять временные интервалы, но и создавать программные таймеры (см. компонент Timer), срабатывающие через гораздо меньшие промежутки времени.
Для получения текущего значения счетчика мультимедийного таймера можно воспользоваться функцией timeGetTime. В бщем случае она возвращает значения, аналогичные значениям, возвращаемым функцией GetTickCount. Счетчик является также 32-битным, обнуляемым приблизительно каждые 49,7 суток. Прототип функции timeGetTime следующий:
function timeGetTime: DWORD; stdcall;
Пример использования этой функции приведен в листинге 7.12.
Теперь несколько слов о том, как получить для рассматриваемого таймера значения минимальной и максимальной точности. Для получения этих данных можно использовать функцию timeGetDevCaps. Эта функция принимает в качестве параметра структуру TTimeCaps и заполняет два ее поля соответствующими значениями. В листинге 7.10 приведен пример возможной реализации функций определения характеристик мультимедийного таймера.
Листинг 7.10. Определение характеристик мультимедиа-таймера
//Получение максимального периода таймера (мс)
function timeGetMaxPeriod(): Cardinal;
var
time: TTimeCaps;
begin
timeGetDevCaps(Addr(time), SizeOf(time));
timeGetMaxPeriod:= time.wPeriodMax;
end;
//Получение минимального периода таймера (мс)
function timeGetMinPeriod(): DWORD;
var
time: TTimeCaps;
begin
timeGetDevCaps(Addr(time), SizeOf(time));
timeGetMinPeriod:= time.wPeriodMin;
end;
Итак, теперь вам известно, как получать параметры таймера, но выше было сказано, что его точность может быть изменена, то есть регулируется. Сделать это можно с помощью функций timeBeginPeriod и timeEndPeriod.
• Первая функция (timeBeginPeriod) вызывается для установления минимальной устраивающей приложение точности таймера. Функция timeBeginPeriod принимает значение требуемой точности таймера в миллисекундах и возвращает TIMERR_NOERROR в случае успеха либо TIMERR_NOCANDO, если требуемая точность не может быть обеспечена.
• Вторая функция (timeEndPeriod) восстанавливает точность таймера, установленную до вызова функции timeBeginPeriod. В функцию timeEndPeriod должно передаваться то же значение, что и в функцию timeBeginPeriod.
В листинге 7.11 продемонстрирован пример использования функций timeBeginPeriod и timeEndPeriod (реализованы функции-оболочки). При использовании обеих функций, представленных в листинге 7.11, нужно помнить, что после вызова функции timeSetTimerPeriod и проведения измерения обязательно должна быть вызвана функция timeRestoreTimerPeriod. Функция timeSetTimerPeriod сохраняет значение установленной точности таймера в глобальной переменной lastPeriod, чтобы можно было не заботиться о сохранении этого значения в коде, использующем таймер.
Листинг 7.11. Функции изменения точности таймера
Var lastPeriod: Cardinal;
//Установка периода таймера (мс) перед началом измерения
function timeSetTimerPeriod(period: Cardinal): Boolean;
begin
if timeBeginPeriod(period) = TIMERR_NOERROR then
begin
//Сохраним значение для восстановления состояния таймера
lastPeriod:= period;
timeSetTimerPeriod:= True;
end
else
//Неудача
timeSetTimerPeriod:= False;
end;
//Восстановление периода таймера (обязательно)
function timeRestoreTimerPeriod(): Boolean;
begin
if timeEndPeriod(lastPeriod) = TIMERR_NOERROR then
timeRestoreTimerPeriod:= True
else
timeRestoreTimerPeriod:= False;
end;
Теперь, после долгого рассмотрения особенностей настройки мультимедиа-таймера, пора привести пример его использования для измерения времени выполнения простейшего отрезка программы (листинг 7.12).
Листинг 7.12. Измерение времени выполнения отрезка программы
procedure TForm1.cmbTimeGoClick(Sender: TObject);
var
summ, arg, maxVal: Int64;
startTime, endTime: Cardinal;
begin
txt TimeResult.Text:= 'Измерение...';
Refresh;
maxVal:= StrToInt(txtTimeMaxVal.Text);
//Устанавливаем маскимальную точность таймера
timeSetTimerPeriod(timeGetMinPeriod());
startTime:= ti meGetTime(); //Начальный момент времени
//Суммируем 64-битные числа
//(как раз и измеряем время его выполнения)
summ:= 0;
arg:= 1;
while (arg <= maxVal) do
begin
Inc(summ, arg);
Inc(arg);
end;
endTime:= ti meGetTime(); //Конечный момент времени
//Восстанавливаем период таймера
timeRestoreTimerPeriod();
//Время выполнения операций (мс)
txtTimeResult.Text:= IntToStr(endTime – startTime);
end;
Создание программного таймера высокой точностиВ самом начале рассмотрения возможностей мультимедиа-таймера было сказано, что в его API заложена возможность создания программных таймеров. Это действительно так. Причем максимальная точность такого таймера может получиться достаточно высокой: на современных компьютерах создание программного таймера с периодом срабатывания 1 мс – не проблема. Правда, использовать максимальную частоту таймера вряд ли стоит: слишком велика вероятность ошибки, по меньшей мере, на 1 мс.
Стоит уяснить, что же за программный таймер должен быть создан и чем он отличается от компонента Timer, помещаемого на форму. Отличается создаваемый таймер, помимо более высокой точности, тем, что его не нужно привязывать к окну (форме): при срабатывании стандартного компонента Timer окну, за которым он закреплен, посылается сообщение WM_TIMER. Программный же таймер работает по-другому, что удобнее рассмотреть на примере.
timerID:= timeSetEvent
(
StrToInt(txt TimeInt erval.Text), //Интервал между срабатываниями таймера
ti meGetMin Period(), //Точность таймера
TimerProc, //Адрес процедуры, вызываемой при каждом срабатывании таймера
0, //Параметр, передаваемый в процедуру обратного вызова
TIME_CALLBACK_FUNCTION or TIME_PERIODIC //Тип таймера
);
В приведенном выше отрывке программы с помощью функции timeSetEvent происходит регистрация и запоминание адреса процедуры TimerProc, вызываемой периодически при срабатываниях таймера. При успешном создании таймера функция timeSetEvent возвращает ненулевое значение – идентификатор созданного таймера. Это значение может использоваться в дальнейшем для определения сработавшего таймера. Значение, возвращенное функцией timeSetEvent, также необходимо при удалении таймера:
timeKillEvent(timerID);
Функция timeKillEvent возвращает целочисленное значение:
• TIMERR_NOERROR – если вызов завершился успешно;
• MM SYSE RR_ INVAL PARAM – если таймера, заданного параметром функции, не существует.
Теперь о процедуре, адрес которой передается в функцию timeSetEvent. В данном примере она выглядит следующим образом (листинг 7.13).
Листинг 7.13. Процедура, вызываемая при срабатывании таймера
procedure TimerProc(uTimerID, uMessage: UINT; dwUser, dw1, dw2: DWORD) stdcall;
begin
//Добавляем текущее значение времени в список (чтобы была
//видна разница между моментами вызова этой процедуры)
Form1.lstTimes.Items.Add(IntToStr(timeGetTime()));
end;
Естественно, действия, выполняемые процедурой TimerProc, могут быть самыми различными. В рассматриваемом случае происходит заполнение списка (List) значениями счетчика срабатываний таймера на момент вызова процедуры (рис. 7.5).
Рис. 7.5. Результат работы таймера
В завершение вновь обратимся к функции timeSetEvent. Чуть ниже будут кратко перечислены предоставляемые ею возможности, которые не использовались в приведенном выше примере.
Как вы могли заметить, последний параметр функции timeSetEvent является битовой маской. Флаги этой маски определяют два аспекта поведения таймера: количество срабатываний таймера и тип действия, которое требуется выполнять при срабатывании таймера.
Количество срабатываний таймера определяется двумя значениями.
• TIME_ONESHOT – таймер срабатывает один раз. Для таких таймеров вызывать функцию timeKillEvent после срабатывания не нужно.
• TIME_PERIODIC – таймер срабатывает периодически через заданные промежутки времени.
Тип действия, выполняемого таймером, задается с помощью следующих констант:
• TIME_CALLBACK_FUNCTION – при срабатывании таймера вызывается процедура, адрес которой был передан третьим параметром;
• TIME_CALLBACK_EVENT_SET – вызывает функцию SetEvent для объекта синхронизации «событие», дескриптор которого передан третьим параметром;
• TIME_CALLBACK_EVENT_PULSE – вызывает функцию PulseEvent для объекта синхронизации «событие», дескриптор которого передан третьим параметром.
К сожалению, использование объектов синхронизации хоть и является темой для интересного разговора, но все же выходит далеко за рамки этой главы. Потому эта тема больше рассматриваться не будет.
Правообладателям!
Это произведение, предположительно, находится в статусе 'public domain'. Если это не так и размещение материала нарушает чьи-либо права, то сообщите нам об этом.