Ассемблер в Delphi

Основное предназначение этой статьи, заполнить пробелы в оригинальной документации по Borland Delphi Developer, при этом весь программный код, а так же теория, полность совместимы со всеми версиями Delphi.

Основное направление статьи, это познакомиться с использованием ассемблера в Object Pascal. Однако, не будем пропускать и те аспекты программирования, которые будут требовать пояснения для конкретных примеров, приведённых в этой статье.

Использование Ассемблера в Борландовком Delphi

Перед тем, как начать, хотелось бы определиться с уровнем знаний, необходимых для нормального усвоения данного материала. Необходимо быть знакомым со встроенными средствами отладки в Delphi. Так же необходимо иметь представление о таких терминах как тип реализации (instantiation), null pointer и распределение памяти. Если в чём-то из вышеупомянутого Вы сомневаетесь, то постарайтесь быть очень внимательны и осторожны при воплощении данного материала на практике. Кроме того, будет обсуждаться только 32-битный код, так что понадобится компилятор не ниже Delphi 2.0.

Зачем использовать Ассемблер? На мой взгляд, Object Pascal, это инструмент, позволяющий генерировать быстрый и эффективный код, однако использование ассемблера в некоторых случаях позволяет решать некоторые задачи более эффективно. За всю работу с Delphi, я пришёл к выводу, что использование низкоуровневого кода необходимо в двух случая.

(1) Обработка большого количества данных. Nb. В данный случай не входит ситуация, когда используется язык запроса данных.

(2) В высокоскоростных подпрограммах работы с дисплеем. Nb. Имеется ввиду использование простых процедур на чистом паскале, но никак не внешних библиотек и DirectX.

В конце статьи мы рассмотрим примеры, которые явно отражают значимость этих критериев, а так же не только когда и где использовать ассемблерные вставки, но и как включать такой код в Delphi.

Что такое Ассемблер? Надеюсь, что Все читатели этой статьи имеют как минимум поверхностное представление о работе процессора. Грубо говоря, это калькулятор с большим объёмом памяти. Память, это не более чем упорядоченная последовательнось двоичных цифр. Каждая такая цифра является байтом. Каждый байт может содержать в себе значение от 0 до 255, а так же имеет свой уникальный адрес, при помощи которого процессор находит нужные значения в памяти. Процессор так же имеет набор регистров (это можно расценить как глобальные переменные). Например eax,ebx,ecx и edx, это универсальные 32-битные регистры. Это значит, что самое большое число, которое мы можем записать в регистр eax, это 2 в степени 32 минус 1, или 4294967295.

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

05/0a/00/00/00
Однако, такая запись абсолютно не читабельна и, как следствие, не пригодна при отладке программы. Так вот Ассемблер, это простое представление машинных команд в более удобном виде. Теперь давайте посмотрим, как будет выглядеть прибавление 10 к eax в ассемблерном представлении:


add eax,10 {a := a + 10}   

А вот так выглядит вычитаение значения ebx из eax


sub eax,ebx {a := a - b }  

Чтобы сохранить значние, можно просто поместить его в другой регистр


mov eax,ecx {a := c }  

или даже лучше, сохранить значение по определённому адресу в памяти


mov [1536],eax {сохраняет значение eax по адресу 1536}  

и конечно же взять его от туда


mov eax,[1536]   

Однако, тут есть важный момент, про который забывать не желательно. Так как регистр 32-битный(4 байта), то его значение будет записано сразу в четыре ячейки памяти 1536, 1537, 1538 и 1539.

А теперь давайте посмотрим, как компилятор преобразует действия с переменными в машинный код. Допустим у нас есть строка


Count := 0;   

Для компилятора это означает, что надо просто запомнить значение. Следовательно, компилятор генерирует код, который сохраняет значение в памяти по определённому адресу и следит, чтобы не произошло никаких накладок, и обзывает этот адрес как 'Count'. Вот как выглядит такой код


mov eax,0   

mov Count,eax
Компилятор не может использовать строку типа


mov Count,0   

из-за того, что как минимум один параметр инструкции должен являться регистром. Если посмотреть на строку


Count := Count + 1;   

то


mov eax,Count   
add eax,1   
mov Count,eax  

Для переменных, тип которых отличается от целого, всё усложняется. Однако, рассмотрим эту тему немного позже, а сейчас предлагаю закрепить теорию практическими примерами.

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


function Sum(X, Y: integer): integer;  
begin   
 Result := X + Y;   
end;  

А вот так будет выглядеть оперция сложения двух целых чисел на ассемблере:


function Sum(X,Y:integer):integer;   
begin   
 asm  
  mov eax,X  
  add eax,Y  
  mov Result,eax  
 end;  
end;  

Этот код прекрасно работает, однако он не даёт нам преимущества в скорости, а так же потерялось восприятие кода. Но не стоит огорчаться, так как те немногие знания, которые Вы почерпнули из этого материала, можно использовать с большей пользой. Допустим, нам необходимо преобразовать явные значения Red,Green, и Blue в цвета типа TColor, подходящие для использования в Delphi. Тип TColor описан как 24-битный True Colour хранящийся в формате целого числа, то есть четыре байта, старший из которых равен нулю, а далее по порядку красный, зелёный, синий.


function GetColour(Red,Green,Blue:integer):TColor;   
begin   
 asm  
{ecx будет содержать значение TColor}  
  mov ecx,0   
{начинаем с красной компоненты}  
  mov eax,Red   
{необходимо убедиться, что красный находится в диапазоне 0<=Red<=255}  
  and eax,255   
{сдвигаем значение красного в правильное положение}  
  shl eax,16   
{выравниваем значение TColor}  
  xor ecx,eax   
{проделываем тоже самое с зелёным}  
  mov eax,Green   
  and eax,255  
  shl eax,8  
  xor ecx,eax  
{и тоже самое с синим}  
  mov eax,Blue   
  and eax,255  
  xor ecx,eax  
  mov Result, ecx  
 end;  
end;  

Заметьте, что я использовал несколько бинарных операций. Эти операции также определены непосредственно в Object Pascal

Автор: Ian Hodger

Добавлено: 01 Августа 2018 07:11:15 Добавил: Андрей Ковальчук

Как узнать, находится ли дискета в дисководе?


type  
  TDriveState(DS_NO_DISK, DS_UNFORMATTED_DISK,   
    DS_EMPTY_DISK, DS_DISK_WITH_FILES);  
  
function DriveState(DrvLetter: Char): TDriveState;  
var  
  Mask: String[6];  
  SearchRec: TSearchRec;  
  oldMode: Cardinal;  
  ReturnCode: Integer;  
begin  
  oldMode: = SetErrorMode(SEM_FAILCRITICALERRORS);  
  Mask:= '?:\*.*';  
  Mask[1] := DrvLetter;  
  {$I-}    ReturnCode := FindFirst(Mask, faAnyfile, SearchRec);  
  FindClose(SearchRec);  
  {$I+}  
  case ReturnCode of  
        0: Result := DS_DISK_WITH_FILES;  
        -18: Result := DS_EMPTY_DISK;  
    { DS_NO_DISK äëÿ DOS, ERROR_NOT_READY äëÿ WinNT, ERROR_PATH_NOT_FOUND äëÿ Win 3.1 }  
    -21, -3: Result := DS_NO_DISK;  
  else  
        Result := DS_UNFORMATTED_DISK;  
  end;  
  SetErrorMode(oldMode);  
end; { DriveState }  

Добавлено: 31 Июля 2018 21:59:54 Добавил: Андрей Ковальчук

Что такое порт. Правила для работы с портами

1.Известно что в компьютере очень много собрано различных устройств , возникает вопрос как операционная система общается с ними. Для этого и служит порт, то есть эта 'дверь' через которую программа (операционная система) может управлять данным устройством (считывать данные, заносить их).Причем я разделяю порты на две категории (это чисто мое разделение) - порты общеизвестные (com lpt) и порты внутренние ,служащие для связи с внутренними устройствами ЭВМ.

2.Некоторые правила для работы с портами Следует иметь в виду что при разработке программ имеющих дело работы с портами следует учитывать следующие факторы :

а) Стараться использовать функции высокого уровня для доступа к портам (в частности winapi) и не прибегать к низкоуровневым операциям чтения/записи портов. Если вы все-таки решили писать низкоуровневое чтение то эти процедуры нужно выносить в отдельную dll или vxd, по следующим причинам - известно, что операционная система windows95/98 а особенно nt являются по своей сути многозадачными системами. То есть если ваша программа обращается конкретно к порту не через динамический вызов функции dll или vxd ( использования механизма dll) а напрямую то это может сказаться на корректной работе системы или даже завалить ее. И даже если в windows95/98 такой подход вполне может работать то в windows nt вследствие его архитектуры не разрешит непосредственное чтение/запись напрямую, а использование механизма dll или vxd позволяет обойти эту проблему.

б)Если вы работаете с каким-то нестандартным портом ввода-вывода
(например портом хранящим состояние кнопок пульта ДУ tvtunera то наверняка в комплекте поставки родного софта найдется dll или vxd для управления этим устройством и отпадет нужда писать код, так я при работе с пультом ДУ tvtunerа использую стандартную dll поставляемую в комплекте, это сразу решило вопросы связанные с управлением портами данного тюнера)Итак, отступление - немного практики:
Маленький пример для работы с портами

(первый пример был уже опубликован в королевстве Дельфи
и представлял собой пример работы с весами ПетрВес)


function portinit : boolean; //инициализация  
var f: thandle;  
ct: tcommtimeouts;  
dcb: tdcb;  
begin  
f := windows.createfile(pchar('com1'), generic_read or  
generic_write,  
file_share_read or file_share_write,  
nil, open_existing,  
file_attribute_normal, 0);  
if (f < 0) or not windows.setupcomm(f, 2048, 2048)or not  
windows.getcommstate(f, dcb) then exit; //init error dcb.baudrate := скоpость;  
dcb.stopbits := стоп-биты;  
dcb.parity := ?етность;  
dcb.bytesize := 8;  
if not windows.setcommstate(f, dcb)  
or not windows.getcommtimeouts(f, ct) then exit; //error  
ct.readtotaltimeoutconstant := 50;  
ct.readintervaltimeout := 50;  
ct.readtotaltimeoutmultiplier := 1;  
ct.writetotaltimeoutmultiplier := 0;  
ct.writetotaltimeoutconstant := 10;  
if not windows.setcommtimeouts(f, ct)  
or not windows.setcommmask(f, ev_ring + ev_rxchar + ev_rxflag + ev_txempty)  
then exit; //error  
result := true;  
end; function donecomm: boolean; //закpыть поpт  
begin  
result := windows.closehandle(f);  
end; function postcomm(var buf; size: word): integer; //пеpеда?а в поpт  
var p: pointer; i: integer;  
begin  
p := @buf;  
result := 0;  
while size > 0 do begin  
if not writefile(f, p^, 1, i, nil) then exit;  
inc(result, i); inc(integer(p)); dec(size);  
application.processmessages;  
end;  
end; function readcomm(var buf; size: word): integer; //пpием из поpта  
var i: integer; ovr: toverlapped;  
begin  
fillchar(buf, size, 0);  
fillchar(ovr, sizeof(ovr), 0); i := 0; result := -1;  
if not windows.readfile(f, buf, size, i, @ovr) then exit;  
result := i;  
end;   

Данный пример был взят мной из многочисленный faq посвященных в delphi в сети ФИДО
Итак,для работы с портами com и lpt нам понадобится знание функций windows api. Вот подробное описание функций, которые нам нужны (в эквиваленте c) для работы с портами.
(извините за возможный местами неточный перевод ,если что поправьте меня если что не так перевел)

