2005年02月24日

  摘 要 树型控件是一种有效的表示方法,在数据库应用程序的开发中经常用它可视化表示对象之间的层次关系。本文介绍了Developer Express inc. 的ExpressQuantumGrid Suite中树型控件 TdxDBTreeList 的使用方法和技巧,特别是由多关系表构成树型结构的情况。

  关键词 树型控件,TdxDBTreeList,多关系表

  简介

  树型控件是一种有效的表示方法,在数据库应用程序的开发中经常用它可视化表示对象之间的层次关系,便于用户的操作和使用。比如:表示一个公司部门的层次结构;磁盘文件系统等。Borland 公司的Delphi 开发系统提供了一般的树型控件TTreeView,它可以在设计时或运行时生成必要的树型结构图,方法是通过生成控件对象的 Items 属性。设计时方式通过手工方式增加items,主要适用于静态、少量的 items 的情况;运行时方式通过程序生成树型结构的 items,它既适合静态又可用于动态的情况。在实际工作中,树状结构中的 items 经常来自数据库的一个或若干个关系表中,若想利用 TTreeView 控件生成相关的树状结构,设计时方式很难实现,运行时方式虽然可以实现,但需要程序员设计大量的代码,而且容易出错。因此,为了提高开发效率,减少重复开发,我们必须使用 data-aware 树型控件。Developer Express inc. 的ExpressQuantumGrid Suite 中树型控件TdxDBTreeList 属于 data-aware 型,items 不仅可以来自数据库的关系表,而且可以为数组等其它数据源。本文介绍了TdxDBTreeList 的使用方法和技巧,特别是由多关系表构成树型结构的情况。

  TdxDBTreeList 概述

  Developer Express inc. 开发的The ExpressQuantumGrid Suite 包含三个功能强大的控件,它们分别是:TdxTreeList 、 TdxDBTreeList 和 TdxDBGrid。TdxDBTreeList 直接继承于 TCustomdxDBTreeListControl ,它是 data-aware 型的,不仅结合标准的树型视图和列表控件的特性,而且具有独特的特性:运行时列定制、一个树结点内编辑任意列的能力和由dataset 生成树结构。

  使用该树型控件关键是设定属性页中几个重要的属性,分别为:DataSource、KeyField、ParentField 和 Bands。其中 DataSource 为提供树型结构的数据源,可以为来自一个或若干个关系表的元组的集合,因此它是必须的且不为空;KeyField 为 DataSource 的元组中唯一标识自身的关键码;树型控件表示的是对象之间的层次关系,除树根结点之外(最上层项目),每一个结点都有一个唯一的双亲结点(直接上层结点)。ParentField 即是元组中标识其双亲结点的 KeyField 。根结点的 ParentField 值必须不是 DataSource 代表的元组中 KeyField 值之一;Bands 控制树型结构显示的列数,通过它提供的 Add 方法增加合适的 Columns ,Columns 有两个重要的属性:Caption 和 FieldName 。Caption 可以设定树型结构的标题,FieldName 代表 DataSource 的元组表示树型结构项目的字段。例如用如下的部门关系表作为 DataSource 形成树型结构:

  表1 unti 关系表


UnitNo unitName superiorUnit
0000 东南大学 -1
0001 计算机系 0000
000101 硬件教研室 0001
000102 软件教研室 0001
0002 外语系 0000
000201 大外教研室 0002
000202 专外教研室 0002


  其中,unitNo 表示部门号;unitName 部门名称;superiorUnit 上级部门号。KeyField 设为unitNo;ParentField 设为 superiorUnit ;Bands 的一列 Columns 的 FieldName 设为 unitName 。这样形成如图1所示的树型结构。



图1 树型结构示例图


  多关系表生成的树结构

  实际应用中经常需要由两个或若干个关系表构成树型结构,从表面上看,TdxDBTreeList 仅支持一个关系表。我们可以巧妙地利用 SQL 中功能强大的 SELECT 语句生成必要的数据源 DataSource 。SELECT 语句不仅可以由一个关系表生成元组集合,还可以由多表生成,语法形式为:

select ………. Union select …….. union select ………。

  如存在如下两个关系表 unit 部门表和 teacher 教师表:


create table unit (
 unitNo varchar2(6) not null, ………..部门号
 unitName varchar2(30), ……….部门名称
 superiorUnitNo varchar2(6), …..上级部门号
 primary key (unitNo ))
 create teacher (
  teacherNo varchar2(8) not null , ……..教师号
  teacherName varchar2(10) , ……..教师名
  unitNo varchar(6), ……..教师所在教研室号
  primary key (teacherNo),
  foreign key unitNo(unitNo) references unit )


  其中,teacher 表的字段 unitNo 为引用unit表的外键。假设unit 的关系表内容同表1;teacher的内容如下:

  表2 teacher 关系表


teacherNo teacherName unitNo
00010001 Computer1 000101
00010002 Computer2 000102
00020001 Foreign1 000201
00020001 Foreign2 000202


  如要生成如图2所示的来自unit 和 teacher 两个关系表数据的树型结构:

     
         图2 待生成的unit 和 teacher 两个关系表数据的树型结构

  可以用下列语句:


select unitNo||’ ‘ as unitNo,unitName, superiorUnit
from depart
union
select teacherNo as unitNo,teacherName as unitName,unitNo||’ ‘ as superiorUnit from teacher


  生成的数据集作为 TdxDBTreeList 的数据源;unitNo 作为 KeyField;superiorUnit 作为 ParentField 等。需要注意的是,其中 uintNo||’ ‘是一种字符串连接运算,之所以要这么做是因为 unit 关系模式中 unitNo 的字段宽度为 6 位,而 teacher 中的 teacherNo 为 8 位,作为 TdxDBTreeList 的 KeyField 必须统一二者的宽度,否则可能出现问题,读者可以亲自试验。

  结论

  树型控件是一种强有力的交互工具,利用第三方提供的 data-aware 树型控件可以效、可靠地开发应用程序。SQL 中的 SELECT 是一个功能强大的语句,灵活的使用它可以实现过程性语言所无法代替的效果。欲了解 TdxDBTreeList 的详细情况可以查看该公司的网站 www.devexpress.com 。

2005年02月16日

delphi运行错误信息



  发表时间:2004-8-10
作者:未知[获得此文档时候没有作者记录,深感抱歉,本文档全为转载]
  


*******************************
* 运 行 错 误 信 息 *
* 运行时出现的错误信息形式为: *
* Run-time error nnn at xxxx *
* 其中 nnn 是运行时的错误编号 *
* xxxx 是运行时的错误地址 *
*******************************

运行时的错误分为以下三类:
* I/O错误:错误编号为100-149
编号 说明
———————————————————
100 磁盘读错误,若要对超过格式文件尾进行读取时
101 磁盘写错误,若磁盘满时,由CloseFile,Write,Writeln或Flush报告
102 没有指定文件,若文件变量没有由Assign或AssignFile赋值,由Reset,Rewrite,Append,Rename和Erase报告
103 文件没有打开,若文件未打开,由CloseFile,Read,Write,Seek,Eof,FilePos,FileSize,Flush,BlockRead或BlockWrite报告
104 输入文件未打开,由Read,Readln,Eof,Eoln,SeekEof或SeekEoln报告有关输入的文本文件未打开
105 输出文件未打开,由Write和Writeln报告有关文本文件没有用Console应用程序生成
106 无效的数据格式,由Read或Readln报告从文本文件读取的数据格式不正确

* 致命错误:错误编号为200-255
编号 说明
———————————————————
200 被零除
201 范围检查错误
202 栈上溢
203 栈上溢错误
204 无效的指针操作
205 浮点上溢
206 浮点下溢
207 无效的浮点操作
215 算术上溢错误
216 存取非法
217 控制-C
218 授权指令
219 无效的TYPECAST
220 无效的变体TYPECAST
221 无效的变体操作
222 没有变体方法调用DISPATCHER
223 不能建立变体数组
224 变体不包含数组
225 变体数组边界错误
226 TLS初始化错误

