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

 
[转帖]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控件用来做开发。

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

经典中的经典! 目 录 译者序 序 前言 第一部分 快速开发的基础 第1Delphi 5下的Windows编程 1 1.1 Delphi产品家族 1 1.2 Delphi是什么 3 1.2.1 可视化开发环境 3 1.2.2 编译器的速度和已编译代码的效 率 4 1.2.3 编程语言的功能及其复杂性 4 1.2.4 数据库结构的灵活性和可扩展性 5 1.2.5 框架对设计和使用模式的扩充 5 1.3 历史回顾 5 1.3.1 Delphi 1 5 1.3.2 Delphi 2 6 1.3.3 Delphi 3 6 1.3.4 Delphi 4 7 1.3.5 Delphi 5 7 1.3.6 未来 7 1.4 Delphi 5的IDE 7 1.4.1 主窗口 8 1.4.2 窗体设计器 9 1.4.3 Object Inspector 9 1.4.4 代码编辑器 9 1.4.5 代码浏览器 10 1.4.6 源代码生成器 10 1.5 创建一个简单的应用程序 11 1.6 事件机制的优势在哪里 12 1.7 加速原型化 13 1.8 可扩展的组件和环境 13 1.9 IDE最重要的十点功能 13 1.10 总结 15 第2章 Object Pascal语言 16 2.1 注解 16 2.2 新的过程和函数特征 17 2.2.1 圆括号 17 2.2.2 重载 17 2.2.3 缺省值参数 17 2.3 变量 18 2.4 常量 19 2.5 运算符 20 2.5.1 赋值运算符 20 2.5.2 比较运算符 20 2.5.3 逻辑表达式 21 2.5.4 算术运算符 21 2.5.5 按位运算符 22 2.5.6 加减运算过程 22 2.6 Object Pascal类型 23 2.6.1 类型的比较 23 2.6.2 字符 24 2.6.3 字符串 24 2.6.4 变体类型 32 2.6.5 Currency 39 2.7 用户自定义类型 39 2.7.1 数组 39 2.7.2 动态数组 40 2.7.3 记录 41 2.7.4 集合 42 2.7.5 对象 43 2.7.6 指针 44 2.7.7 类型别名 46 2.8 强制类型转换和类型约定 46 2.9 字符串资源 47 2.10 测试条件 47 2.10.1 if语句 47 2.10.2 case语句 48 2.11 循环 49 2.11.1 for循环 49 2.11.2 while循环 49 2.11.3 repeat...until 50 2.11.4 Break()过程 50 2.11.5 Continue()过程 50 2.12 过程和函数 50 2.13 作用域 50 2.14 单元 55 2.14.1 uses子句 55 2.14.2 循环单元引用 56 2.15 包 56 2.15.1 使用Delphi的包 56 2.15.2 包的语法 56 2.16 面向对象编程 57 2.17 使用Delphi对象 58 2.17.1 声明和实例化 58 2.17.2 析构 59 2.18 方法 59 2.18.1 方法的类型 60 2.18.2 属性 61 2.18.3 可见性表示符 62 2.18.4 友类 62 2.18.5 对象的秘密 63 2.18.6 TObject:所有对象的祖先 63 2.18.7 接口 63 2.19 结构化异常处理 66 2.19.1 异常类 68 2.19.2 执行的流程 70 2.19.3 重新触发异常 71 2.20 运行期类型信息 72 2.21 总结 72 第3章 Win32 API 73 3.1 对象:以前和现在 73 3.1.1 内核对象 73 3.1.2 GDI和用户对象 75 3.2 多任务和多线程 75 3.3 Win32内存管理 76 3.3.1 什么是线性内存模式 76 3.3.2 Win32系统是怎样管理内存的 76 3.4 Win32的错误处理 78 3.5 总结 78 第4章 应用程序框架和设计 79 4.1 理解Delphi环境和项目的体系结构 79 4.2 构成Delphi 5项目的文件 79 4.2.1 项目文件 80 4.2
©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值