createfile handle createfile( lpctstr lpfilename,
// указатель на строку pchar с именем файла 
dword dwdesiredaccess,// режим доступа 
dword dwsharemode,// share mode 
lpsecurity_attributes lpsecurityattributes,
// указатель на атрибуты 
dword dwcreationdistribution,// how to create 
dword dwflagsandattributes,// атрибуты файла 
handle htemplatefile // хендл на temp файл 
); Пример кода на Дельфи 
< вырезано> 
commport := 'com2'; 
hcommfile := createfile(pchar(commport), 
generic_write, 0, nil, 
open_existing, file_attribute_normal, 
0); 

< вырезано>
Параметры
lpfilename
Указатель на строку с нулевым символом в конце (pchar) ,
которая определяет название создаваемого объекта (файл,
канал, почтовый слот, ресурс связи (в данном случае порты),
дисковое устройство, приставка, или каталог)
dwdesiredaccess
Указывает тип доступа к объекту ,принимает значение
generic_read - для чтения
generic_write - для записи (смешивание с generic_read
операцией generic_read and generic_write предостовляет полный доступ )
dwsharemode
Набор разрядных флагов, которые определяют как объект может быть разделен по доступу к нему.
Если dwsharemode - 0, объект не может быть разделен.
Последовательные операции открытия объекта будут терпеть неудачу,
пока маркер(дескриптор) открытого объекта не будет закрыт.
Фактически предоставляется монопольный доступ. Чтобы разделять объект(цель), используйте комбинацию одних или большее количество следующих значений:
file_share_delete (Только для windows nt)
file_share_read
file_share_write
lpsecurityattributes
Указатель на структуру security_attributes, которая определяет
может ли возвращенный дескриптор быть унаследован дочерними процессами.
Если lpsecurityattributes НУЛЕВОЙ, маркер не может быть унаследован.
Используется только в windows nt.
dwcreationdistribution
Определяет поведение функции если объект уже существует и
как он будет открыт в этом случае Принимает одно из следующих значений :
create_new
Создает новый объект (файл) Выдает ошибку если указанный объект (файл) уже существует.
create_always
Создает новый объект (файл) Функция перезаписывает существующий объект (файл)
open_existing
Открывает объект (файл) Выдает ошибку если указанный объект (файл) не существует.(Для более детального смотрите sdk)
open_always
Открывает объект (файл), если он существует. Если объект (файл) не существует,
функция создает его, как будто dwcreationdistribution были create_new.
truncate_existing
Открывает объект (файл). После этого объект (файл) будет
усечен до нулевого размера.Выдает ошибку если указанный объект (файл) не существует.
dwflagsandattributes
Атрибуты объекта (файла) , атрибуты могут комбинироваться
file_attribute_archive
file_attribute_compressed
file_attribute_hidden
file_attribute_normal
file_attribute_offline
file_attribute_readonly
file_attribute_system
file_attribute_temporary
htemplatefile
Определяет дескриптор с generic_read доступом к временному объекту(файлу).
Временный объект(файл)поставляет атрибуты файла и расширенные атрибуты
для создаваемого объекта (файла)
ИСПОЛЬЗУЕТСЯ ТОЛЬКО В windows nt windows 95: Это значение должно быть установлено в nil.
Возвращаемые значения Если функция преуспевает, возвращаемое значение - открытый дескриптор
к указанному объекту(файлу). Если файл не существует - 0.
Если произошли функциональные сбои, возвращаемое значение - invalid_handle_value.
Чтобы получить расширенные данные об ошибках, вызовите getlasterror. Обратите внимание !
Для портов, dwcreationdistribution параметр должен быть open_existing,
и htemplate должен быть nil. Доступ для чтения-записи должен быть определен явно. security_attributes Структура содержит описание защиты для объекта и определяет,
может ли дескриптор быть унаследован дочерними процессами.


typedef struct _security_attributes  
{ dword nlength; 
lpvoid lpsecuritydescriptor; 
bool binherithandle; 
} security_attributes;  

Параметры nlength
Определяет размер, в байтах, этой структуры.
Набор это значение к размеру структуры security_attributes В windows nt
функции которые используют структуру security_attributes, не
lpsecuritydescriptor
Дескриптор указывающий на описатель защиты для объекта,
Если дескриптор ПУСТОЙ объект может быть назначен в наследование дочерними процессами.
binherithandle
Определяет, унаследован ли возвращенный дескриптор, когда новый дескриптор, создан.
Если это значение принимает ИСТИНУ новый дескриптор наследует от головного.
Замечания
Указатель на структуру security_attributes используется
как параметр в большинстве функций работы с окнами в win32 api.
---------------------
Структура dcb Структура dcb определяет установку управления для последовательного порта ввода-вывода
(нам она понадобится для разбора примера с программой управления весами ПетрВес) Примечание : В местах где нельзя дать точный перевод
будет дано определение на английском из msdk и приблизительный его перевод
Описание в эквиваленте c


typedef struct _dcb { // dcb 
dword dcblength; // Размер dcb 
dword baudrate; // Скорость пересылки данных в бодах; 
// текущая скорость в бодах 
dword fbinary: 1; // binary mode, no eof check 
// двоичный режим , не проверять конец 
// данных (по умолчанию значение = 1) 
dword fparity: 1; // Включить проверку четность (по умолчанию 
// значение = 1) 
dword foutxctsflow:1; // cts управление потоком выхода 
dword foutxdsrflow:1; // dsr управление потоком выхода 
dword fdtrcontrol:2; // dtr Тип управления потоком скорости 
// передачи данных 
dword fdsrsensitivity:1; // dsr sensitivity (чувствительность) 
dword ftxcontinueonxoff:1; // xoff continues tx (стоп-сигнал 
// продалжает выполнение) 
dword foutx: 1; // xon/xoff out flow control (СТАРТ- 
// СИГНАЛ / СТОП-СИГНАЛ для управления 
// выходящим потоком (по умолчанию 
// значение = 1) 
dword finx: 1; // xon/xoff in flow control (СТАРТ- 
// СИГНАЛ / СТОП-СИГНАЛ для управления 
// входящим потоком (по умолчанию 
// значение = 1) 
dword ferrorchar: 1; // enable error replacement (включить 
// проверку погрешностей по умолчанию=1) 
dword fnull: 1; // enable null stripping (отвергать 
// пустой поток данных (по умолчанию=1)) 
dword frtscontrol:2; // rts управление потоком данных 
dword fabortonerror:1; // abort reads/writes on error 
// (проверять операции чтения/записи 
// по умолчанию=1) 
dword fdummy2:17; // reserved ЗАРЕЗЕРВИРОВАНО 
word wreserved; // not currently used НЕ ДЛЯ 
// ИСПОЛЬЗОВАНИЯ 
word xonlim; // transmit xon threshold (порог 
// чувствительности старт-сигнала) 
word xofflim; // transmit xoff threshold (порог 
// чувствительности стоп-сигнала) 
byte bytesize; // Бит в байте (обычно 8) 
byte parity; // 0-4=no,odd,even,mark,space 
// (четность байта) 
byte stopbits; // 0,1,2 = 1, 1.5, 2 (стоповые биты) 
char xonchar; // tx and rx xon character (вид 
// старт сигнал в потоке) 
char xoffchar; // tx and rx xoff character (вид 
// стоп сигнал в потоке) 
char errorchar; // error replacement character (какой 
// сигнал погрешности,его вид) 
char eofchar; // end of input character (сигнал 
// окончания потока) 
char evtchar; // received event character РЕЗЕРВ 
word wreserved1; // reserved; do not use НЕ ДЛЯ 
// ИСПОЛЬЗОВАНИЯ 
} dcb;  
  
with mode do  
begin  
  
baudrate := 9600;  
  
bytesize := 8;  
  
parity := noparity;  
  
stopbits := onestopbit; // одино?ный стоп-бит  
  
flags := ev_rxchar + ev_event2;  
  
end; 

Параметры : dcblength
Размер dcb структуры.
baudrate
Определяет скорость в бодах, в которых порт оперирует.
Этот параметр может принимать фактическое значение скорости в бодах,
или один из следующих стандартных индексов скорости в бодах:
cbr_110 cbr_19200
cbr_300 cbr_38400
cbr_600 cbr_56000
cbr_1200cbr_57600
cbr_2400cbr_115200
cbr_4800cbr_128000
cbr_9600cbr_256000
cbr_14400 fbinary
Определяет, допускается ли двоичный (бинарный) способ передачи данных.
win32 api не поддерживает недвоичные (небинарные)
способы передачи данных в потоке порта, так что этот параметр
должен быть всегда ИСТИНЕН.
Попытка использовать ЛОЖЬ в этом параметре не будет работать.
Примечание : Под windows 3.1 небинарный способ передачи допускается,
но для работы данного способа необходимо заполнит параметр
eofchar который будет восприниматься конец данных.
fparity
Определяет, допускается ли проверка четности.
Если этот параметр ИСТИНЕН, проверка четности допускается
foutxctsflow
cts (clear-to-send) управление потоком выхода
foutxdsrflow
dsr (data-set-ready) управление потоком выхода
fdtrcontrol
dtr (data-terminal-ready) управление потоком выхода
Принимает следующие значения :
dtr_control_disable
Отключает линию передачи дынных
dtr_control_enable
Включает линию передачи дынных
dtr_control_handshake
enables dtr handshaking. if handshaking is enabled,
it is an error for the application to adjust the line by using the escapecommfunction function.
Допускает подтверждению связи передачи данных
Если подтверждение связи допускается, это - погрешность для того чтобы регулировать(корректировать)
линию связи, используя функцию escapecommfunction.
fdsrsensitivity
specifies whether the communications driver is sensitive to the state of the dsr signal.
if this member is true, the driver ignores any bytes received, unless the dsr modem input line is high.
Определяет возможна ли по порту двухсторонняя передача в ту и в другую сторону сигнала.
ftxcontinueonxoff
Определяет, останавливается ли передача потока ,
когда входной буфер становится полный, и драйвер передает сигнал xoffchar.
Если этот параметр ИСТИНЕН, передача продолжается после того,
как входной буфер становится в пределах xofflim байтов, и драйвер передает
сигнал xoffchar, чтобы прекратить прием байтов из потока .
Если этот параметр ЛОЖНЫЙ, передача не продолжается до тех пор ,
пока входной буфер не в пределах xonlim байтов,
и пока не получен сигнал xonchar, для возобновления приема .
foutx
Определяет, используется ли управление потоком СТАРТ-СИГНАЛА / СТОП-СИГНАЛА
в течение передачи потока порту. Если этот параметр ИСТИНЕН, передача останавливается,
когда получен сигнал xoffchar и начинается снова, когда получен сигнал xonchar.
finx
specifies whether xon/xoff flow control is used during reception. if this member is true,
the xoffchar character is sent when the input buffer comes
within xofflim bytes of being full, and the xonchar character is sent
when the input buffer comes within xonlim bytes of being empty.
Определяет, используется ли управление потоком СТАРТ-СИГНАЛА / СТОП-СИГНАЛА
в течение приема потока портом. Если этот параметр ИСТИНЕН,сигнал xoffchar посылается ,
когда входной буфер находится в пределах xofflim байтов, а сигнал xonchar посылается
тогда когда входной буфер находится в пределах xonlim байтов или является пустым
ferrorchar
Определяет, заменены ли байты, полученные с ошибками четности особенностью,
указанной параметром errorchar Если этот параметр ИСТИНЕН, и fparity ИСТИНЕН, замена происходит.
fnull
Определяет, отвергнуты ли нулевые(пустые) байты. Если этот параметр ИСТИНЕН,
нулевые(пустые) байты, будут отвергнуты при получении их.
frtscontrol
rts управление потоком " запрос пересылки " .
Если это значение нулевое, то по умолчанию устанавливается rts_control_handshake.
Принимает одно из следующих значений:
rts_control_disable
Отключает строку rts, когда устройство открыто
rts_control_enable
Включает строку rts
rts_control_handshake
enables rts handshaking. the driver raises the rts line
when the " type-ahead" (input)
buffer is less than one-half full and lowers
the rts line when the buffer is more than three-quarters full.
if handshaking is enabled, it is an error for the application
to adjust the line by using the escapecommfunction function.
Допускает rts подтверждение связи. Драйвер управляет потоком пересылки.
rts выравнивается , когда входной буфер - меньше чем половина полного и
понижается, когда буфер - больше 2/3 полного .Если подтверждение связи
допускается, это используется для регулирования передачи данных
escapecommfunction.
rts_control_toggle
specifies that the rts line will be high if bytes are available for transmission.
after all buffered bytes have been sent, the rts line will be low.
Определяет, что буфер будет высокий при подготовке данных для передачи.
После того, как все байты отосланы, буфер rts будет низок.
fabortonerror
Определяет, закончена ли операции чтения/записи, если происходит погрешность.
Если этот параметр ИСТИНЕН, драйвер закрывает все операции
чтения/записи с состоянием погрешности при возникновении оной.
Драйвер не будет принимать никакие дальнейшие действия,
пока не дождется подтверждения погрешности в передоваемых
(принимаемых) данных, вызывая функцию clearcommerror.
fdummy2 br> ЗЗАРЕЗЕРВИРОВАНО microsoft
wreserved
ЗАРЕЗЕРВИРОВАНО microsoft
xonlim
Определяет минимальное число байтов, находящихся во в
xofflim
Определяет максимальное число байтов, находящихся во входном буфере прежде, br> чем будет генерирована подача СТОП-СИГНАЛА. Максимальное число байтов,
ппозволенных во входном буфере вычитается из размеров, в байтах, самого входного буфера.
bytesize
Определяет число битов в байтах, переданных и полученных. br> parity
Определяет схему четности, которую нужно использовать.
Этот параметр может быть одним из следующих значений:
evenparity
markparity
noparity
oddparity
stopbits
Определяет число стоповых битов, которые нужно использовать.
Этот параметр может быть одним из следующих значений:
onestopbit1 stop bit
one5stopbits1.5 stop bits
twostopbits2 stop bits
xonchar
Определяет значение СТАРТ-СИГНАЛА для передачи и приема.
xoffchar
Определяет значение СТОП-СИГНАЛА для передачи и приема.
errorchar
Определяет значение СИГНАЛА ОШИБКИ (генерируемого при ошибке четности) для передачи и приема.
eofchar
Определяет значение сигнала конца данных.
evtchar
Определяет значение сигнала события.
wreserved1
ЗАРЕЗЕРВИРОВАНО microsoft
Дополнение : Когда структура dcb использует 'ручной' выбор конфигурации ,
следующие ограничения используются для bytesize и stopbits параметров :
Число информационных разрядов должно быть от 5 до 8 битов.
Использование 5 информационных разрядов с 2 стоповыми битами -
недопустимая комбинация, как - 6, 7, или 8 информационных разрядов с 1.5 стоповыми битами.