* 操作系统错误
编号 说明
———————————————————
1899L 不能生成端点映射数据库
1752L 不能执行操作
1751L 入口点非法
1753L 端点映射中没有更多可用的端点
5L 存取非法
1331L 帐号当前不能用,因此不能登录
1793L 用户帐号过期
1327L 用户名是有效的,但一些限制不能验明用户
57L 网络适配器硬件错误
1379L 指定的别名已存在
1344L 分配用于更新的内存块不足
85L 本地设备名已在使用
183L 试图建立已存在的文件
1074L 系统当前正以最新最好的配置运行
7L 存储控制块被破坏
534L 运算结果超出32位
174L 文件系统不支持锁定类型的原子变化
199L 操作系统不能运行这一程序
160L 传递给DosExecPgm的参数串不正确
22L 设备不能识别的命令
1361L 安全描述符不在要求的格式
66L 网络资源类型不正确
1200L 指定的设备名无效
119L 系统不支持请求的命令
10L 环境不正确
193L %1不是有效的基于Windows的应用程序
11L 企图装载不正确格式的程序
1346L 指定的模拟级无效或没有提供要求的模拟级
1340L 试图建立继承的ACL或没有继承的ACE
24L 程序指定的命令长度不正确
1365L 登录段与要求的操作状态不一致
67L 网络名找不到
58L 指定的服务器不能执行请求的命令
53L 网络路径没有找到
161L 指定的路径名无效
230L 管道状态无效
1206L 网络连接协议被损坏
1204L 指定的网络提供者名无效
60L 远程适配器不兼容
159L 线程ID地址不正确
1349L 符号对象类型不当
20L 系统不能找到指定的设备
2202L 指定的用户名无效
1348L 请求的确定信息类是无效的
1009L 配置记录数据库被破坏
1010L 配置记录密钥无效
1102L 遇到磁带头或分区
1076L 当前引导已接受为最新的控制设置
109L 管道结束
111L 文件名太长
1111L I/O总线重新设置
170L 请求的资源在使用
142L 此时系统不能执行JOIN或SUBST
120L 输入的API只能在Windows/NT模式下工作
1003L 由于一些原因不能完成的功能
173L 锁定请求对提供撤消区未解决
266L 不能使用
1407L 不能找到Windows类
1368L 表示要试图通过命名管道的模拟还没有读取
82L 不能生成目录或文件
1205L 不能打开网络连接协议
1351L 没有联系的域控制器或在域内对象被保护,因此不能存取必要信息
1310L 委托组不能禁止
1347L 试图打开匿名的符号,匿名级的符号不能打开
1011L 配置记录密钥不能打开
1012L 配置记录密钥不能读取
1013L 配置记录密钥不能写
1021L 试图在易变的父关键字下建立稳定的子关键字
129L 应用程序%1不能在Windows模式下运行
1436L 子窗口不能有菜单
1059L 指定子循环服务从属
1410L 类已存在
1411L 类不存在
1412L 类仍在打开的窗口中
1418L 线程没有打开的剪切板
1201L 设备当前没有连接,但要记住连接
1421L 没有找到控制ID
1121L 由于超时已到,一系列的I/O操作完成
23L 数据错误
16L 目录不能删除
1065L 指定的数据库不存在
1425L 传递给ReleaseDC的无效HDC
1051L 终止控制已送到其他独立运行服务的服务中
1435L 不能破坏其他线程产生的对象
55L 指定的网络资源不再有效
1202L 试图记住先前记住的设备
2404L 设备由激活的进程在使用,不能断开连接
1107L 当装载磁带时,找不到磁带的分区信息
145L 目录非空
144L 目录不是根目录的子目录
130L 试图使用文件句柄来打开磁盘操作
267L 目录名无效
157L 段已丢失,不能锁定
107L 由于没有插入交换磁盘,程序终止
1393L 磁盘结构损坏,不能在读取
112L 磁盘上没有足够的空间
1127L 即使重试,存取硬盘也失败
1126L 在存取硬盘时,即使重试,存取校准操作也失败
1128L 即使磁盘控制器重新设置,存取硬盘也失败
1114L DLL初始化例程失败
1356L 指定的域已存在
1357L 在该版本中试图超出每个服务器域的限制
1810L 指定域的名字或安全ID与该域的受托信息不一致
108L 磁盘在使用或被其他进程锁定
1221L 工作族或域名已被网络上的其他计算机使用
52L 网络上存在重名
1078L 名字已在作服务器名或服务显示名使用
196L 操作系统不能运行这一应用程序
994L 对EA的存取非法
276L 在安装文件系统上的EA文件被损坏
255L EA不一致
277L 在安装文件系统的EA文件上的EA表用完
275L EA不适合在缓冲区中
282L 安装文件系统不支持扩展属性
1100L 在操作中出现磁带尾部标志
203L 系统不能找到输入的环境选项
1129L 遇到磁带的物理尾部
1501L 没有打开的日志文件事件,因此事件登录服务没有启动
1503L 在读取之间事件日志文件已改变
1500L Eventlog登录文件之一破坏
1064L 在处理控制请求时出现意外
101L 专门信号被其他进程所拥有
192L 操作系统不能运行%1
1208L 出现扩展错误
83L INT24失败
1063L 服务进程不能连接到服务控制器
1392L 文件或目录被损坏,不能在读取
80L 文件存在
1006L 文件卷已变化,因此打开的文件不再有效
2L 系统不能找到指定的文件
1101L 磁带存取达到文件标志
206L 文件名或后缀太长
1125L 软盘控制器返回与登记不一致的结果
1122L 在软盘上没有找到ID地址标志
1123L 软盘扇区ID域与软盘控制器道地址不匹配
1124L 软盘控制器报告出现不能由软驱识别的错误
1007L 请求的操作在全屏幕模式不能执行
31L 连接到系统上的设备不正常
1360L 通配存取类型包含在存取屏蔽中
1429L 该异常分支只能为全局设置
1318L 指定的组已存在
39L 磁盘满
38L 到达文件末
1428L 没有模块处理,不能设置非局部的异常处理
1431L 异常分支没有安装
1409L 热键已登记
1419L 热键没有登记
1441L 所有的DeferWindowsPosHWND必须具有相同的父
1324L 在更新口令时,该返回状态表示新口令中包含不允许的值
202L 操作系统不能运行%1
122L 传递给系统调用的数据区太小
1358L 由于灾难性的介质错误或磁盘数据结构破坏引起请求的操作不能完成
1383L LSA数据库内部不一致
1359L SAM遇到内部数据库不一致的错误,可防止SAM的进一步操作
1800L 指定的优先级无效
1448L 滚动条范围大于0×7FFF
1338L 安全描述符结构无效
198L 操作系统不能运行%1
180L 系统检测到不正确的段号
1799L 指定的分隔符文件无效
1352L SAM服务器状态错误,不能完成期望的操作
1057L 帐号名无效或不存在
1052L 对该服务请求的控制无效
1071L 指定的服务数据锁定无效
1213L 指定的服务名格式无效
1215L 指定的共享名格式不正确
1449L SHOWWINDOWS命令无效
1337L SID结构无效
209L 公布的信号不正确
1439L 参数无效
189L 操作系统不能运行%1
188L 操作系统不能运行%1
1335L 子权限值无效
114L 目标内部文件标识符不正确
1444L 线程ID无效
1784L 提供的用户缓冲区对请求操作失效
118L 写后验证开关参数值不正确
1400L 窗口处理无效
1329L 用户帐号限制,不能从源工作站登录
1117L 由于I/O设备错误,请求不能完成
996L 重叠的IO时间不在发信号状态
997L 重叠的IO操作在处理中
197L 操作系统当前不能配置来运行应用程序
1119L 不能打开与其它设备共享IRQ的设备,至少有一使用该IRQ的其他设备已打开
147L 没有足够的可用资源来处理这一命令
133L 由于驱动器已包含连接,JOIN或SUBST命令不能使用
134L 试图在已连接的驱动器上使用JOIN或SUBST命令
146L 指定的路径正在用替换
149L 试图替代先前已替代的驱动器目录
135L 试图在已替代的驱动器上使用JOIN或SUBST命令
194L 操作系统不能运行%1
138L 系统试图替代已替代的目录
140L 系统试图替代已替代的目录
1430L 日常异常处理已安装
1018L 在有删除标志的登记键上非法操作
1020L 试图在已有子键或值的登记键建立符号连接
154L 输入的卷标超过11个字符的限制,前11个字符写在磁盘上,其余字符自动删除
1322L 请求的操作被禁止或删除最新的其余管理员帐号
1434L 该列表框不支持制表符
1416L 列表符ID没有找到
1390L 试图修改用户口令,但没有提供要求的LM口令
1303L 联接局部RPC,要求用户话路密钥
167L 试图锁定文件失败的区域
33L 由于其他进程已锁定部分文件,该文件不能被进程存取
212L 端锁定,因此不能重新分配
1502L 事件登录文件已满
1326L 由于用户名或验证信息不正确,试图登录无效
1380L 请求登录的类型(如网络,服务等)没有被目标系统授权
1366L 登录话路ID已在使用
1363L 试图启动新的话路管理程序或用已在使用的LSA登录话路
1385L 请求登录的类型(如网络,服务等)没有授权
1334L 没有更多可分配的LUID
164L 在系统中不能生成更多的线程
1110L 驱动器中的介质已改变
1378L 指定的帐号名不是别名的成员
1320L 指定的用户帐号已在指定的组帐号中或由于组中有成员,不能删除
1377L 指定的帐号名不是别名的成员
1321L 指定的用户帐号不是指定组帐号的成员
1374L 由于组是基本组,因此成员不能从该组中删除
208L 取决文件名符号*或?输入不正确或指定的全局文件符号更多
126L 不能找到指定的模块
234L 需要更多的数据
1120L 一系列的I/O操作被其他写到串口的操作完成
317L 系统不能找到消息号为0x%1的消息
131L 试图在超出文件头的位置移动文件指针
215L 不能嵌套调用LoadModule
1792L 试图登录,但网络登录服务没有启动
88L 网络写失败
64L 指定的网络名不再有效
65L 网络存取非法
54L 网络在忙
6118L 该工作组的服务器列表当前不可用
232L 管道在进程中关闭
1104L 在磁带存取中,到达数据标志尾部
1309L 试图由当前不在模拟客户的线程上操作模拟符号
1391L ACL不包含可继承的组件
1019L 系统不能分配记录文件要求的空间
1807L 使用的帐号在内域受托帐号中,使用正常的帐号或远程用户帐号来存取该服务器
1809L 使用的帐号在服务器受托帐号中,使用正常的帐号或远程用户帐号来存取该服务器
1311L 当前没有可用的登录服务器来服务登录请求
1808L 使用的帐号在工作站受托帐号中,使用正常的帐号或远程用户帐号来存取该服务器
1112L 由于驱动器中没有介质,磁带查询失败
18L 没有更多的文件
259L 没有更多的可用数据
113L 没有更多的可用内部文件标识符
1203L 没有网络提供者接收给定的路径
2138L 没有网络或网络未启动
89L 此时系统不能启动另一进程
1302L 对该帐号没有指定限额
1447L 窗口不能有滚动条
1350L 试图对没有安全性的对象操作
1116L 由于没有关机在处理中,试图放弃关机失败
205L 在命令子树中没有进程有信号句柄
62L 在服务器上存储等待打印文件的空间不足
1376L 指定的别名不存在
1355L 指定的域不存在
1319L 指定的组不存在
1312L 指定的登录话路不存在,可能已终止
1387L 不存在的新成员不能添加到别名中
1364L 指定的验证包未知
1313L 指定的特权不存在
1317L 指定的用户不存在
1437L 窗口不能具有系统菜单
1008L 试图引用不存在的符号
1786L 工作站没有受托秘密
1787L 域控制器对该工作站没有帐号
1113L 对目标多字节代码页存在的Unicode字符没有映射
1394L 对指定的登录话路没有用户话路密钥
125L 磁盘没有卷标
1417L 没有找到通配符
998L 对内存位置的无效存取
1445L 用非MDI子窗口调用DefMDIChildProc
1332L 映射的信息没有转变
1300L 不是所有权限赋给调用者
1442L 窗口不是子窗口
2250L 网络连接不存在
1207L 不能枚举非容器
26L 指定的磁盘不能存取
8L 没有足够的空间处理该命令
1130L 没有足够的空间处理该命令服务器
136L 系统试图删除没有连接的驱动器
158L 段已锁定
1362L 请求的动作只受登录进程的限制,调用进程没有登记为登录进程
288L 试图释放不被调用者拥有的互斥法
21L 驱动器没有准备好
1017L 系统试图装载或恢复文件到登记中,但指定的文件格式不正确
17L 系统不能将文件移动到不同的驱动器上
137L 系统试图删除没有替代的驱动器
50L 不支持网络请求
1022L 改变请求正在完成,信息没有返回在调用者的缓冲区.调用者现在需要模拟文件,以找到所做的修改
1386L 没有提供必要的NT交叉保密口令而试图以安全帐号管理员改变用户口令
1304L WindowsNT的口令太复杂,无法将其转换为Windows的网络口令(该口令返回NULL字符串)
110L 系统不能打开指定的设备或文件
2401L 有打开文件或请求在连接期
995L 由于线程退出或应用程序请求,I/O操作放弃
28L 打印机没有纸
84L 处理该请求没有足够的空间
14L 处理该操作没有足够的空间
1105L 磁带没有分区
1330L 用户帐号的口令已到期
1325L 在更新口令,该状态表示违反一些口令更新规则
148L 指定的路径现在不能用
3L 系统不能找到指定的路径
231L 所有的管道实体在忙
535L 在管道的另一端有进程
536L 等待进程打开管道的另一端
233L 在管道的另一端没有进程
1446L 下拉式菜单已激活
1131L 已检测到潜在的僵局条件
63L 要打印的等待文件被删除
1802L 打印机已存在
1795L 指定的打印驱动程序已安装
61L 打印队列已满
1415L 使用局部的DIALOG窗口字
1314L 请求的权限客户机没有
127L 指定的过程没有找到
1067L 进程突然终止
30L 系统不能从指定的驱动器读取
72L 指定的打印机或磁盘驱动器已暂停
1794L 重定向器在使用,不能卸载
1015L 登记文件的文件结构或文件的内存印象系统损坏或文件不能恢复
1016L 登记初始化I/O操作有不可恢复的错误,登记不能完成读,写等操作
1014L 包含系统登记数据的文件之一已恢复成功
201L 操作系统不能运行%1
51L 远程计算机不可用
1220L 试图建立LAN管理服务器话路,但已建立了很多
71L 网络请求不接受
1816L 处理该命令没有足够的定额
1812L 指定的图象文件不包含资源部分
1815L 指定的资源语言ID在图象文件中找不到
1814L 指定的资源名在图象文件中找不到
1813L 指定的资源类型在图象文件中找不到
1306L 两个版本级不兼容
207L 2环堆栈在使用
200L 代码段不能大于等于64KB
1370L 在登记事务委托中出现错误
1369L 登记子树的事务状态与请求的操作不兼容
143L 系统不能替代相同的驱动器或目录
1440L 屏幕已锁定
1382L 秘密的长度超出允许的最大长度
27L 驱动器没有找到请求的扇区
25L 驱动器不能找到磁盘上指定的区域和道
132L 文件指针不能设置在指定的设备或文件上
102L 信号设置不能关闭
187L 指定的系统信号名没有找到
105L 该信号的先前物主关系已终止
121L 信号超时期已到
106L 在驱动器1中插入磁盘
1118L 串行设备没有初始化,串行设备没有安装
1341L GUID分配服务器此时禁止
1811L 服务器在使用中,不能卸载
1342L 此时允许GUID分配服务器
1056L 服务实体已在运行
1061L 服务在此时不能接受控制消息
1055L 服务数据库锁定
1075L 从属服务不存在或已做删除标志
1068L 从属服务或组启动失败
1058L 指定服务禁止,不能启动
1060L 指定作为安装的服务不存在
1073L 指定的服务已存在
1069L 由于登录失败,服务不能启动
1072L 指定的服务已做删除标志
1077L 引导后没有启动的服务已启动
1054L 不能产生该服务的线程
1062L 服务没有启动
1053L 服务对启动没有响应或及时地控制请求
1066L 服务已返回一服务指定错误代码
1070L 在启动后,服务挂在启动等待状态
1219L 提供的证书与已有证书集冲突
1433L LB_SETCOUNT发送到非缓慢的列表框
1103L 磁带存取达到设置标志
36L 打开的共享文件太多
70L 远程服务暂停或在启动的过程中
32L 由于文件被其他进程使用,该进程则不能存取
1115L 系统关闭在处理中
162L 信号已暂挂
156L 接受进程拒绝信号
1301L 要映射的一些信息没有传送
1371L 试图在与内置帐号不兼容的SAM帐号上操作
1372L 请求的操作不能在指定的组上执行,由于它的内置的特殊组
1373L 请求的操作不能在指定的用户上执行,由于它的内置的特殊组
1001L 递归太深,堆栈溢出
141L 系统试图SUBST一驱动器到已替代驱动器的目录
139L 系统试图替代一驱动器到已替代驱动器的目录
0L 操作成功的完成
999L 存取分页的文件错误
150L 系统追踪的信息不在指定的CONFIG.SYS文件中或追踪不允许
210L 信号句柄没有设置
1406L CreateWindows失败,建立WS_CHILD类的顶级窗口
1375L 试图建立作为基本使用的符号,但该符号已在使用.一次只能有一个基本的符号
56L 已达到网络BIOS命令限制
1384L 在试图登录中,用户的安全堆集太多的安全ID
1333L 请求的LUD数不能在单个的分配中分配
214L 太多的动态连接模块连接到该程序中或动态模块中
152L 已设置太多的信号
68L 超出局域网网卡的名字限制
4L 系统不能打开该文件
298L 给信号太多的邮件
1381L 在单个系统中可存储的最大秘密数超出
103L 信号不能重新设置
100L 不能建立另一系统信号
69L 网络BIOS话路超出
1389L 指定太多的SID
155L 不能建立另一线程
1790L 网络登录失败
1788L 在主域与受托域之间的受托关系失败
1789L 在工作站与主域之间的受托关系失败
1108L 试图锁定缺少弹出介质的机构
1109L 卸载介质失败
59L 出现意外的网络错误
1796L 指定的处理器未知
1798L 打印处理器未知
1797L 打印驱动器未知
1305L 遇到或指定的版本对服务来说未知
1785L 磁盘介质不能辨认,可能是未格式化
1005L 卷不包含识别文件系统
1316L 指定的用户已存在
240L 话路取消
128L 没有等待的子进程
1423L 窗口不是一命令框
1420L 窗口不是一有效的对话框
1408L 无效的窗口,属于其他的线程
29L 系统不能写到指定的驱动器
19L 介质写保护
34L 驱动器中错误的磁盘,将%2(卷系列号%3)插入到驱动器%1中
1323L 在更新口令时,该状态表示提供的口令值不正确
-1L 无效的输入句柄
-2L 无效的输入句柄
-7L 输入参数超出范围
-5L LZFile文件结构内存不足
-6L 不正确的全局句柄
-3L 损坏的压缩文件结构
-4L 输出文件空间不足
-8L 不能识别的压缩算法
0L 没有错误
1768L 在服务器中出现编址错误
1713L 服务器已在听从
1711L 对象UUID已登记
1746L 连接不包含鉴别信息
1726L 远程过程调用失败
1727L 远程过程调用失败,不能执行
1791L 远程过程调用已在该线程处理中
1764L 不支持请求的操作
1720L 不能生成端点
1740L 端点重复
1760L 入口已存在
1761L 入口没有找到
1769L 在服务器上的浮点操作被零除
1771L 在服务器上出现浮点上溢
1770L 在服务器上出现浮点下溢
1898L 组成员没有找到
1755L 入口名不完善
1759L 界面没有找到
1766L 在RPC中出现内部错误
1749L 安全上下文无效
1702L 连接句柄无效
1734L 数组边界无效
1706L 端点格式无效
1736L 名称语法无效
1707L 网络地址无效
1724L 网络选项无效
1900L 对象通用唯一标识符为空
1704L RPC协议序列无效
1700L 字符串连接无效
1705L 字符串UUID失效
1733L 标志无效
1709L 超时值无效
1756L 版本选项无效
1742L 调用的最大数太小
1762L 命名服务不可用
1718L 没有连接
1725L 在该线程中没有远程过程调用
1765L 允许的模拟没有安全上下文可用
1708L 没有找到端点
1735L 连接不包含入口点名
1806L 没有足够的连接
1757L 没有足够的成员
1719L 没有协议序列
1714L 没有登记的协议序列
1715L 服务器没有听从
1710L 对象UUID没有找到
1721L 没有足够的资源完成该操作
1745L 过程号超出范围
1728L 出现RPC协议错误
1744L RPC协议序列没有找到
1703L 不支持RPC协议序列
1731L 服务器没有足够的内存完成该操作
1723L 服务器太忙,不能完成该操作
1722L 服务器不可用
1743L 字符串太长
1712L 类型UUID已登记
1748L 未知的鉴别级
1747L 未知的鉴别服务
1741L 未知的鉴别类型
1750L 未知的授权服务
1717L 未知的界面
1716L 未知的管理类型
1737L 不支持命令语法
1730L 服务器不支持转换语法
1732L 不支持类型UUID
1739L 没有可用于构造UUID的网络地址
1701L 连接处理为不正确的类型
1767L 服务器试图做整数被零除
1783L 存根接收到不正确的数据
1782L 字节计算太小
1781L 枚举值超界
1772L 用于自动处理连接的可用服务器已用完
1780L 空引用指针传递到存根
1779L 存根不能得到调用句柄
1773L 由DCERPCCHARTRANS指定的文件不能打开
1774L 包含字符转换表的文件小于512KB
1777L 在调用中上下文处理改变
1776L 上下文句柄与任何已知的不匹配
1778L 传递给远程调用的连接句柄不匹配
1775L 空的上下句柄作为参数传递
************************************************************************************************

