从Form1.Caption = “Hello World” 说起
与很多人一样,我的第一个Windows程序也是从Visual Basic开始的,我们的文章标题就是来自VB中的一条简单语句,作用是改变程序窗体标题。当然在不同的开发工具中也可以表现为以下形式:
Visual Basic :
Form1.Caption = “Hello World” 或 me.Caption = “Hello World” 或直接 Caption = “….”
C++Builder: Form1->Caption = “Hello World”;
Delphi : Form1.Caption := ‘Hello World’;
Visual C++ : SetWindowText(“Hello World”);
以及原始的SDK中的实现形式:
SetWindowText(hMain, “Hello World”); (假设hMain是主窗体的句柄)
这么列出来不是为了比较编程语言的不同,我们的问题是:当我们简单的将一个字符串赋给Caption(这儿也可以是别的变量名)后,编译器又做了那些事来设置窗体标题的。答案很简单:编程语言可以有很多种,但Windows只有一个,它们都调用了Api函数:SetWindowText(),或直接发一个WM_SETTEXT消息给指定窗体。
SetWindowText函数原型为:
BOOL SetWindowText(
HWND hWnd, // handle of window or control
LPCTSTR lpString // address of string
);
两个参数分别为:窗体句柄,与字符串地址,并且根据操作的成功与否返回:TRUE, FALSE;由此看来Visual C++的调用形式倒比较容易理解,MFC类库封装了一组同名函数,并通过C++类的封装,简化了操作,具体的MFC实现大致是如下形式:
void CWnd::SetWindowText(LPCTSTR lpszString)
{
ASSERT(::IsWindow(m_hWnd)); //先检查m_hWnd是否为有效的窗体句柄
//调用Api函数,m_hWnd是CWnd类的内部成员变量,代表该窗体的句柄
::SetWindowText(m_hWnd, lpszString);
}
这是MFC简化了Windows编程的一个表现。
当然SetWindowText不只是用来置对话框或主窗体标题,由于我们在屏幕上看到的各种按钮(Button),编辑框(Edit), 文本标签(Static或Label)等控件实际上都是大大小小的窗体或子窗体,因此你也可以由此设置某个按钮的标题。反之要获得窗体的标题可以用GetWindowText函数,函数原型与SetWindowText类似。虽然Set, Get, WindowText很容易理解但还是没有简单的:
设置标题用: s1 = “Hello World”, Caption = s1
获得标题用 s1 = Caption
看起来更直观。
好的,那么我们就来看看C++Builder与Delphi中是如何实现的。
在刚刚使用C++Builder的时候,我除了惊讶C++Builder设计应用程序的快捷方便外,另一个令人困惑的问题就是当我写了 Form1->Caption = “Hello World”;时C++Builder是如何实现SetWindowText操作的,熟悉C++的朋友都知道: 假设Form1是一个类的实例,那么当我们写出 Form1.bOk = true; 或Form1.nSize = 12;这样的语句时我们是在对一个类的成员变量赋值。如果Form1是一个指针则以上句子就是: Form1->bOk = true; Form1->nSize = 12;而对一个变量成员赋值的单条语句除了简单的赋值操作外,按照C++的语法并不会做别的事。如果要进行有关操作如显示一个消息框,就应该调用相应的成员函数。当时我的理解是C++Builder一定是在内部调用了相应的Set,Get开头的成员函数。并且没有理由的认为编译器是当我们按下开始编译后先扫描一下当前的代码行,碰到Caption = “….”这样的语句就把它替换为内部的 Form1.SetCaption(“….”);
当然事情不会这么简单,由于C++Builder与Delphi共享同一个VCL类库,甚至可以使用以Pascal语言编写的组件库,因此C++Builder在某种意义上讲已经不是一个纯粹的C++编译环境了,显然Borland公司对C++Builder做了一些小手脚,使之与Delphi相兼容。这么做好处很多,二者使用类似的IDE环境,使用同一种窗体(*.dfm)格式,最重要的是使用同一个VCL类库。只要使用过一种开发环境后,就很容易掌握另一个开发工具。缺点是在我们在C++Builder中调试程序时很容易就会跟踪到一大堆以begin, end开始的Pascal语句。也就是前面说的二者使用同一个VCL类库的结果。很遗憾当时我对Pascal语言一窍不通,也没兴趣看,因此对于C++Builder中的实现形式还是不清楚,只是隐隐约约觉得是C++Builder调用的某些Pascal代码在实现以上操作。
后来因为”工作需要”,要维护一个用Delphi写的考试程序,临时突击了一下Pascal语言。其中看到Pascal类的实现时才终于解决了我在C++Builder中的疑问。与C++类似,在Object Pascal中也支持相似的类实现。有自己的:private, protected,public,以及类的继承等等。一个简单的Pascal类大致为以下形式:
TMyPoint = class(TObject)
private
FX, FY : Integer;
public
function PtInRect(const r : TRect) : boolean;{判断当前的点是否在指定的Rect中}
function GetX : integer;
function GetY : Integer;
//设置FX 为 nX,并进行错误检测,也可以根据需要执行某些操作
procedure SetX(const nX : integer);
procedure SetY(const nY : integer);
end;
其中的SetX, GetX分别用来设置与获得当前的X位置。将FX设为类的私有变量并通过SetX函数设置FX的好处在于当调用SetX时若给出了一个不合适的值(如:负数,或超出特定范围)时能够给出一个Assert断点,以提醒程序员,或在实际执行中一律将不合适的值转换为0,并抛出一个异常。
当然我们会想如果既能简单的: pt.X := 12; a1 := pt.Y;而且在赋值时又能进行错误检测就更方便了;幸运的是Borland的程序员在设计VCL (Visual Component Library)时也是这么想的,由此在Delphi中引入了一组新的关键字: property,read,write;以实现以上需要,改写的TmyPoint类为如下形式:
1: TMyPoint = class(TObject)
2: private
3: FX, FY : Integer;
4: procedure SetX(const nX : integer) ;
5: procedure SetY(const nY : integer) ;
6: function GetX : Integer;
7: function GetY : Integer;
8: public
9: {判断当前的点是否在指定的Rect中}
10: function PtInRect(const r : TRect) : boolean;
11: property X : Integer read GetX write SetX ;
12: property Y : Integer read GetY write SetY ;
13: end;
14:
15: procedure TMyPoint.SetX(const nX: integer);
16: begin
17: if (nX<0) or (nx>1000) then
18: FX := 0
19: else
20: FX := nX;
21: end;
22:
23: function TMyPoint.GetX: Integer;
24: begin
25: result := FX;
26: end;
27:
这样当你执行: pt.X := 200时编译器知道你要执行一个write操作,就会自动跳到procedure TMyPoint.SetX(const nX: integer);处以进行赋值,反之当为read操作时则调用GetX函数。如果GetX只是简单的返回内部的FX值也可以将11行改为:
property X : Integer read FX write SetX ;
由此可知:当我们在Delphi,或C++Builder中写下Form1.Caption = “…..”时,真正执行的是某个具体的函数,而不是赋值操作,在这背后并没有什么魔法存在,一切是那么的清楚。如同C++的操作符重载一样,Delphi也重载了类的”.”操作符,这么做不仅看起来更直观,减少了代码书写,从某种意义上讲也体现了面向对象程序设计的思想。
好了,该谈谈VB了,实际上VB并不是一个完全面向对象的语言,缺少许多必备的语言特性,如虽然提供了类的实现,却不允许类的继承,此外在类型检查,错误处理上也不够健壮。最糟糕的是微软并没有像VC6.0一样提供类似的MFC源代码。当你想跟踪VB中的InputBox,Msgbox,Sqr,trim函数时你是看不到具体实现的。这样就阻止了我们去了解VB中 Caption = “Hello World”的实现原理,当然擅长DEBUG的朋友可以自己去发掘。好消息是据说在VB.net中BASIC语言已被扩展为完全的面向对象语言,并支持异常处理。我翻了几本VB.net与C#的书,感觉上VB.net中的Basic更像C++语言了,比如在异常处理这一块,而C#倒很像现在的VB,增加了一大堆”.”操作。(顺便说一句,因为在学习.Net上大家还处在盲人摸象的阶段,所以书店里的.Net系列图书大都很糟糕,请小心挑选。)
小结:
本文简单的分析了VB,VC,C++Builder,Delphi中Caption = “…”操作的实现原理。所谓“存在的就是合理的“不等于我们看到就都是理所当然的。在进行程序设计时,多问几个为什么有助于提高自己的程序设计水平。
好了,不经意间拉拉扯扯写了这么多,唯一的问题是这样的文章适合什么人看,高手不用看,太简单了。新手又可能看不大懂我在说什么:p,如果你喜欢这篇文章或有什么问题与建议请与我联系(fpefans@sina.com)。
by 纪杨 2-5-2002
如要转载,请保持文章的完整性,别的随便。