Автор: Дмитрий Кузан

Добавлено: 31 Июля 2018 07:20:56 Добавил: Андрей Ковальчук

Управление мышью

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

Воздействие мышью на интерфейсные элементы приложения разработчик может отслеживать при помощи целой группы методов-обработчиков.

На нажатие кнопки мыши реагирует метод


type  
  
TMouseEvent = procedure (Sender: TObject;  
  
Button: TMouseButton;  
  
Shift: TShiftState; X, Y: Integer) of object;  
  
property OnMouseDown: TMouseEvent;  

В параметре Button передается признак нажатой кнопки:


type TMouseButton = (mbLeft, mbRight, mbMiddle);  

Параметр shift определяет нажатие дополнительной клавиши на клавиатуре:


type TShiftState = set of (ssShift, ssAlt, ssCtrl, ssLeft, ssRight, ssMiddle, ssDouble);  

Параметры х и у возвращают координаты курсора.

На отпускание кнопки мыши реагирует метод:


type  
  
TMouseEvent = procedure (Sender: TObject;  
  
Button: TMouseButton;  
  
Shift: TShiftState; X, Y: Integer) of object;  
  
property OnMouseUp: TMouseEvent;  

Его параметры описаны выше.

При перемещении мыши можно вызывать метод-обработчик


TMouseMoveEvent = procedure(Sender: TObject;  
  
Shift: TShiftState; X, Y: Integer) of object;  
  
property OnMouseMove: TMouseMoveEvent;  

Если у разработчика нет необходимости так подробно отслеживать состояние мыши, можно воспользоваться двумя другими методами:


property OnClick: TNotifyEvent;  
  
property OnDblClick: TNotifyEvent;  

Первый реагирует на щелчок кнопкой, второй — на двойной щелчок.

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


property Cursor: TCursor;  

Для управления дополнительными возможностями мыши для работы в Internet (ScrollMouse) предназначены три метода обработчика, реагирующие на прокрутку:


property OnMouseWheel: TMouseWheelEvent;  

вызывается при прокрутке;


property OnMouseWheelUp: TMouseWheelUpDownEvent;

вызывается при прокрутке вперед;


property OnMouseWheelDown: TMouseWheelUpDownEvent;  

вызывается при прокрутке назад.

В VCL имеется класс TMouse, содержащий свойства мыши, установленной на компьютере. Обращаться к экземпляру класса, который создается автоматически, можно при помощи глобальной переменной Mouse. Свойства класса представлены в табл. 27.1.

В качестве примера обработки управляющих воздействий от мыши рассмотрим пример DemoMouse. Он очень прост. Перемещение мыши с нажатой левой кнопкой обеспечивает выделение прямоугольного фрагмента. Такую функцию вы можете наблюдать в любом графическом редакторе, а исходный код проекта использовать в собственных разработках (листинг 27.2).

Таблица 27.1. Свойства и методы класса mouse

Объявление

Тип

Описание

property Capture: HWND;

Pu

Дескриптор элемента управления, над которым находится мышь

property CursorPos: TPoint;

Pu

Содержит координаты указателя мыши

property Draglmmediate: Boolean;

Ro



При значении True реакция на нажатие выполняется немедленно

property DragThreshold: Integer;

Ro

Задержка реакции на нажатие

property MousePresent: Boolean;

Ro

Определяет наличие мыши

type UINT = LongWord; property RegWheelMessage: UINT;

Ro

Задает сообщение, посылаемое при прокрутке в ScrollMouse

property WheelPresent: Boolean;

Ro



Определяет наличие ScrollMouse

property WheelScrollLines : Integer;

Ro

Задает число прокручиваемых линий

Листинг 27.2. Модуль главной формы проекта DemoMouse


unit Main;  
  
interface  
  
uses  
  
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,  
  
ExtCtrls, ComCtrls;  
  
type  
  
TMainForm = class(TForm) ColorDlg: TColorDialog;  
  
StatusBar: TStatusBar; Timer: TTimer;  
  
procedure FormMouseDown(Sender: TObject;  
  
Button: TMouseButton;  
  
Shift: TShiftState; X, Y: Integer);  
  
procedure FormMouseUp(Sender: TObject;  
  
Button: TMouseButton;  
  
Shift: TShiftState; X, Y: Integer);  
  
procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);  
  
procedure TimerTimer(Sender: TObject);  
  
private  
  
MouseRect: TRect;  
  
IsDown: Boolean;  
  
RectColor: TColor;  
  
public  
  
{ Public declarations }  
  
end;  
  
var  
  
MainForm: TMainForm;  
  
implementation {$R *.DFM}  
  
procedure TMainForm.FormMouseDown(Sender: TObject; Button: TMouseButton;  
  
Shift: TShiftState; X, Y: Integer);  
  
begin  
  
if Button = mbLeft then with MouseRect do  
  
begin  
  
IsDown := True; Left := X; Top := Y; Right := X; Bottom := Y;  
  
Canvas.Pen.Color := RectColor;  
  
end;  
  
if (Button = mbRight) and ColorDlg.Execute then RectColor := ColorDlg.Color;  
  
end;  
  
procedure TMainForm.FormMouseUp(Sender: TObject;  
  
Button: TMouseButton;  
  
Shift: TShiftState; X, Y: Integer);  
  
begin  
  
IsDown := False;  
  
Canvas.Pen.Color := Color;  
  
with MouseRect do  
  
Canvas.Polyline([Point(Left, Top), Point(Right, Top), Point(Right,  
  
Bottom), Point(Left, Bottom), Point(Left, Top)]);  
  
with StatusBar do  
  
begin  
  
Panels[4].Text := ''; Panels [5] .Text := ";  
  
end;  
  
end;  
  
procedure TMainForm.FonnMouseMove(Sender: TObject; Shift: TShiftState; X,  
  
Y: Integer);  
  
begin  
  
with StatusBar do  
  
begin  
  
Panels[2].Text := 'X: ' + IntToStr(X);  
  
Panels[3].Text := 'Y: ' + IntToStr(Y);  
  
end;  
  
if Not IsDown then Exit; Canvas.Pen.Color := Color; with mouserect do  
  
begin  
  
Canvas.Polyline([Point(Left, Top), Point(Right, Top),  
  
Point(Right, Bottom), Point(Left, Bottom), Point(Left, Top)]);  
  
Right := X;  
  
Bottom := Y;  
  
Canvas.Pen.Color := RectColor;  
  
Canvas.Polyline([Point(Left, Top), Point(Right, Top),  
  
Point(Right, Bottom), Point(Left, Bottom), Point(Left, Top)]);  
  
end;  
  
with StatusBar do begin  
  
Panels [4] .Text := 'IHwpMHa: ' + IntToStr(Abs(MouseRect.Right - MouseRect.Left));  
  
Panels[5].Text := 'BacoTa: ' + IntToStr(Abs(MouseRect.Bottom - MouseRect.Top));  
  
end; end;  
  
procedure TMainForm.TimerTimer(Sender: TObject);  
  
begin  
  
with StatusBar do  
  
begin  
  
Panels[0].Text := 'flaTa: ' + DateToStr(Now); Panels[1].Text := 'BpeMH: ' + TimeToStr(Now);  
  
end;  
  
end;  
  
end.  

При нажатии левой кнопки мыши в методе-обработчике FormMouseDown включается режим рисования прямоугольника (isDown := True) и задаются его начальные координаты.

При перемещении мыши по форме проекта вызывается метод-обработчик FormMouseMove, в котором координаты курсора и размеры прямоугольника передаются на панель состояния. Если левая кнопка мыши нажата (isDown = True), то осуществляется перерисовка прямоугольника.

При отпускании кнопки мыши в методе FormMouseUp рисование прямоугольника прекращается (isDown := False).

Если была нажата правая кнопка мыши, то метод-обработчик FormMouseDown обеспечивает отображение диалога выбора цвета, который позволяет сменить цвет линий прямоугольника.

Метод-обработчик TimerTimer обеспечивает отображение на панели состояния текущей даты и времени.

Добавлено: 31 Июля 2018 07:19:27 Добавил: Андрей Ковальчук

Последовательный порт RS-232

Введение.
Автоматизация различных систем с помощью компьютера меня интересовала всегда. Но когда я начал заниматься этой задачей, то столкнулся с множеством проблем. Одна из главных проблем это литература, в которой в доступной для меня форме был бы освещен данный вопрос. Но литературы по данной теме очень мало, особенно в нашем небольшом городке. Взять, например книгу в магазине за 300 руб. в которой уделяется искомому вопросу 2-3 страницы неинтересно, а покупать 2-3 книги дорого. Вы скажете "Сходи в библиотеку и нет проблем", о библиотеке я тоже думал. Но и там проблема с книгами стоит остро. Денег на новые книги у них нет, так как книги по компьютерной тематике в основном печатаются в коммерческих типографиях и поэтому стоят дорого.

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