delphi编译错误信息


  发表时间:2004-8-10
作者:未知[获得此文档时候没有作者记录,深感抱歉,本文档全为转载]
  


*******************************
* 编 译 错 误 信 息 *
*******************************
‘;’ not allowed before ‘ELSE’ ElSE前不允许有“;”
‘<clause>’ clause not allowed in OLE automation section 在OLE自动区段不允许“<clause>”子句
‘<name>’ is not a type identifier <name>不是类型标识符
‘<name>’ not previously declared as a PROPERTY <name>前面没有说明PROPERTY
‘GOTO <label>’ leads into or out of TRY statement GOTO <label>进入或超出TRY语句的范围
<clause1> clause expected, but <clause2> found 要求子句<clause1>,但出现<clause2>
16-Bit fixup encountered in object file ‘<text>’ 在对象文件<text>遇到16位修复
486/487 instructions not enabled 不能用486/487指令
Abstract methods must be virtual or dynamic 抽象方法必须为虚拟的或动态的
Array type required 需要数组类型
Assignment to FOR-Loop variable ‘<Name>’ 给FOR循环变量<Name>赋值
Bad argument type in variable type array constructor 在变量类型数组结构中不正确的参数类型
Bad file format ‘<name>’ 错误的文件格式<name>
Bad file format: <Filename> 错误的文件格式<Filename>
Bad global symbol definition: ‘<Name>’ in object file ‘<Filename>’ 对象文件’<Filename>’中错误的全局符号定义’<Name>’
Bad unit format: <Filename> 错误的单元格式<Filename>
BREAK or CONTINUE outside of loop BREAK或CONTINUE超出循环
Cannot add or subtract relocatable symbols 不能增加或减少可重置的符号
Cannot assign to a read-only property 不能指定只读属性
Cannot BREAK, CONTINUE or EXIT out of a FINALLY clause 超出FINALLY子句的范围,不能使用BREAK,CONTINUE或EXIT语句
Cannot initialize local variables 不能初始化局部变量
Cannot initialize multiple variables 不能初始化多个变量
Cannot initialize thread local variables 不能初始化线程局部变量
Cannot override a static method 不能覆盖静态方法
Cannot read a write-only property 不能读取只写属性
Case label outside of range of case expression CASE标号超出了CASE表达式的范围
Circular unit reference to <Unitname> 对单元<Unitname>循环引用
Class already has a default property 类已具有默认的属性
Class does not have a default property 类没有默认的属性
Class or object types only allowed in type section 在类型区段只允许有类或对象类型
Class type required 需要类类型
Close error on <Filename> 文件<Filename>关闭错误
Compile terminated by user 用户中止编译
Constant expected 要求常量
Constant expression expected 要求常量表达式
Constant expression violates subrange bounds 常量表达式超出子界范围
Constant object cannot be passed as var parameter 常量对象不能作为变量参数传递
Constant or type identifier expected 要求常量或类型标识符
Constants cannot be used as open array arguments 常量不能用作打开数组参数
Constructing instance of ‘<name>’ containing abstract methods 构造的<name>实体包含抽象的方法
Could not compile used unit ‘<Unitname>’ 不能用单元<Unitname>编译
Could not create output file <Filename> 不能建立输出文件<Filename>
Could not load RLINK32.DLL 不能加载RLINK32.DLL
Data type too large: exceeds 2 GB 数据类型太大:超过2GB
Declaration of <Name> differs from previous declaration <Name>的说明与先前的说明不同
Default property must be an array property 默认的属性必须为数组属性
Default values must be of ordinal, pointer or small set type 默认的值必须为序数、指针或小集类型
Destination cannot be assigned to 目标不能指定
Destination is inaccessible 目标不能存取
Dispid ‘<number>’ already used by ‘<name>’ DISPID标识号已被<name>使用
Dispid clause only allowed in OLE automation section DISPID子句只能在OLE自动区段中使用
Division by zero 除数为零
Duplicate case label CASE标号重复
Duplicate tag value 重复的标志值
Dynamic method or message handler not allowed here 这里不允许有动态方法或信息处理程序
Dynamic methods and message handlers not allowed in OLE automation section在OLE自动区段不允许有动态方法或消息处理程序
Element 0 inaccessible – use ‘Length’ or ‘SetLength’ 元素0不能存取-使用LENGTH或SETLENGTH
Error in numeric constant 数值常量错误
EXCEPT or FINALLY expected 要求EXCEPT或FINALLY
EXPORTS allowed only at global scope EXPORTS只允许在全局范围使用
Expression has no value 表达式没有值
Expression too complicated 表达式太复杂
Field definition not allowed in OLE automation section 在OLE自动区段中不允许域定义
Field definition not allowed after methods or properties 在方法或属性后不允许域定义
Field or method identifier expected 要求域或方法标识符
File not found: <Filename> 文件<Filename>没有找到
File type not allowed here 这儿不允许文件类型
For loop control variable must be simple local variable FOR循环控制变量必须为简单局部变量
For loop control variable must have ordinal type FOR循环控制变量必须为序数类型
FOR or WHILE loop executes zero times – deleted FOR或WHILE循环执行零次-删除
FOR-Loop variable ‘<name>’ cannot be passed as var parameter FOR循环变量<name>不能作为参数传递
FOR-Loop variable ‘<Name>’ may be undefined after loop 在循环后的FOR循环变量<NAME>是不确定的
Function needs result type 函数需要结果类型
Identifier redeclared: ‘<name>’ 标识符<name>重复说明
Illegal character in input file: ‘<char>’ ($<hex>) 在输入文件中的非法字符’<char>’
Illegal message method index 非法的消息方法指针
Illegal reference to symbol ‘<Name>’ in object file ‘<Filename>’ 在对象文件<filename>中对符号<name>的非法引用
Illegal type in OLE automation section: ‘<typename>’ 在OLE自动区段中的非法类型<typename>
Illegal type in Read/Readln statement 在Read/Readln语句中的非法类型
Illegal type in Write/Writeln statement 在Write/Writeln语句中的非法类型
Inaccessible value 不可存取的值
Incompatible types: ‘<name>’ and ‘<name>’ 不兼容的类型<name>和<name>
Incompatible types: <text> 不兼容的类型<text>
Inline assembler stack overflow 内联汇编溢出
Inline assembler syntax error 内联汇编语法错误
Instance variable ‘<name>’ inaccessible here 实体变量<name>在这里不能存取
Integer constant or variable name expected 要求整形常量或变量名
Integer constant too large 整型常量太大
Internal error: <ErrorCode> 内部错误<errorcode>
Invalid combination of opcode and operands 操作码与操作对象的无效组合
Invalid compiler directive: ‘<Directive>’ 无效的编译指令<DIRECTIVE>
Invalid function result type 无效的函数值类型
Invalid message parameter list 无效的消息参数列表
Invalid register combination 无效的寄存器组合
Invalid typecast 无效的TYPECASE
Label ‘<Name>’ is not declared in current procedure 在当前的过程中没有说明标号<NAME>
Label already defined: ‘<Labelname>’ 标号<LABELNAME>已经定义
Label declaration not allowed in interface part 在界面部分不允许标号说明
Label declared and referenced, but not set: ‘<label>’ 标号被<label>说明及引用,但不能设置
Label expected 要求标号
Left side cannot be assigned to 左边不能赋值
Line too long (more than 255 characters) 行太长(超出255个字符)
Local class or object types not allowed 不允许局部的类或对象类型
Local procedure/function ‘<Name>’ assigned to procedure variable 局部过程/函数<NAME>赋给过程变量
LOOP/JCXZ distance out of range LOOP/JCXZ距离超出范围
Low bound exceeds high bound 下界超过上界
Memory reference expected 要求内存引用
Method ‘<name>’ hides virtual method of base type ‘<name>’ 方法<NAME>隐藏了基类型为<NAME>的虚拟方法
Method ‘<name>’ not found in base class 在基类中没有找到方法<NAME>
Method identifier expected 要求方法标识符
Missing ENDIF directive 缺少ENDIF指令
Missing operator or semicolon 缺少操作符或分号
Missing or invalid conditional symbol in ‘$<symbol>’ directive 在$<symbol>指令中缺少或无效的条件符号
Missing parameter type 缺少参数类型
Necessary library helper function was eliminated by linker 必要的库帮助函数被连接程序删除
No definition for abstract method ‘<name>’ allowed 抽象方法<NAME>没有定义
Not enough actual parameters 没有足够的实际参数
Number of elements differs from declaration 元素数与说明不同
Numeric overflow 数值溢出
Object or class type required 需要对象或类类型
Object type required 需要对象类型
Only register calling convention allowed in OLE automation section 在OLE自动区段中只允许寄存器调用约定
Operand size mismatch 运算对象大小匹配
Operator not applicable to this operand type 运算符不使用于这一运算对象类型
Order of fields in record constant differs from declaration 在记录常量中的域次序与说明不同
Ordinal type required 需要序数类型
Out of memory 内存溢出
Overflow in conversion or arithmetic operation 转换或算术操作溢出
Overriding automated virtual method ‘<name>’ cannot specify a dispid 覆盖的自动虚拟方法<NAME>不能指定DISPID
PACKED not allowed here 这里不允许PACKED
Pointer type required 需要指针类型
Procedure cannot have a result type 过程不能有结果类型
Procedure DISPOSE needs destructor 过程DISPOSE需要destructor
Procedure FAIL only allowed in constructor 过程FAIL只允许在constructor方法中
Procedure NEW needs constructor 过程NEW需要constructor方法
PROCEDURE or FUNCTION expected 要求PROCEDURE或FUNCTION
Procedure or function name expected 要求过程或函数名
Program or unit ‘<name>’ recursively uses itself 程序或单元递归
Property ‘<name>’ does not exist in base class 在基类中<NAME>属性不存在
Published property ‘<name>’ cannot be of type <type> Published属性<NAME>不能具有类型<type>
Published Real48 property ‘<name>’ must be Single, Double or Extended Published REAL属性<NAME>必须为Single, Double或Extended
Re-raising an exception only allowed in exception handler 在意外处理中只允许重新引起意外处理
Read error on <Filename> 文件<FILENAME>读出错
Record, object or class type required 需要记录,对象或类类型
Redeclaration of ‘<name>’ hides a member in the base class <NAME>的重新说明隐藏了基类中一个元素
Redeclaration of property not allowed in OLE automation section 在OLE自动区段中不允许属性重复说明
Return value of function ‘<Functionname>’ might be undefined 函数<Functionname>的返回值可能没有定义
Seek error on <Filename> 在<FILENAME>中搜索错误
Segment/Offset pairs not supported in Borland 32-bit Pascal 在Borland 32位的PASCAL中不支持Segment/Offset对
Sets may have at most 256 elements 集至少有256个元素
Size of published set ‘<name>’ is >32 bits published集<NAME>的大小大于32字节
Slice standard function only allowed as open array argument Slice标准函数只允许作为打开数组参数
Statement expected, but expression of type ‘<type>’ found 要求语句,但出现类型<TYPE>的表达式
Statements not allowed in interface part 在界面中不允许的语句
String constant too long 字符串常量太长
String constant truncated to fit STRING[<number>] 字符串常量截取到适合STRING[<number>]
Strings may have at most 255 elements 字符串至少255个元素
Structure field identifier expected 要求结构域标识符
Syntax error in real number 实数语法错误
System unit out of date or corrupted: missing ‘<name>’ 系统单元超出日期或损坏:缺少<NAME>
Text after final ‘END. 编译器忽略END.后的文本
This form of method call only allowed for class methods 该方法的窗体只允许类方法
This form of method call only allowed in methods of derived types 该方法的窗体只允许在导出类型的方法中
This type cannot be initialized 这一类型不能初始化
Thread local variables cannot be ABSOLUTE 线程局部变量不能是ABSOLUTE
Thread local variables cannot be local to a function or procedure 线程局部变量对函数不能是局部的
Too many actual parameters 太多的实际参数
Too many conditional symbols 太多的条件符号
Type ‘<name>’ has no type info 类型<NAME>没有类型信息
Type ‘<Name>’ is not yet completely defined 类型<NAME>至今没有定义
Type ‘<name>’ must be a class to have a PUBLISHED section 类型<NAME>必须是在PUBLISHED区段中的类
Type ‘<name>’ must be a class to have OLE automation 类型<NAME>必须是具有OLE自动的类
Type ‘<name>’ needs finalization – not allowed in file type 类型<NAME>需要初始化-不允许在文件类型中
Type ‘<name>’ needs finalization – not allowed in variant record 类型<NAME>需要初始化-不允许在变体记录
Type expected 要求TYPE
Type not allowed in OLE Automation call 在OLE自动调用中不允许的类型
Type of expression must be BOOLEAN 表达式的类型必须为BOOLEAN型
Type of expression must be INTEGER 表达式的类型必须为INTEGER型
TYPEINFO standard function expects a type identifier TYPEINFO标准函数要求类型标识符
TYPEOF can only be applied to object types with a VMT TYPEOF只能用于具有VMT的对象类型
Types of actual and formal var parameters must be identical 形参与实参必须一致
Undeclared identifier: ‘<name>’ 未说明的标识符<NAME>
Unexpected end of file in comment started on line <Number> 以行<Number>开始的注释中出现不应有的文件结束
Unit <Unit1> was compiled with a different version of <Unit2> 单元<UNIT1>与不同版本的<UNIT2>编译
Unit name mismatch: ‘<Unitname>’ 单元名不匹配<UNITNAME>
Unknown directive: ‘<Directive>’ 未知的指令<DIRECTIVE>
Unnamed arguments must precede named arguments in OLE Automation call 在OLE自动调用中未命名的参数必须在命名的参数前
Unsatisfied forward or external declaration: ‘<Procedurename>’ 不满足的向前或外部说明<Procedurename>
Unterminated string 未结束的字符串
Value assigned to ‘<Name>’ never used 赋给<NAME>的值从未使用过
Variable ‘<name>’ inaccessible here due to optimization 由于优化,变量名<NAME>在这里不能存取
Variable ‘<name>’ is declared but never used in ‘<name>’ 变量名<NAME>已说明,但不曾使用
Variable ‘<Name>’ might not have been initialized 变量名<NAME>可能没有初始化
Variable required 需要变量
Virtual constructors are not allowed 不允许虚拟的constructors方法
Write error on <Filename> 文件<Filename>写错误
Wrong or corrupted version of RLINK32.DLL RLINK32.DLL版本错误或不能用
***********************************************************************************************

