VCL源码分析方法论(以TButton.Caption属性的由来为例)
最近一段时间似乎流行源码分析:)我也来谈谈在过去一段时间里对VCL源码的分析方法方面的一点体会,本文将不探讨VCL类库的构架和设计模式方面的东本,只是以我们常见的控件属性/方法的实现过程作简单的说明,希望对初学者有所帮助。
VCL分析方法
例:TButton.Caption属性的由来
(本文仅以此献给DELPHI初学者)
用过一段时间DELPHI的朋友,都会对VCL源码感兴趣。本人也常常在各大论坛见到一些网友研究讨论过关于VCL源码的贴子。不过,很多网友很努力的想看懂,可最后还是半途而废,因为他们总是理不出个头绪、看得云里雾里。笔者我也有看源码的习惯,没事的时候就点点鼠标右键,总是希望得到一些侥幸的收获和开发技巧。不过万事都得先有个基本前题,就像人上学的过程一样(这里指正常人)要按部就班的来,一般不可能小学一毕业就直接去念大学,除非他(她)是个天才或经过特别培训。所以各位GGJJDDMM,看VCL源码也是有个基本前题的,首先你得熟悉WIN32 API/SDK,如果你说不知道的话,可以参考书籍《Programming Windows》(中文名《WINDOWS 程序设计》)。其次是你应当对Object Pascal比较熟悉,或者你曾经对DELPHI的组件进行过扩展(做过组件开发),那么我相信你对Object Pascal已经熟悉。不熟也不要紧,DELPHI的在线帮助就有对Object Pascal的讲述,如果英文太差也不要紧,网上也有很多热心网友翻译过来的中文帮助和语言参考书。
言归正传,我们这篇文章的主题是对VCL源码的分析,分析当然有一个分析方法的问题,总不能随便打开一个源程序,逮着一个函数就分析一个函数吧:)所以我们也应该有选择,有目的的分析。想想我们每天编码时都会遇到的属性有哪些?呵呵,NAME,CAPTION,VISIBLE,还有一些控件的TEXT(如EDIT1.TEXT)。那么我们就以控件的CAPTION来分析吧。当然不是每个控件都有CAPTION属性的,我们这里就用TButton类的Caption属性进行分析。打开每天我们都会使用的DELPHI,在FORM窗体上放一个按钮,得到一个Button1的按钮控件,按F12打开源程序,有没有找到这段代码呢:
Button1: TButton;
对了,在TButton上点击鼠标右键,在弹出的上下文菜单中选择第一项Find Declaration,找到TButton类的定义,如下所示:
TButton = class(TButtonControl)
private
FDefault: Boolean;
FCancel: Boolean;
FActive: Boolean;
FModalResult: TModalResult;
procedure SetDefault(Value: Boolean);
end;
原来TButton继承于TButtonControl类,呵呵:)
在左边的对象窗口(Exploring Unit.pas窗口)中找到TButton的CAPTION属性,如下图:
双击CAPTION属性,找到定义CAPTION属性的源码,大家可能发现什么都没有,只有一个
property Caption;
呵呵,写过组件的朋友都知道,按理Caption属性应该有读/写文本的方法啊?在哪里去了呢,呵呵,这里没有出现,当然应该在它的父类里了(这里只是申明Caption出来的地方),我们顺着刚才的方法继续在TButtonControl,发现也没有,最终我们在TControl类里找到了这个CAPTION,至于为什么是protected成员,我就不多说了:
protected
property Caption: TCaption read GetText write SetText stored IsCaptionStored;
看看GetText、SetText就是操作文本属性的函数了,我们找到GetText、SetText定义如下:
function GetText: TCaption;
procedure SetText(const Value: TCaption);
还有TCaption,它的定义居然是一个自定义类型:
TCaption = type string;
说明GetText返回值和SetText的调用参数本来也就是一个string型的:)
下面我们来看看GetText源码:
// 下面我们来看看GetText源码:
function TControl.GetText: TCaption;
var
Len: Integer;
begin
Len := GetTextLen;//得到文本长度
SetString(Result, PChar(nil), Len);// 设置Result返回以Len指定的长度
if Len <> 0 then GetTextBuf(Pointer(Result), Len + 1);//长度不为空,Result得到文本数据
end; // 如果不明白GetTextBuf的用法,看看如下的代码:
procedure TForm1.Button1Click(Sender: TObject);
var
Buffer: PChar;
Size: Byte;
begin
Size := Edit1.GetTextLen; //得到EDIT1的文本长
Inc(Size);
GetMem(Buffer, Size); //创建EDIT1文本长度大小的缓存空间
Edit1.GetTextBuf(Buffer,Size); //由缓存得到文本,Buffer里的值就是Edit1.Text
Edit2.Text := StrPas(Buffer); //Buffer转换为PASCAL字符类型数据
FreeMem(Buffer, Size); //释放内存
end;
以上程序的行为同以下程序相当:
procedure TForm1.Button1Click(Sender: TObject);
begin
Edit2.Text := Edit1.Text;
end; // 回到GetText函数,其中GetTextLen的作用是得到文本长度,GetTextBuf得到文本数据。SetText就更简单了,定义如下:
procedure TControl.SetText(const Value: TCaption);
begin
if GetText <> Value then SetTextBuf(PChar(Value));
end; // 意思是如果设定的Value与原来的不同,则重新设置缓存文本。为了更深入VCL底部,我们再看看GetTextLen如何实现的(其实SetTextBuf和GetTextLen的实现过程相似):
function TControl.GetTextLen: Integer;
begin
Result := Perform(WM_GETTEXTLENGTH, 0, 0);//WM_派发的是WINDOWS标准消息
end; // 看到这里想必大家都明白了,如果还不明白(没用过Perform),我看再看看Perform,它到底做了什么:
function TControl.Perform(Msg: Cardinal; WParam, LParam: Longint): Longint;
var
Message: TMessage;
Begin
{你的消息赋予TMessage }
Message.Msg := Msg; ;
Message.WParam := WParam;
Message.LParam := LParam;
Message.Result := 0;//0表示返回不处理
if Self <> nil then WindowProc(Message);//不为空,将消息交给TControl的窗口过程WindowProc处理
Result := Message.Result;//返回结果
end; // 这里主要再看看WindowProc做了什么,TControl里面WindowProc是这样定义的:
property WindowProc: TWndMethod read FWindowProc write FWindowProc;
//在TControl的Create函数中:
constructor TControl.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FWindowProc := WndProc;
end; // 可见我们还要找到TControl 的WndProc过程才能明白究竟,WndProc过程定义如下:
procedure TControl.WndProc(var Message: TMessage);
var
Form: TCustomForm;
KeyState: TKeyboardState;
WheelMsg: TCMMouseWheel;
begin
if (csDesigning in ComponentState) then
begin
Form := GetParentForm(Self);
if (Form <> nil) and (Form.Designer <> nil) and
Form.Designer.IsDesignMsg(Self, Message) then Exit
end;
if (Message.Msg >= WM_KEYFIRST) and (Message.Msg <= WM_KEYLAST) then
begin
Form := GetParentForm(Self);
if (Form <> nil) and Form.WantChildKey(Self, Message) then Exit;
end
else if (Message.Msg >= WM_MOUSEFIRST) and (Message.Msg <= WM_MOUSELAST) then
begin
if not (csDoubleClicks in ControlStyle) then
case Message.Msg of
WM_LBUTTONDBLCLK, WM_RBUTTONDBLCLK, WM_MBUTTONDBLCLK:
Dec(Message.Msg, WM_LBUTTONDBLCLK - WM_LBUTTONDOWN);
end;
case Message.Msg of
WM_MOUSEMOVE: Application.HintMouseMessage(Self, Message);
WM_LBUTTONDOWN, WM_LBUTTONDBLCLK:
begin
if FDragMode = dmAutomatic then
begin
BeginAutoDrag;
Exit;
end;
Include(FControlState, csLButtonDown);
end;
WM_LBUTTONUP:
Exclude(FControlState, csLButtonDown);
else
with Mouse do
if WheelPresent and (RegWheelMessage <> 0) and
(Message.Msg = RegWheelMessage) then
begin
GetKeyboardState(KeyState);
with WheelMsg do
begin
Msg := Message.Msg;
ShiftState := KeyboardStateToShiftState(KeyState);
WheelDelta := Message.WParam;
Pos := TSmallPoint(Message.LParam);
end;
MouseWheelHandler(TMessage(WheelMsg));
Exit;
end;
end;
end
else if Message.Msg = CM_VISIBLECHANGED then
with Message do
SendDockNotification(Msg, WParam, LParam);
Dispatch(Message);//派发消息
end; // 这里主要讲讲Dispatch方法,它根据传入的消息调用消息的句柄方法,如果在组件类和它的父类都没有找到消息的处理句柄,Dispatch方法便会调用Defaulthandler(默认的消息处理方法),如下:
procedure TObject.Dispatch(var Message);
asm
PUSH ESI
MOV SI,[EDX]
OR SI,SI
JE @@default
CMP SI,0C000H
JAE @@default
PUSH EAX
MOV EAX,[EAX]
CALL GetDynaMethod
POP EAX
JE @@default
MOV ECX,ESI
POP ESI
JMP ECX @@default:
POP ESI
MOV ECX,[EAX]
JMP DWORD PTR [ECX] + VMTOFFSET TObject.DefaultHandler//调用默认的消息处理方法
end; // 而默认的消息处理如下,在SYSTEM.PAS单元里:
procedure TObject.DefaultHandler(var Message);
begin
end;
由以上代码看好像是没有任何处理过程,跟踪Object.DefaultHandler的汇编执行动作call dword ptr[ecx-$10],即调用Object.DefaultHandle,看看做何处理:
{Object.DefaultHandle}
Ret
Lea eax,[eax+$00]
即一个返回处理!
从最表面的Button.caption,我们走到了编译器层,可见所有东西都能找到它固有的原点!以caption的分析为基础,我们可以继续分析name属性和其它一些方法/函数。希望我这篇‘散文’能给大家理出点头绪:)
参考:http://blog.csdn.net/iseekcode/article/details/4868883
VCL源码分析方法论(以TButton.Caption属性的由来为例)的更多相关文章
- Vue.js 源码分析(六) 基础篇 计算属性 computed 属性详解
模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的.在模板中放入太多的逻辑会让模板过重且难以维护,比如: <div id="example">{{ messag ...
- vuex源码分析(二) state及strict属性 详解
state也就是vuex里的值,也即是整个vuex的状态,而strict和state的设置有关,如果设置strict为true,那么不能直接修改state里的值,只能通过mutation来设置 例1: ...
- Vue.js 源码分析(十) 基础篇 ref属性详解
ref 被用来给元素或子组件注册引用信息.引用信息将会注册在父组件的 $refs 对象上.如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素:如果用在子组件上,引用就指向组件实例,例如: ...
- jQuery-1.9.1源码分析系列(八) 属性操作
jQuery的属性操作主要包括 jQuery.fn.val jQuery.fn.attr jQuery.fn.removeAttr jQuery.fn.prop jQuery.fn.removePro ...
- Spring源码分析(八)AbstractBeanDefinition属性
摘要:本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 在上一篇中已经完成了XML文档到GenericBeanDefiniton ...
- knockout源码分析之computed(依赖属性)
一.序列图 二.主要代码文件 1.dependentObservable.js:主要包含ko.computed相关方法的处理2.dependencyDetection.js:主要包含依赖的监控上下文对 ...
- jQuery源码分析系列
声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://git ...
- jQuery-1.9.1源码分析系列完毕目录整理
jQuery 1.9.1源码分析已经完毕.目录如下 jQuery-1.9.1源码分析系列(一)整体架构 jQuery-1.9.1源码分析系列(一)整体架构续 jQuery-1.9.1源码分析系列(二) ...
- jquery2源码分析系列
学习jquery的源码对于提高前端的能力很有帮助,下面的系列是我在网上看到的对jquery2的源码的分析.等有时间了好好研究下.我们知道jquery2开始就不支持IE6-8了,从jquery2的源码中 ...
随机推荐
- php 文件上传类 实例分享
最近在研究php上传的内容,找到一个不错的php上传类,分享下. <?php /** * 文件上传类 * class: uploadFile * edit: www.jbxue.com */ c ...
- Python线性时间排序——桶排序、基数排序与计数排序
1. 桶排序 1.1 范围为1-M的桶排序 如果有一个数组A,包含N个整数,值从1到M,我们可以得到一种非常快速的排序,桶排序(bucket sort).留置一个数组S,里面含有M个桶,初始化为0.然 ...
- oracle-11g-配置dataguard
1.环境信息:系统:oracle-linux 5.7 数据库版本:Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit P ...
- windows鼠标消息处理与键盘模拟函数
1.鼠标坐标问题 BOOL GetWindowRect( HWND hWnd, LPRECT lpRect ); RECT x;//定义一个二维数组x ::GetWindowRect(hwn ...
- 【转载】link和@import的区别
link和@import的区别 原文地址:http://www.cnblogs.com/zbo/archive/2010/11/17/1879590.html 页面中使用CSS的方式主要有3种:行内添 ...
- cocos3.2中如何创建一个场景
1.可以将一些比较通用的东西放到Common.h中,这是一个.h文件,必须手动添加,且保证在classes目录里 #ifndef __COMMON_H__ #define __COMMON_H__ # ...
- python学习小结3:函数
Python是对接口编程,而不是对数据类型编程.例如我们定义了一个函数,在函数里用到了in这个接口,那么只要传入的参数实现了这个接口就可以,我们不在乎它是list还是tuple. 简单的函数 使用de ...
- description 数组的中文打印
打印一个对象:NSLog(@"%@", stu); 默认情况下打印的时对象的名字和内存地址:这时需要重写description方法 // 重写description方法 - (NS ...
- listview中getview异步加载网络图片
前言:本以为异步加载挺简单,因为网上代码多,但真想要做好,还真不那么简单,从看代码到弄懂再到自己写,实在是有太多的东西需要学了,用了两天的时间,终于弄出来了,因为用到回调函数,所以理解起来可能难度有点 ...
- ios7去除手势滑动返回
if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) { sel ...