2007-3-17 11:23
skyjacker
CnPackTip#5:Delphi 中的 Idle
CnPackTip#5:Delphi 中的 Idle
Written by SkyJacker
http://www.cnpack.org
CnPack IV QQ Group: 130970
Thx: Passion
2007-3-16
在学习 ActionList 时, 了解了 Delphi 中的 Idle 。
得出了以下几点认识:
1、Action 是由 Applicaton 内部处理的。
2、Delphi 不是通过处理 WM_IDLE 消息进入 Idle 处理的。
3、Application 本身的意外处理函数也可能产生意外。
4、也许世上本无 WM_IDLE。
TApplication.Run; 内部处理了 Idle 过程和Action。
处理的顺序是:
1、首先处理Hint
2、用户也可以自定义Idle,如果用户定义了,处理用户定义的。
3、处理 action Idle
因此,如果在用户的 Idle 中这样写,会禁止 Action 的 Update。
procedure TFrmTest.MyIdle(Sender: TObject; var Done: Boolean);
begin
Done := false; // 禁止 Action 的 Update
end;
相关 Vcl 如下:
跟随 TApplication 的运行过程, 查看 action 的 Update 如何被调用.
1、
procedure TApplication.Run;
begin
FRunning := True;
try
AddExitProc(DoneApplication);
if FMainForm <> nil then
begin
case CmdShow of
SW_SHOWMINNOACTIVE: FMainForm.FWindowState := wsMinimized;
SW_SHOWMAXIMIZED: MainForm.WindowState := wsMaximized;
end;
if FShowMainForm then
if FMainForm.FWindowState = wsMinimized then
Minimize else
FMainForm.Visible := True;
repeat // 每个 Application 都是在不断的循环着
try
HandleMessage; // 进入消息循环处理
except
HandleException(Self); // 意外处理,顺便看一下
end;
until Terminated;
end;
finally
FRunning := False;
end;
end;
// Application 默认的意外处理,看看也挺有意思:)
procedure TApplication.HandleException(Sender: TObject);
begin
if GetCapture <> 0 then SendMessage(GetCapture, WM_CANCELMODE, 0, 0);
// 返回捕获当前鼠标的窗口,
// The WM_CANCELMODE message is sent to the focus window when a dialog box or message box is displayed;
// this enables the focus window to cancel modes, such as mouse capture.
if ExceptObject is Exception then
begin
if not (ExceptObject is EAbort) then // 疑问一:如果是 EAbort, 那么怎么办? 交给了操作系统?
if Assigned(FOnException) then // 处理用户自定义的 OnException
FOnException(Sender, Exception(ExceptObject))
else
ShowException(Exception(ExceptObject)); // 显示意外信息 E.Message
end else
SysUtils.ShowException(ExceptObject, ExceptAddr);
end;
疑问二:
procedure TApplication.ShowException(E: Exception);
var
Msg: string;
begin
Msg := E.Message;
if (Msg <> '') and (AnsiLastChar(Msg) > '.') then Msg := Msg + '.';
// 在消息的末尾加个 '.' ,但是太不严格了吧。因为在 '.' 的前面还有其他常见的 ascii 字符。
// 这一句到底要实现什么功能呢?谁能告诉我。
// 直接将这一句注释掉,应该更好。或者干脆写 msg = '未知 E.Message';
// 为什么注释掉更好,继续往下看就会更明白了。
MessageBox(PChar(Msg), PChar(GetTitle), MB_OK + MB_ICONSTOP);
end;
AnsiLastChar 返回的是指向字符串最后一个字符的指针。
那么 (AnsiLastChar(Msg) > '.') 可以这样写吗?一个指针和一个字符的比较。
测试发现: 如果 一个 PChar 与 一个字符比较, Delphi 会自动将 PChar 转为 PChar^。
有意思吧。
再来 AnsiLastChar 源码
function AnsiLastChar(const S: string): PChar;
var
LastByte: Integer;
begin
LastByte := Length(S);
if LastByte <> 0 then
begin
while ByteType(S, LastByte) = mbTrailByte do Dec(LastByte);
Result := @S[LastByte];
end
else
Result := nil; // 如果 S = '' ,则返回 nil
end;
注意了,如果使用 AnsiLastChar 要注意空指针的问题:
比如:
Msg := '';
if (AnsiLastChar(Msg) >= '.') then
将会出现访问非法内存 $00000000 的错误。
再返回来看
Msg := E.Message;
if (Msg <> '') and (AnsiLastChar(Msg) > '.') then Msg := Msg + '.';
也就是说, Delphi 并不能保证 E.Message 不为空。因此,加了 (Msg <> '') 的条件。
问题又来了,既然不能保证 E.Message 不为空,又因为 if A and B 这种条件会因为编译条件的不同而产生不同的结果。
因此,将编译选项改为完全计算,在Compiler Options 对话框中选择Complete Boolean Evaluation 选项,
那么,"Read of Address 00000000" 应该是意外处理中的意外了。
2、
procedure TApplication.HandleMessage;
var
Msg: TMsg;
begin
if not ProcessMessage(Msg) then // 如果没有获得任何消息,进入自定义过程 Idle
Idle(Msg);
end;
3、
procedure TApplication.Idle(const Msg: TMsg);
var
Control: TControl;
Done: Boolean;
begin
Control := DoMouseIdle;
if FShowHint and (FMouseControl = nil) then
CancelHint;
Application.Hint := GetLongHint(GetHint(Control));
Done := True;
try
if Assigned(FOnIdle) then FOnIdle(Self, Done); // 用户自定义 Idle
if Done then DoActionIdle; // 如果 Done = true , 处理 action Idle
except
HandleException(Self);
end;
if (GetCurrentThreadID = MainThreadID) and CheckSynchronize then
Done := False;
if Done then WaitMessage;
end;
4、
action相关
procedure TApplication.DoActionIdle;
var
I: Integer;
begin
for I := 0 to Screen.CustomFormCount - 1 do
with Screen.CustomForms[I] do
if HandleAllocated and IsWindowVisible(Handle) and
IsWindowEnabled(Handle) then
UpdateActions;
// 满足3个条件,才会处理 action:
//可视对象已经被创建、可视的、使能的
end;
procedure TCustomForm.UpdateActions;
var
I: Integer;
procedure TraverseClients(Container: TWinControl);
var
I: Integer;
Control: TControl;
begin
if Container.Showing then
for I := 0 to Container.ControlCount - 1 do
begin
Control := Container.Controls[I];
if (csActionClient in Control.ControlStyle) and Control.Visible then
Control.InitiateAction;
if Control is TWinControl then
TraverseClients(TWinControl(Control));
end;
end;
begin
if (csDesigning in ComponentState) or not Showing then Exit; // 设计态或者非显示则退出
{ Update form }
InitiateAction; // 执行 action 的 Update
{ Update main menu's top-most items }
if Menu <> nil then
for I := 0 to Menu.Items.Count - 1 do // 更新顶级菜单
with Menu.Items[I] do
if Visible then InitiateAction;
{ Update any controls }
TraverseClients(Self); // 执行控件关联的 action Update
end;
InitiateAction 调用:
procedure TControl.InitiateAction;
begin
if ActionLink <> nil then ActionLink.Update;
end;
function TBasicAction.Update: Boolean;
begin
if Assigned(FOnUpdate) then
begin
FOnUpdate(Self);
Result := True;
end
else Result := False;
end;
5、
消息处理
function TApplication.ProcessMessage(var Msg: TMsg): Boolean;
var
Handled: Boolean;
begin
Result := False;
if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then
begin
Result := True;
if Msg.Message <> WM_QUIT then
begin
Handled := False;
if Assigned(FOnMessage) then FOnMessage(Msg, Handled);
if not IsHintMsg(Msg) and not Handled and not IsMDIMsg(Msg) and
not IsKeyMsg(Msg) and not IsDlgMsg(Msg) then
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
end
else
FTerminate := True;
end;
end;
如果 PeekMessage 不到消息,就按 Delphi 的idle。
如果给 Application 不断发 WM_IDLE,除非用户在 Application 自定义处理 WM_IDLE 消息,
否则Delphi Appliction 不会处理。
也就是说 Delphi IDLE 就是无消息时候的处理,没有什么特别的。
如果有一点消息,就不会触发 idle,比如鼠标点击不放,idle 即 action update 也不会执行了。
在 Delphi Help 中解释空闲的定义是“应用程序不执行任何代码时为空闲”,
其实改为无应用程序要处理的消息时,应该更容易理解。
本来想测试一次 Applcaction 接收 Windows在消息队列空闲时发出的WM_IDLE 消息,
不过在 Messages.pas 居然没有 WM_IDLE 的定义. 谁能告诉我 WM_IDLE 的序号是多少?
关于 WM_IDLE 的一些资料:
[1] http://www.microsoft.com/msj/0797/c0797.aspx 它是用 MFC 说明,其中有一句:
"The WM_IDLE message will not be processed for modal dialogs; that is, when a DoModal is executing. There is, however, a MFC "private" message that is somewhat documented. It is the the WM_KICKIDLE message. You must add an entry to the message map for the dialog class header. "
如果 Delphi 使用 WM_IDLE 是不是也要考虑 ShowModal 的问题?
[2] http://www.tutorials-ne.com/ui/Drag-Drop-62308/
WM_IDLE
If you do a search on the web for the WM_IDLE windows message there will be
many hits. According to many sites, the WM_IDLE message is generated by the
operating system itself when the application's message queue is empty.
However, I couldn't find WM_IDLE anywhere within MSDN. Is this message indeed
defined by Windows? And if so, would it be possible for me to hook it?
WM_IDLE 在哪里?
[3] http://www.cs.sjsu.edu/faculty/beeson/courses/cs130/LectureNotes/2-HowWindowsWorks/2-HowWindowsWorks.html
This loop executes until there are no more messages in the message queue, and then it terminates.
Premature termination is prevented by WM_IDLE messages
that the operating system generates when nothing else is happening.
什么叫 "operating system generates when nothing else is happening" ?
也许世上本无 WM_IDLE。 可能都是自己定义并发出来的。
http://www.pudn.com/view/downloads64/sourcecode/windows/directx/224349/birdeye_view.dpr__.htm
while (TRUE) do
begin
if PeekMessage(uMsg, 0, 0, 0, PM_NOREMOVE) then
begin
if not GetMessage(uMsg, 0, 0, 0) then
break;
TranslateMessage(uMsg);
DispatchMessage(uMsg);
end
else
begin
PostMessage(hWindow, WM_IDLE, 0, 0);
WaitMessage;
end;
end;
2007-3-17 11:28
skyjacker
进程System Idle process是什么?
[url]http://tech.163.com[/url] 2006-05-16 03:25:46 来源: 网易学院(广州)
System Idle Process它不是一个真正的进程,它是核心虚拟出来的,
有多任务操作系统都有的!当系统没有可用的进程时,系统就处于空运行状态,
这时System Idle Process SYSTEM在运行!
故它占用99%CPU时间,这也说明了你的机器负荷很轻!
当你用软件解压一个大的文件时,就能看到,System Idle Process SYSTEM占用CPU时间变化。
System Idle 它是表示你系统剩余的CPU资源!如果它占的CPU资源为0那估计你该重新启动了。。。。
当然也可以通过关闭其他占用大量内容的进程来解决该问题。
2007-3-18 11:36
bahamut8348
LS出手就是不一样,总是长篇大论的,眼晕中……
2007-3-18 16:18
Passion
Delphi的Application收不到消息时为Idle,
Windows当程序空闲的时候给它发个Idle,
2007-3-18 16:21
Passion
我觉得if (Msg <> '') and (AnsiLastChar(Msg) > '.') 应该没啥问题吧,
毕竟这个if中应该是从左到右求值的,第一个为False第二个就不会再求了,
许多编译器都这个规则。
2007-3-18 17:06
skyjacker
1、"Windows当程序空闲的时候给它发个Idle"
如何在Delphi Application 中接收这个Idle 呢?好像 WM_IDLE 是程序自定义的,不是Windows 固定的消息。
2、"我觉得if (Msg <> '') and (AnsiLastChar(Msg) > '.') 应该没啥问题吧,
毕竟这个if中应该是从左到右求值的,第一个为False第二个就不会再求了,
许多编译器都这个规则。"
Delphi 默认的编译规则是这样的。如果在Compiler Options 对话框中选择Complete Boolean Evaluation 选项就不一样了。
即使按照默认规则, Delphi 在编译一些复杂的程序时,也会按照完全计算的方式。它并不总是那么智能。
等上班后,我看看能不能找个例子说明一下。
2007-3-18 19:34
Passion
Complete Boolean Evaluation 选项改变的话Delphi也不会重编译VCL源码吧?
2007-3-19 09:42
shenloqi
SkyJacker
短路运算的编译规则很容易,应该不存在什么所谓的复杂程序,而且进行短路运算也不算智能。
我不知道你为什么有这样的印象,不过我认为你可能把短路运算与传递函数参数混淆了,在Delphi中传递函数参数的时候它总是要先计算出结果然后再调用函数,这时候如果想依赖前面的参数作为后面参数的前提条件自然会出错了,而在同一个条件判断语句中,两段表达式的确是短路运算的。
(C里面有些语句与表达式的语法是相同的,也就是有时候看上去是一个语句其实也可以是一个表达式,而且C编译器在准备函数参数和条件分支时与Delphi的处理也不一样,所以有时候看上去C的代码应该与Delphi一样,可是C的代码就不会进行提前计算,而Delphi却会)
2007-3-19 10:13
kendling
哈,又学到东西了。
2007-3-19 14:50
skyjacker
"我不知道你为什么有这样的印象"
的确有这种印象。
去年我维护一个程序时,发现的问题。
不过当时我并不知道 Complete Boolean Evaluation 选项的作用。
通过 Cpu View 看,是最后才判断是否跳过。
setnz al
setnz bl
and al,bl
....
因此,使用了内部 if Length(S) =0 then 处理了此代码。
源代码段:
// 协议分析
while (Length(S) <> 0) and (S[1] <> '@') do
begin
//
S := Copy(....)
if Length(S) =0 then
break;
end;
今天一看,原来的程序设置了“Complete Boolean Evaluation”。
所以,应该是我的无知,当时误会了 Delphi 编译器。
终于又解决了一块心病。
Thx: Passion, shenloqi 以及 kendling的精神支持。
2007-3-19 16:15
Passion
提个运算优先级的小问题:
Thx: Passion, shenloqi 以及 kendling的精神支持。
是指:
Thx: (Passion, shenloqi 以及 kendling)的精神支持。
还是:
Thx: Passion, shenloqi 以及 (kendling的精神支持)。
:lol:
2007-3-19 17:30
skyjacker
呵呵:lol:
模糊匹配吧
什么是模糊匹配? 我也不清楚.:mad:
2007-3-20 10:08
kendling
[quote]原帖由 [i]Passion[/i] 于 2007-3-19 16:15 发表
提个运算优先级的小问题:
Thx: Passion, shenloqi 以及 kendling的精神支持。
是指:
Thx: (Passion, shenloqi 以及 kendling)的精神支持。
还是:
Thx: Passion, shenloqi 以及 (kendling的精神支持)。
:lol: ... [/quote]
:L 这个比喻......
2007-3-20 10:55
jAmEs_
呵呵
页:
[1]
Powered by Discuz! Archiver 5.0.0
© 2001-2006 Comsenz Inc.