Delphi的子类化控件消息, 消息子类化

Posted 游子日月长

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Delphi的子类化控件消息, 消息子类化相关的知识,希望对你有一定的参考价值。

所谓的子类化,网上有很多说明,我就说我个人的随意理解,可能有误,请列位看官斟酌理解。

所谓子类化,个人理解就是拦截某个控件的消息以及样式来进行自己的特定处理以达到特殊的功能需求。这个子类化,可以有子类化别人的程序的控件,也有子类化自己程序的控件。

 

子类化别人的,就需要注入到别人的程序内部,然后做对应处理拦截,我这里主要针对的是自己程序的处理。

这个就比较简单了,有API函数SetWindowLong,用这个函数,就可以拦截某WinControl的Wndproc窗口过程了。

 

在Delphi中,所有的消息处理,实际上都是用Application来代理处理以及转发派遣的,

所以,不必要SetWindowLong,就可以拦截所有控件的消息,Application.OnMessage中处理就可以,比较容易,

不过,这个消息事件中有个比较蛋疼的,就是控件的释放消息WM_Destroy,里面捕捉不到

 

所以,可以考虑到用SetWindowLong替换掉窗口过程,在这个新窗口过程中处理这消息。

替换的方式是

SetWindowLong(ControlHandle,GWL_WNDPROC,newProc);

这个newProc过程是

function NewWndProc(hwnd: THandle;msg: UINT;wparam: wParam;lParam: LParam);integer;stdcall;

就可以这样

function NewWndProc(hwnd: THandle;msg: UINT;wparam: wParam;lParam: LParam);integer;stdcall;

begin

end;

然后

SetWindowLong(ControlHandle,GWL_Wndproc,LongInt(@NewWndproc));

但是这样处理,就是一个全局函数,如果要通用,就要专门定义一个全局变量,用来代理处理

我这里说的一种方式,就是直接在一个类当中来处理,我想这样说,应该很多人都会说,很容易了,

Delphi自带的有一个函数MakeObjectInstance,用这个函数,就可以将这个窗口函数定义到类内部来使用,

对应方式就是

复制代码
type
  TTest = class
  private
    FP,OldP: Pointer;
    procedure NewProc(var msg: TMessage);
  public
    constructor Create;    
    destructor Destroy;
    procedure HookWNdproc(hwnd: THandle);
  end;


constructor TTest.Create;
begin
  Fp := MakeObjectInstance(NewProc);  
end;

destructor Destroy;override;
begin
  FreeObjectInstance(Fp );
  inherited;
end;

procedure TTest.HookWNdproc(hwnd: THandle);
begin
  OldP := Pointer(SetWindowLong(hwnd,GWL_Wndproc,Fp));
end;
复制代码

这里,应该明白的人,就已经知道了,NewProc过程中,没有控件的句柄传递过来,这个就是Delphi处理过了,目的是无句柄的消息派遣传递,也可以用这个来处理的。

 

中间的问题就出在MakeObjectInstance这个函数中,本函数的内容,可以自行去Delphi中看,我就不弄上来了。

函数的目的是将控件的窗口过程Hook导向了Delphi的一个内部处理函数StdProc,这个函数在Classes单元中,是上面声明的标准的窗口过程函数


function NewWndProc(hwnd: THandle;msg: UINT;wparam: wParam;lParam: LParam);integer;stdcall;

那么我们就可以知道,实际上NewProc实际上调用的还是StdProc这个函数,那么既然如此,那么就肯定还是能够获得里面传递过来的参数的。那么这里就涉及到了程序的函数调用的一个原理,这个东西,实际上在汇编课程中,应该会讲到,就算不讲,自己反一下Delphi的源码就可以看出来,函数调用初期,进入函数的时候,都会有对应的

push ebp
mov  ebp,esp

这样的语句,这个Push ebp目的就是压入上一次的函数环境的堆栈,以便于函数调用完成之后,能够顺利返回,所以,从这里,我们就可以知道上一次函数的调用堆栈在ebp中,而上一个函数就是function NewWndProc(hwnd: THandle;msg: UINT;wparam: wParam;lParam: LParam);integer;stdcall;这个函数了,那么知道了他的调用堆栈,获取堆栈中的参数就很容易了咯,可以来看NewWndProc的堆栈情况,

Windows的Stdcall回调函数的参数传递方式是从从右往左进栈,参数压栈之后还会压入一个现场,所以可以知道Hwnd的参数就在这个现场后面,

那么就可以知道,这个Hwnd的值了

代码如下:

复制代码
procedure TTest.NewProc(var msg: TMessage);
var
  controlHandle: THandle;
begin
  asm
    mov  edx,[ebp] //stdproc个函数的堆栈顶
    mov  edx,[edx+8] //Hwnd参数,参数之后压入了一个现场,所以+8
  end;
end;
复制代码

那么这里就可以用

复制代码
type
  TTest = class
  private
    FP,OldP: Pointer;
    procedure NewProc(var msg: TMessage);
  public
    constructor Create;    
    destructor Destroy;
    procedure HookWNdproc(hwnd: THandle);
  end;

procedure TTest.NewProc(var msg: TMessage);
var
  ControlHandle: THandle;
begin
  asm
    mov  edx,[ebp]
    mov  edx,[edx+8]
    mov  controlHandle,edx
  end;
  //通过ControlHandle来判定控件,做通用处理
end;

constructor TTest.Create;
begin
  Fp := MakeObjectInstance(NewProc);  
end;

destructor Destroy;override;
begin
  FreeObjectInstance(Fp );
  inherited;
end;

procedure TTest.HookWNdproc(hwnd: THandle);
begin
  OldP := Pointer(SetWindowLong(hwnd,GWL_Wndproc,Fp));
end;
复制代码

于是,这个淫荡的法则,完成了。

以上是关于Delphi的子类化控件消息, 消息子类化的主要内容,如果未能解决你的问题,请参考以下文章

win32 c++ 在没有子类化的编辑控件中检测“输入”?

.NET 中 ActiveX 控件的子类化窗口过程

将静态控件子类化到对话框窗口

自绘LISTVIEW的滚动条(Delphi实现)

子类化 UITableViewCell 并在 Storyboard 中使用它

子类化视图控制器:前向声明