Эта статья впервые опубликована в ClarionOnline ( http://www.clariononline.com ) in volume 2 issue 2 (Сентябрь 1998)


Sub-class процедуры

Каждый раз, когда вы пытаетесь выключить компьютер когда выполнялняется Clarion-программа, выводится  сообщение "Application still active. Quit the application before quitting Windows"? Можете быть уверены, что раз это случается с вами, то, вероятно, это случается и с вашими клиентами.

Однако, решение существует, и оно называется sub-classing. Sub-classing влечет за собой написание процедуры, которая получает события (events) до цикла Accept() Clarion. Если вы работаете с некоторым особым программным обеспечением, аппаратурой, или просто хтите лучше управлять своим приложением, то вы можете получить события, которые посылает Windows вашему приложению, вместо того чтобы цикл Accept() их отбросил.

Можно sub-class-ировать как окно, так и application frame, но только один раз для каждого окна. Если вы попытаетесь сделать это дважды для одного окна или application frame, то положение станет довольно быстро грушеобразным.

Чтобы sub-class окно, в первую очередь нужна процедура, воспринимающая события Windows. Эта процедура получает четыре параметра - хендл окна, идентификатор сообщения, первый параметр сообщения, и второй параметр сообщения. На самом деле, прототип этой процедуры различен для 16-и и 32-разрядных приложений, и об этом легко забыть при модификации существующего 16-разрядного приложения в 32-разрядное. Если оно перестало работать, проверьте прототип. Эту ошибку делают все.

Затем, вам нужно прототипировать две функции Windows API, SetWindowLong и CallWindowProc. Опять, 16-и и 32-разрядные прототипы совпадают по принимаемым параметрам, но тип параметров отличается.

Функция SetWindowLong используется для изменения атрибута заданного окна. Одним из атрибутов является адрес процедуры IE окна: процедуры, принимающей события, посылаемые окну.

Чтобы на самом деле установить вашу новую процедуру, нужно вставить некоторый код после открытия окна и оператором Accept(). Не важно где это сделать, главное между ними.

Если предположить, что окно называется TestWindow, а новая sub-class процедура называется SubClassProc, то код будет следующим:

OPEN(TestWindow)
ReturnAddr = SetWindowLong(TestWindow{PROP:Handle},GWL_WNDPROC,ADDRESS(SubClassProc))
ACCEPT;
CASE EVENT();

(GWL_WNDPROC это equate, который использует Windows для обозначения изменяемого атрибута окна).

В последних версиях Clarion есть свойства (properties), которые обозначают то же самое - Prop:WndProc и Prop:ClientWndProc. Чтобы использовать их сделайте просто следующее:

OPEN(TestWindow)
ReturnAddr = TestWindow{Prop:WndProc}
TestWindow{Prop:WndProc} = ADDRESS(SubClassProc)
ACCEPT
CASE EVENT()

Не забудьте сначала сохранить адрес текущей процедуры, иначе вы не сможете передать ей любые события, которые вы не хотите обрабатывать в своей sub-classed процедуре. Теперь, самые умные из вас заметили, что имеется два свойства, и вы сильно желаете знать какая между ними разница. Наилучший ответ - не имею понятия. Я начал использовать Windows API и sub-classed процедуры до того как появились эти свойства, и человек того типа, кто думает "Ничего не трогай, пока работает".

Другая функция Windows API, CallWindowProc, используется в вашей новой процедуре для передачи событий, которые вы не хотите обрабатывать, предыдущему установленному обработчику - в нашем случае, это цикл Accept() Clarion.

В нашем примере, мы хотим чтобы приложение автоматически закрывалось при выключении компьютера. Когда мы пытаемся это сделать, Windows посылает событие в каждое работающее приложение. Это событие WM_QUERYENDSESSION, и вот выдержка из документации API:

"Сообщение WM_QUERYENDSESSION посылается когда пользователь выбрал завершение 
сессии Windows или когда приложение вызывает функцию ExitWindows. Если любое  
приложение возвращает нуль, то сессия Windows не завершается. Windows прекращает 
посылать сообщения WM_QUERYENDSESSION, как только одно приложение возвращает нуль."

Итак, наша sub-class процедура будет обрабатывать событие WM_QUERYENDSESSION. Оно должно также обрабатывать событие WM_ENDSESSION:

"Сообщение WM_ENDSESSION посылается в приложение после того как Windows 
обработает результат сообщения WM_QUERYENDSESSION. Сообщение WM_ENDSESSION
информирует приложение завершилась ли сессия Windows."

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

Наша настоящая процедура будет выглядеть наподобие (фактически, точно такой) следующей:

SubClassProc FUNCTION (Prm:hWnd, Prm:wMsg, Prm:wParam, Prm:lParam)
WM_QUERYENDSESSION Equate(00011H)
WM_ENDSESSION Equate(00016H)
CODE
Case Prm:wMsg
OF WM_QUERYENDSESSION
  ! Если вы получили это сообщение, это означает что Windows хочет закрыть приложение.
  ! Поэтому, если возвратить TRUE, то вы получите событие WM_ENDSESSION.
  ! Если вернуть FALSE, то Windows не закроется.
  RETURN(TRUE)
OF WM_ENDSESSION
  ! Windows хочет закрыть приложение.
  RETURN(TRUE)
ELSE
  ! Передать событие и его парамнтры в процедуру окна по цепочке
  RETURN(CallWindowProc(ReturnAddr,Prm:hWnd,Prm:wMsg,Prm:wParam,Prm:lParam))
END

Ловушки

Теперь, после представления как прекрасны sub-classed процедуры, несколько вещей, которых вы должны опасаться:

Не используйте Message или Stop в sub-class процедуре - если вы принимаете события каждые 10 секунд, а пользователь затрачивает минуту на просмотр и закрытие сообщения, то будут проблемы.

(Это относится к любым процедурам, которые могут вызываться из sub-class процедуры, а не только из нее самой). Может быть проблема не очевидна непосредственно, но в конце концов она приведет к краху системы.

Sub-class процедура должна выполняться так быстро, как это только возможно - опять, если вы получаете события каждые 10 секунд, а ваш код выполняется 30 секунд, то у вас будут проблемы. Код должен обрабатывать только то что необходимо, максимально быстро, перед возвратом в ОС.

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

Выводы

Вы видели как можно использовать Windows API для sub-class окна и принимать события, которые вы обычно не можете принять. Все что нужно помнить это правильные прототипы функций, и что прототипы почти всегда разные у16-и и 32-разрядных приложений.

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

Загрузить исходный текст


Back to my home page. Or send me mail at paula@attglobal.net

(c) 1999 Paul Attryde