帖]delphi5开发人员指南学习笔记(1)

时间:2022-11-08 23:19:41
 
[转帖]delphi5开发人员指南学习笔记(1)
我学delphi断断续续有两年时间了,中间曾头脑发热看过一本C++的书,又看了一本api sdk编程的书,后来又想参加计算机水平考试,看了几个月复习资料,但都半途而废。两年来,什么也没学好。痛定思痛,下决心只学DELPHI,决定先找一本精典书看看。今年四月份,在大富翁论坛上发贴,寻找看DELPHI5开发人员指南的学习伙伴,有几位朋友回了帖,于是就看了起来。今年5月29日,我上课的时候,一个学生告诉我,找到一个申请免费主页空间的地方,于是就有了本站的诞生,由于网站是在两三天时间里匆忙做成的,所以现在站点里的内容都是我原来在网上收集的,所以有不少网友批评我。一个站点只有有自己的东西才有生命力,才能吸引别人常来,我也知道这个道理,但自己文笔生涩,而且也没什么可写的。于是想到把自己看书的过程以笔记的形式写下来,一来做为学习的总结,二来可以和大家交流,逼迫自己坚持学下去,防止再次半途而弃。现在我已经看到第九章了,所以前面的几部分都是我补写的。这个笔记我想这样写,一般一篇对应书上一章,每篇分两部分内容,一是本章的主要内容和我的理解和体会,二是本章中我的疑点。本人才疏学浅,恳请大家多多指正。

第一章 delphi5下的windows编程

  虽然本章内容不多,但有几个概念我还是第一次接触:

  1、无约定的编程方式
  与传统的WINDOWS程序(就是直接用API的SDK编程)相比,delphi中一般不需要直接对WINDOWS消息进行处理,DELPHI通过事件机制对WINDOWS消息机制进行了封装。程序员可以在事件的处理中实现对WINDOWS消息的处理。无约定的编程方式是指程序员可以在DELPHI的事件处理程序中什么事都不做,系统照样可以正常运行。因为DELPHI会自动调用这个对象类的基类中的消息处理过程来处理消息(我不太清楚“调用对象类的基类中的消息处理过程来处理消息”和后面第五章中自定义消息处理过程中用的“Inherited”的作用是否相同)。

  虽然事件机制的无约定的编程方式不能对WINDOWS消息进行直接灵活的控制,但可以使你在对WINDOWS消息机制还不太清楚的情况下也能进行WINDOWS程序设计。当然如果你的水平很高,也可以绕开事件机制,直接对WINDOWS消息进行截获和控制,也就是说DELPHI对不同层次的人都是适用的。说到这里,我又想起大富翁上早期一个比较delphi和VB的帖子,把VB比喻成傻瓜像机,把delphi比喻成带傻瓜功能的专业像机,真是形象。

  2、加速原型化
  用户界面的设计和程序的布局就称为原型化。相信用过API编程,再用可视化编程环境的人对这一点都是非常认同的。   

  总结
  本章是全书的引入,没什么实际内容,讲了讲DELPHI的产品家族历史。然后对DELPHI的IDE(集成开发环境)做了一些介绍,完成了DELPHI下最简单的一个例程。虽然自己用DELPHI这么长时间了,说实话,对它的IDE环境还是很不熟悉,我想主要是看书看得多,但动手动的少的原因,很多菜单、快捷键和功能从来都没用过,今后一定要多动手,连开发环境都不熟悉,怎么用的得心应手呢?“公欲善其事,必先利其器”就是这个道理吧。

  万事开头难,终于写完了自己的第一篇笔记,也不知道自己讲的对不对。希望大家多多鼓励,多到留言板提出宝贵意见。


第二章 object pascal语言(上)

  这一章内容很丰富,可以说就是一本object pascal的语法书。介绍了object pascal语言的基本语法和语义,内容包括:注释、变量、运算符、函数、过程和类型,还对面向对象编程、对象、域、属性、方法、Tobject、接口、异常处理和RTTI等方面。

  我以前看过一点object pascal方面的语法,所以对前面的一部分内容看得较快,从字符串以后看的稍认真一点。

一、注解

  object pascal支持三种注解:{}、(*  *)、//
  需要注意的是,object pascal中注解不能嵌套。

二、函数和过程的新特征

  1、当过程或函数没有参数时,圆括号可以省略。
  2、从delphi4开始引入了函数重载的概念。需要重载的函数要用overload声明。如:
  procedure hello(i:integer);overload;
  procedure hello(i:string);overload;
  procedure hello(d:double);overload;
  但重载要小心使用,因为重载使程序的执行和调试更加困难,所以对重载既不要回避,也不要滥用(那么为什么还要用重载呢,重载有什么好处呢,是不是不得已的办法呢?)。
  3、缺省值参数
  当过程或函数有缺省值时,调用时可不提供参数。
  声明有缺省值参数的函数或过程时,在参数类型后加一个等号和缺省值就行了。
  如:procedure abc(s:string;i:integer=0);在调用该过程时,可以两个参数都指定,也可只指定一个参数,另一个用缺省值。
  如:abc('hello',26); abc('hello');
使用缺省值参数的规则:
  只能放在参数列表的最后;必须是有序类型、指针类型或集合类型;必须是数值参数或常量参数,不能是引用参数或无类型参数。

三、变量

  在object pascal中,变量声明通常和变量初始化分开。在var块中,只能对全局变量赋初值。delphi会自动对全局变量赋初值,当程序开始时,所有整形变量赋为0,浮点数赋为0.0,指针赋为nil,字符串赋为空工,所以在源代码中,不必为全局变量赋零初值。

四、常量

  在object pascal中,对常量赋初值时,通过不必声明常量的类型,如果指定了常量的类型,delphi会把这个常量当作赋初值的变量。在程序中,常量值是只读的,不能试图改变。

五、运算符

  只谈谈按位运算符,这种运算符能修改变量的单独各位,如把一个数按位左移(shl)或右移(shr),或对两个数按位执行与(and)、或(or),取反(not)、异或(xor)运算。  

六、object pascal类型

  1、object pascal最大的特点是数据类型特别严谨,这是pascal编译器一贯的风格,属于强类型语言,basic属于弱类型语言,变量不声明就可以使用,害人不浅。
  2、在字长不同的计算机中,一个字符在长度上并不一定就是一个字节,所以不要想当然的认为一个字符就是一个字节,应该使用sizeof()函数,返回类型或实例的长度。
  3、还要特别注意字符串,ansistring字符串是delphi缺省的字符串,它的长度没有限制,最长可达4G,并且是生存期自管理类型,即它是动态分配的并且在它离开作用域时就自动释放资源,最重要的是它以null结束,和windows系统的字符串兼容,它能被当作pchar使用,但还要强制转换成pchar类型,需注意的是,使用了把ansistring类型强制转换成pchar的函数或过程后,要手工把它的长度恢复为原来以null结束的长度,用realizelengh()函数来恢复。所以一般情况下应尽可能使用ansistring,因为它是生存期自管理类型,而pchar的分配和释放是人工操作的。只有在用到api函数时,用pchar类型比较简单。ansistring类型有引用计数功能(指几个字符串能指向相同的物理地址,复制字符串仅是复制了指针,而不是复制实际的字符串,所以速度很快,只有当它改变后,系统才会为它实际分配空间,把修改过的字符串复制到自己的空间,并释放一个引用数)。我们常用的string类型当编译指示$H为正时是ansistring类型,当$H为负时是shortstring类型,就是我们在DOS下的pascal中使用的字符串,和widows api使用的字符串不兼容,大家要注意。
  4、变体类型 variant。delphi2开始引入变体类型,主要是为了支持ole自动化操作。因为它在编译期类型是不确定的,能够在运行期动态改变类型。它是生存期自管理类型,可以支持所有简单数据类型,可以强制类型转换成简单类型。
   如:var v:variant;
