zoukankan      html  css  js  c++  java
  • inherited在消息中的作用(编译器根据inherited所在的函数,直接转换成对祖先类同名动态函数的调用,或者转换成对DefaultHandler的调用)

    好奇一下。看来Object Pascal确实与Windows深入结合了。

    unit Unit1;
    
    interface
    
    uses
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs, StdCtrls, ExtCtrls;
    
    const
      UM_Test = WM_USER + 100;
    
    type
      TForm1 = class(TForm)
        Button1: TButton;
        procedure Button1Click(Sender: TObject);
      private
        { Private declarations }
      public
        { Public declarations }
        procedure MyMessage(var Msg: TMessage); message UM_Test;
      end;
    
    var
      Form1: TForm1;
    
    implementation
    
    {$R *.dfm}
    
    procedure TForm1.Button1Click(Sender: TObject);
    begin
       Perform(UM_Test, 0, 0);
    end;
    
    procedure TForm1.MyMessage(var Msg: TMessage);
    begin
      inherited;
      ShowMessage('Hello');
    end;
    
    end.

    在message处理中和其他不一样的是inherited不会因为没有在祖先中找到一样的函数或者方法而将inherited失效,他会传入缺省的消息处理.
    这里调用TFORM1的祖先的消息处理,由于tform和tcustomform没有这个实现UM_Test函数,所以直接调用的是tcustomform的defaulthandle.(注意这个方法是对twincontrol的override)。

    但是,如果本类重载了DefaultHandler函数,就会直接调用本类的DefaultHandler函数:

    unit Unit1;
    
    interface
    
    uses
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs, StdCtrls, ExtCtrls;
    
    const
      UM_Test = WM_USER + 100;
    
    type
      TForm1 = class(TForm)
        Button1: TButton;
        procedure Button1Click(Sender: TObject);
      private
        { Private declarations }
      public
        { Public declarations }
        procedure MyMessage(var Msg: TMessage); message UM_Test;
        procedure DefaultHandler(var Message); override;
      end;
    
    var
      Form1: TForm1;
    
    implementation
    
    {$R *.dfm}
    
    procedure TForm1.Button1Click(Sender: TObject);
    begin
       Perform(UM_Test, 0, 0);
    end;
    
    procedure TForm1.DefaultHandler(var Message);
    begin
      with TMessage(Message) do
      begin
          if Msg = UM_Test then ShowMessage('DefaultHandler');
      end;
      inherited;
    end;
    
    procedure TForm1.MyMessage(var Msg: TMessage);
    begin
      inherited;
      ShowMessage('Hello');
    end;
    
    end.

     顺便再看看这样改写的效果:

    procedure TForm1.DefaultHandler(var Message);
    begin
      with TMessage(Message) do
      begin
          if Msg = UM_Test then ShowMessage('DefaultHandler');
          if Msg = WM_SETTEXT then ShowMessage('WM_SETTEXT');
      end;
      inherited;
    end;

     理论解释:

    因为WndProc里面调用了Dispatch,而Dispatch实际上是尝试调用动态方法,在动态方法表中查找Index是指定的方法,如果一直找到TObject都找不到Index是这个的方法,就会调用DefaultHandler。
    这里面有几个概念,动态方法表和虚方法表,不要混了。
    动态方法实际上在Class中是个最大65536的函数数组。调用的时候会根据Index到这个数组中来调用。而消息方法实际就是动态方法,方法声明后面的Message实际上就是Index。
    VCL之所以对消息处理效率比VC等高,就是因为这个动态方法表,实际调用的就是用消息做下标直接调用方法。最多就是找几层继承的父类。而比VC的遍历要快。

    Inherited动态方法的时候就是到父类的动态方法表找Index等于本方法Index的方法。如果父类没有这个方法就再到父类的父类找。都找不到就调用DefaultHandler。

    动态方法和虚方法相比优点是省内存,另外能对Message的处理比较方便。虚方法不能做到对消息的方便处理。
    虚方法则比较费内存,每个派生类都要有一套虚方法表。但是被调用的时候比动态方法要快,不需要到父类去找。

    理论解释2:

    你恰恰理解反了,不是Inherited会调用Dispatch,而是Dispatch会调用动态方法.
    消息的调用次序实际上是
    WndProc->Dispatch->动态方法或者DefaultHandler方法.

    TControl.WndProc的最后一句就是Dispatch.
    然后Dispatch就找Index的动态方法调用.
    如果找不到就调用到DefaultHandler.
    如果找到,就调用动态方法.
    如果动态方法里面调用了Inherited,那么实际上inherited是编译器处理的.如果在祖宗里面有对应index的方法,那么就直接编译成调用该方法.如果祖宗里面没有该index的方法编译成调用DefaultHandler(存疑).
    实际上这样处理效率比运行时处理要快.

    Inherited必须由编译器处理的,因为Inherited有两种含义.动态方法和虚方法的实现机制不同必然要根据方法编译成不同的代码.

    ====================================================================

    procedure TObject.Dispatch(var Message);
    asm
        PUSH    ESI
        MOV     SI,[EDX] //获取index
        OR      SI,SI
        JE      @@default //如果是0,到default,也就是调用DefaultHandler
        CMP     SI,0C000H
        JAE     @@default //如果不在合理范围内,到default,也就是调用DefaultHandler
        PUSH    EAX
        MOV     EAX,[EAX]
        CALL    GetDynaMethod  //获取动态方法
        POP     EAX
        JE      @@default  //如果GetDynaMethod返回的是nil,到default,也就是调用DefaultHandler
        MOV     ECX,ESI
        POP     ESI
        JMP     ECX //跳转到动态方法
    
    @@default:
        POP     ESI
        MOV     ECX,[EAX]
        JMP     DWORD PTR [ECX] + VMTOFFSET TObject.DefaultHandler
    end;
    
    procedure       GetDynaMethod;
    {       function        GetDynaMethod(vmt: TClass; selector: Smallint) : Pointer;       }
    asm
            { ->    EAX     vmt of class          }
            {       SI      dynamic method index    }
            { <-    ESI pointer to routine  }
            {       ZF = 0 if found         }
            {       trashes: EAX, ECX          }
    
            PUSH    EDI
            XCHG    EAX,ESI
            JMP     @@haveVMT
    @@outerLoop:
            MOV     ESI,[ESI]
    @@haveVMT:
            MOV     EDI,[ESI].vmtDynamicTable //根据vmt获取该类的动态方法表
            TEST    EDI,EDI
            JE      @@parent
            MOVZX   ECX,word ptr [EDI]
            PUSH    ECX
            ADD     EDI,2
            REPNE   SCASW
            JE      @@found //跳到found
            POP     ECX
    @@parent:
            MOV     ESI,[ESI].vmtParent //为空,那么找父类的
            TEST    ESI,ESI //测试父类是否为空
            JNE     @@outerLoop //跳出循环
            JMP     @@exit //退出
    
    @@found:
            POP     EAX
            ADD     EAX,EAX
            SUB     EAX,ECX         { this will always clear the Z-flag ! }
            MOV     ESI,[EDI+EAX*2-4]
    
    @@exit:
            POP     EDI
    end;

    对应的pascal代码如下

    procedure TObject.Dispatch(var Message);
    type
      //THandlerProc = procedure(Self: Pointer; var Message) { of object };
      THandlerProc = procedure(var Message) of object;
    var
      MsgID: Word;
      Addr: Pointer;
      M: THandlerProc;
    begin
      MsgID := TDispatchMessage(Message).MsgID;//消息id,也就是动态方法表的索引
      if (MsgID <> 0) and (MsgID < $C000) then
      begin
        Addr := GetDynaMethod(PPointer(Self)^, MsgID);//获取class的动态方法表
        if Addr <> nil then//如果拿到了动态方法就调用
        begin
          //THandlerProc(Addr)(Self, Message)
          TMethod(M).Data := Self;
          TMethod(M).Code := Addr;
          M(Message);
        end
        else
          Self.DefaultHandler(Message);//如果找不到动态方法则调用defaultHandler
      end
      else
        Self.DefaultHandler(Message);//如果index范围不在应该在的范围内也调用DefaultHandler
    end;
    
    
    function GetDynaMethod(vmt: TClass; selector: SmallInt): Pointer;
    type
      TDynaMethodTable = record
        Count: Word;
        Selectors: array[0..9999999] of SmallInt;
        {Addrs: array[0..0] of Pointer;}
      end;
      PDynaMethodTable = ^TDynaMethodTable;
    var
      dynaTab: PDynaMethodTable;
      Parent: Pointer;
      Addrs: PPointer;
      I: Cardinal;
    begin
      while True do
      begin
        dynaTab := PPointer(PByte(vmt) + vmtDynamicTable)^;//根据vmt获取该类的动态方法表
        if dynaTab <> nil then
        begin
          for I := 0 to dynaTab.Count - 1 do
            if dynaTab.Selectors[I] = selector then
            begin
              Addrs := PPointer(PByte(@dynaTab.Selectors) + dynaTab.Count * SizeOf(dynaTab.Selectors[0]));
              Result := PPointer(PByte(Addrs) + I * SizeOf(Pointer))^;
              Exit;//能找到则退出
            end;
        end;
        Parent := PPointer(PByte(vmt) + vmtParent)^;//找不到则找父类的
        if Parent = nil then Break;//如果父类是nil,也就是tobject了,则退出
        vmt := PPointer(Parent)^;
      end;
      Result := nil;
    end;
  • 相关阅读:
    七层协议&网络配置
    解决跨域问题
    拖拽 ‘vue-grid-layout’ 插件了解下
    详解vuex
    在腾讯出差的日子
    对象的解构赋值应用
    MQTT项目请求设置
    五分钟搞定Go.js
    Chrome使用video无法正常播放MP4视频的解决方案
    微信小程序地图开发总结
  • 原文地址:https://www.cnblogs.com/findumars/p/3703078.html
Copyright © 2011-2022 走看看