2005年01月29日

 

初级优化篇

说到优化,很多人又不屑一顾了,“现在计算机速度都那么快了,再快那么百分之几有什么意义啊”。这么说确实有些道理,现在的编译器编译后的结果已经是充分优化过了,除了图形图像多媒体等特定软件的开发外、多数情况下刻意的优化确实没必要,但是如果开发人员在编写代码的时候已经具有了优化意识,在完成优化的同时,又能保证了甚至提升开发效率,何乐而不为呢?

当然,算法的设计都是优化的核心,绝大多数情况下,程序的执行效率高低主要由开发人员对程序整体把握,算法的设计等来决定!但有时候针对细节的优化也是有一定意义的!

而且这种优化在很多情况下也并不需要直接通过汇编来写代码实现,但这种情况下却也能体现出掌握汇编知识的优越性!

如下面两个函数:

function GetBit(i: Cardinal; n: Cardinal): Boolean;
begin
  Result := Boolean((i shr n) and 1);
end;

function GetBit(i: Cardinal; n: Cardinal): Boolean;
begin
  Result := Boolean((1 shl n) and i);
end;

对应的汇编代码:

MOV ECX, EDX
SHR EAX, CL
AND EAX, $01

MOV ECX, EDX
MOV EDX, $01
SHL EDX, CL
AND EAX, EDX