begin
v:='hello world';//是字符串类型
      v:=1; //是整数
      v:=3.14; //是浮点数 string(v) //把它强制类型转换成字符串型
      v:=true; //是布尔型
      v:=createloeobject('word,basic');//是ole对象
  注意,由于要对variant类型进行兼容性检查,并进行必要的转换,所以导致额外的开支较多,最好不要使用variant类型,如确实要使用,最好先显式的对variant进行强制类型转换。

七、用户自定义类型
  1、数组
  object pascal允许建立除文件类型外的各种类型数组。注意:object pascal中数组的下标不一定从0开始,如,a:array[28..36] of integer; 该数组的下标就是从28开始的,所以在for循环中使用数组时一定要小心。hign(数组变量名),low(数组变量名)函数分别返回数组的下标的上边界和下边界。
  object pascal编译器特别允许,字符数组的下标通常从0开始,这样它就能传递给需要pchar类型的函数。
  2、动态数组
  动态数组在编译时不知道它的维数,可在运行时动态分配。它在声明时不指定维数。如:a:array of string; 在使用动态数组前,要用setlengh()过程为动态数组分配内存,如:setlengh(a,33),为数组a分配33个元素的空间。以后就可以象访问普通数组一样来访问a。动态数组的下标通常以0为基准,并且是生存期自管理类型,用完后不需要显式释放。如果在离开作用域前需要手工释放,把nil赋值给动态数组即可,如a:=nil;
  也可以定义动态数组,声明如下
   a:array of array of integer; //声明一个两维的动态数组
   setlengh(a,3,5);//给刚声明的动态数组分配内存
   a[0,2]:28;//给分配过空间的动态数组赋值
  3、记录
   定义记录:
    type
     a=record
     i:integer;
     j:string;
     end;
  使用记录:
    var
     x:a;
     begin
     x.i:=30;
     x.j:='hello';
     .....;
     end;
  4、集合
  集合是object pascal特有的数据类型。它表示一组有序数、字符、或枚举值。一个集合最多只能有255个元素,并且元素只能是有序类型。声明如下:
   type
    x=set of 1..10; //集合x的可能值为1到10
  使用集合:
   a:set of 1..10;
   a:=(1,3,5,7);
  集合的操作符:
  关系运算符:in,用来判断一个元素是否在一个集合中,if (3 in a) then...
  增删运算符:+或include()过程用来在集合中增加元素,-或exclude()过程用来在集合中删除一个元素。如a:=a+[2];exclude(a,2);
  交集运算符:*,a*b产生集合a和b的交集。

  5、对象
  在object pascal中,对象类型也可当作记录类型,只是它还包括函数和过程。delphi中的对象和c++中的对象在内存中的布局不一样,在delphi中不能用c++的对象,反之也一样。
  定义对象:
   type
对象类型名=class(父对象名)
     变量定义;
     函数和过程定义;
end;

  6、指针
  指针表示内存的位置。object pascal中能用指针类型是pointer,它是列类型指针,只指向内存地址,不管数据类型,所以建议大家使用有类型指针。有类型指针,在type部分用^(或pointer)运算符声明:
   type
    a=^integer;  //a是指向integer类型的指针
    foo=record
    s:string;
    i:string;
   end;
   a1=^foo;    //a1是一个指向记录的指针 

   var
    p:pointer; //p是无类型指针的一个实例
    p1:a1; //p1是a1指针类型的一个实例
  如果一个指针没有指向任何数据,它的值为nil,它就被称为零(nil)指针或空(null)指针。要访问一个指针指向的内容,在指针变量的后面加上^运算符,这种方法称为对指针取内容。如:
   program a;
    type
    myrec=record
    i:integer;
    s:string;
    r:real;
    end;
    pmyrec=^myrec; //定义一个指向记录的指针类型
   var
   rec:pmyrec; //定义一个myrec指针类型的实例变量
   begin
    new(rec); //为rec指针变量实例分配内存
    rec^.i:=10; //为rec指针指向的记录中的域赋值
    rec^.s:='hello'; 
    rec^.r:=6.23;
    ........;
    dispose(rec);  //释放rec的空间
   end.
在上例中,函数new()可以很安全的为一个指针分配指定长度的内存空间。具体空间长度由指针的类型决定。但该函数不能为pointer和pchar类型分配内存,因为编译器不知道它们需要多大的内存空间。dispose()函数用来释放new()函数分配的内存。

  7、类型别名
   object pascal能为已定义的类型创建的新的名字,称为别名。如为integer类型创建一个新的名字myinteger。如:
   type
    myinteger=integer;
这样,以后凡用到integer类型的地方,都可以使用myinteger来代替。

  8、强制类型转换
  通过强制类型转换,能使编译器把一种类型的变量当作另一种类型的变量来使用,这非常有用,因为编译器对函数的形参和实参的类型匹配检查是非常严格的,为能通过检查,经常需要把一个变量的类型转换为另一种类型。如: 
   var
    c:char; b:byte;
    begin
     c:='a';
     b:=byte(c); //先把c强制转换成byte类型,再赋值给b;
    end.
注意,只有两个变量的数据长度一样时,才能进行强制类型转换。

第二章 object pascal语言(下)

十、测试条件
  1、if语句
  if (x=7) and (y=8) then....注意,如果判断语句的条件有多个,要用括号把各个条件分别括起来。
  2、case语句
  pascal中的case语句就象c++中的switch语句,用来在多个可能的情况下选择一个。case语句的选择因子必须是有序类型,如integer类型,不能用非有序的类型如字符串类型作选择因子。

十一、循环
  循环是一种能重复执行某一动作的语言结构。
  1、for循环。适合于事先知道循环次数的情况。
  2、while循环。当条件为真时,执行循环。典型的应用例子是,当文件没有到达文件结尾时,对文件进行某种操作。先判断再执行。如:
  program filelist;
  var
   f:textfile;
   s:string;
  begin
   assignfile(f,'a.txt'); //把文本文件a.txt赋值给变量名f
   reset(f);
   while not eof(f) do
    begin
    readln(f,s);
    writeln(s);
    end;
   closefile(f); //关闭文件f
  end.

  3、repeat...untile循环。一直做某事,直到条件为真时为止,和while语句相似,但它是先执行再判断,所以至少能把循环体执行一次。 
  4、break过程。在for、while、repeat循环中,调用break(),可直接跳出循环,执行循环语句后的语句。在循环体中,当某个条件为真,需要立即跳出循环时使用它。
  5、continue过程。在循环体中调用该过程可以跳过本次循环的剩余代码,直接开始下一次循环。

