Текст книги "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
UserName := '';
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, BatteryLif eTime, BatteryFull-Lif eTime предельно ясны, то извлечение информации из полей 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 status.BatteryFlag and 1 <> 0 then batFlags := 'Высокий ';
if status.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 status.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: DWORD; //Размер структуры (байт)
dwMemoryLoad: DWORD; //Процент загрузки физической памяти
dwTotalPhys: DWORD; //Полный объем физической памяти
dwAvailPhys: DWORD; //Объем свободной оперативной памяти
dwTotalPageFile: DWORD; //Полный объем файла подкачки
dwAvailPageFile: DWORD; //Объем свободного пространства
//в файле подкачки
dwTotalVirtual: DWORD; //Полный объем виртуальной памяти
dwAvailVirtual: 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 Гбайт адресного пространства) единолично и не может без дополнительных усилий манипулировать данными других процессов. Значения в двух последних полях CTpyKTypbiTMemoryStatus (и нижняя группа текстовых полей на форме рис. 7.3) как раз и показывают использование приложением предоставляемого ему адресного пространства.
Механизм виртуальной памяти является довольно удобной надстройкой, скрывающей ограниченность аппаратных ресурсов компьютера. Ограниченный объем оперативной памяти компенсируется использованием места на диске (файла подкачки, страничного файла). В этот файл записываются для временного хранения неиспользуемые страницы памяти (блоки данных по несколько Кбайт), давая возможность помещать другие данные, нужные приложению, в оперативную память.
Теперь вернемся к форме, показанной на рис. 7.3. Группа текстовых полей Оперативная память показывает полный и свободный объем реально установленной на компьютере оперативной памяти (за вычетом памяти, используемой для системных нужд). Использование этого вида памяти иллюстрирует индикаторРгодгезБВаг на форме. Назначение правой группы текстовых полей (Файл подкачки) должно быть также очевидным.
Из цифр, выведенных в текстовые поля на форме (рис. 7.3), можно также определить, что общий объем памяти, доступной приложениям (всего было запущено 30 процессов), на испытуемом компьютере составлял около 1,26 Гбайт. Если представить, что память использовалась всеми процессами одинаково, то получается примерно 43 Мбайт на каждого, не считая памяти, резервируемой для самой ОС Windows.
7.2. Системное время
Этот раздел посвящен отнюдь не простому получению текущего времени или даты (благо эти функции можно найти и в библиотеке 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
hwTimerGetCounter := 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 принимает значение требуемой точности таймера в миллисекундах, возвращает TIMERR_NOERROR в случае успеха либо Т IMERR_NOCANDO, если требуемая точность не может быть обеспечена.
• Вторая функция восстанавливает точность таймера такой, какой она была до вызова функции 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
txtTimeResult.Text := 'Измерение…';
Refresh;
maxVal := StrToInt(txtTimeMaxVal.Text);
//Устанавливаем маскимальную точность таймера
timeSetTimerPeriod(timeGetMinPeriod());
startTime := timeGetTime(); //Начальный момент времени
//Суммируем 64-битные числа
//(как раз и измеряем время его выполнения)
summ := 0;
arg := 1;
while (arg <= maxVal) do
begin
Inc(summ, arg);
Inc(arg);
end;
endTime := timeGetTime(); //Конечный момент времени
//Восстанавливаем период таймера
timeRestoreTimerPeriod();
//Время выполнения операций (мс)
txtTimeResult.Text := IntToStr(endTime – startTime);
end;
В самом начале рассмотрения возможностей мультимедиа-таймера было сказано, что в его API заложена возможность создания программных таймеров. Это действительно так. Причем максимальная точность такого таймера может получиться довольно большой: на современных компьютерах создание программного таймера с периодом срабатывания 1 мс – не проблема. Правда, использовать максимальную частоту таймера вряд ли стоит: слишком велика вероятность ошибки как минимум на 1 мс.
Теперь уясним, что же за программный таймер мы создаем и чем он отличается от компонента Timer, помещаемого на форму. А отличается наш таймер, кроме высокой точности, тем, что его не нужно привязывать к окну (форме): при срабатывании стандартного компонента Timer окну, за которым он закреплен, посылается сообщение WM_TIMER. Создаваемый же нами таймер работает по-другому, что удобнее рассмотреть на примере.
timerID := timeSetEvent
(
StrToInt(txtTimeInterval.Text), //Интервал между
//срабатываниями таймера
timeGetMinPeriod(), //Точность таймера
TimerProc, //Адрес процедуры, вызываемой при каждом
//срабатывании таймера
0, //Параметр, передаваемый в процедуру
//обратного вызова
TIME_CALLBACK_FUNCTION or TIME_PERIODIC //Тип таймера
);
В приведенном выше отрывке программы с помощью функции timeSetEvent происходит регистрация и запоминание адреса процедуры TimerProc, вызываемой периодически при срабатываниях таймера. При успешном создании таймера функция timeSetEvent возвращает ненулевое значение – идентификатор созданного таймера. Оно может использоваться в дальнейшем для определения, какой именно таймер сработал. Значение, возвращенное функцией timeSetEvent, также необходимо при удалении таймера:
timeKillEvent(timerlD);
Функция timeKillEvent возвращает целочисленное значение:
• TIMERR_NOERROR – если ее вызов завершился успешно;
• MMSYSERR_INVALPARAM – если таймера, заданного параметром функции, не существует.
Теперь о процедуре, адрес которой мы передаем в функцию 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'. Если это не так и размещение материала нарушает чьи-либо права, то сообщите нам об этом.