它们的作用一样,都是取i某位的值,为1返回True,0返回False!

表面上看可能都会认为两个函数的执行效率一样,实际上还是有区别的,第一段程序是的移位操作是对i进行的,按照Delphi中默认的调用约定register,此时的i的值是存在寄存器EAX中,移位操作可直接完成;而第二段程序则不同,要对立即数1完成移位操作,必须先将其传送到寄存器,由此也就必然多出一条指令!当然也不是所有情况下,指令少就一定比指令多要快,具体执行时还要考虑指令执行的时钟周期和指令的配对等问题(后面再介绍些),独立出来也说明不了问题,只有在具体代码环境中才好作比较。

一般情况下这种效率上的执行差异实在是太微不足道了,但在编程期间时刻保持着优化的意识绝不是件坏事!如果此类代码位于循环的最里层,N个时钟周期经过大量循环的累积,产生的执行效率差异也可能变的很大!

上面只是个很小的例子,由此可以看出在开发中如果能站在汇编的角度思考一些问题,能在保证开发效率的同时用高级语言编写出更有效率的细节代码!但还有很多时候,细节优化还要用使用嵌入汇编代码来完成,而且有些时候由于嵌入汇编代码应用,还能使代码编写变得更有效率。

如需要将一个32位数的字节顺序颠倒,在Delphi中,完全用高级语言实现怎么做?用移位可以,多次调用内建函数Swap也可以,但是如果想到一条BSWAP指令,这一切变得很简单。

function SwapLong(Value: Cardinal): Cardinal;
asm
  BSWAP EAX
end;

注:同上,Value的值是存在寄存器EAX中,而32位数的值也通过EAX返回,所以只需要一句即可。

当然多数的嵌入汇编优化没有这么简单,不过通过大学里所学的那一点点汇编知识也很难做到更深入的优化,也只能通过不断的积累,对比编译后的汇编代码获取经验!好在多数情况下,细节优化并不是程序设计的主体。

但如果所开发程序涉及到图形图像多媒体等方面,还是有必要进行更深入的优化的!好在不管是浮点指令的优化还是应用MMX、SSE、3DNow等完成优化,Delphi6都能提供良好的支持。即使是想早期版本的Delphi支持这些CPU扩展指令集或者想要支持以后新的CPU指令集,利用Delphi在嵌入汇编中所支持的DB、DW、DD、DQ等四条汇编指令(在Borland的Delphi6官方语言手册里只说支持DB、DW、DD)插入相关指令的数值表示也能灵活的实现。

如:

DW $A20F //CPUID

DW $770F //EMMS
DB $0F, $6F, $C1 //MOVQ MM0, MM1

了解指令只是基础,在围绕FPU,MMX,SSE设计完算法后,想更深一步的进行优化,还必须了解一些CPU本身的技术特性。

先看看下面两段代码:

asm
  ADD [a], ECX
  ADD [b], EDX
end

asm
  MOV EAX, [a]
  MOV EBX, [b]
  ADD EAX, ECX
  ADD EBX, EDX
  MOV [a], EAX
  MOV [b], EBX
end

第二个效率高?错了,如上面说的,指令少不意味着执行效率高,查查相关资料可知,第一段代码的两条指令执行的时钟周期为3(每条指令都需要完成读、改、写三步),第二段代码中的6条指令执行的时钟周期都为1。那么说两段代码效率一样?又错了,实际上第二段代码执行效率比第一段代码要高!为什么?因为奔腾级以后的CPU都有两条流水线来执行指令,所以当相邻的两条指令能够完成配对,那么它们就能够同时执行!具体到上面的两段代码来说具体原因又是什么呢?

第一段代码中的两条指令虽然可以完成配对,但需要的总执行时钟周期为5而不是3,而第二段代码的六条指令可以两两之间并行执行,所以也就导致了这个结果。

说到这里,都是些很浅显的例子,本身给不了大家太多的帮助。如果真的想优化特定程序,还是找些FPU,MMX优化的专题文章看看,或者找来技术手册好好专研专研“乱序执行”和“分枝预测”等技术。只希望各位在上大学的朋友们不要只专注于那些“能赚钱”的开发工具和时髦的新技术,能把更多的时间花在打基础上,有了扎实的基础才能快速掌握新知识、才能用更快的时间掌握新的开发工具、才能…(省略一千字)。

不过话又说回来,知识还是要用来解决实际问题的,如果每天就只在技术细节上做文章,也许能成为一个出色的黑客,但绝对开发不出一流的软件。所以还是要以创造价值为根本目的。所以…不说了,再说下去就真不像技术文章了。^_^

附:程序优化除了考虑执行效率以外,当然也要考虑体积的问题(体积小才能更快的载入内存,更快的完成指令译码等工作),比如清空EAX寄存器都是用SUB EAX, EAX或XOR EAX, EAX而不会用MOV EAX, $0,虽然它们的执行时钟周期都是1,但前者的指令长度(2字节)明显比后者(5字节)短。但因为上面说的都是些细节,所以没提到体积的问题。更多的缩小体积的问题还是交给编译器去解决吧,在编写嵌入ASM代码的同时稍微注意一下就可以了。

 

前言

很多人脑子里都有这么一些概念:

“汇编啊?那是‘高手’们的专利,我用不上”、“我又不和系统打交道,学汇编干嘛啊”、“用汇编写程序的人是白痴(CSDN论坛里看到的原话),太没效率了”