十二、过程和函数
  在object pascal中,函数或过程的参数有三种传递方式:
  1、值参数传递。这是object pascal的默认传递方式,在这种传递方式中,程序将这个变量的值又复制了一个本地副本,然后把这个副本放到函数栈中,函数或过程只对这个副本进行操作,而不会影响原来的变量的值。如:
  procedure foo(v:string);
  begin
  v:='world';
  end;
  var 
   s:string;
  begin
   s:='hello';
   foo(s);  //在该过程中修改的只是s的一个副本,s 本身并没有被修改。
   writeln(s); //s的值仍然是'hello',而不是'world'。
  end.
  2、引用参数传递。程序把参数的地址传给函数或过程,这时函数或过程对参数的操作会直接改变参数变量的值。声明时只需在参数表中加上关键字var就行了。还是上面那个例子,在引用参数传递方式下就是:
   procedure foo(var v:string);
   begin
    v:='world'; //在这里,参数v的值在调用中被改变了。
   end;
  var 
   s:string;
  begin
   s:='hello';
   foo(s);  //在该过程中参数v的值在调用中被改变了。
   writeln(s); //s的值变成了'world'。
  end.

  3、常量参数传递。传递给函数或过程的参数如果不想被改变,可以用关键字const把参数声明成常量参数。如procedure foo(const v:string);(这一点我不是很清楚,既然这种情况下参数的值不能被改变,那么这种方式和值参数传递方式有什么不同呢?) 
  每一个object pascal函数都有一个隐含的本地变量,它就是result,它包含函数的返回值。

十三、作用域
  作用域是指一个过程、函数或变量能被编译器识别的范围。如,全局变量和常量的作用域是整个程序,而过程和函数中的局部变量的作用域只是这个过程或函数。

十四、单元
  pascal程序就是以单元为模块的。单元由函数和过程组成,单元中的函数和过程能被主程序调用,一个单元至少由四部分组成:
1、标题部分。一个unit语句,这一句必须放在单元文件的开头,以标识单元的名称。且单元的名称必须和单元文件的名称相同。
  2、uses子句,用来列出该单元要引用的其它单元。一个单元可以有两个uses子句,一个在接口部分,一个在实现部分。
  2、接口部分。用interface关键字,且这个关键字独占一行。这部分用来定义能被程序和其它单元共享的内容,如声明变量、常量、类型、过程、函数等。注意,在这一部分的过程或函数只声明,不定义。
  3、实现部分。用implementation关键字,在这部分具体实现在接口部分声明的过程和函数。

  注意,这里有一个循环单元引用的问题,即单元A调用单元B,单元B也调用单元A,在程序中出现循环单元引用,说明了程序的设计有缺陷,应该避免在程序中出现循环单元引用,解决的办法是,把单元A和单元B*有的代码移到第三个单元中,然后单元A和单元B只引用单元C就行了。如果确实需要循环单元引用的,必须在单元A中的接口部分的uses子句中引用单元B,在单元B的实现部分的USES子句中引用单元A。

十五、包
  包是把若干个单元集中在一起,以类似于dll的形式存储的模块。应用程序在运行时才链接包中的单元,在编译和链接时不包含包中的单元,而且包中的单元能被多个应用程序共享,这点和dll是相同的。

  1、使用delphi中的包。要在程序中使用包很简单,只要在project|options的packages对话框中先中build with runtime packages复选框,以后当编译和运行程序时,应用程序就会动态地链接运行期包中的单元,而不把包链接到exe或dll中。
  2、在delphi中创建包。在delphi中是用包编辑器来创建包的,file|new|package菜单项可以启动包编辑器,包编辑器会产生一个delphi包源文件(*.dpk),dpk文件的格式很简单:
  package 包标识符
   requires package1,package2,....;//该包需要调用其它的包
   contains //这个包中包含的单元
    unit1 in 'unit1.pas';
    unit2 in 'unit2.pas';
    ........;
    end.
注意,contains中包含的单元不能同时被requires中的包所包含。

十六、面向对象的编程
  面向对象的编程把对象做为程序的模块。对象是数据和行为的集合。面向对象的编程有三个特点:
  1、封装性。把数据和代码结合在一起,对外隐藏了实现的细节,封装性的好处是有利于程序的模块化。
  2、继承性。一个新的对象能继承父对象的属性和方法,这一点就象遗传。继承性的好处是可以共享代码
  3、多态性。就是一个对象类型可以产生多个对象实例,每个实例还可以有所不同。

  关于多重继承。object pascal不支持多继承。c++支持。
  对象的域。就是对象中的数据变量,相当于c++中的数据成员。
  对象的方法。就是对象中的过程和函数,相当于c++中的成员函数。
  对象的属性。属性是对象外部程序访问对象内部数据和函数、过程的接口。它对外隐藏了一个对象的具体实现细节。注意,外部程序不要直接访问对象的域,而要通过对象的属性来访问。
  基于对象:能操纵对象但不能创建对象,也不能派生对象。
  面向对象:既能操纵对象,也能创建和派生对象。

十七、结构化异常处理
  结构化异常处理是一种处理错误的手段,使应用程序能从致命的错误中得到恢复。delphi中预定义了一些错误异常类,如内存溢出、被零除、文件输入输出错误等。也可以定义自己的异常类。 
  1、try...finally...end;
  在上面的结构中,不管在try部分出现任何情况,finally部分的语句一定会得到执行。这个结构相当于对计算机说:“HI,程序,请执行try和finally之间的代码,如果执行完毕或出现异常,就执行finally和end之间的代码。”这个结构尤其适用于释放资源。
  2、try...except...end;
该结构相当于对计算机说:“HI,程序,请执行try和except之间的代码,如果出现异常就跳到except和end之间,进行异常的捕捉和处理。”这个结构常用来捕捉特定的异常。
  以上两种异常处理结构常嵌套使用,既保证了某些必需的操作(如关闭文件、释放内存等),又能对异常做出响应和处理。如:
  program fileio;
  uses classes,dialogs;
  var
   f:textfile;
   s:string;
  begin
   assign(f,'foo.txt');
   try
    try
     reset(f);
     readln(f,s);
    finally
     closefile(f);
    end; 
   except
    on EinoutError do
    showmessage('文件输入输出错误!');
   end;
  end.
  3、try...except...else...end;