Данная статья была задумана для объединения в себя всю ту информацию, которую я почерпнул в ходе своего изыскания в интернете.

История стандарта rs-232.
В 1969 г. Группой ведущих промышленных корпораций США был введен стандарт на соединение оборудования. Ассоциация электронной промышленности США (eia) опубликовала вариант С своего рекомендуемого стандарта (recommended standart - rs) номер 232. Этот стандарт был озаглавлен "Интерфейс между оконечным оборудованием обработки данных и оконечным оборудованием линии с использованием последовательного обмена данными в двоичной форме" и известен просто как стандарт rs-232c. МККТТ ввел свой собственный вариант этого стандарта в виде стандартов v.24 и v.28.

Министерство обороны США выпустило практически идентичный стандарт mil-std-188c.

Хотя стандарт rs-232c был весьма популярен, определяемый им физический интерфейс далек от совершенства. Система передачи данных (передатчик, приемник, соединительные кабеля), реализованная в соответствии с техническими условиями стандарта rs-232c, должна гарантированно обеспечивать передачу сигнала со скоростями, не превышающими всего лишь 20 Кбит/с . Ассоциация электронной промышленности США ввела рекомендуемые стандарты для систем, работающих при больших скоростях, но стандарт rs-232c продолжает оставаться основной реализации последовательного интерфейса для ibm-совместимых персональных компьютеров.
Модификация d этого стандарта была введена в 1987 г. В ней были определены некоторые дополнительные линии тестирования, а также закреплено то, что многие рассматривали как недостаток стандарта rs-232c.

Самой последней (июль 1991 г.) модификацией стандарта rs-232 является стандарт eia/tia-232e. В модификации Е нет никаких технических изменений, которые могли бы привести к проблемам совместимости с оборудованием, согласованным с предыдущими вариантами этого стандарта.

Проблема.
Под ms-dos приложение управляет всем компьютером. Это развязывало программисту руки. Достижение максимальной скорости работы осуществлялось непосредственным доступом к аппаратным средствам.

Под windows 3.x эта свобода отчасти была ограничена. К примеру вы уже не имели полный доступ к экрану. Проблема объясняется легко: с тех пор, как пользователь мог запускать любое количество приложений, не было никакой гарантии, что приложения не получали одновременно те же самые аппаратные средства.

Другая проблема - вы уже должны были считаться с параллельно запущенными задачами, а не требовать у компьютера в свое распоряжение все ресурсы. win 3.x осуществляет кооперацию параллельных задач, означая, что каждое приложение должно исходить из концепции совместного существования и не монополизировать ресурсы, а пользоваться услугами специализированного диспетчера. Захват cpu на длительное время здесь не приветствуется.

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

Факт, но тенденция отбивания рук от прямого доступа к железу победила на платформе win32 (windows nt и windows 95). Это операционные системы с истинной многозадачностью. Каждый поток (выполняемый модуль) получает определенный квант процессорного времени. Когда лимит процессорного времени исчерпан, или появляется поток с более высоким приоритетом, система прекращает обслуживать первый поток, даже в случае, если он не завершен. Это переключение между потоками может произойти между двумя ассемблерными инструкциями, нет никакой гарантии, что поток сможет завершить определенное количество инструкций, прежде чем у него отнимут процессорное время, к тому же неизвестно как долго ждать следующей порции процессорного времени. Это приводит к проблеме с прямым доступом к аппаратным средствам. Например, типичное чтение из порта формируется из нескольких ассемблерных инструкций:


mov dx, addressport  
mov al, address  
out dx, al  
jmp wait  
wait:  
mov dx, dataport  
in al, dx  

Состояние всех регистров при переключении потоков сохраняется, состояние i/o портов (последовательные порты, порты ввода/вывода) - нет. Так, велика вероятность что другие приложения производят другие операции с i/o портом, в то время как вы "застряли" между инструкциями 'out' и 'in'.

Документированный путь.
Для решения этой проблемы мы должны как-то сообщить всем другим приложениям, что "К настоящему времени myprog использует порт 546, и всем оставаться на своих местах до моего особого распоряжения." В этом случае подошел бы мьютекс. К сожалению, для использования созданного мьютекса все приложения должны знать его имя. Но даже если бы это было возможно, вы легко можете наткнуться на другие заковыристые проблемы. Рассмотрим два приложения - app1 и app2. Оба пытаются выполнить вышеприведенный код. К несчастью, они созданы разными программистами с разным взглядом на технологию доступа, поэтому app1 сначала требует addressportmutex, в то время как app2 требует dataportmutex. И, по печальному совпадению, когда app1 получает addressportmutex, система переключается на app2, которое захватывает dataportmutex и получается праздник смертельного объятия. app2 не может получить адрес порта, т.к. его захватило app1. app1 не может получить данные порта, т.к. это захватило app2. И все чего-то ждут...

Правильное решение - создание драйвера устройства, которой единолично владеет портами/памятью. Доступ к аппаратным средствам осуществляется посредством api. Вот типичный вызов:


getioportdata(addressport, dataport : word) : byte; 

getioportdata сначала создает мьютекс, который защищает от вторжения (возможно все) порты, затем дает доступ к портам и, наконец, уничтожает его перед возвратом в вызвавшему функцию оператору. В случае, когда функцию пытаются вызвать несколько потоков, управление получает только один, остальные в это время ждут.

Создание драйвера устройства дело нелегкое. Он должен быть создать с помощью ассемблера или c и невероятно труден в отладке. Более того, из-за соображений безопасности драйверы устройств для windows 95 (vxd) не совместимы с драйверами для windows nt (vdd, virtual device driver - виртуальный драйвер устройства). Говорят, что в будущих версиях они будут совместимы, и windows nt 6.0 и windows 2000 будут использовать одни и те же драйвера, но пока разработчики вынуждены заниматься созданием двух различных версий.
Для получения более подробной информации рекомендую обратиться к следующим ресурсам:

microsoft windows 95 device driver kit 
microsoft windows nt device driver kit 
microsoft press "systems programming for windows 95" автора walter oney


Также вы можете ознакомиться с библиотекой vireo vtoolsd на предмет написания vxd в c, расположенной по адресу
Не документированный путь.

Вышеуказанная проблема не слишком реальна. Приложение, которое имеет непосредственный доступ к аппаратным средствам, обычно использует некоторые специализированные аппаратные средства. Конфигурация типа той, которая стремиться запустить только одно приложение имеет единственную цель - получить монопольный доступ к этим аппаратным средствам. В этом случае создание драйверов устройств очень нерентабельно. В конце концов, причина хотя бы в том, что это работает под windows, что можно получить свободно (почти) классный gui, а не в том, чтобы 10 приложений работало одновременно.

К счастью, в windows 95 заложена совместимость с windows 3.x. Это означает, что директивное использование i/o портов также возможно, поскольку до сих пор находятся в эксплуатации множество 16-битных программ, которые просто не могут работать по другому. Просто в этом случае при кодировании вам придется спуститься до уровня ассемблера. Автор следующего кода arthur hoornweg (hoornweg@hannover.sgh-net.de ):
//Базовые адреса двух com портов, для справки:

com1 - 3f8h
com2 - 2f8h


function getport(p:word):byte; stdcall;  
begin  
asm  
push edx  
push eax  
mov dx,p  
in al,dx  
mov @result,al  
pop eax  
pop edx  
end;  
end;  
  
procedure setport(p:word;b:byte);stdcall;  
begin  
asm  
push edx  
push eax  
mov dx,p  
mov al,b  
out dx,al  
pop eax  
pop edx  
end;  
end;  

francois piette также предлагает свое решение прямого доступа к портам i/o на страничке .

Как насчет nt?
Но все вышесказанное под windows nt работать не будет. nt более "прочная" операционная система, поэтому если она позволит в любое время кому попало обращаться к любым аппаратным средствам, она не была бы такой устойчивой. Кроме того, nt является кроссплатформенной системой, поэтому доступ к i/o портам может кардинально различаться при работе на различных процессорах.

Но тем не менее даже под nt можно добраться непосредственно до i/o портов, правда только на x86 процессорах. Это не является документированной особенностью, и, вероятно, исчезнет в будущих версиях этой операционной системы.

Я не обладаю достаточно полной информацией по этому вопросу, но интересующая нас статья d. roberts в майском номере журнала dr. dobb's journal за 1996 год так и называется "direct port i/o and windows nt." К сожалению, я так и не нашел времени проверить приведенный там код. Статью и посвященный ей флейм вы можете почитать по адресу.

Также рекомендую ознакомиться с опубликованной в windows developer journal статьей "port i/o under windows." Опубликована karen hazzah в июне 1996 года. Статью и посвященный ей флейм вы можете найти по адресу.

Визуальный компонент comm32.
Вы спросите "Все это хорошо. Но есть ли визуальный компонент сторонних фирм, работающих с com портом?". Да есть. И он называется comm32. На мой взгляд, он один из лучших на сегодняшний день. Чтобы вам было легче с ним разобраться я приведу пример, реализации данного компонента.

Программа называется psion. Она задумывалась для тестирования теплосчетчиков clorius.

В первый edit программы мы вводим сетевой адрес теплосчетчика. По умолчанию он равен 0. С помощью второго мы посылаем команды теплосчетчику. Третий edit служит для вывода информации, которую теплосчетчик посылает нам.
Вот исходный текст программы написанной на delphi5:


type txxxx=array[1..255] of char; //Определяем символьный массив pxxxx=^txxxx;  
  
//Функция отвечающая за подсчет контрольной суммы  
function tform1.checksum(astr: string): char;  
var crc,i: integer; //Вводим свои целочисленные переменные  
begin  
crc:=0;  
for i := 1 to length(astr) do  
crc:=crc+ord(astr[i]);  
crc:=(crc and $3f) + $30;  
result:=chr(crc);  
end;  
  
//Функция сравнивания контрольной суммы с полученными данными  
function tform1.comparechecksum(astr: string; cs: char): boolean;  
begin  
result:=checksum(astr)=cs;  
end;  
  
//Возвращает тело пакета без сетевого адреса и контрольной суммы  
function tform1.getinput: string;  
var l:integer;  
begin  
result:='';  
l:=length(finput);  
if inputstate = 1 then  
begin  
if starttime+3000 < gettickcount then inputstate := 2;  
exit;  
end;  
if l<3 then exit;  
if comparechecksum(copy(finput,1,l-2),copy(finput,l-1,1)[1])=true then  
begin  
inputstate := 0;  
netnumber:=finput[1];  
addredt.text:=netnumber;  
result:=copy(finput,2,l-3);  
end  
else  
inputstate := 3;  
end;  
  
//Данная процедура возникает, когда мы пытаемся послать команду  
//устройству  
procedure tform1.setoutput(const value: string);  
var xxxx:txxxx;  
s:string;  
l,i:integer;  
begin  
s:=netnumber+value;  
s:=s+checksum(s)+#13;  
l:=length(s);  
if l>255 then exit;  
for i:=1 to l do xxxx[i] := s[i];  
inputstate := 1;  
finput:='';  
commportdriver1.senddata(@xxxx,l);  
starttime:=gettickcount;  
end;  
  
//Процедура возникает при запуске программы  
procedure tform1.formcreate(sender: tobject);  
begin  
netnumber:='0';  
commportdriver1.connect;  
end;  
  
//Процедура возникает при выходе из программы  
procedure tform1.formdestroy(sender: tobject);  
begin  
commportdriver1.disconnect;  
end;  
  
