[转帖]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控件用来做开发。 总结:这两章,可以说看得最轻松,主要是我把介绍底层实现细节的部分都搁过去了,只是了解了一些概念性的知识,每个人的程序不同,如果你是第二遍或第三遍看,那么这一章还是应该仔细学习的。 |