这个结构和上面的非常相似,只不过它除了能捕捉特定的异常外,还能捕捉其它异常,适用于捕捉没有预料到的异常。   4、异常类
  异常是一种特殊的对象实例,它在异常发生时才实例化,在异常被处理后自动删除,异常对象的基类被称为exception。在该对象中,最重要的是message属性,它是一个字符串,提供了对异常的解释,它所提供的信息是由发生的异常来决定的。
  5、异常的执行流程
  在一个异常发生后,应用程序的流程就跳到异常处理过程,一直等到该异常实例被处理结束并释放空间后才返回。异常的执行流程取决于调用栈,所以跳转的范围是整个程序,而不限于一个过程或单元。 
  6、重新触发异常
  当要对try...except中的一条语句进行特殊处理,并要使异常能传给外层默认的异常处理过程时,就需要重新触发异常处理。如下例:
 try
  .....;
  try
   .....;
  except
   on EsomeException do //当捕捉到某个异常后
    begin
     raise; //通过关键字raise把该异常传给外层的异常处理过程
    end;
  end;
 except
  on 'EsomeException' do something; //外层异常处理过程捕捉到该异常,并做出某些处理。
 end;
   
  总结:这一章内容非常多,我感到它是对后面章节做的一次object pascal的语法准备,基本上是理论性的知识,实例不多,有些内容如delphi对象的方法、属性、接口等,讲的较深,不容易理解,我也是大致看了看,有些地方没搞清楚,等到以后用到时,再回过头来认真看。不然,容易陷入理论和概念的泥潭,反而不好。


第三章 WIN32 API

 

  主要内容:本章对win32系统做了一个简单的讲解,并不是win32系统的详细文档,那也不是一章的篇幅所能包容的。另外还对win32和win16的区别做了叙述并讲解了几个概念。通过本章,能对win32系统有一个大致的了解。

  一、win32系统的对象
  win32系统中有两种基本的对象类型:内核对象和GDI/用户对象。这里的对象和上一章讲的对象的是不同的概念。
  1、内核对象。
  内核对象是win32系统原有的。它包括:事件、文件映射、文件、邮件槽、互斥、管道、进程、线程、信号灯等,win32 api包含了针对不同内核对象的函数。

  线程和进程(win32管理对象的基础)

  进程:就是一个正在运行的应用程序,或应用程序的一个实例。在win32中,可以同时激活几个进程,每个进程都有4GB的地址空间,在这4GB的空间中,包含已分配的内存、线程、文件映射和该进程调用的dll等。进程是惰性的,即进程本身不执行任何代码,每个进程都拥有一个且只有一个主线程,主线程在进程的环境中执行代码。每个进程都有一个它自己的实例句柄,这个实例句柄放在一个叫Hinstance的全局变量中,进程可以把Hinstance传递给那些需要实例句柄的WIN32 API函数中。 

  线程:是一种操作系统对象,代表一个进程中的要被执行的代码的路径。一个进程除只有一个主线程外,还可以有多个一般线程。

  内核对象只能被创建它的进程访问,不能被其它进程访问。当一个内核对象被进程创建后,它存在于该进程的地址空间中,该进程可以获到这个内核对象的句柄。
  在win16系统中,没有内核对象的概念。

  2、GDI/用户对象
  GDI对象包括:画刷、画笔、字体、调色板、位图、区域等。在WIN32系统中,GDI对象被进程创建后,存储在该进程的地址空间中,所以GDI对象不能被多个进程共享。每个进程都有一个自己的LDT(局部描述符表),用来存储该进程中所有的GDI对象的句柄,
  用户对象包括:窗口、窗口类、原子等。
  用户对象和GDI对象在些类似,也是由WIN32的用户子系统管理的。然而用户对象的句柄不象GDI对象那样保存在进程的地址空间中,而是有一个专门的用户句柄表来管理用户对象,所以用户对象可以在不同的进程间共享。

  二、多任务、多线程
  多任务:指操作系统能同时运行多个应用程序。但实际上是操作系统把CPU轮流为每个应用程序服务,并不是真正的多任务,只是用户感觉不到罢了。
  WIN32系统的多任务是抢先式多任务,它的任务有优先级,而早期的WINDOWS中的多任务没有优先级。在WIN32中,操作系统把CPU时间分成片,然后把这些时间片分配给每个线程。WIN32基于每个线程的优先级来管理时间的分配。
  多线程:是指应用程序内部的多任务,是和操作系统级的多任务不同层次的多任务。这意味着应用程序可同时进行不同的处理。一个进程可以有多个线程,每个线程都有各自不同的执行代码。一个线程可能要依赖另一个线程,这时,必须要使多个线程间保持同步。同步技术能使多个线程协同执行。

  三、WIN32的内存管理
  1、线性内存模式
  WIN16使用的是段式内存管理模式,地址用基地址:偏移量的形式表示。它还有一个限制,当数据结构超过64K时,很难管理。
  WIN32采用32位线性内存管理模式。每个地址都代表唯一的内存位置,不再分偏移量和基地址。在这种管理模式下,没有64K的限制,每一个进程都有自己的4GB的地址空间。可以安排很大的数据结构。
  2、WIN32系统是怎样管理内存的
  你的计算机不太可能装有4GB的内存,那么WINDOWS是怎样获得比物理内存大得多的地址空间呢?原因是WIN32系统使用的是虚拟地址,32位地址并不真的代表物理内存的一个位置,而是32位的虚拟地址。因此,每个进程获得的4GB的地址空间也是虚拟地址空间,其中上端的2GB属于WINDOWS,下端的2GB是放置应用程序以及可以分配内存的地方。这种内存模式的优点是,一个进程中的线程不能访问其它进程的地址空间。因为同样的地址空间如$54545454在不同的进程中指向不同的位置。
  因此,在WIN32中,我们说每个进程都有4GB的地址空间,并不是真的为它分配4GB的内存,而是指进程具有访问4GB地址空间的能力,一个进程真正能访问的内存的大小取决于1、计算机上安装了多少物理内存;2、磁盘上有多少空间可以被页交换文件使用。对一个进程来说,物理内存和页交换文件都是按页来划分使用的。

  在WIN32中,程序员有三种方式来使用内存:虚拟内存、内存映射文件和堆。

  1、虚拟内存。为了使用虚拟内存,WIN32提供了一些底层API函数。这些函数的名称一般为VirtualXXXX(),它们提供了分配、释放、锁定虚拟地址的功能。如:
  Vi r t u a l A l l o c ( )  在进程的虚拟地址空间中保留和/或分配页
  Vi r t u a l F r e e ( )  释放进程虚拟地址空间中的页
  Vi r t u a l L o c k ( )  锁定进程虚拟地址空间的一段区域,使其不能被交换到磁盘的分页文件中
  Vi r t u a l U n L o c k ( )  解除对指定区域的内存的锁定,使其可以被交换到分页文件中
  Vi r t u a l Q u e r y ( )  返回有关进程虚拟地址空间的信息
  Vi r t u a l Q u e r y E x ( )  作用与virtualquery( )类似,但它可以指定进程
  Vi r t u a l P r o t e c t ( )  修改进程虚拟地址空间中已分配页的保护模式
  Vi r t u a l P r o t e c t E x ( )  作用与virtualprotect( )类似,但它可以指定进程

  2、内存映射文件(也叫文件映射对象)。它允许象访问动态分配的内存那样访问磁盘文件,通过将磁盘文件的全部或部分映射到进程的地址空间来实现的。只要使用一个指针,就可访问文件中的数据。
  3、堆。堆是可被分配的小块的连续的内存。使用堆可以有效的分配和使用动态内存。有专门的API函数提供使用堆的功能。这些函数的名称一般为HeapXXXX(),如:
  HeapCreate( )  在虚拟地址空间中保留一块连续的地址,并且在物理内存中为堆首部分配空间
  HeapAlloc( )   在一个堆中分配一块不可移动的内存
  HeapReAlloc( ) 在一个堆中重新分配一块内存,可以改变堆的大小和属性
  HeapFree( )   从堆中释放用H e a p A l l o c ( )分配的内存
  HeapDestroy( ) 删除用H e a p C r e a t e ( )创建的堆

  四、WIN32的错误处理 
  大多数WIN32 API函数都返回TRUE或FALSE,以表示函数调用成功或失败。如果函数调用不成功,必须使用API函数GetLastError()来获得出错线程的错误代码。错误代码与线程有关,因些GetLastError()必须在出错线程的环境中使用。如:
  if not createprocess(commandline,nil,nil,nil,false,normal_prioaity_class,
nil,nil,startupinfo,processinfo) then //如果创建进程失败
raise excuption.create('error creating process:'+inttostr(getlasterror));
  
  在上面的代码中,如果createprocess()函数调用失败,就触发一个异常,并用getlasterror()函数来显示错误代码。上面的这个例子在错误处理中是很常用的。
  delphi5的sysutils.pas单元提供了一个标准的异常类和两个工具函数,这两个函数是Win32Check()和RaiseLastWin32Error(),能够触发EWin32Error异常。可以利用这些例子程序进行自己的错误处理。

  总结:通过本章的学习,只是对WIN32系统的几个概念有一个大致了解,随着学习的深入,有必要对WIN32系统进行更进一步的学习。我感到,对于刚开始接触delphi,尤其是刚接触编程的人来说,这一章是完全可以先省略过去的。