//Процедура возникает при ответе устройства  
procedure tform1.commportdriver1receivedata(sender: tobject; dataptr: pointer; datasize: integer);  
var px:pxxxx;  
i:integer;  
begin  
inputstate := 4;  
application.processmessages; finput:='';  
px:=dataptr;  
for i := 1 to datasize do  
begin  
finput:=finput+px^[i];  
end;  
inputstate := 5;  
application.processmessages;  
edit2.text:=input;  
end;  
  
//Процедура возникает при подборе визуального состояния программы  
procedure tform1.setinputstate(const value: integer);  
begin  
finputstate := value;  
case value of  
0: caption:='Данные успешно приняты';  
1: caption:='Ждем ответа';  
2: caption:='Таймаут';  
3: caption:='Пакет принят с ошибкой';  
4: caption:='Принимаем ответ';  
5: caption:='Ответ получен';  
end;  
end;  
  
//Процедура возникает при нажатии клавиши "Отправить"  
procedure tform1.sendbtnclick(sender: tobject);  
begin  
output:=outputedt.text;  
sendbtn.enabled:=false;  
repeat  
edit2.text:=input;  
until inputstate<>1;  
sendbtn.enabled:=true;  
end;  
  
//Процедура возникает при изменении сетевого адреса устройства  
procedure tform1.addredtchange(sender: tobject);  
begin  
netnumber:=addredt.text[1];  
end;   

Если возникнут, какие либо вопросы или вы встретите какие либо технические неувязки в данной статье, вы можете мне написать по e-mail.

Автор статьи: Фофанов Дмитрий

Добавлено: 31 Июля 2018 07:17:55 Добавил: Андрей Ковальчук

Играем с CMOS, или Проснись и пой, компьютер мой

Недавно наконец-то сбылась моя давняя мечта: в моем доме завелось разумное детище человеческого разума — компьютер. В порядке жизни семьи произошел качественный скачок. Целыми днями не смолкает музыка собранной мною коллекции из лучшего, что есть у знакомых и друзей. Пересмотрены все лучшие фильмы, только появляющиеся в прокате («хай живе пиратський ринок» — это можете не печатать, я знаю, что это нехорошо, но ничего не могу с собой поделать). Долгие тоскливые вечера превратились в череду яростных напряженных баталий на полях виртуального мира. Жену, ранее относившуюся к идее покупки компьютера скептически и допустившую трату значительной суммы, потакая моей блажи, теперь могут оторвать от экрана монитора только силы материнского инстинкта, вызываемые настойчивыми изъявлениями недовольства нашей маленькой дочери. При этом ее рейтинги почти во всех играх уже обогнали мои (мне еще и на службу ходить надо).

Но у меня лично особый восторг вызывают такие «способности» компьютера, как возможность планирования заданий и управления включением-выключением питания компа, что вкупе с детальным анализом своей деятельности, а также правильной настройкой планировщика и шедулера приближают поведение нашего железного друга к уровню «разумного», пунктуального, ничего и никогда не забывающего, верного помощника-секретаря. Мой duron’чик подсказывает мне каждый час время. По приходу с работы на обед проигрывает пару моих любимых песен и ставит последний фильм, напоминает про незавершенные дела, назначенные встречи и т. д. Для этого я использую всем известные программы Планировщик заданий, входящий в начинку windows 98, и outlook из пакета ms office.

А теперь к сути проблемы. Наше сказочное совместное сосуществование с компом омрачало маленькое неудобство. Как и для большинства горожан, самое тяжелое время дня для меня — утро. И для того чтобы скрасить мучительный процесс перехода из сладких объятий сна в железные тиски реальности, я ставлю на включение минут за 15 до момента, когда надо вставать, парочку композиций любимой музыки (кидаю ярлыки композиций в автозагрузку). Пятнадцати минут как раз хватает на то, чтобы свыкнуться с мыслью, что я — человек с планеты Земля, представитель не самой ее богатой части, и если не отнесу сейчас свое тело на работу, то потеряю какие-то деньги и это кое кого расстроит. Последняя композиция своим хорошим энергичным драйвом придает непослушному телу необходимую скорость для преодоления земного притяжения и отрыва его от постели. Как «попросить» комп проснуться в нужное время? Просто, скажете вы, зайди в setup и измени настройки соответствующей опции. Фу, как грубо. Ведь это просто неудобно — необходимо перезагружать компьютер, чтобы войти в setup, потом выключать компьютер в процессе загрузки или ждать запуска системы и выключать заново. И это всякий раз, когда необходимо изменить какие либо параметры, т. е. практически каждый день. А программ, «умеющих» программировать будильник, к своему сожалению и недоумению, я не нашел. Пришлось напрягать извилину. Вникнув в вопрос, я понял причину отсутствия таких программ. Рассудим логически — время будильника, как и множество других параметров, доступных нам из встроенной программы setup (например, частота разгона процессора или пароль), являются настройками bios’a, bios же пишется под конкретный стандарт конкретного железа. Числовые значения этих параметров хранятся в энергонезависимой (т. е. на своей батарейке) памяти cmos. Естественным выводом из этого будет то, что и структура cmos у таких машин тоже может быть разная, т. е. значения одних и тех же параметров (час включения машины, например) у разных компьютеров могут храниться по разным адресам. Стандарт Плуга и Плэя регулирует правила движения в области, находящейся между bios’ом и дровами ОС, а нужные нам примочки находятся на уровень ниже — под bios’ом. Так, проведя ряд экспериментов над машинами друзей, я пока что выявил закономерность зависимости system bios и, соответственно, структуры cmos от используемого чипсета материнской платы, вид которого в свою очередь диктуется типом процессора — это могут быть продукты amd под слот socket a, intel под socket 370 или другими менее известными и распространенными. Но как мне кажется, в самом общем случае следует ожидать, что у материнок разных производителей, или даже одного производителя, но с разными характеристиками (наличие/отсутствие isa, тип чипсета, наконец, и т. д.) структура cmos будет разной. «Проклятые покемоны», — пробурчите вы и махнете рукой на идею найти такие необходимые нам программы. И правильно сделаете, скажу я вам, мы их напишем сами. Подумайте — даже ведущие программисты шарашки Макрософт, фамилии которых светятся в пасхальных яйцах почему-то таких дорогих софтин, когда-то, как и мы, пачкали пеленки и одно время даже не умели говорить. А значит, они не лучше нас и, что уж точно, мы не хуже их. Просто мы привыкли, что за нас уже кто-то все сделал, надо только порыться в закромах Сети. Выходим из режима поиска и начинаем творить.

Хочу сразу оговорить: все нижеизложенное реализовано мною на языке delfi. Полного листинга программ не предоставляю, т. к. основной целью этой статьи считаю довести саму идею, которая сама по себе несложна, а также основные моменты ее реализации.

Для начала, что такое cmos? С точки зрения программиста, это специальная область энергонезависимой памяти, в которой хранятся значения параметров настроек bios’a, например пароль (читайте цикл статей «bios и его настройки»). Даже в выключенном состоянии комп «помнит» пароль и кучу других настроек, которые вы задали, когда последний раз заходили в setup, или выставленных по умолчанию, если вы даже не подозреваете об их существовании. (держим клавишу delete при старте). Размер cmos — 128 байт, при этом не все адреса обязательно используются.

Для начала нам необходима функция, с помощью которой мы могли бы прочитать значение, хранящееся по заданному адресу cmos. Процедуры извлечения и записи параметров из cmosa я написал в виде ассемблерных вставок. Ваше право — выполнить их как вам угодно, лично я до сих пор не нашел в delphi стандартных процедур доступа к портам.

Функция чтения значения параметра cmos и rts, находящегося по адресу i:


function tform1.readcmos(i:byte):byte;  
var zn:byte;  
begin  
//входные параметры  
//i — адрес ячейки, чтение значения которой производится  
//выходные параметры  
//zn — прочтенное значение  
asm //ассемблерная вставка  
mov al,i //номер ячейки, из которой производится чтение  
out 70h,al //заносим это значние в порт cmos rts  
wait  
xor ax,ax //очистка регистра ax  
in al,71h //вводим в регистр al из порта значение ячейки smoc rts  
wait  
mov zn,al //присваеваем zn значение ячейки  
end;  
result:=zn;  
end;  

Для того чтобы просмотреть все содержимое cmos, создадим цикл по і =0..127, в котором последовательно будем извлекать значение по адресу і и запоминать . Для этого кинем на форму объекты button1 и memo1. В обработчике события onclick объекта button1 зададим цикл, в котором к свойству lines:tstrings (список строк) объекта memo1 будем прибавлять строку, состоящую из адреса и значения по этому адресу в десятичном и шестнадцатеричном виде:


procedure tform1.button1click(sender: tobject);  
begin  
memo1.lines.clear; //удаляет все строки из списка  
for i:=0 to 127 do  
//прибавление к списку строки  
memo1.lines.add(inttostr(i) + ' = ' + inttostr(readcmos(i))+ ' = ' + format('%0x',  
[readcmos(i)]));  
end;  

Нам придется запоминать созданный «образ» cmos. Кинем на форму второй button2 и в обработчике события onclick зададим сохранение списка строк memo1.lines в файле:


procedure tform1.button2click(sender: tobject);  
begin  
//сохранение “образа” cmos в файле ff.ff на диске С  
memo1.lines.savetofile('c:ff.ff');  
end;  

Теперь главное. Как узнать, по какому адресу находится нужный нам параметр? А в данном случае нас интересует день, час и минута будильника. Перезагрузите компьютер, войдите в setup, пункт ..., опцию ...... и выставьте эти значения в ноль (так легче проследить изменения). Загрузитесь. Просмотрите cmos и запомните его в файле (можете сразу обратить внимание на адреса, по которым записаны нули, но это не обязательно то, что мы ищем). Перезагрузитесь снова. Меняете значение параметра, допустим, часа на произвольную величину, например, три. Загружаетесь. Просматриваете cmos и сравниваете с сохраненным. Сравнение реализуем следующим образом: кинем на форму еще один объект memo2 и button3. В обработчике события объекта button3 onclick загрузим из файла (созданного нами при прошлом сохранении «образа» cmos) свойство memo2.lines (список строк). Затем в цикле i = 0..127 будем сравнивать строки memo1.lines (текущие настройки) и memo2.lines (настройки до перезагрузки и изменения). Если строки не совпадают, конкатенируем (присоединим, по-нашему) к соответствующей строке memo2.lines три восклицательных знака: [DELPHI]procedure tform1.button3click(sender: tobject); begin //загрузка “образа” cmos, сохраненного до перезагрузки memo2.lines.loadfromfile('c:ff.ff'); //сравнение строк и пометка несовпадающих; по окончании цикла у нас будут стоять восклицательные знаки напротив адресов и значений, которые изменились for i:=0 to 127 do if memo1.lines.strings[i]<>memo2.lines.strings[i] then memo2.lines.strings[i]:=memo2.lines.strings[i]+ ' !!!'; end;
Теперь внимание! По адресам 0,2.4 находятся значения секунд, минут и часов системного времени (слава Богу, это стандартизировано), поэтому, естественно, значения по этим адресам всегда могут отличаться от сохраненных ранее. Также каждую секунду, прыгает значение по адресу 12. Исключите эти адреса из рассмотрения. Главное — ищите адрес, по которому значение параметра изменилось на три (было 0, стало 3) — это и есть адрес, по которому хранится час будильника. Далее читай внимательно. На три изменилось еще одно значение. Дело в том, что значения параметров всех адресов, начиная с некоторого, суммируются, и эта сумма хранится в двух байтах cmos где то в области последних адресов, которые нам тоже необходимо выяснить. Это так называемая контрольная сумма. После включения питания комп тестирует cmos путем его суммирования и сравнения полученной суммы с контрольной суммой. Поэтому если ты не дочитал статью, а кинулся и сам уже написал процедуру записи значения в cmos и проделал это без соответствующего изменения контрольной суммы, то при следующей перезагрузке комп только жалобно пискнет, выдаст сообщение, что у него что-то с головой, тьфу, с cmos’ом не в порядке и выставит все настройки по умолчанию (не самый лучший вариант). Так что набирайся терпения и читай дальше. Вот таким образом изменения происходят по двум адресам:

…
79 = 3 =3 !!! значение часа
…
123 = 12 = С два байта
124 = 35 = 23 !!! контрольной суммы
…


Учти, что младший байт контрольной суммы хранится по большему адресу (у меня по 124), а старший по меньшему (у меня — по 123). Также учти, что измениться может только младший байт, но может и старший, если младший увеличится больше чем на 256 (ff — максимум содержимого одного байта), и тогда произойдет перенос единицы в старший.

Чтобы избежать случайностей, проделай все это с одним только параметром и неизменными другими, и потом то же самое с другими параметрами. Таким образом ты выяснишь, по каким адресам у тебя находится то, что тебе нужно, в нашем случае час, минута и день будильника. Обрати внимание на поведение контрольной суммы — когда значение по более старшему адресу достигает 256, оно обнуляется, и увеличивается содержимое по более младшему адресу )происходит перенос единицы) — мы это будем учитывать при записи значения в cmos, к чему сейчас и приступим.

Напишем две процедуры — одну только для записи значения по адресу, другую для записи значения по адресу с соответствующим изменением контрольной суммы:


procedure form1.writetocmos(i:byte;zn:byte);  
begin  
//процедура записи значения zn по адресу i  
asm  
mov al,i // Установка адреса cmos  
out 70h,al  
wait  
mov dx,71h  
mov al,zn  
out dx,al //запись знечения в адрес adress  
end;  
end;  

Добавлено: 31 Июля 2018 07:15:33 Добавил: Андрей Ковальчук

FileMapping - один из способов обмена данными между процессами

Как-то при написании монитора клавиатуры (в народе - "клавирного шпиона") столкнулся с проблемой передачи данных между динамически подключаемыми библиотеками (DLL). Надо было где-то хранить хэндл окна и сравнивать его с текущим. Проблема же заключалась в том, что библиотеки при загрузке отображаются в адресное пространство каждого вызвавшего их процесса, т.е. при каждом вызове библиотеки она загружается всякий раз в новое адресное пространство. Вот и получается, что в случае, если два различных процесса загрузят одну и ту же DLL, манипулирование с любыми данными внутри одной DLL никак не скажется на этих же данных внутри другого экземпляра той же самой DLL.

Мне же требовалось, чтобы к хэндлу окна был доступ из любого процесса, загрузившего эту DLL. Одним из способов решения поставленной задачи оказалось использование объекта файлового отображения (FileMapping). Правда, поставленную задачу я решил несколько другим, более элегантным способом, возможно о нем расскажу в другой раз. Однако для "общего случая" обмена данными между процессами использование FileMapping подходит лучше всего. В этой статье использование FileMapping для DLL рассматриваться не будет, т.к. понять "как все работает" будет проще на примере небольшого простенького приложения.

Как все работает

Под "памятью" в Windows подразумевается не только физическая память (ОЗУ), но также память, резервируемая операционный системой на жестком диске. Этот вид памяти называется "виртуальной памятью" и образует так называемый страничный свап-файл или "файл подкачки". По мере необходимости операционная среда обращается к этому файлу, чтобы поместить в него пока ненужные системе данные или наоборот считать в ОЗУ уже потребовавшиеся данные (как все это система делает надо спросить у Билла Гейтса). Но, не вдаваясь в подробности реализации страничных файлов подкачки, мы можем использовать эту особенность Windows в своих интересах. Например, поместить каким-то способом в свап-файл нужные уже НЕ СИСТЕМЕ, А НАМ данные, чтобы затем можно было бы обратиться к этому файлу из любого другого процесса и считать или изменить нужные данные. К счастью, WinAPI предоставляет способ работы со свап-файлом посредством так называемого объекта файлового отображения.

FileMapping - объект файлового отображения

Чтобы создать объект файлового отображения, считать или записать в него необходимые данные, а затем распрощаться с этим объектом мы, соответственно, должны:
- создать объект (CreateFileMapping);
- получить доступ к данным (MapViewOfFile);
- закрыть доступ к данным (UnMapViewOfFile);
- попрощаться с объектом (CloseHandle).
На самом деле функций, работающих с объектом файлового отображения несколько больше, но нам будет достаточно этих.
CreateFileMapping или создание объекта файлового отображения.

Если вспомнить написанное выше, то страничный файл операционной системы используется как расширение памяти. Под расширением понимается процесс подкачки (swap) ОЗУ, вызывающийся системой по мере необходимости. Таким образом, создав объект файлового отображения, связанный со страничным свап-файлом, мы получим в качестве результата выделение глобально доступной памяти. Так как эта память является общедоступной, то любой другой процесс, создавший экземпляр объекта файлового отображения, будет иметь доступ к этим данным.


Function CreateFileMapping(hFile: THandle;  
                           lpFileMappingAttributes: PSecurityAttributes;  
                           flProtect, dwMaximumSizeHigh, dwMaximumSizeLow: DWORD;  
                           lpName: PChar): THandle;

hFile - файловый идентификатор - в результате присвоения этому аргументу значения $FFFFFFFF или определенной в Windows.pas константы MAXDWORD мы свяжем создаваемый объект файлового отображения со страничным свап-файлом.
lpFileMappingAttributes - указатель на запись типа TSecurityAttributes, в нашем случае значение этого аргумента игнорируется - присваиваем ему значение NIL.
flProtect - способ совместного использования создаваемого объекта, в нашем случае разрешим доступ на чтение и запись - присвоим ему значение PAGE_READWRITE.
dwMaximumSizeHigh - старший разряд 64-битного значения размера выделяемого объема памяти для совместного доступа (более 4 Gbt)- нам столько не надо :) - присвоим этому аргументу значение 0.
dwMaximumSizeLow - Спасибо славным жителям Королевства, указавшим на ошибку в описании этого аргумента! В особенности г-ну Uno, который замечательно все объяснил! (см. комментарий к статье) - младший разряд 64-битного значения размера выделяемого объема памяти для совместного доступа - если размер dwMaximumSizeHigh равен 0, то размер этого аргумента должен быть НЕ МЕНЬШЕ РАЗМЕРА ДАННЫХ помещаемых в файл подкачки. При этом надо учитывать то, что размер "страницы" для Intel составляет 4 KBt, т.е. указав этот размер 1 байт мы получим "карман для совместного доступа" :) в 4 KBt, если указать 4001, то в объект файлового отображения можно будет поместить уже до 8Kbt данных и т.д.
lpName - имя объекта файлового отображения.
Результат функции - в случае успешного выполнения функции получим описатель (хэндл) созданного объекта файлового отображения, тип THandle. По этому описателю будем обращаться к нужному объекту файлового отображения. Если в силу каких-либо причин создать объект файлового отображения не удалось, функция вернет в результате 0.

MapViewOfFile или подключение объекта файлового отображения к адресному пространству.

Будучи созданным при помощи функции CreateFileMapping, объект файлового отображения должен отображать связанный с ним файл (в нашем случае страничный свап-файл операционной системы) в адресное пространство процессов. В результате выполнения MapViewOfFile мы получим начальный адрес нужных нам данных.


Function MapViewOfFile(hFileMappingObject: THandle;  
                      dwDesiredAccess: DWORD;  
                      dwFileOffsetHigh, dwFileOffsetLow,  
                      dwNumberOfBytesToMap: DWORD): Pointer;  

hFileMappingObject - описатель (хэндл) созданного выше объекта файлового отображения.
dwDesiredAccess - способ доступа к полученным данным, в нашем случае установим доступ на чтение и запись FILE_MAP_WRITE.
dwFileOffsetHigh, dwFileOffsetLow - 64-битное смещение от начала map-файла, установим эти значения в 0.
dwNumberOfBytesToMap - указывает сколько байт будем из файла считывать, если этот аргумент имеет значение 0, то будет считан весь файл.
Результат функции - в случае успешного выполнения функции получим начальный адрес данных объекта файлового отображения, иначе NIL. Тип Pointer - указатель.

UnMapViewOfFile или прекращение отображения данных.

Для отключения от текущего процесса объекта файлового отображения используется функция UnMapViewOfFile.


Function UnMapViewOfFile(lpBaseAddress: Pointer): Boolean;  

lpBaseAddress - в этот аргумент должно быть помещено значение, возвращаемое функцией MapViewOfFile.
Результат функции - в случае успешного выполнения функции получим TRUE, иначе FALSE.

CloseHandle или закрытие объекта файлового отображения.

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


Function CloseHandle(hFileMapObj:THandle):Boolean;  

hFileMapObj - описатель (хэндл) объекта файлового отображения, полученный в результате выполнения функции CreateFileMapping.
Результат функции - в случае успешного выполнения функции получим TRUE, иначе FALSE.

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

Прототипы функций (кроме CloseHandle) и необходимые константы определены в файле Windows.pas. Этот файл находится в созданной при установке Delphi директории BorlandDelphi_XSourceRtlWin

Однако "чистая теория" не дает полного представления о том, как все может выглядеть на практике. Поэтому рассмотрим небольшой практический пример.

Небольшой практический пример

Поместите на форму TEdit, 2 экземпляра TBitBtn, TTimer и 3 экземпляра TLabel.
В строку ввода будем вводить свои данные в виде текста. Далее, нажав на кнопку OK, передадим данные в объект файлового отображения. Считывать данные из этого объекта будем на событие OnTimer. Описатель объекта файлового отображения поместим в глобальную переменную hFileMapObj типа THandle, указатель на начальный адрес данных объекта поместим в глобальную переменную lpBaseAddress типа PChar (хотя в результате выполнения функции MapViewOfFile возвращается переменная типа Pointer, компилятор на указание типа PChar вместо Pointer не "ругается", т.к. по большому счету PChar - это тоже указатель).


unit Main;  
  
interface  
  
uses  
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, Buttons, ExtCtrls;  
  
type  
TForm1 = class(TForm)  
edVariable: TEdit;  
lbEnterValues: TLabel;  
bbOK: TBitBtn;  
bbExit: TBitBtn;  
lbShowValue: TLabel;  
lbVariable: TLabel;  
Timer1: TTimer;  
procedure FormCreate(Sender: TObject);  
procedure bbExitClick(Sender: TObject);  
procedure FormClose(Sender: TObject; var Action: TCloseAction);  
procedure bbOKClick(Sender: TObject);  
procedure Timer1Timer(Sender: TObject);  
private  
{ Private declarations }  
public  
{ Public declarations }  
end;  
  
var  
Form1: TForm1;  
//глобальные переменные  
hFileMapObj:THandle;//описатель FaleMapping  
lpBaseAddress:PChar;//"указатель" (см. выше) на начальный адрес данных  
implementation  
  
{$R *.DFM}  
  
procedure TForm1.FormCreate(Sender: TObject);  
begin  
//создадим FileMapping с именем MySharedValue  
//и передадим его хэндл в глобальную переменную hFileMapObj  
hFileMapObj:=CreateFileMapping(MAXDWORD,Nil,PAGE_READWRITE,0,4,'MySharedValue');  
If (hFileMapObj=0) Then  
//ошибочка вышла  
ShowMessage('Не могу создать FileMapping!')  
Else  
//подключим FileMapping к адресному пространству  
//и получим начальный адрес данных  
lpBaseAddress:=MapViewOfFile(hFileMapObj,FILE_MAP_WRITE,0,0,0);  
If lpBaseAddress=Nil Then  
//ошибочка вышла  
ShowMessage('Не могу подключить FileMapping!');  
end;  
  