同意的朋友一定不少!诚然现在的软件已经越来越庞大越,来越复杂,程序开发人员已经远离了那个只和二进制0、1代码,汇编助记符打交道的年代!就算是写系统软件也是如此,PC上的操作系统、编译工具绝大多数代码也是用高级语言完成的!如果现在有人说要完全用汇编在PC上开发软件(不是写程序),那确实不值得大家推崇!但任何问题都要辩证的来看,但如果能在应用高级语言开发软件的过程中合理的应用所学的汇编知识绝对是有百利而无一害(因为这里是说Delphi和汇编,移植等问题不考虑在内)。知识是用来解决实际问题的,所以没有必要排斥汇编,应该让其充分融入到我们应用高级语言开发的过程中去!虽然一味的强调技术至上不对,但也不能因此完全忽略代码细节。最好还是能在开发效率和执行效率中找到一个平衡。

如果掌握了汇编,如果使用其相关知识呢?

首当其冲的当然是利用嵌入汇编编写程序。在Delphi中的嵌入汇编程序很多情况可以参照Win32ASM。但由于嵌入汇编的特殊性,这里并不支持EQU、PROC、STRUC、SEGMENT等伪指令,它们实现的大部分功能已由Object Pascal实现。另外在寄存器的修改方面,除了ESI、EDI、EBP等外,EBX也不能被随便修改,因为在Delphi中用EBX保存Self指针。如果一定要用,必须用栈来保存/恢复寄存器中的原始数据。当然嵌入汇编只是解决问题的手段,不是目的,汇编代码会降低程序的可读性、可维护性能,在同等情况下理应优先考虑如何用高级语言实现。

利用嵌入汇编编写程序自然也不是学习汇编的唯一目的,更重要的掌握了一种从深层次理解程序的方法。即使不直接用汇编写程序,将这种用汇编思考问题的思想融入到用高级语言编写程序的过程中去,汇编一样成为我们分析解决问题的利器。另外汇编知识对于程序调试一样也有着不可替代的作用,在很多情况汇编层的调试可以更快的解决问题。由此可见,有些情况下汇编知识的应用不光是为了提高程序执行效率,有时候也是为了提高开发效率。

许多朋友并没有认真学习过汇编,就开始大喊汇编无用,大学课本无用就显得太不负责任了。有些朋友在大学里只求掌握几种时髦的开发工具和最新的开发技术,而忽视汇编这类基础知识的学习也不见得是明智的做法。相信只要冯.诺伊曼的计算机体系不发生改变,现有的汇编知识必然会有其用武之地。

 

2.调用动态链接库(DLL)方式

第二种方法比第一种方法实现起来麻烦一些。在这种方法中,FORTRAN程序首先被编译成Windows标准的动态链接库文件(DLL, Dynamic-Link Library),然后在Delphi中调用。在FORTRAN语言程序设计中,本文采用Compaq Visual Fortran6.6编译器,可以容易地生成动态链接库。

在这种方式混合编程中,由于需要在两种不同的语言之间进行内存中的数据交换,因此,其数据类型必须一一对应。由于不同语言的数据类型所对应的存储方式、数据传递方式不尽相同,而且程序调试需要在两个不同的编译器中进行,因此这种方法编译调试较为麻烦,不易解决编译中出现的一些问题。

在生成动态链接库的FORTRAN子程序中,必须采用以下方法进行说明:

DECATTRIBUTES DLLEXPORT::SUB_NAME
      DECATTRIBUTES ALIAS:’Sub_AliasName’::SUB_NAME

上面第一句话中,关键字DLLEXPORT表明这个子程序在动态链接库中可被外部调用,SUB_NAME为此子程序在动态链接库中的程序名;第二句话中的ALIAS给该程序名另赋一个别名,因为FORTRAN默认情况下编译出的程序名为大写字母,别名中可以改变。

函数和程序的调用中,参数传递的方式有两种。一种是传递参数地址的方式,即Call by Refence,另一种是传递值的方式,即Call by Value。在CVF生成动态链接库时,默认的通信协议为’_StdCall’,其参数传递方式是第一种。而在Delphi中,参数的传递方式跟参数本身的类型相关。如有一子过程定义:

Procedure Sub_Name(Const x1,x2:Double; Var x3,x4:Double);

这里,x1,x2被定义为常数类型的双精度实数,其参数传递方式为第二种,即Call by value,其值在传递中保持不变;x3, x4为变量类型的双精度整数,其传递方式为第一种,即Call by Reference,其值在计算中可以被改变*。在默认情况下,即参数前面没有说明属于那种类型的参数,则默认为常数类型。

也可以采用Delphi中的指针变量来获得与FORTRAN默认条件下完全相同的调用方式:

Procedure Sub_Name(x1,x2:Pointer;Var x3,x4:Double);

Pointer表明x1,x2是无类型指针(也可以采用有类型指针,这里从略),这时如果在另一程序中定义了四个双精度数a1,a2,a3,a4,按如下方式调用:

   Sub_Name(@a1,@a2,a3,a4)

a1,a2,a3,a4中的值在计算前后都是可以改变的。

以指针变量作为虚参对于DelphiFORTRAN的混合编程中数组的传递很有意义。因为Delphi的数组和FORTRAN中不同,Delphi中固定的数组和动态数组的传递方式是不相同的。采用指针,就没有那么费事。如上例中,如果a1,a2是数组,那么其调用方式为:

    Sub_Name(@a1[0],@a2[0],a3,a4)

就是将第一个数组元素的地址传递过去。如果不是第一个元素的地址,是后面某个元素的地址,则虚实数组的结合从这个元素开始。

如果虚参中出现了字符串,则有两种传递方式。一种是根据FORTRAN中的标准,同时传递字符串内容和长度,这时,FORTRAN中的子程序定义为:

Subroutine Sub_Name(x1,x2,Str,x3, x4)

DECATTRIBUTES DLLEXPORT::SUB_NAME
         DECATTRIBUTES ALIAS:’Sub_Name’::SUB_NAME

  Real(8),Dimension(:)::x1,x2

  Character(Len=*) Str

  Real(8),Intent(Out)::x3,x4

Delphi中相应的接口过程定义为:

Procedure Sub_Name(Const x1,x2:Pointer;

Str:String;

Len:Integer;

 Var x3, x4:Double);

则按如下方式调用:

  Sub_Name(@a1[0],@a2[0],Str,Length(Str),a3,a4);

另一种方式更为简便,因为在Delphi中,字符串被视为动态数组,以Call by Refence方式传递,因此,可先在FORTRAN中使用编译字将字符串的传递方式强制为地址传递方式,即:

Subroutine Sub_Name(x1,x2,Str,x3, x4)

DECATTRIBUTES DLLEXPORT::SUB_NAME
         DECATTRIBUTES ALIAS:’Sub_Name’::SUB_NAME

  Real(8),Dimension(:)::x1,x2

  Character(Len=*) Str

  Real(8),Intent(Out)::x3,x4

DEC$ Attributes Refence::Str

这时,Delphi中相应的接口过程定义为:

Procedure Sub_Name(Const x1,x2:Pointer; Str:String; Var x3, x4:Double);

则按如下方式调用:

 Sub_Name(@a1[0],@a2[0],Str, a3,a4);

第二种混合编程方式的实现过程是:首先在FORTRAN子程序按上述方法定义好,并编译成动态链接库ForSub.Dll;然后在Delphi中按如下方法定义动态链接库子过程接口。在接口区(Interface)中定义过程首部:

Procedure Sub_Name(Const x1,x2 : Pointer;

 Str : String;

Var x3, x4 : Double); Stdcall;

在实现区(Implementation)中加上以下语句:

Procedure Sub_Name; external ‘ ForSub.dll’ name ‘ Sub_Name ‘;

这样,在Delphi程序设计中就可以像调用自己的子程序一样调用Sub_Name了。

由于FOR90引入了模块(Module)单元,可以将一些相关的数据和方法封装在模块里,因此,对一个模块中不同的子程序进行上述的定义,则在一个动态链接库中获得多个可被外部程序调用的子程序。在Delphi中要调用这些子程序,对每一个都需要编写Delphi中的子过程接口。

 




* FOR90中借鉴了这种区分输入输出参数的定义方式,引入了关键字Intent,在参数定义中,Intent(in)表示该参数在传递过程中是不改变的,Intent(Out)表示是可以改变的,并且FOR90中规定得更为严格。

 

众所周知,FORTRAN强于数值计算,尤其是如果计算主要针对复数进行,则FORTRAN更有无可比拟的优势。FORTRAN是所有语言中唯一将复数定义为一种标准数据类型的语言。但是FORTRAN语言在可视化程序设计方面是非常欠缺的,至少目前还没有一家厂商推出具有RAD特性的FORTRAN编译集成开发环境。因此,当用FORTRAN实现了一种大型的科学计算以后,却难以将这种计算转变为数据输入简易、结果显示方便的WINDOWS可视化应用程序。这一点,采用Delphi很容易实现。因此,在许多情况下,使用FORTRANDelphi的混合编程可同时具有二者的优点。

本文采用两种不同的方法来实现混合编程。一种是直接执行可执行文件的方式,一种是调用动态链接库中子程序的方式。在第一种方式下,在Delphi程序设计中直接执行FORTRAN程序的执行文件,通过文件来进行数据交换;在第二种情况下,首先将FORTRAN程序编译成动态链接库(DLL),在Delphi程序设计中,调用此动态链接库中某个子程序来完成某项计算。这两种方式各有优缺点。第一种方式的调试较为简单,不存在不同语言之间的数据类型的不匹配问题。但是,这种方式下,在Delphi中无法实现对程序运行的有效监督,同时,以文件进行数据交换在操作中也不太方便,效率也不高。第二种方式则整合了两种程序之间的差别,如果编制成功,程序运行时看不出混合语言编程的痕迹,但是这种方式调试起来特别麻烦。一般说来,对于已有的输入输出较为复杂的FORTRAN程序,可以考虑第一种方式,而对于相对简单的,或者自己着手编制的新的程序,可选用第二种。

 

1.执行可执行文件(exe)方式

Windows中提供了API函数WinExec来执行存在的执行文件。该函数定义为:

UINT WinExec(LPCSTR lpCmdLine, UINT uCmdShow );

参数说明如下:
        LPCSTR lpCmdLine: 包含要执行的命令行。