第四章 应用程序框架和设计

  本章主要介绍delphi的项目管理和体系结构。

  一、delphi项目的组成
  delphi的项目由若干种文件组成,有的在设计期创建,有的在编译期创建。
  1、项目文件(扩展名是.DPR)
该文件也是主程序文件,是程序主窗体和其它自动创建窗体实例化的地方。在项目文件里可以做以下工作1、程序的初始化;2、显示启动画面。在该文件的uses子句中,列出了该项目中所有的窗体单元。
  2、单元文件(扩展名是.PAS)
单元文件就是pascal的源代码文件。就是我们编写代码的主要场所。
  3、窗体文件(扩展名是.DRM)
窗体文件存储窗体的二进制信息,当创建一个窗口时,delphi将同时创建一个窗体文件和一个同名的单元文件。
  4、资源文件(扩展名是.RES)
资源文件中包含二进制的数据,也称为资源,如图标等。这些资源将链接到应用程序的可执行文件中。
  5、项目选项文件(*.DOF)和桌面设置文件(*.DSK)
项目选项文件保存project|option菜单命令所设置的项目选项,该文件与项目有关。
  桌面设置文件保存tools|option菜单命令所设置的桌面选项,该文件与项目无关,作用于delphi环境。
  6、备份文件
  从第二次保存开始,delphi会自动为项目文件、单元文件、窗体文件备份文件,它是上次保存的文件的副本。通过设置也可不生成备份文件。
  
  二、delphi项目管理的建议
  1、一个项目一个目录。
  这样可以把一个项目的文件与另一个项目的文件分开,避免混淆或覆盖。
  2、建立单独的共享单元
  最好把需要共享的代码,放到单独的单元中。然后建一个目录,把所有的共享单元放入其中,再把这个目录添加到project|option对话框的directorier/conditional页上的search path框中,以便让delphi知道到哪去找共享单元。以后,当一个项目要用到共享单元时,只要在uses子句中加入单元名就行了。
  全局标识符单元(该单元专门用来声明全局标识符)就是最常用的共享单元。
  3、多项目管理
  通常一个产品由多个项目组成,这些项目相互依赖,如多层应用程序的每一层都是一个项目,能被其它项目调用的dll也是一个独立的项目。delphi5提供了项目组的管理功能,通过项目管理器能够把几个项目组织在一起,组成一个项目组。在一个项目组中,每个项目的文件,最好放在一个单独的文件夹中,用来共享的单元或窗体,最好也放在一个单独的文件夹中。

  三、delphi5项目的框架类 
  在delphi中,tform、tapplication、tscreen这三个类具有重要的作用。如大多数delphi应用程序至少有一个tform类的实例(即程序的主窗体),delphi vcl应用程序只能有一个tapplication类的实例和一个tscreen类的实例。

  1、tform类
  tform类delphi5应用程序的焦点,因为大多数情况下,整个应用程序都是围绕主窗体转的。窗体有两种显示模式:模态窗体和非模态窗体。
  A、显示模态窗体(showmodal):以模态方式显示的窗体,必须关闭该窗体,程序的输入焦点才能转移到其它窗体。如对话框就是典型的模态显示窗体。
  begin
  modalform:=tmodalform.create(application);//动态创建tmodalform类的一个实例,并把该实例赋值给modalform变量。
   try
    if modalform.showmodal=mrok then //如果该窗体以模态方式显示成功
    {do something;}
   finally
    modalform.free; //释放窗体实例
    modalform:=nil; //把该窗体变量赋值为nil
   end;
  end.
  B、显示非模态窗休(show):对于以非模态方式显示的窗休,用户可以在该窗体和其它窗体间*的切换输入焦点。
  begin
   if not assigned(modaless) then //检查窗体类tmodaless的实例modaless是否已经存在,如果不存在就动态创建一个,这条语句可以防止创建一个窗体类的多个实例。
   modaless:=tmodaless.create(application);
   modaless.show; //以非模态方式显示这个窗体
  end.

  对于动态创建的窗体,用完之后,必须对它进行释放。模态窗体的释放语句一般包含在它的创建例程中,关闭模态显示的窗体后,紧接着就把它释放了,如上面A中的代码。需要注意的是非模态窗体的释放问题。点击关闭按键,并没有真正把它从内存中释放,它仍存在内存中。如果用户想在关闭窗体时就从内存中释放它,必须处理它的onclose事件和ondestroy()事件。代码如下:
  在onclose事件中添加如下代码:action:=cafree;//关闭时,释放窗体实例
  在ondestroy事件中添加如下代码:modaless:=nil; //将窗体实例变量赋值为nil

