CnPack Forum » 技术板块灌水区 » CnPackTip#5:Delphi 中的 Idle


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]
查看完整版本: CnPackTip#5:Delphi 中的 Idle


Powered by Discuz! Archiver 5.0.0  © 2001-2006 Comsenz Inc.