系统将在以下范围查找应用程序:
            应用程序启动位置
            当前目录位置
            Windows system目录
            Windows 目录
            path中设置的路径列表
        UINT uCmdShow: 定义了以怎样的形式启动程序的常数值。具体说明如下:
           SW_HIDE 隐藏窗口,活动状态给令一个窗口 
           SW_MINIMIZE 最小化窗口,活动状态给令一个窗口
 
           SW_RESTORE 用原来的大小和位置显示一个窗口,同时令其进入活动状态
 
           SW_SHOW 用当前的大小和位置显示一个窗口,同时令其进入活动状态
 
           SW_SHOWMAXIMIZED 最大化窗口,并将其激活
 
           SW_SHOWMINIMIZED 最小化窗口,并将其激活
 
           SW_SHOWMINNOACTIVE 最小化一个窗口,同时不改变活动窗口
 
           SW_SHOWNA 用当前的大小和位置显示一个窗口,不改变活动窗口
 
           SW_SHOWNOACTIVATE 用最近的大小和位置显示一个窗口,同时不改变活动窗口
 
           SW_SHOWNORMAL SW_RESTORE相同 

如果Str为一记录可执行文件的路径及文件名变量,则WinExec ( Pchar ( Str ), SW_SHOWNORMAL )表示在正常状况下执行该可执行文件。

 

Delphi.NET 内部实现分析(5)

2.5 其它

  
在了解了Borland.Delphi.System中的几个重要部分之后,剩下的就是一些零零碎碎的扫尾工作。

2.5.1 
类型别名

  
为兼容Delphi中的特有类型,Borland.Delphi.System单元中定义了很多类型别名。
如我们前面分析过的TObject就是System.Object的别名。
//—————————————–Borland.Delphi.System.pas–
type
  TDateTime = type Double;
  Extended = type Double;   // 80 bit reals are unique to the Intel x86 architecture
  Comp = Int64 deprecated;

  TGUID = packed record
    D1: LongWord;
    D2: Word;
    D3: Word;
    D4: array[0..7] of Byte;
  end;
//—————————————–Borland.Delphi.System.pas–
  
对于DelphiTDateTime类型来说,它在实现上是以一个Double8字节浮点数存储的,
兼容OLE自动化中的时间格式。在Delphi.NET中继承了这一存储方式,而没有直接使用BCL
提供的System.DateTime结构,不过仍然可以使用DateTime.FromOADate
DateTime.ToOADate
方法在System.DateTimeTDateTime之间双向转换。
格式存储说明如下(from MSDN)

  OLE 
自动化日期以浮点数形式实现,其值为距 1899  12  30 日午夜的天数。
例如,1899  12  31 日午夜表示为 1.01900  1  1 日早晨 6 点表示为 2.25
1899 
 12  29 日午夜表示为 -1.01899  12  29 日早晨 6 点表示为 -1.25
  
只有刻度值大于或等于正的或负的 31241376000000000  DateTime 对象才可以表示为
OLE 
自动化日期。未初始化的 DateTime(即刻度值为 0 的实例)将转换为等效的未初始化
OLE 
自动化日期(即值为 0.0 的日期,它表示 1899  12  30 日午夜)。

  
ExtendedComp则只是一个简单的别名。TGUID也只是一个简单的重定义。
  
值得注意的是这里的packed关键字。在CLR中,类的成员的物理位置对程序本身是没有意义的,
CLR
可以任意安排字段的位置以进行字节对齐等等优化操作。而为了与现有代码进行交互,CLR提供了
StructLayoutAttribute
属性允许限定类型的内部物理结构。在Delphi.NET中可以通过packed
关键字定义此结构的成员必须按定义的次序在内存中排列,即LayoutKind.Sequential的形式。
而在Delphi.NET中,所有的record在实现上都是ValueType的子类,即值类型,直接在堆栈上操作。

2.5.2 
异常

  
TObject一样,Delphi中异常类继承链的根ExceptionDelphi.NET中,也只是BCL的异常类
System.Exception
的一个别名,而只是通过class helper提供源代码级兼容性。
//—————————————–Borland.Delphi.System.pas–
  Exception = System.Exception;
  ExceptionHelper = class helper for Exception
  private
    class function CreateMsg(const Msg: string): Exception;
    function GetHelpContext: Integer;
    procedure SetHelpContext(AHelpContext: Integer);
  public
    // Doc: The help context return zero(0) if exception’s helplink property
    //  cannot be parsed into an integer.
    property HelpContext: Integer read GetHelpContext write SetHelpContext;

    // constructor Create(const Msg: string) is provided by the CLR class
    class function CreateFmt(const Msg: string; const Args: array of const): Exception;
    class function CreateHelp(const Msg: string; AHelpContext: Integer): Exception;
    class function CreateFmtHelp(const Msg: string; const Args: array of const;
      AHelpContext: Integer): Exception;
  end;
  ExceptionClass = class of Exception;

  EConvertError = class(Exception);

threadvar
  _ExceptObject: TObject;

function ExceptObject: TObject;
//—————————————–Borland.Delphi.System.pas–
  
几个类函数Create*(…)都只是对System.Exception构造函数的包装而已,
用于保存异常相关帮助文件路径的System.Exception.HelpLink属性,则被Delphi.NET
用于保存HelpContext,因为在VCL中,所有的异常都是共用一个帮助文件TApplication.HelpFile
  
ExceptObject函数则由编译器支持,提供访问当前被抛出的异常的手段。此函数在Delphi
通过VCL维护的SEH异常链获取,而在Delphi.NET中只好由编译器在异常被截获后手动赋值给线程局部存储变量
_ExceptObject
,然后再由ExceptObject函数读出,这只是语法一级兼容Delphi而已。
  
不过这个预览版好像没有提供threadvar的支持,只是把它简单的放到Borland.Delphi.System单元的
全局变量中,作为自动生成Unit类的一个静态成员变量而已,并非线程安全!在Borland.Delphi.Classes
中甚至直接把TThread的定义注释掉,实现不提供。估计还在开发中……sigh

2.5.3 
断言(Assert)

  
断言负责在调试模式下检测一个条件是否成立,失败则引发异常。
//—————————————–Borland.Delphi.System.pas–
interface

{ debugging functions }

procedure _Assert(const Message, Filename: AnsiString; LineNumber: Integer);

type
  EAssertionFailed = class(Exception)
  public
    ShortMessage: string;
    Filename: string;
    LineNumber: Integer;
  end;

resourcestring
  SAssertionFailed = ’%s (%s at %d)’;

implementation

procedure _Assert(const Message, Filename: AnsiString; LineNumber: Integer);
var
  LException: EAssertionFailed;
begin
  { TODO : Should we be using System.Diagnostics.Debug.Assert/Fail? }
  {$MESSAGE WARN ’Assert doesn”t use CreateFmt because it returns the wrong type’}
  LException := EAssertionFailed.Create(Format(SAssertionFailed, [Message, Filename, LineNumber]));
  LException.ShortMessage := Message;
  LException.Filename := Filename;
  LException.LineNumber := LineNumber;
  raise LException;
end;
//—————————————–Borland.Delphi.System.pas–
  _Assert
函数的定义基本上是EAssertionFailed异常的一个简单包装。因为Delphi没有提供类似
C++
__FILE____LINE__之类的预定义宏,故而只能由编译器在用户使用到Assert函数时,
将当前文件名、行号等调试信息编译进代码中,即在编译器一级提供断言实现。

//—————————————–Borland.Delphi.System.pas–
function Assigned(const AGCHandle: GCHandle): boolean;
begin
  Result := AGCHandle.IsAllocated;
end;
//—————————————–Borland.Delphi.System.pas–

//—————————————–GCHandle.cs–
namespace System.Runtime.InteropServices {
  public struct GCHandle {
    private IntPtr m_handle;

    public bool IsAllocated { get { return m_handle != IntPtr.Zero; } }
  }
}
//—————————————–GCHandle.cs–
  Assigned
则是对一个引用类型变量进行检测,与Delphi类似,Delphi.NET中直接通过检测引用类型值
是否为空(null)判断是否有效,但对于值类型则将之与0进行比较。

2.5.4 
随机数

  Delphi.NET
中的随机数基本上是对BCL相关类的一个简单包装,而BCL的随机数算法与VCL一样弱智,
简单的功能还凑合,BCLSystem.Security.Cryptography.RandomNumberGenerator相比之下
随机性就好得多,不过要付出速度上的代价。

//—————————————–Borland.Delphi.System.pas–
interface

{ random functions }

var
  RandSeed: LongInt = 0;

procedure Randomize;

function Random(const ARange: Integer): Integer; overload;
function Random: Extended; overload;

implementation

var
  LastRandSeed: Integer = -1;
  RandomEngine: System.Random;

procedure InitRandom;
begin
  if LastRandSeed <> RandSeed then
  begin
    if RandSeed = 0 then
      RandomEngine := System.Random.Create
    else
      RandomEngine := System.Random.Create(RandSeed);
    LastRandSeed := RandSeed;
  end;
end;

procedure Randomize;
begin
  LastRandSeed := -1;
  RandSeed := 0;
end;

function Random(const ARange: Integer): Integer;
begin
  InitRandom;
  Result := RandomEngine.Next(ARange);
end;

function Random: Extended;
begin
  InitRandom;
  Result := RandomEngine.NextDouble;
end;
//—————————————–Borland.Delphi.System.pas—

 

Delphi.NET 内部实现分析(6)

 

不好意思,实在想不到有什么值得说的了,只好草草结束了 

2.5.5 
其它.其它

  Borland.Delphi.System
单元虽然比Delphi中的System单元小的多,
但其中也充斥着大量常用但是实现代码枯燥的函数。如

  
数字处理函数集
  
字符串处理函数集
  
命令行信息获取函数集(CmdLine/ParamCount/ParamStr)
  
格式化输出函数集(Format)
  
文本文件(Text类型,而File类型文件不提供支持)///写等函数集
  
动态数组管理(System.Array类型的简单包装)
  
当前路径及目录操作函数集
  
集合类型(CLR中并无集合概念,Set实现上是字节数组的简单包装)
  
其它一些杂项函数
  
等等等等

  
这些零散代码基本上都是对BCL相应功能的简单包装,这里就不一一详述了。

2.5.6 
小结

  
至此,对Delphi.NET中核心单元Borland.Delphi.System单元的介绍
就告一段落了。通过对此单元的分析,我们大致了解了Delphi.NET中对于Delphi
一些核心概念的实现或模仿思路,但不排除在正式版中实现有所改变。