应该绝对避免如下的代码,因为它会创建一个窗体类的多个实例,造成内存的大量浪费:
  begin
   form1:=tform1.create(application);
   form1.show;
  end;

  2、Tapplication类
  任何一个基于窗体的(也只有基于窗体的)delphi应用程序都有一个全局变量application,它是tapplication类的实例。控制台程序和服务程序没有窗口,也就没有全局变量application.
  Tapplication类封装了一些属性和方法,使应用程序能在windows环境下正常运行。这些方法中,有的定义窗体类,有的用于创建程序的主窗口,有的处理消息,有的激活应用程序,以及处理异常等。Tapplication没有在对象属性管理器中出现,所以它的属性不能在设计时设置或改变,大多数情况下,只能在运行时设置Tapplication的属性、事件和方法。

  Tapplication的属性:
  A、Tapplication.exename属性:返回应用程序的全路径和文件名。配合使用extractfilename、extractfilepath、extractfileext函数,可以从该属性中分离出应用程序主EXE文件的文件名、全路径和文件的扩展名。
  B、Tapplication.mainform属性:通过该属性可以访问程序主窗口的任何属性和方法,如Tapplication.mainform.caption可以得到主窗口的标题。如需访问其它窗口的属性和方法,需要先把那个窗体指定为主窗体,然后才能利用该属性来访问。
  C、Tapplication.handle属性:该属性返回应用程序的句柄
  D、Tapplication.icon属性:该属性设置当应用程序最小化时在任务栏上显示的图标。
  E、Tapplication.title属性:该属性设置应用程序在任务栏上图标后的说明文字。 
  F、Tapplication.active属性:该属性是只读属性,表明应用程序是否激活。
  G、Tapplication.componentcount属性:该属性表明应用程序所包含的组件的个数。
  H、Tapplication.components属性:该属性是一个数组,它的元素是组件,元素的个数由上一个属性决定。如:
  var i:integer;
  begin
   for i:=0 to application.componentcount-1 do 
   listbox1.items.add(application.components[i].classname);
  end;
  I、Tapplication.terminate属性:该属性表明应用程序是否关闭。如果关闭则该属性值为TRUE。

  Tapplication的方法:
  A、Createform(tform1,form1)方法:创建窗口类的一个实例,第一个参数是某个窗口类,第二个参数返回创建的窗口类的实例。该方法和tform1.create(application)的作用基本相同。但一般情况下,创建窗口实例时,用后一种方法。
  B、Tapplication.processmessage()方法:该方法用于从windows消息队列中检索等待处理的消息并进行处理。我感到这个方法很重要,但书上却没对它进行详细讲解,所以关于它的具体信息我也不得而知。
  C、Tapplication.run方法:首先,该方法会建立一个退出过程,以保证当应用程序退出时,所有的组件都会得到释放,然后它就建立一个循环来处理消息,直到程序结束。delphi会自动把该方法放到项目的主代码块中,不需手工调用。
  D、Tapplication.terminate方法:终止应用程序的执行。

  3、screen类
  该类封装了应用程序运行时屏幕的状态信息。它不能作为组件加到窗口上,也不能在程序运行时动态创建它。delphi5会自动创建一个Tscreen类的全局变量,叫screen,这个全局变量的属性就有用。
  A、activecontrol:表明当前屏幕上哪个控件具有焦点。
  B、activeform: 当前屏幕上哪个窗口具有焦点。
  C、cursor:设置应用程序的光标形状。 
  D、cursors:该属性是一个列表,列出屏幕所支持的各种光标形状。
  E、formcount:应用程序中窗口的个数
  F、forms:应用程序中窗口的列表
  G、fonts:应用程序中字体的列表
  H、height:屏幕的高度,以像素为单位
  I、width:屏幕的宽度,以像素为单位

  四、应用程序的体系结构

  书上用了几页的篇幅来讲体系结构,但我还是不明白什么是体系结构?如果哪位同仁知道,还清指教。

  五、一些项目管理的功能
  1、在项目中添加资源。在项目中添加资源(如光标、位图、图标等)的步骤:
    使用专门的资源编辑器来创建资源文件。 
    在项目文件中加入以下语句:
     {$r 资源文件名.res}//把指定的资源文件链接到应用程序中
     {$r *.res} //把与项目同名的资源文件链接到应用程序中
    通过tbitmap.loadfromresourcename()或tbitmap.loadfromsourceID()函数来调用资源文件中的资源。
  2、改变屏幕光标:
    screen.cursor:=crhourglass;//砂漏光标
    screen.cursor:=crdefault; //缺省光标,赋值号后的是预定义的常量,这些常量值的范围是从0到-20。要改变光标形状,只要把一个常量赋给SCREEN.CURSOR属性就行了。 
  3、在主窗口显示前先显示一个封面
  显示一个封面的步骤如下:
  A、创建一个主窗体后,再创建一个当作封面的窗体(splashform)。
  B、确保splashform没有被自动创建。
  C、把splashform的bordericon属性设为[],borderstyle属性设为bsnone.
  D、把一个image组件和一个timer组件放在splashform上,把image组件的对齐属性设为alclient。
  E、选择image组件的picture属性,为该组件调入一个位图。
  F、把timer组件的interval属性设为3000(单位是毫秒,即封面的显示时间),在它的ontimer事件中添加如下代码:timer1.enabled:=false;
  G、在项目文件中加入代码,使项目文件变成如下的样子:
  program 项目名;
  uses forms;
   mainform in 'mainform.pas' {mainform};
   splashform in 'splashform.pas' {splashform};
  {$R *.res}
  begin
   application.initialize;
   //以下是创建和显示封面的代码
   splashform:=tsplashform.create(application);//动态创建封面窗口
   splashform.show;//显示封面
   splashform.update;//强制刷新封面
   //以下通过一个定时器来延时
   while splashform.timer1.enabled do //通过这一句来延时
    application.processmessages;//这一句不太清楚,好象是让应用程序去检索消息队列,即不做任何事情
   application.createform(tmaionform,mainform);//创建主窗口
   splashform.hide;//封面窗口隐藏
   splashform.free;//释放动态创建的封面窗口
   application.run;
  end.

  总结:这一章除了介绍delphi项目管理的注意事项外,还讨论了delphi的三个框架类,分别介绍了它们的属性、事件和方法,最后还列出了几个有用的例子,非常实用,如动态创建窗口并防止创建某一窗口类的多个实例,显示封面等,请大家一定要亲自动手试一试。 

第五章 理解WINDOWS消息

  这一章较详细的讲解了什么是消息,windows的消息系统是如何工作的、delphi的消息系统、消息处理过程等内容,我的感觉是这一章讲的很好,也配有不少例子,通过本章的学习,能够对windows的消息处理机制和delphi的消息系统有个大致的认识,除了可以在事件中响应消息外,还可以自定义消息处理过程,来捕获和处理消息,可以使程序员对消息的控制更加灵活。总之,这一章可以使你对windows下编程的认识大大的前进一步。

一、什么是消息?
  消息就是WINDOWS发出的一个通知,告诉应用程序某个事件发生了,如单击鼠标、改变窗口尺寸等。消息本身是一个记录类型(Tmsg),这个记录中包含有消息的类型(与Tmsg的message域的值对应)及其它信息(与Tmsg的Wparam和Lparam域的值对应)。win32预定义了许多消息常量(delphi是在message单元中定义这些消息常量的),每个消息常量对应一种类型的消息。消息常量通常以WM开头,即WINDOWS MESSAGE的意思。如下:

