CnPack 开源软件项目 - QQ群上5月17号讨论的几个问题总结
  网站首页 下载中心 每日构建 文档中心 捐助我们 开发论坛 关于我们 致谢名单 English


 Google 搜索

内容: 
 最新下载


 
CnWizards 1.5.1.1219
[2024-11-03]

 
CnVCL 组件包 20241103
[2024-11-03]

 
CnPack 密码算法库 20241103
[2024-11-03]
  每日构建版下载
  专家包时间线
 项目相关链接


 
CnPack GitHub 首页
GIT 使用说明
申请加入 CnPack
CnPack 成员名单
 网站访问量

今日首页访问: 141
今日页面流量: 603
全部首页访问: 5296748
全部页面流量: 21342587
建站日期: 2003-09-01

 
QQ群上5月17号讨论的几个问题总结
 
CnPack 开源软件项目 2004-06-05 21:53:11

cnpack-group,您好!

    昨天看代码看得头晕脑胀,后来遇见了一段垃圾代码,于是在CnPack的QQ群
上和大伙儿讨论了一阵子,沈龙强兄给出了不少有用的意见,下面总结一下。

问题全是由偶然碰到的一段古怪的垃圾代码引起的:

// 以下是古怪代码
  var
    Form1: TForm1;

  procedure TForm1.FormClose(var Action: TCloseAction)
  begin
    Action := caFree;
    FreeAndNil(Form1);
  end;
// 以上是古怪代码

    这段代码中,窗体关闭的时候,作者怕不能释放,除了写了个Action:=caFree外,
还特意FreeAndNil了Form1这个变量,——这里Form1是程序的主窗体。
    代码不能这样写。—— 这似乎用不着讨论了,问题是这样写会出些什么毛病,以
及毛病是怎样出来的。

问题一:主窗体在OnClose中提前释放,会导致程序退出时失去响应。

    “佛的光辉”兄说Form1是主窗体时,这样写会导致程序退出时失去响应。一测试
果然是这样,原因是啥?
    看看TCustomForm的Close过程,前面是设置CloseAction,然后调用
DoClose(CloseAction);以触发OnClose事件,接着根据CloseAction进行窗体状态设置:

      ……
      DoClose(CloseAction);
      if CloseAction <> caNone then
        if Application.MainForm = Self then Application.Terminate
        else if CloseAction = caHide then Hide
        else if CloseAction = caMinimize then WindowState := wsMinimized
        else Release;

    主窗体Close掉后程序会退出,这个现象可以用在if Application.MainForm =
Self then Application.Terminate 这段代码解释。但如果DoClose中调用的OnClose
的事件处理中把Form本身先释放了,那么Application的MainForm就变成了nil,再也不
等于Self了(释放Form1这个对象的实例不会影响到这个过程的Self指针,因此Self指针
不变,被“悬空”了)。所以Application没能Terminate,程序主窗体关掉了,
Application这个隐藏窗口却继续在进行消息循环,所以会造成失去响应的假像。

问题二:Form创建的过程以及OnCreate等事件的顺序问题。

    这里只写写结论,讨论的过程太不好整理了。
    Form的OldCreateOrder为True的时候,DoCreate在Create里头被调用,触发
OnCreate事件;DoDestory在Destroy中调用,触发OnDestroy事件。为False的时候,
DoCreate在AfterConstruction里头调用,DoDestory在BeforeDestruction里头调用;
但不管怎样,DoCreate的时候,子控件都已经通过InitInheritedComponent创建了;
DoDestroy的时候,子控件都没有被释放。所以我们能在OnCreate和OnDestroy的时候
放心地引用form上的控件。

    但如果想在OnCreate事件里头引用Form1这样的变量,就需要注意了。
如果你是通过Form1 := TForm1.Create(nil)这样的形式来创建Form1的,那么Create
执行完毕后,Form1才会被赋值。
    如果这个Form的OldCreateOrder偏偏又为True,那么OnCreate在Create里头被触发,
此时OnCreate里头千万不能引用Form1,因为它还没被赋值。
    如果是通过Application.CreateForm(TForm1, Form1)这样使用的,则可放心,
CreateForm的代码中,NewInstance后,就会给Form1赋值:

  Instance := TComponent(InstanceClass.NewInstance);
  TComponent(Reference) := Instance;

    看见了吧?是先分配的对象空间并赋引用值后再调用的Instance.Create(Self);
此时Create过程中,Reference也就是Form1已经被正确地赋予对象引用值了,
只是这个对象还没完成初始化而已。
    如果使用Self,则不管啥情况下,都不存在上边的问题。

问题三:Form的OnCreate事件里头抛出异常时窗体不自动中止并销毁。

    大伙儿可能都知道,一般一个对象在Create的时候如果抛出异常,则编译器会自动
调用其Destroy函数并FreeInstance,为的是保证内存不泄漏。我们把OldCreateOrder
设为True,在OnCreate里头手工raise一个异常,发现窗体仍然能正常创建并显示。
    其实这个问题很简单,看看DoCreate的代码:

  if Assigned(FOnCreate) then
  try
    FOnCreate(Self);
  except
    if not HandleCreateException then
      raise;
  end;

    原来它早就写了一个try except,把OnCreate中的异常给抓住了。对于为什么要这
样写还不太清楚,可能就是为了防止小异常影响大窗体显示?
    从这里头得到一个小教训就是,不是所有的DoXxxxx过程都是这样写的:

  if Assigned(FOnXxxxx) then
    FOnXxxxx(Self);

    算是一点小经验吧。^_^

最后一个问题:S := TForm1(nil).Caption; 不会出错?

    这个问题完全是调试上述问题时的副产品,当时用了个nil给Form1来引用它,
结果发现TForm1(nil).Caption这样的用法不会出内存访问冲突,为什么?
    看看TControl.GetText过程就可以明白。

  function TControl.GetText: TCaption;
  var
    Len: Integer;
  begin
    Len := GetTextLen;
    SetString(Result, PChar(nil), Len);
    if Len <> 0 then GetTextBuf(Pointer(Result), Len + 1);
  end;

    先GetTextLen,Len为0的话就啥都不做了。看看GetTextLen怎么做的。

  function TControl.GetTextLen: Integer;
  begin
    Result := Perform(WM_GETTEXTLENGTH, 0, 0);
  end;

    那么又得看Perform方法了,里头最重要的三句是:

  Message.Result := 0;
  if Self <> nil then WindowProc(Message);
  Result := Message.Result;

    这里和Free一样会判断Self指针是否为nil,不为nil才调窗口过程,
为nil则直接返回,所以不出错。

    问题总结完毕。累死了,手都酸了。

        致
礼!

          Passion  passion@cnvcl.org
                  CnPack 开发组管理员
                  http://www.cnvcl.org
         2004-05-18



本文已阅读 8145 次
来自: CnPack 开源软件项目

返回上级下一主题

相关主题:


版权所有(C) 2001-2024 CnPack 开发组 网站编写:Zhou Jinyu