procedure TForm1.bbExitClick(Sender: TObject);  
begin  
//закроем форму  
Close;  
end;  
  
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);  
begin  
//кое-что надо сделать при закрытии формы:  
//отключим FileMapping от адресного пространства  
UnMapViewOfFile(lpBaseAddress);  
//освободим объект FileMapping  
CloseHandle(hFileMapObj);  
//теперь форму можно закрыть  
Action:=caFree;  
end;  
  
procedure TForm1.bbOKClick(Sender: TObject);  
begin  
//поместим в адресное пространство свои данные  
//переменная типа PChar имеет в конце завершающий #0, значит при считывании данных  
//система сама сможет определить, где находится конец нужных данных  
StrPCopy(lpBaseAddress,edVariable.Text);  
end;  
  
procedure TForm1.Timer1Timer(Sender: TObject);  
begin  
//считаем нужные данные, обратившись по начальному адресу  
//данных адресного пространства FileMapping  
lbVariable.Caption:=PChar(lpBaseAddress);  
end;  
  
end.  

Постскриптум

Статья нисколько не претендует на академичность изложения, поскольку сведения были почерпнуты из совершенно различных источников, которые во многом друг другу противоречат и в определениях и в понятиях. Однако, как говорится, "все работает"...

Запустите несколько экземпляров приложения и посмотрите, как все работает.

Добавлено: 30 Июля 2018 19:15:52 Добавил: Андрей Ковальчук

Delphi и системная информация о ресурсах компьютера

Компьютеры - вещь слишком сложная, чтобы работать в принципе. Поэтому то, что они работают хоть как-то, уже чудо.

Иногда Delphi-приложениям может не хватать функциональной полноты стандартной библиотеки компонентов и тогда бывает необходимо обратиться к Microsoft Win32 API (Application Programming Interface - интерфейса взаимодействия прикладной программы с операционной системой). Почти все функции из Microsoft Win32 API описаны в модуле windows.pas (который по умолчанию включается в cекцию uses новых модулей). Cледует заметить, что часть из этих функции ведет себя по разному в зависимости от текущей операционной системы (Windows 95, 98, NT).

Разработаем программу, показывающую нам некоторую системную информацию о компьютере. В частности, хотелось бы получить информацию о версии ОС, ее директориях, свойствах экрана, ресурсах памяти, имени пользователя и компьютера, дате BIOS. Помимо этого, разрешим пользователю изменять настройки клавиатуры, встроенного динамика и хранителя экрана.

Процесс визуального проектирования описывать не будем; рассмотрим лишь страницу «Параметры». Для удобства управления параметрами клавиатуры положим на нее две компоненты TTrackBar. Изменим свойство Name на tbKeyboardDelay и tbKeyboardSpeed. Изменим свойство PageSize на 1. Для tbKeyboardDelay установим Max=3 и для tbKeyboardSpeed. Max=31. Для управления свойствами хранителя экрана используем TCheckBox (свойство Name сменим на cbScreenSaverActive, Caption на &‘Хранитель экрана&’) и TMaskEdit (свойство Name=&’edSSTimeOut&’ и EditMask=&’!999;1;&’). Аналогично добавим TCheckBox (свойство Name=&’cbSpeaker&’, Caption=&’Использование встроенного динамика&’ ).

Рассмотрим текст программы. В список включаемых модулей uses добавим registry. Добавим описание процедур в раздел public описания TfmMain.


type  
TfmMain = class(TForm)  
...  
procedure FormCreate(Sender: TObject);  
procedure Change(Sender: TObject);  
private  
{ Private declarations }  
public  
{ Public declarations }  
KeyboardDelay,  
KeyboardSpeed,  
ScreenSaveTimeOut : integer;  
procedure ParametersInfo;  
procedure ShowSomeInfo;  
procedure BIOSInfo(OS : string);  
procedure HardwareInfo;  
procedure MemoryInfo;  
procedure VideoInfo;  
procedure OSInfo;  
end;  
  
var fmMain: TfmMain;  
  
implementation  
uses Registry;  
{$R *.DFM}  

Сначала получим информацию о компьютере. Используем функцию GetComputerName для получения имени компьютера, функцию GetUserName для получения имени пользователя и функцию GetSystemInfo для получения информации о процессоре (наиболее полно данная функция реализована в Windows NT, где она возвращает и кол-во процессоров и их тип и т.д.).


// Информация о компьютере.  
procedure TfmMain.HardwareInfo;  
var Size : cardinal;  
PRes : PChar;  
BRes : boolean;  
lpSystemInfo : TSystemInfo;  
begin  
// Имя компьютера  
Size := MAX_COMPUTERNAME_LENGTH + 1;  
PRes := StrAlloc(Size);  
BRes := GetComputerName(PRes, Size);  
if BRes then laCompName_.Caption := StrPas(PRes);  
// Имя пользователя  
Size := MAX_COMPUTERNAME_LENGTH + 1;  
PRes := StrAlloc(Size);  
BRes := GetUserName(PRes, Size);  
if BRes then laUserName_.Caption := StrPas(PRes);  
// Процессор  
GetSystemInfo(lpSystemInfo);  
laCPU_.Caption := 'класса x' + IntToStr  
(lpSystemInfo.dwProcessorType);  
end;  

Перейдем к параметрам экрану. Здесь мы будем использовать и Win32 API функции и стандартные объекты VCL. Так для получения разрешения экрана нам понадобится объект TScreen (его свойства Width и Height). Остальные параметры мы получим через контекст драйвера устройства DC используя функцию GetDeviceCaps.


// Информация о видеосистеме.  
procedure TfmMain.VideoInfo;  
var DC : hDC;  
c : string;  
begin  
// Разрешение экрана  
laWidth_.Caption := IntToStr(Screen.Height);  
laHeight_.Caption := IntToStr(Screen.Width);  
// Информация о глубине цвета.  
DC := CreateDC('DISPLAY',nil,nil,nil);  
laBitsPerPixel_.Caption :=   
IntToStr(GetDeviceCaps(DC,BITSPIXEL));  
laPlanes_.Caption :=   
IntToStr(GetDeviceCaps(DC,PLANES));  
case GetDeviceCaps(DC,BITSPIXEL) of  
8 : c := '256 цветов';  
15 : c := 'Hi-Color / 32768 цветов';  
16 : c := 'Hi-Color / 65536 цветов';  
24 : c := 'True-Color / 16 млн цветов';  
32 : c := 'True-Color / 32 бит';  
end;  
laColors_.Caption := c;  
DeleteDC(DC);  
end;  

Также будет интересна информация о памяти. Здесь нам поможет функция GlobalMemoryStatus, возвращающая информацию по объему физической и виртуальной памяти.


// Информация о памяти.  
procedure TfmMain.MemoryInfo;  
var lpMemoryStatus : TMemoryStatus;  
begin  
lpMemoryStatus.dwLength := SizeOf(lpMemoryStatus);  
GlobalMemoryStatus(lpMemoryStatus);  
with lpMemoryStatus do begin  
laFreeMemory.Caption :=   
laFreeMemory.Caption +   
IntToStr(dwMemoryLoad) + '%';  
laRAM_.Caption := Format('%0.0f Мбайт',  
[dwTotalPhys div 1024 / 1024]);  
laFreeRAM_.Caption := Format('%0.3f Мбайт',  
[dwAvailPhys div 1024 / 1024]);  
laPF_.Caption := Format('%0.0f Мбайт',  
[dwTotalPageFile div 1024 / 1024]);  
laPFFree_.Caption := Format('%0.0f Мбайт',  
[dwAvailPageFile div 1024 / 1024]);  
end;  
end;  

Узнаем информацию о ОС. Функция GetWindowsDirectory вернет путь к каталогу, где установлена система, функция GetSystemDirectory - к системному каталогу. Для определения версии ОС воспользуемся функцией GetVersionEx.


// Информация о Windows.  
procedure TfmMain.OSInfo;  
var PRes : PChar;  
Res : word;  
BRes : boolean;  
lpVersionInformation : TOSVersionInfo;  
c : string;  
begin  
// Каталог, где установлена Windows  
PRes := StrAlloc(255);  
Res := GetWindowsDirectory(PRes, 255);  
if Res > 0 then laWinDir_.Caption :=   
StrPas(PRes);  
// Системный каталог Windows  
Res := GetSystemDirectory(PRes, 255);  
if Res > 0 then laSysDir_.Caption :=   
StrPas(PRes);  
// Имя ОС  
lpVersionInformation.dwOSVersionInfoSize :=   
SizeOf(TOSVersionInfo);  
BRes := GetVersionEx(lpVersionInformation);  
if BRes then  
with lpVersionInformation do case dwPlatformId of  
VER_PLATFORM_WIN32_WINDOWS :  
if dwMinorVersion=0 then c := 'Windows 95'   
else c := 'Windows 98';  
VER_PLATFORM_WIN32_NT : c := 'Windows NT';  
VER_PLATFORM_WIN32s : c := 'Win 3.1 with Win32s'  
end;  
laVersion_.Caption := c;  
// Дата создания BIOS-а  
if c='Windows NT' then BIOSInfo('NT') else BIOSInfo('95');  
end;  

В предыдущем отрывке программы внимательный читатель заметил вызов функции BIOSInfo с параметром, характеризующем текущую ОС. Опишем эту функцию. Важно отметить, что способ получения информации о дате BIOS различен. Для NT получим информацию из реестра, а для Windows 95/98 из соответствующего участка памяти. Эти два способа взаимоисключаемы, так как у Windows 95/98 нет соответствующего раздела реестра, а прямой доступ к памяти в NT невозможен.


// Информация о дате создания BIOS-а.  
procedure TfmMain.BIOSInfo(OS : string);  
var p : pointer;  
s : string[255];  
begin  
if OS='NT' then begin with TRegistry.Create do  
try RootKey := HKEY_LOCAL_MACHINE;  
if OpenKeyReadOnly  
('HARDWARE\DESCRIPTION\System')  
then laBIOSDate_.Caption :=   
ReadString('SystemBiosDate')  
finally Free;  
end;  
end  
else try  
s[0] := #8;  
p := Pointer($0FFFF5);  
Move(p^,s[1],8);  
laBIOSDate_.Caption :=   
copy(s,1,2) + '/' + copy(s,4,2) + '/' +copy (s,7,2);  
except laBIOSDate_.Caption := 'XX.XX.XXXX';  
end;  
end;  

Рассмотрим функцию SystemParametersInfo, которая позволяет управлять некоторыми настройками системы. Область применения данной функции для NT и Windows 95/98 различна. Умышленно выберем некоторую общую часть для обеих систем.


// Информация о параметрах  
procedure TfmMain.ParametersInfo;  
var Bl : boolean;  
begin  
// Разрешен ли PC Speaker  
SystemParametersInfo(SPI_GETBEEP,0,@Bl,0);  
cbSpeaker.Checked := Bl;  
// Активен ли хранитель экрана  
SystemParametersInfo  
(SPI_GETSCREENSAVEACTIVE,0,@Bl,0);  
cbScreenSaverActive.Checked := Bl;  
// Интервал вызова хранителя экрана  
SystemParametersInfo  
(SPI_GETSCREENSAVETIMEOUT,0,  
@ScreenSaveTimeOut,0);  
// Настройки клавиатуры  
SystemParametersInfo  
(SPI_GETKEYBOARDDELAY,0,  
@KeyboardDelay,0);  
SystemParametersInfo  
(SPI_GETKEYBOARDSPEED,0,  
@KeyboardSpeed,0);  
end;  
  