消息常量标识符  常量值     含   义
 WM_ACTIVE  $0006 窗口被激活或被取消激活
 WM_CHAR    $0102 按下某个键,并且已经发送了WM_KEYDOWN和WM_KEYUP消息
 WM_CLOSE   $0010 窗口将要关闭
 WM_KEYDOWN  $0100 按下某一个键
 WM_KEYUP   $0101 释放某个键
 WM_PAINT   $000F 窗口客户区需重画
 WM_TIMER   $0113 发生了定时器事件
 WM_QUIT   $0012 程序将要退出
 WIN32还预定义有许多消息常量,详细请查阅MSDN或delphi帮助的message单元。

二、WINDOWS消息系统是如何工作的
  windows消息系统由三部分组成:
  1、消息队列。windows为每一个应用程序(进程)建立一个消息队列,并维护该队列。应用程序必须从自己的消息队列获取消息,然后分派给程序的某个窗口。
  2、消息循环。应用程序通过消息循环机制从消息队列获取消息,再把消息分派给某个窗口,然后继续从消息队列检索下一条消息,再分派给某个窗口,如此循环不止,直到应用程序关闭。
  3、窗口过程。每个窗口都有一个窗口过程,来接收和处理传递给它的消息。窗口过程是一个回调函数,处理一个消息后,它要返回一个值给windows。
  windows消息系统的核心是消息循环。
  
  在windows系统中,一个消息从产生到被某个窗口过程响应有五个步骤:
  1、系统中发生了某个事件。
  2、windows把这个事件翻译成消息,然后把消息加进对应程序(进程)的消息队列的末尾。
  3、应用程序的消息循环从消息队列的头部接收到消息。
  4、应用程序把接收到的消息传递给指定的窗口。
  5、窗口的窗口过程完成对消息的处理。

三、delphi的消息系统
  delphi的VCL封装了windows消息系统的很多细节,如消息循环已封装在vcl的form单元中,这样,我们就不必考虑怎样从消息队列中检索消息,也不必考虑怎样把检索到的消息分派给某个窗口这些细节了,我们只要在事件处理过程中书写处理消息的代码就行了。delphi还把消息记录(就是本章前面提到的windows的消息记录TMSG)映射为TMESSAGE记录。某些消息处理后,需要窗口过程给windows返回一个值时,只要把返回值赋给tmessage记录中的result域就行了。

四、消息处理过程
  在标准的windows应用程序中,消息是由窗口过程处理的。由于delphi封装了窗口过程,所以在delphi中进行消息处理就简单多了,我们可以在事件中处理消息,也可以编写自己的消息处理过程来处理消息。使用事件很简单,我相信只要用过delphi的人都会,这里只谈如何自定义消息处理过程。

  定义消息处理过程也比较简单,步骤如下:
  1、消息处理过程在窗口对象的private部分声明。
  2、这个过程以procedure关键字开头,过程名和要响应的消息的标识符一致,不要下划线。如:要响应WM_PAINT消息,那么该消息处理过程名就是WMPAINT。
  3、消息处理过程的参数必须是tmessage类型,并加上关键字var。
  4、声明消息处理过程时,最后必须使用关键字message,后面跟上要处理的消息的常量值或标识符,标识符必须全部大写。如:procedure wmpaint(var msg:tmessage);message WM_PAINT;
  5、该消息处理过程必须在implementation部分具体实现,在实现时要在过程名的前面加上该过程所属的对象类型,后面不加关键字message和消息标识符。如上面声明的消息处理过程是form1的消息处理过程,那么,它的实现代码是: 
  procedure tform1.wmpaint(var msg:tmessage);
  begin
   ......;//对消息做出自己想做的某些操作,也可以什么也不做。
   inherited;//这一句不能少,它的意思是对该消息做完自己的处理后,还要把该消息传递给tform1的父类,让它的父类再做一些善后的操作。千万记住,在自定义的消息处理过程中这一句不能少,并且位于消息处理过程的最后一句。
  end;

  从上面的例子中可以看出,响应delphi的事件是无约定的,即在事件处理过程中可以什么事也不做(在第一章讲过)。但消息处理不是无约定的,最少必须有一句inherited。上面的代码可以这样认为,写一个消息处理过程是为了做一些自己想做的事,调用inherited是为了做一些windows想做的事。
  下面看一个完整的消息处理过程。在下面的例子中,定义了一个响应鼠标左键单击消息的消息处理过程。运行后在窗口的任意位置点击左键,自定义的消息处理过程就会捕获到这个消息,弹出一个消息框。不好意思,只能通过右击鼠标才能关闭程序。

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

type
TForm1 = class(TForm)
private
procedure wmLbuttonDOWN(var msg:tmessage);message WM_LBUTTONDOWN; //声明一个消息处理过程
{ Private declarations }
public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

{$R *.dfm}
procedure tform1.wmLbuttonDOWN(var msg:tmessage);//实现消息处理过程
begin
showmessage('捕获鼠标左键单击消息'); //在消息处理中做出自己的操作
inherited;//把消息传递给tform1的父类,让windows再做一些基本操作
end;

end.

五、发送消息
  就象windows发送消息给应用程序一样,我们也可以在一个应用程序的多个窗口和控件之间发送消息。delphi提供了两种方法,可以在一个应用程序的内部发送消息。
  1、调用perform()方法。VCL的perform()方法适用于Tcontrol类的所有派生对象,该方法可以向应用程序中的任何一个窗口或控件发送消息,前提是要知道窗口或控件的实例名。调用perform()后,它要等到消息得到处理后才返回。
  2、使用api函数sendmessage()或postmessage()。前提是要知道对象的句柄。

  sendmessage():该函数把消息直接发送到指定窗口的窗口过程,等消息处理后才返回。速度快,但由于它直接发送到窗口过程,没有经过先进先出的消息队列,所以可能引起线程的阻塞。
  function sendmessage(hwnd:HWND;msg:UNIT;wparam:WPARAM;lparam:LPARAM);lresult;stdcall;   其中参数hwnd是接收该消息的窗口的句柄。

  postmessage():该函数把消息发送到指定窗口所在的线程的消息队列中,然后立即返回。
  function postmessage(hwnd:HWND;msg:UNIT;wparam:WPARAM;lparam:LPARAM);boolean;stdcall;

  这两个函数的调用方式完全一样,但返回值不一样,sendmessage()返回该消息被处理的结果值。postmessage()返回一个布尔值,以表明该消息是否已放到消息队列中。 