题外话:

  
首先感谢大家的热心支持,这是督促我这个懒人写完文章(哪怕是草草结束)的最大动力,
也希望这篇文章能够对大家了解即将到来的Delphi.NET、迎接.NET时代有所帮助。
  
这个系列文章到这里估计也就暂时告一段落了,因为时间仓促、准备不足而且
笔者水平有限,只涉及到Delphi.NET在实现上与Delphi不同的部分内容,
Delphi.NET的改变来说只是冰山一角而已。本来还想扩大一点分析面,
但考虑到Delphi.NETRTL其它单元大多只是对原有Delphi代码的BCL封装移植
技术难度并不大,对Delphi熟悉的读者直接阅读源程序可能比看我的文章更容易一些。
因此在分析完涉及到一些底层只是的Borland.Delphi.System后就此打住,
虽然有些虎头蛇尾之嫌,但总免得背画蛇添足之骂名 :)
  
至于构建在Delphi.NETRLT之上的应用层架构VCL和以后可能要支持的CLX
我就没有太多精力写文章介绍了。因为就目前实现的VCL代码来看,只是将以前的VCL代码
managed
化而已,实现上还是使用Windows那套传统API管理窗口,与BCL
System.Windows.Forms.Form
根本不搭界。这样一来在Delphi.NET中又多了一个选择
VCL or CLX or System.Windows.Forms.Form…sigh
,是好是坏只能待时间评判。
  
文中如果有解释不够清楚的地方,大家可以跟贴提出。也欢迎来信
于我讨论Delphi.NETCLR相关问题。  再次感谢大家的支持! :)

 

Delphi.NET 内部实现分析(4)

2.4 消息

  
对于类的可重载方法而言,最常见的实现方法是构建一张VTable表,每个方法占一个slot
但这种处理方法受到空间和时间上的限制,在处理大量方法如众多窗口消息的处理方法时有局限性。
为处理这个矛盾,MFC使用宏定义一套独立于类的消息处理函数表,ATL干脆要求编译器在合适
时候不使用VTable以此来进行优化。而Delphi则通过提供类似于虚拟方法的动态方法
dynamic method
来解决这个矛盾。
  
虚方法virtual method保存在VMT中,生成代码为使用效率进行优化,适合完成普通的类继承
重载方法的实现;动态方法则根据一个编号保存在一张单独的表中,生成代码为程序代码大小优化,
适合实现由祖先类定义的大量可重载但并不经常被使用的方法,最典型的例子就是Windows消息处理函数。
因此我们在定义一个Windows消息处理函数时,后面会跟一个message关键字,指定动态方法编号。
//—————————————–Forms.pas–
type
  TScrollingWinControl = class(TWinControl)
  begin
    procedure WMSize(var Message: TWMSize); message WM_SIZE;
    …
//—————————————–Forms.pas–
  
也可以使用dynamic关键字定义动态方法,由系统自动分配方法编号。
  
Delphi中,这张动态方法表的指针保存在VMTvmtDynamicTable域中,
动态方法被调用时会使用TObject.Dispatch方法调用GetDynaMethod函数
从动态方法表中,根据传入消息结构的第一个字段编号,查找编号符合的动态方法,
如果不存在匹配的编号,则调用TObject.DefaultHandler虚方法处理异常情况。
  
而在Delphi.NET中,Borland无法再使用VMT用作数据存储,但为了提供源代码级
兼容性,只能设法模拟Delphi中的语义。
  
好在CLR提供了特性Attribute这种非常好用而且有用的功能,能够将任意的静态或
动态的数据及功能附加到任意的语法元素上。具体原理和使用请参考前面提过的书籍和文章。
Delphi.NET
在预览版中,对Attribute的支持还很弱,不过就我测试,在2002-11-14
的测试版本中,已经可以不只局限于Delphi.NET文档中所说只能用于类型了,估计正式版
发布时应该能够提供完整的支持。
  Delphi.NET
中对动态方法的模拟就是通过MessageMethodAttribute完成的。
当用户使用dynamic定义动态方法,或者使用message定义消息处理函数时,编译器
会自动给此方法添加一个[MessageMethodAttribute(1)]特性,并指定编号。
TObjectHelper.Dispatch则通过枚举类型所有方法,找出带有MessageMethodAttribute
特性的动态方法,根据MessageMethodAttribute.ID判断应该调用的动态方法。
如果没有找到则还是调用TObject.DefaultHandler方法处理异常。为了提高查找效率,
Borland.Delphi.System
单元中还实现了一个缓存,使用HashMethodMaps
将搜索过的类与其动态方法列表缓存起来,下次使用直接可以定位获取。
//—————————————–Borland.Delphi.System.pas–
type
  MessageMethodAttribute = class(TCustomAttribute)
  private
    FID: Integer;
  public
    constructor Create(AID: Integer);
    property ID: Integer read FID;
  end;

  TObjectHelper = class helper for TObject
    …
    procedure Dispatch(var Message);
  end;
//—————————————–Borland.Delphi.System.pas–
  MessageMethodAttribute
特性定义供内部使用,在dyanicmessage关键字
被使用时将自动附加到相应的动态方法上。如

  procedure SayHello(var Message: TMessage); dynamic;

  procedure SayHello(var Message: TMessage); message 1;

  [MessageMethodAttribute(1)]
  procedure SayHello(var Message: TMessage);

  
这几种写法基本上是等价的,只不过dynamic关键字不指定动态方法编号。
//—————————————–Borland.Delphi.System.pas–
function GetMessageID(Obj: TObject): Integer;
var
  Field: FieldInfo;
begin
  Field := Obj.GetType.GetFields[0];
  Result := Integer(Field.GetValue(Obj));
end;
//—————————————–Borland.Delphi.System.pas–
  
TObject.Dispatch而言,参数Message可变,但第一个字段必须是一个Integer
指明此Message需要被Dispatch到哪个编号的动态方法。GetMessageID函数就负责
Reflection从一个类型中获取消息编号。注意这里并没有做异常情况检测,意味着如果
传递无效的类型进来可能导致异常。
  TObjectHelper.Dispatch
代码主要是通过在MethodMaps中查表定位动态方法,
如果没有缓存就使用TMethodMap.Create构造类型与动态方法的映射关系,代码略过。

 

Delphi.NET 内部实现分析(3.4)

由此我们可以看出,Delphi.NET中使用了从内嵌子类到class helper种种方法,
才总算解决了从传统继承模型和内存模型迁移到CLR以及FCL类树的过程,迁移过程不可谓不艰辛。
虽然这种解决方法不能算是完美,但相信Borland也是在综合评估了诸多其它手段之后,
才做出这样的选择,付出了一些代价、如class helper,也取得了不少的成果、源代码级兼容较强。
这种映射模型到底行不行,我想只能有待时间来做评论。
  
最后我们来看看Delphiisas关键字是如何在Delphi.NET中实现的
//—————————————–Borland.Delphi.System.pas–
function _IsClass(Obj:TObject; Cls:TClass): Boolean;
var
  t1, t2: System.Type;
begin
  if not Assigned(Obj) then
    Result := false
  else
  begin
    t1 := Obj.GetType;
    t2 := System.Type.GetTypeFromHandle(_TClass(Cls).FInstanceType);
    if t1 = t2 then
      Result := true
    else
      Result := t1.IsSubclassOf(t2);
  end;
end;
//—————————————–Borland.Delphi.System.pas–
  _IsClass
函数实现很简单,检测对象有效性后直接通过判断两个类型的继承关系检测。
//—————————————–System.pas–
function _IsClass(Child: TObject; Parent: TClass): Boolean;
begin
  Result := (Child <> nil) and Child.InheritsFrom(Parent);
end;
//—————————————–System.pas–
  
相比之下Delphiis实现更简单,直接用TObject.InheritsFrom实现。
Delphi.NET
之所以不象Delphi那样直接使用TObject.InheritsFrom实现is关键字,
是因为相对于Type.IsSubclassOf方法来说,TObjectHelper.InheritsFrom方法
使用的Type.IsInstanceOfType方法代价较大。
  Type.IsSubclassOf
方法只是从传入类型开始,一级一级查看其父类是否自己。
//—————————————–Type.cs–
    public abstract class Type : MemberInfo, IReflect
    {
        public virtual bool IsSubclassOf(Type c)
        {
            Type p = this;
            if (p == c)
                return false;
            while (p != null) {
                if (p == c)
                    return true;
                p = p.BaseType;
            }
            return false;
        }
    }
//—————————————–Type.cs–
  
Type.IsInstanceOfType则要考虑RemotingCOM、接口以及运行时类型等等
诸多复杂因素,因而不适合用在is/as这样频繁使用的关键字实现上。
//—————————————–Borland.Delphi.System.pas–
function _AsClass(Obj:TObject; Cls:TClass): TObject;
begin
  Result := Obj;
  if not _IsClass(Obj, Cls) then
    raise System.FormatException.Create(‘Invalid Cast’);
end;
//—————————————–Borland.Delphi.System.pas–
  as
操作符的实现,只是简单的赋值加检测而已,因为CLR是单根结构,所以转换总是成功的,
只需在转换后用is操作符检测,抛出异常情况就行。
//—————————————–System.pas–
function _AsClass(Child: TObject; Parent: TClass): TObject;
{$IFDEF PUREPASCAL}
begin
  Result := Child;
  if not (Child is Parent) then
    Error(reInvalidCast);   // loses return address
end;
//—————————————–System.pas–
  
可以看到Delphi中的实现也是非常类似的。

  
最后一个相关函数是_ClassCreate,用于实现类型的创建与构造。
//—————————————–Borland.Delphi.System.pas–
function _ClassCreate(Cls: TClass; Params: Array of TObject): TObject;
begin
  Result := System.Activator.CreateInstance(Cls.SystemType, Params);
end;
//—————————————–Borland.Delphi.System.pas–
  
DelphiSystem.pas中冗长的_ClassCreate函数实现相比,Delphi.NET无需关心
类的内存获取、构造异常的截获以及Self指针的修正等等,只是简洁的通过System.Activator
完成所需功能,这就是底层有一个强大完善类库支持的优势所在。
  
至此,Borland.Delphi.System单元中关于元类、类与对象的相关定义及实现就基本上分析完了,
虽然只有寥寥百来行代码,但它为DelphiCLR上的映射打下了坚实的基础。
  
下一节我们将进一步看看Delphi中消息与方法的映射关系是如何在Delphi.NET中模拟的。