// Отображение настроек  
procedure TfmMain.ShowSomeInfo;  
begin  
tbKeyboardDelay.Position := 3 - KeyboardDelay;  
tbKeyboardSpeed.Position := KeyboardSpeed;  
edSStimeOut.EditMask := IntToStr  
(ScreenSaveTimeOut div 60);  
end;  

Также позволим пользователю изменять и сохранять настройки системы по своему вкусу. Здесь также будем использовать функцию SystemParametersInfo. Для компонентов tbKeyboardSpeed, tbKeyboardDelay, cbScreenSaverActive, cbSpeaker, edSSTimeOut в ObjectInspector перейдем на закладку Events и изменим событие OnChange (для tbKeyboardSpeed, tbKeyboardDelay) , OnClick (для cbScreenSaverActive, cbSpeaker) и OnExit для edSSTimeOut на Change. Таким образом, все пять вышеперечисленных компонент после изменений состояний передадут управление нижеприведенной процедуре.


// Сохранение изменений параметров системы  
procedure TfmMain.Change(Sender: TObject);  
var Sen : TComponent;  
begin  
Sen := Sender as TComponent;  
// Вкл/Выкл PC Speaker-а.  
if (Sen.Name='cbSpeaker') and cbSpeaker.Checked  
then SystemParametersInfo  
(SPI_SETBEEP,1,nil,SPIF_UPDATEINIFILE)  
else SystemParametersInfo  
(SPI_SETBEEP,0,nil,SPIF_UPDATEINIFILE);  
// Вкл/Выкл активности хранителя экрана.  
if (Sen.Name='cbScreenSaver') and cbScreenSaverActive.Checked  
then SystemParametersInfo  
(SPI_SETSCREENSAVEACTIVE,1,nil,SPIF_UPDATEINIFILE)  
else SystemParametersInfo  
(SPI_SETSCREENSAVEACTIVE,0,nil,SPIF_UPDATEINIFILE);  
// Изменение значения задержки перед повтором с клавиатуры  
if (Sen.Name='tbKeyboardDelay') then SystemParametersInfo(  
SPI_SETKEYBOARDDELAY,3-tbKeyboardDelay.Position,nil,  
SPIF_SENDWININICHANGE);  
// Изменение значения скорости ввода с клавиатуры  
if (Sen.Name='tbKeyboardSpeed') then SystemParametersInfo(  
SPI_SETKEYBOARDSPEED,tbKeyboardSpeed.Position,nil,  
SPIF_SENDWININICHANGE);  
// Изменение интервала запуска хранителя экрана  
if (Sen.Name='edSSTimeOut') then SystemParametersInfo(  
SPI_SETSCREENSAVETIMEOUT,StrToInt(edSSTimeOut.Text)  
*60,nil,SPIF_UPDATEINIFILE);  
end;  

И ,наконец, вызовем все эти процедуры при создании формы.


// Вызов информационных процедур при создании формы.  
procedure TfmMain.FormCreate(Sender: TObject);  
begin  
HardwareInfo;  
MemoryInfo;  
VideoInfo;  
ParametersInfo;  
ShowSomeInfo;  
OSInfo;  
end;  

Использование Delphi совместно c фунциями Microsoft Win32 API позволит программисту создать более функционально богатые и гибкие приложения.

Добавлено: 30 Июля 2018 19:04:35 Добавил: Андрей Ковальчук

Скорость работы процессора, точный таймер

Начиная с pentium mmx, intel ввели в процессор счетчик тактов на 64 бита (Присутствуэт точно и в К6). Для того чтобы посотреть на его содержание, была введена команда "rdtsc" (подробное описание в интеловской мануале).

Эту возможность можно использовать для реализации сабжа.
Посоку Делфя не вкурсе насчет rdtsc, то пришлось юзать опкод (0f31).
Привожу простенький примерчик юзания, Вы уж извините - немножко кривоват получился,
да и ошибка компалера какая-то вылезла :( (v4 bld5.104 upd 2). Кому интересно, поделитесь своими соображениями по этому поводу. Особенно интерисует работа в режиме когда меняется частота процессора (duty cycle, standby).

Проверялось под еНТями на Пне 2 333.


// (c) 1999   
isvunit unit1;  
interfaceuses windows, messages, sysutils, classes, graphics,  
controls, forms,dialogs, stdctrls, buttons, extctrls;  
type tform1 = class(tform)  
label1: tlabel;  
timer1: ttimer;  
label2: tlabel;  
label3: tlabel;  
button1: tbutton;  
button2: tbutton;  
label4: tlabel;  
procedure timer1timer(sender: tobject);  
procedure formactivate(sender: tobject);  
procedure button1click(sender: tobject);  
procedure button2click(sender: tobject);  
private  
{ private declarations }  
public  
{ public declarations }  
counter:integer;  
//Счетчик срабатывания таймера  
start:int64;  
//Начало роботы  
previous:int64;  
//Предыдущее значение  
pstart,pstop:int64;  
//Для примера выч. времени  
currate:integer;  
//Текущая частота проца  
function getcpuclick:int64;  
function gettime(start,stop:int64):double;  
end;  
var form1: tform1;implementation{$r *.dfm}  
// Функция работает на пнях ММХ или выше а  
// также проверялась на К6  
function tform1.getcpuclick:int64;  
begin  
asm db 0fh,31h  
// Опкод для команды rdtsc mov dword ptr result,eax mov dword ptr result[4],edx  
end;  
// Не смешно :(. Без ?той штуки  
// Компайлер выдает internal error c1079  
result:=result;  
end;  
// Время в секундах между старт и стоп  
function tform1.gettime(start,stop:int64):double;  
begin  
try result:=(stop-start)/currate except result:=0;  
end;  
end;  
// Обработчик таймера считает текущую частоту, выводит ее, а также  
// усредненную частоту, текущий такт с момента старта процессора.  
// При постоянной частоте процессора желательно интервал братьпобольше  
// 1-5с для точного прощета частоты процессора.  
procedure tform1.timer1timer(sender: tobject);  
var i:int64;  
begin  
i:=getcpuclick;  
if counter=0 then start:=i else  
begin  
label2.caption:=format('Частота общая:%2f',[(i-start)/(counter*timer1.interval*1000)]);  
label3.caption:=format('Частота текущая:%2f',[(i-previous)/(timer1.interval*1000)]);  
currate:=round(((i-previous)*1000)/(timer1.interval));  
end;  
label1.cap примера  
procedure tform1.button1click(sender: tobject);  
begin  
pstart:=getcpuclick;  
end;  
// Останавливаем отсчет времени и показуем соко  
// прошло секунд  
procedure tform1.button2click(sender: tobject);  
begin  
pstop:=getcpuclick;  
label4.caption:=format!  
('Время между нажатиями:%gсек',[gettime(pstart,pstop)])  
end;  
end.  

Добавлено: 26 Июля 2018 20:59:28 Добавил: Андрей Ковальчук

HotKeys - горячии клавишы

HotKeys - комбинации клавиш, на которые может реагировать приложение, даже если оно не имеет фокуса или запущено в трее.

Hotkey состоит из клавиши-модификатора (Win, Alt, Control, Shift), и нажатия на любую другую клавишу, которая имеет виртуальный код.

Для того чтобы научить программу обрабатывать горячие клавиши, углубимся в загадочные джунгли API,

но прежде этого создадим обработчик события WM_HOTKEY. Для этого объявим в классе TForm1 следующий метод: private

procedure WM_HotKeyHandler (var Message: TMessage);  
message WM_HOTKEY;  

И определим его вот таким образом:
procedure TForm1.WM_HotKeyHandler (var Message: TMessage);  
  var  
    idHotKey: integer; //идентификатор, но об этом - позже  
    fuModifiers: word; //модификатор MOD_XX  
    uVirtKey: word; //код виртуальной клавиши VK_XX  
begin  
  // параметры сообщения получаем так:  
  idHotkey:= Message.wParam;  
  fuModifiers:= LOWORD(Message.lParam);  
  uVirtKey:= HIWORD(Message.lParam);  
  
  //теперь - небольшая проверочка:  
  if (fuModifiers = MOD_ALT) AND (uVirtKey = VK_F10) then  
    caption:='Alt-F10 нажата';  
  inherited;  
end;  

В этом примере обработчик сообщения WM_HOTKEY проверяет, являются ли полученные параметры сигналом о нажатии комбинации Alt-F10, и в случае положительного ответа в заголовок окна главной формы выводится соответствующая строка.

Теперь обратимся непосредственно к созданию горячей клавиши - в нашем примере это будет все та же Alt-F10. Вначале ее нужно зарегистрировать в системе. Как это делается? При помощи функции:
BOOL RegisterHotKey(HWND hWnd, int id, UINT fsModifiers, UINT vk);  

hWnd - окно, обрабатывающее сообщение WM_HOTKEY,
fsModifiers - модификаторы (MOD_ALT, MOD_CONTROL, MOD_SHIFT, MOD_WIN),
vk - виртуальный код клавиши (см. константы с префиксом VK_).
Параметр id заслуживает отдельного разговора.

Для приложения значение id может лежать в диапазоне 0000h..BFFFh, а для разделяемых динамических библиотек диапазон будет таким: C000h..FFFFh.

Однако во избежание конфликтов между горячими клавишами различных процессов целесообразно использовать значение, возвращаемое функцией GlobalAddAtom, передавая ей в качестве параметра некую null-terminated строку длиной до 255 символов.

Вот как это делается:

Объявим глобальную переменную keyid: integer;

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

Итак, создаем и регистрируем горячую клавишу:
procedure TForm1.Button1Click(Sender: TObject);  
begin  
keyid:=GlobalAddAtom('My Hotkey'); //создаем атом  
RegisterHotKey(handle,// сообщение о HotKey будет получать форма  
keyid, // регистрируем атом как id  
MOD_ALT,// модификатор у нас - клавиша Alt  
VK_F10 // вирт. клавиша - F10  
);  
end;  

А следующий код отменяет зарегистрированную клавишу, и удаляет атом:
procedure TForm1.Button3Click(Sender: TObject);  
begin  
UnregisterHotKey(handle, keyid);  
GlobalDeleteAtom(keyid);  
end;  

Рассмотрим еще один важный аспект работы с клавиатурой - способ отслеживания состояние клавиш Num Lock, Caps Lock, Scroll Lock и Insert.

Понятное дело, что стандартными средствами Delphi тут не обойтись.

Будем снова раскапывать API. Во-первых, зададим переменную Key типа word.

Этой переменной можем присвоить значение одной из констант:
VK_NUMLOCK  
VK_CAPITAL  
VK_SCROLL  
VK_INSERT  

Теперь:
Var state: TKeyboardState;  
begin  
GetKeyboardState(state); //получить состояние клавиши  
if Odd(state[VK_NUMLOCK]) then ; //клавиша "включена"  
//как управлять состоянием клавиши?  
state[key] := state[key] XOR 1; //циклично переключить  
state[key] := state[key] OR 1; //включить  
state[key] := state[key] AND (NOT 1); //выключить  
SetKeyboardState(state); //установим новое значение  
end;  

Программное переключение раскладки клавиатуры - если Microsoft Word это умеет, то почему не попробовать и нам?

ActivateKeyboardLayout(0,HKL_NEXT) - циклично переключает раскладку.

Загрузить русскую можно с помощью кода:
LoadKeyboardLayout('00000419', KLF_ACTIVATE),

а английскую
LoadKeyboardLayout('00000409',KLF_ACTIVATE).

Вот, пожалуй, и все премудрости работы с клавиатурой.

Добавлено: 19 Апреля 2018 07:15:12 Добавил: Андрей Ковальчук