六、发送自定义消息
  在应用程序内部发送自定义消息,有三个步骤:
  1、在const部分自定义一个消息。如:
  const
   SX_MYMESSAGE=WM_USER+100;//注意,消息标识符一定要全部大写,自定义的消息常量值的范围在WM_USER+100到WM_USER_$7FFF之间。
  2、在某个事件中发送第一步定义的消息。可以用perform()、sendmessage()或postmessage()发送。如:
  form2.perform(SX_MYMESSAGE,0,0);//把消息发送到窗口form2;
  sendmessage(form2.handle,SX_MYMESSAGE,0,0);//把消息发送到窗口form2的窗口过程;
  postmessage(form2.handle,SX_MYMESSAGE,0,0);//把消息发送到窗口form2所在线程的消息队列。
  3、定义一个消息处理过程来接收和处理该消息。方法就是本章第四部分所讲的。

  下面给出一个完整的发送自定义消息的例子。

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
const
SX_MYMESSAGE=WM_USER+100;//自定义一个消息
type
TForm1 = class(TForm)
Button1: TButton;//caption属性设置为"perform方式发送"
Button2: TButton;//caption属性设置为"sendmessage方式发送"
Button3: TButton;//caption属性设置为"postmessage方式发送" 
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
private
procedure sxmymessage(var msg:tmessage);message SX_MYMESSAGE;
       //声明一个消息处理过程,用来处理自定义的消息。
{ Private declarations }
public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

procedure tform1.sxmymessage(var msg:tmessage);//具体定义消息处理过程的实现细节。
begin
case msg.lparam of //根据发送方法的第三个参数来识别是哪个方法发送的。
1:showmessage('窗口form1接收到perform()方法发送的自定义消息SX_MYMESSGE!');
2:showmessage('窗口form1接收到sendmessage()函数发送的自定义消息SX_MYMESSGE!');
3:showmessage('窗口form1接收到postmessage()函数发送的自定义消息SX_MYMESSGE!');
end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
form1.perform(SX_MYMESSAGE,0,1);  //在按纽一的单击事件中,用perform方法发送消息,第三个参数用来标识发送方,表明是第几个按纽发送的。
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
sendmessage(form1.handle,SX_MYMESSAGE,0,2);
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
postmessage(form1.handle,SX_MYMESSAGE,0,3);
end;

end.

七、事件和消息之间的关系

  vcl的事件系统封装了许多windows消息。delphi的事件系统实际上就是程序员和windows消息之间的接口,许多vcl的事件都对应着一个windows消息,如:
  事件      消息
 onclick   WM_XBUTTONDOWN
 oncreate   WM_CREATE
 onkeydown  WM_KEYDOWN
  还有很多事件都对应一个windows标准消息,但请注意,我们在日常使用时,尽量用事件而不要用消息,因为事件的处理是无约定的。

总结:这一章我感到如果以前没有接触过windows编程的人,可能会感到比较难。我看了两遍才有以上认识,恐怕里面还有错误的地方,还有的地方现在还是摸棱两可。如有的朋友说,在windows系统中,所有的应用程序共用一个消息队列,而根据书上的内容我的理解是应该每个程序(确切说应该是每个进程)都拥有一个自己的消息队列,但这个问题一直没有得到确切的答案,请高手指点.

第六章 代码标准文档

  本章内容主要为开发组提供一种方法、一种标准、使整个开发组在编程时有一致的格式,这样,每个人编写的代码都能被其它人理解。比如缩进、变量的命名规则、语句的格式等。这些内容在本站的delphi入门栏目中有五篇《Delphi程序员代码编写标准指南》详细进行介绍,所以本章的笔记就不再重复。

 

第七章 使用ActiveX控件

  32位的delphi支持ActiveX控件,通过给delphi安装ActiveX控件可以极大的扩展delphi的功能。

  本章介绍了ActiveX控件的概念,如何安装、使用ActiveX控件,怎样发布带有ActiveX控件的应用程序等内容,还对ActiveX控件的一些细节进行了介绍,由于后面还有专门的章节讲解怎样编写ActiveX控件,所以我把本章后半部关于ActiveX控件细节的部分搁了过去,只了解了一下有关ActiveX控件的概念和安装使用方法。

  一、什么是ActiveX控件
  ActiveX控件是利用OLE技术和ActiveX技术的自定义控件,它是基于与应用程序无关的思想设计的。从本质上说,一个ActiveX控件就是一个ActiveX服务器,它能提供所有的OLE功能和服务、可视化编辑、拖放和OLE自动化。ActiveX控件有很多,如字处理器、WWW浏览器、表格等。   二、何时使用ActiveX控件
  在下列两种情况下考虑使用ActiveX控件:
  1、没有合适的delphi组件可以使用时。
  2、想使用多种编程语言进行开发,想在多个开发平台间共享一些控件时。
  但需注意的是,尽管ActiveX控件可以无缝的集成到delphi的IDE中,但在应用程序中使用ActiveX控件也有缺点,最明显的问题是,delphi本身的组件可直接内建在应用程序的可执行文件中,它可以和应用程序中的其他组件直接通信,但ActiveX控件是通过COM与应用程序通信的,所以它的速度没有delphi自带的组件快。

  三、怎样安装ActiveX控件
  当ActiveX控件安装到delphi后,ActiveX控件就会出现在delphi组件面板的ActiveX页中,我们就可以象使用delphi组件一样来使用ActiveX控件。把ActiveX控件安装到delphi组件面板的步骤如下:
  1、点击菜单component->import activex control;
  2、新窗口的上半部显示了在系统中已经注册过的ActiveX控件的列表,和ADD、REMOVE按纽,用于注册和移除ActiveX控件。如果该ActiveX控件还没有注册,单击ADD,然后在某个文件夹中选择这个控件的OCX文件或DLL文件,单击OPEN,就完成注册了;
  3、注册后,在窗口的下半部你就可以安装已注册过的ActiveX控件。首先在窗口上半部的列表中选择该ActiveX控件,在下半部指定单元文件名、组件面板的页标签(默认是ACTIVEX页)、搜索路径以及封装OCX的类名(这些都可用默认值),然后单击INSTALL或CREATE按纽,会打开INSTALL对话框,让你选择安装组件的包,这里可以选择一个已有的包来安装该控件,也可创建一个新的包来安装该控件。
  4、单击OK,该ActiveX控件就安装到组件面板中了。不信,你可以打开组件面板看一看。
  
  四、在应用程序中使用ActiveX控件
  当ActiveX控件放到组件面板上后,它的用法就和普通的delphi组件没有什么区别了。可以拖放,也可以用Object Inspector面板设置ActiveX控件的属性。

  五、发布带有ActiveX控件的应用程序
  当发布带有ActiveX控件的应用程序时,注意,一定要把控件及相关的文件也发布给用户。
  1、必须附带ActiveX控件的OCX文件或DLL文件。因为ActiveX控件的OCX文件或DLL文件没有被链接到应用程序的可执行文件中,而且用户在运行应用程序之前,还要在注册表中注册ActiveX控件。当然,注册ActiveX控件也可以编程完成。
  2、一些ActiveX控件可能还需要外部的其它DLL文件或其它文件,这些文件也要发布给用户。
  3、一些ActiveX控件有许可文件,不要把许可文件发布给用户,以防止用户把ActiveX控件用来做开发。

  总结:这两章,可以说看得最轻松,主要是我把介绍底层实现细节的部分都搁过去了,只是了解了一些概念性的知识,每个人的程序不同,如果你是第二遍或第三遍看,那么这一章还是应该仔细学习的。