2004年08月22日

首先强调,这不是开玩笑! 有一个国家每时每刻都在通过卫星监听我国的无线通讯.还不断派飞机装载高灵敏天线,抵近我国边境线监听卫星收不清的小功率信号。据说能听清沿海城市的无绳电话。南海撞机事件的起因也在于此。
对于巨大的通信内容,依靠监听人员一个个听是不可能的,只能用计算机来处理。人们设定了若干关键词,只要通讯内容中出现了所设的关键词,电脑就会自动录下全次通讯内容并提醒有关人员审听。 在这里建议各位在大家使用手机或接听拔打手机等无线通汛工具时,请不要说 你好,再见,谢谢等,而用“潜艇出动,导弹发射,解放台湾”等美国所设的关键词代替。这样的话,它就是再增加一百倍成本也难用人工听完中国大陆庞大的通话内容
顺便告诉你:在你和情人说完情话后,加上一句本拉登或是轰炸台北,在几十年之后可能会在某国档案中找到你今天的情话录音(@_@)
两拐两拐,我是洞妖,你的方位你的方位…
洞妖洞妖,我在你左翼,我在你左翼…
洞妖明白,洞妖明白,现在赶往预定地点集结待命…
两拐明白…
然后学校大门左边的两拐和右边的洞妖一起走向马路对面的拉面馆..
侦察机上的监听人员拼命记录,妄图继续追踪… …………
请转载,转载,再转载!让地球上所有的中国人都知道!谢谢,拜托!^_^

2004年08月21日

5) 锁定/解锁驱动器
入口:
  AH = 45h
  AL
    = 0 锁定驱动器
    = 1 驱动器解锁
    = 02 返回锁定/解锁状态
    = 03h-FFh – 保留
  DL = 驱动器号

返回:
  CF = 0, AH = 0 成功
  CF = 1, AH = 错误码

  这个调用用来缩定指定驱动器中的介质.
  所有标号大于等于 0×80 的可移动驱动器必须支持这个功能.如果
在支持可移动驱动器控制功能子集的固定驱动器上使用这个功能调用,将会成功返回.
  驱动器必须支持最大255次锁定, 在所有锁定被解锁之前,不能在物理上将驱动器解锁. 解锁一个未锁定的驱动器,将返回错误码 AH= B0h.如果锁定一个已锁定了255次的驱动器, 将返回错误码 AH = B4h.
  锁定一个没有介质的驱动器是合法的.

6) 弹出可移动驱动器中的介质
入口:
  AH = 46h
  AL = 0 保留
  DL = 驱动器号

返回:
  CF = 0, AH = 0 成功
  CF = 1, AH = 错误码

  这个调用用来弹出指定的可移动驱动器中的介质.
  所有标号大于等于 0×80 的可移动驱动器必须支持这个功能.如果
在支持可移动驱动器控制功能子集的固定驱动器上使用这个功能调用,将会返回错误码 AH = B2h (介质不可移动).如果试图弹出一个被锁定的介质将返回错误码 AH = B1h (介质被锁定).
  如果试图弹出一个没有介质的驱动器, 则返回错误码 Ah =31h (驱动器中没有介质).
  如果试图弹出一个未锁定的可移动驱动器中的介质,Int13h会调用 Int15h (AH = 52h) 来检查弹出请求能否执行.如果弹出请求被拒绝则返回错误码(同Int15h). 如果弹出请求被接受,但出现了其他错误, 则返回错误码 AH =B5h.

7) 扩展定位
入口:
  AH = 47h
  DL = 驱动器号
  DS:SI = 磁盘地址数据包(Disk Address Packet)

返回:
  CF = 0, AH = 0 成功
  CF = 1, AH = 错误码

  这个调用将磁头定位到指定扇区.

8) 取得驱动器参数
入口:
  AH = 48h
  DL = 驱动器号
  DS:SI = 返回数据缓冲区地址

返回:
  CF = 0, AH = 0 成功
    DS:SI 驱动器参数数据包地址,(参见前面的文章)
  CF = 1, AH = 错误码

  这个调用返回指定驱动器的参数.

9) 取得扩展驱动器介质更换检测线状态
入口:
  AH = 49h
  DL = 驱动器号

返回:
  CF = 0, AH = 0 介质未更换
  CF = 1, AH = 06h 介质可能已更换

  这个调用返回指定驱动器的介质更换状态.
  这个调用与 Int13h AH = 16h 子功能调用相同,只是允许任何驱动器标号.如果对一台支持可移动介质功能子集的固定驱动器使用此功能,则永远返回 CF = 0, AH = 0.
  简单地将可移动介质锁定再解锁就可以激活检测线,而无须真正更换介质.

10) Int 15h 可移动介质弹出支持
入口:
  AH = 52h
  DL = 驱动器号
返回:
  CF = 0, AH = 0 弹出请求可能可以执行
  CF = 1, AH = 错误码 B1h 或 B3h 弹出请求不能执行

  这个调用是由 Int13h AH=46h弹出介质功能调用内部使用的 

三. 接口规范

1. 寄存器约定
  在扩展 Int13H 调用中一般使用如下寄存器约定:

  DS:SI ==> 磁盘地址数据包( disk address packet)
  dl  ==> 驱动器号
  ah  ==> 功能代码 / 返回码

  在基本 Int13H 调用中, 0 – 0×7F之间的驱动器号代表可移动驱动器
0×80 – 0xFF 之间的驱动器号代表固定驱动器. 但在扩展 Int13H调用中
0×80 – 0xFF 之间还包括一些新出现的可移动驱动器, 比如活动硬盘等.
这些驱动器支持先进的锁定,解锁等功能.
  ah 返回的错误码除了标准 Int13H调用规定的基本错误码以外,又增加了以下错误码:

 B0h 驱动器中的介质未被锁定

 B1h 驱动器中的介质已经锁定

 B2h 介质是可移动的

 B3h 介质正在被使用

 B4h 锁定记数溢出

 B5h 合法的弹出请求失败

2. API 子集介绍
 1.x 版的扩展 Int13H 调用中规定了两个主要的 API 子集.

 第一个子集提供了访问大硬盘所必须的功能, 包括 检查扩展In13H
是否存在( 41h ), 扩展读( 42h ), 扩展写( 43h ), 校验扇区( 44h ),
扩展定位( 47h ) 和 取得驱动器参数( 48h ).
 第二个子集提供了对软件控制驱动器锁定和弹出的支持, 包括检查扩展Int13H 是否存在( 41h ), 锁定/解锁驱动器( 45h ), 弹出驱动器( 46h),
取得驱动器参数( 48h ), 取得扩展驱动器改变状态( 49h ), int 15h. 如果使用了调用规范中不支持的功能, BIOS 将返回错误码 ah =01h, CF = 1.

3. API 详解

1) 检验扩展功能是否存在
入口:
  AH = 41h
  BX = 55AAh
  DL = 驱动器号

返回:
  CF = 0
   AH = 扩展功能的主版本号
   AL = 内部使用
   BX = AA55h
   CX = API 子集支持位图
  CF = 1
   AH = 错误码 01h, 无效命令

  这个调用检验对特定的驱动器是否存在扩展功能.如果进位标志置 1则此驱动器不支持扩展功能. 如果进位标志为 0, 同时 BX = AA55h, 则存在扩展功能. 此时 CX 的 0 位表示是否支持第一个子集,1位表示是否支持第二个子集.
  对于 1.x 版的扩展 Int13H 来说, 主版本号 AH = 1. AL是副版本号, 但这仅限于 BIOS 内部使用, 任何软件不得检查 AL 的值.

2) 扩展读
入口:
  AH = 42h
  DL = 驱动器号
  DS:SI = 磁盘地址数据包(Disk Address Packet)

返回:
  CF = 0, AH = 0 成功
  CF = 1, AH = 错误码

  这个调用将磁盘上的数据读入内存. 如果出现错误, DAP 的BlockCount项中则记录了出错前实际读取的数据块个数.

3) 扩展写
入口:
  AH = 43h
  AL
   0 位 = 0 关闭写校验
       1 打开写校验
   1 – 7 位保留, 置 0
  DL = 驱动器号
  DS:SI = 磁盘地址数据包(DAP)
返回:
  CF = 0, AH = 0 成功
  CF = 1, AH = 错误码

  这个调用将内存中的数据写入磁盘. 如果打开了写校验选项,但 BIOS不支持, 则会返回错误码 AH = 01h, CF = 1. 功能 48h可以检测BIOS是否支持写校验.
  如果出现错误, DAP 的 BlockCount项中则记录了出错前实际写入的数据块个数.

4) 校验扇区
入口:
  AH = 44h
  DL = 驱动器号
  DS:SI = 磁盘地址数据包(Disk Address Packet)

返回:
  CF = 0, AH = 0 成功
  CF = 1, AH = 错误码

  这个调用校验磁盘数据,但并不将数据读入内存.如果出现错误, DAP 的BlockCount 项中则记录了出错前实际校验的数据块个数.

第二部分 技术资料
第一章 扩展 Int13H 技术资料

一. 简介
  设计扩展 Int13H 接口的目的是为了扩展 BIOS 的功能,使其支持
多于1024柱面的硬盘, 以及可移动介质的琐定, 解锁及弹出等功能.

二. 数据结构

1. 数据类型约定
  BYTE  1 字节整型 ( 8 位 )
  WORD  2 字节整型 ( 16 位 )
  DWORD 4 字节整型 ( 32 位 )
  QWORD 8 字节整型 ( 64 位 )

2. 磁盘地址数据包 Disk Address Packet (DAP)
  DAP 是基于绝对扇区地址的, 因此利用 DAP, Int13H可以轻松地逾
越1024 柱面的限制, 因为它根本就不需要 CHS 的概念.
  DAP 的结构如下:

  struct DiskAddressPacket
  {
    BYTE PacketSize;  //数据包尺寸(16字节)
    BYTE Reserved;   //==0
    WORD BlockCount;  //要传输的数据块个数(以扇区为单位)
    DWORD BufferAddr;  //传输缓冲地址(segment:offset)
    QWORD BlockNum;   //磁盘起始绝对块地址
  };

  PacketSize 保存了 DAP 结构的尺寸,以便将来对其进行扩充. 在
目前使用的扩展 Int13H 版本中 PacketSize 恒等于 16. 如果它小于
16, 扩展 Int13H 将返回错误码( AH=01, CF=1 ).
  BlockCount 对于输入来说是需要传输的数据块总数,对于输出来说
是实际传输的数据块个数. BlockCount = 0 表示不传输任何数据块.
  BufferAddr 是传输数据缓冲区的 32 位地址(段地址:偏移量). 数据
缓冲区必须位于常规内存以内(1M).
  BlockNum表示的是从磁盘开始算起的绝对块地址(以扇区为单位),
与分区无关. 第一个块地址为 0. 一般来说, BlockNum 与 CHS地址的关系是:
  BlockNum = cylinder * NumberOfHeads +
       head *SectorsPerTrack +
       sector – 1;

  其中 cylinder, head, sector 是 CHS 地址,NumberOfHeads 是磁盘
的磁头数, SectorsPerTrack 是磁盘每磁道的扇区数.
  也就是说 BlockNum 是沿着 扇区->磁道->柱面的顺序记数的. 这一顺序是由磁盘控制器虚拟的,磁盘表面数据块的实际排列顺序可能与此不同(如为了提高磁盘速度而设置的间隔因子将会打乱扇区的排列顺序).

3. 驱动器参数数据包 Drive Parameters Packet
 驱动器参数数据包是在扩展 Int13H的取得驱动器参数子功能调用中
使用的数据包. 格式如下:
  struct DriveParametersPacket
  {
    WORD InfoSize;     // 数据包尺寸 (26 字节)
    WORD Flags;      // 信息标志
    DWORD Cylinders;    // 磁盘柱面数
    DWORD Heads;      // 磁盘磁头数
    DWORD SectorsPerTrack; //每磁道扇区数
    QWORD Sectors;     // 磁盘总扇区数
    WORD SectorSize;    // 扇区尺寸 (以字节为单位)
  };
  信息标志用于返回磁盘的附加信息, 每一位的定义如下:

  0 位:
    0 = 可能发生 DMA 边界错误
    1 = DMA 边界错误将被透明处理
    如果这位置 1, 表示 BIOS 将自动处理 DMA边界错误, 也就是说
    错误代码 09H 永远也不会出现.

  1 位:
    0 = 未提供 CHS 信息
    1 = CHS 信息合法
    如果块设备的传统 CHS几何信息不适当的话, 该位将置 0.

  2 位:
    0 = 驱动器不可移动
    1 = 驱动器可移动

  3 位: 表示该驱动器是否支持写入时校验.

  4 位:
    0 = 驱动器不具备介质更换检测线
    1 = 驱动器具备介质更换检测线

  5 位:
    0 = 驱动器不可锁定
    1 = 驱动器可以锁定
    要存取驱动器号大于 0×80 的可移动驱动器,该位必须置 1
    (某些驱动器号为 0 到 0×7F的设备也需要置位)
  6 位:
    0 = CHS 值是当前存储介质的值(仅对于可移动介质), 如果
    驱动器中有存储介质, CHS 值将被返回.
    1 = CHS 值是驱动器支持的最大值(此时驱动器中没有介质).

  7 – 15 位: 保留, 必须置 0.

三. 系统启动过程简介

  系统启动过程主要由一下几步组成(以硬盘启动为例):

  1. 开机 :-)
  2. BIOS 加电自检 ( Power On Self Test — POST )
   内存地址为 0ffff:0000
  3. 将硬盘第一个扇区 (0头0道1扇区, 也就是BootSector)
   读入内存地址 0000:7c00 处.
  4. 检查 (WORD) 0000:7dfe 是否等于 0xaa55,若不等于
   则转去尝试其他启动介质,如果没有其他启动介质则显示
   ”No ROM BASIC” 然后死机.
  5. 跳转到 0000:7c00 处执行 MBR 中的程序.
  6. MBR 首先将自己复制到 0000:0600 处,然后继续执行.
  7. 在主分区表中搜索标志为活动的分区.如果发现没有活动
   分区或有不止一个活动分区, 则转停止.
  8. 将活动分区的第一个扇区读入内存地址 0000:7c00处.
  9. 检查 (WORD) 0000:7dfe 是否等于 0xaa55,若不等于则
   显示 “Missing Operating System” 然后停止,或尝试
   软盘启动.
  10. 跳转到 0000:7c00处继续执行特定系统的启动程序.
  11. 启动系统 …

  以上步骤中 2,3,4,5 步是由 BIOS 的引导程序完成.6,7,8,9,10
步由MBR中的引导程序完成.

  一般多系统引导程序 (如 SmartFDISK, BootStar, PQBoot等)
都是将标准主引导记录替换成自己的引导程序, 在运行系统启动程序
之前让用户选择要启动的分区.
  而某些系统自带的多系统引导程序 (如 lilo, NT Loader等)
则可以将自己的引导程序放在系统所处分区的第一个扇区中, 在 Linux
中即为 SuperBlock (其实 SuperBlock 是两个扇区).

  注: 以上各步骤中使用的是标准 MBR,其他多系统引导程序的引导
过程与此不同.

第一部分 简 介
一. 硬盘结构简介

        1. 硬盘参数释疑

  到目前为止, 人们常说的硬盘参数还是古老的CHS(Cylinder/Head/Sector) 参数. 那么为什么要使用这些参数,它们的意义是什么?它们的取值范围是什么?
  很久以前, 硬盘的容量还非常小的时候,人们采用与软盘类似的结构生产硬 盘. 也就是硬盘盘片的每一条磁道都具有相同的扇区数.由此产生了所谓的3D参 数 (Disk Geometry). 既磁头数(Heads), 柱面数(Cylinders),扇区数(Sectors), 以及相应的寻址方式.

  其中:

  磁头数(Heads)表示硬盘总共有几个磁头,也就是有几面盘片, 最大
为 255 (用 8 个二进制位存储);
  柱面数(Cylinders) 表示硬盘每一面盘片上有几条磁道,最大为 1023
(用 10 个二进制位存储);
  扇区数(Sectors) 表示每一条磁道上有几个扇区, 最大为 63(用 6
个二进制位存储).
  每个扇区一般是 512个字节, 理论上讲这不是必须的,但好象没有取别的值的.

  所以磁盘最大容量为:

  255 * 1023 * 63 * 512 / 1048576 = 8024 MB ( 1M =1048576 Bytes )
或硬盘厂商常用的单位:
  255 * 1023 * 63 * 512 / 1000000 = 8414 MB ( 1M =1000000 Bytes )

  在 CHS 寻址方式中, 磁头, 柱面, 扇区的取值范围分别为 0到Heads-1, 0到Cylinders-1, 1到Sectors (注意是从 1 开始).

 2. 基本 Int 13H 调用简介

  BIOS Int 13H 调用是 BIOS提供的磁盘基本输入输出中断调用, 它可以 完成磁盘(包括硬盘和软盘)的复位, 读写, 校验, 定位, 诊断,格式化等功能. 它使用的就是 CHS 寻址方式, 因此最大识能访问 8 GB 左右的硬盘 (本文中如不作特殊说明, 均以 1M = 1048576 字节为单位).

 3. 现代硬盘结构简介

  在老式硬盘中, 由于每个磁道的扇区数相等,所以外道的记录密度要远低 于内道, 因此会浪费很多磁盘空间 (与软盘一样). 为了解决这一问题,进一步提高硬盘容量, 人们改用等密度结构生产硬盘. 也就是说,外圈磁道的扇区比内圈磁道多. 采用这种结构后, 硬盘不再具有实际的3D参数,寻址方式也改为线性寻址, 即以扇区为单位进行寻址.
  为了与使用3D寻址的老软件兼容 (如使用BIOSInt13H接口的软件), 在硬盘控制器内部安装了一个地址翻译器,由它负责将老式3D参数翻译成新的线性参数. 这也是为什么现在硬盘的3D参数可以有多种选择的原因(不同的工作模式, 对应不同的3D参数, 如 LBA, LARGE, NORMAL).

 4. 扩展 Int 13H 简介

  虽然现代硬盘都已经采用了线性寻址, 但是由于基本 Int13H 的制约, 使用 BIOS Int 13H 接口的程序, 如 DOS 等还只能访问 8 G以内的硬盘空间. 为了打破这一限制, Microsoft 等几家公司制定了扩展 Int 13H 标准(Extended Int13H), 采用线性寻址方式存取硬盘, 所以突破了 8 G的限制, 而且还加入了对可拆卸介质 (如活动硬盘) 的支持.

二. Boot Sector 结构简介

 1. Boot Sector 的组成

  Boot Sector 也就是硬盘的第一个扇区, 它由 MBR (MasterBoot Record), DPT (Disk Partition Table) 和 Boot Record ID 三部分组成.

  MBR 又称作主引导记录占用 Boot Sector 的前 446 个字节( 0 to 0×1BD ), 存放系统主引导程序 (它负责从活动分区中装载并运行系统引导程序).
  DPT 即主分区表占用 64 个字节 (0×1BE to 0×1FD),记录了磁盘的基本分区信息. 主分区表分为四个分区项, 每项 16 字节,分别记录了每个主分区的信息 (因此最多可以有四个主分区).
  Boot Record ID 即引导区标记占用两个字节 (0×1FE and0×1FF), 对于合法 引导区, 它等于 0xAA55, 这是判别引导区是否合法的标志.

 2. 分区表结构简介

  分区表由四个分区项构成, 每一项的结构如下:

  BYTE State   : 分区状态, 0 =未激活, 0×80 = 激活 (注意此项)
  BYTE StartHead : 分区起始磁头号
  WORD StartSC     : 分区起始扇区和柱面号,底字节的低6位为扇区号, 高2位为柱面号的第 9,10 位, 高字节为柱面号的低 8 位
  BYTE Type   : 分区类型, 如0×0B = FAT32, 0×83 = Linux 等, 00 表示此项未用,07 = NTFS
  BYTE EndHead  : 分区结束磁头号
  WORD EndSC  :分区结束扇区和柱面号, 定义同前
  DWORD Relative :在线性寻址方式下的分区相对扇区地址(对于基本分区即为绝对地址)
  DWORD Sectors : 分区大小 (总扇区数)

  注意: 在 DOS / Windows 系统下,基本分区必须以柱面为单位划分
( Sectors * Heads 个扇区), 如对于CHS 为 764/255/63 的硬盘,分区的最小尺寸为 255 * 63 * 512 / 1048576 = 7.844 MB.

 3. 扩展分区简介

  由于主分区表中只能分四个分区, 无法满足需求,因此设计了一种扩展 分区格式. 基本上说, 扩展分区的信息是以链表形式存放的,但也有一些特别的地方.
  首先, 主分区表中要有一个基本扩展分区项,所有扩展分区都隶属于它, 也就是说其他所有扩展分区的空间都必须包括在这个基本扩展分区中.对于 DOS / Windows 来说, 扩展分区的类型为 0×05.
  除基本扩展分区以外的其他所有扩展分区则以链表的形式级联存放, 后一个扩展分区的数据项记录在前一个扩展分区的分区表中,但两个扩展分区的空间并不重叠.
  扩展分区类似于一个完整的硬盘, 必须进一步分区才能使用.但每个扩 展分区中只能存在一个其他分区. 此分区在 DOS/Windows环境中即为逻辑盘. 因此每一个扩展分区的分区表(同样存储在扩展分区的第一个扇区中)中最多只能有两个分区数据项(包括下一个扩展分区的数据项).

2004年08月19日

http://www.cishengtang.com/2003photo.htm

看了之后,本想写点什么,但是打算写时却发现,一个字也写不出来。很少有这种强烈的震撼感,这种感觉是要靠心灵来传递的,只记的看到最后时,我的眼里含着泪水。虽然我不是富裕的人,但是我还是有自己的电脑,还是能够上网,还是能够享受那些照片里的人享受不到的。

我只是个普通人,没有能力去解决这种社会问题。

浮华背后的丧钟,为谁鸣??

2004年08月18日

3.CALLBACK函数。

我觉得这是C语言的一个创举,虽然它很简单,就象如何把鸡蛋竖起来一样,但是你如果没想到的话,嘿嘿。如果说静态入口函数实现了一个可管理的宏观的话,CallBack就是实现了一个可进化的微观:它使得一个函数可以在不重新编译的情况下实现功能的添加!但是在最最早期的时候,也有蛮多人持反对态度,因为它用了函数指针。函数指针虽然灵活,但是由于它要访问内存两次才可以调用到函数,第一次访问函数指针,第二次才是真正的函数调用。它的效率是不如普通函数的。但是在一个不太苛刻的环境下,函数调用本身就不怎么耗时,函数指针的性能又不是特别糟糕,使用函数指针其实是一个最好的选择。但是函数指针除了性能,最麻烦的地方就是会导致程序的“支离破碎”。试想:在程序中,你读到一个函数指针的时候,如果你愣是不知道这个函数指针指向的是哪个函数,那个感觉真的很糟糕。(可以看后面的文章,要使用先进的程序框架,避免这样的情况)

三、Event和Message

看了上面的描述,相信大家多少有些明白为什么要使用Event和Message了。具体的函数调用会带来很多的问题(虽然从效率上讲,这样做是很好的)。为了提高程序的灵活性,Event和Message的办法产生了。用名字解析的办法代替通常的函数调用,这样,如果双方对这样的解析是一致的话,就可以达到一个统一。不过Event和Message的作用还不仅仅是如此。

Event和Message还有建立进程间通信的功能。进程将自己的消息发给“控制中心”(简单的就是一个消息队列,和一个while循环不断的取消息队列的内容并执行),控制程序得到消息,分发给相应的进程,这样其他进程就可以得到这个消息并进行响应。

Event和Message是很灵活的,因为你可以随时添加或者关闭一个进程,(仅仅需要添加分发消息的列表就可以了)Event和Message从程序实现上将我觉得是一样的,只不过概念不同。Event多用于指一个动作,比如硬件发生了什么事情,需要调用一个什么函数等等。Message多用于指一个指示,比如什么程序发生了什么操作命令等等。

四、小结

其实编程序和写文章一样,都是先有一个提纲,然后慢慢的丰富。先抽象化得到程序的骨架,然后再考虑各个方面的其他内容:程序极端的时候会发生什么问题?程序的这个地方的功能现在还不完善,以后再完善会有什么问题?程序是不是可以扩展的?

C语言中的面向对象思想
经常听见别人说面向对象的程序设计,以前在学校上课的时候,也有开面向对象程序设计这门课。可是不幸的是,这些都是以C++,甚至VC++为基础的。而更加不幸的是,多年以来我一直是一个C的使用者。在学校的时候,我主要做的是硬件上的驱动层,和底层功能层。在工作以后,又做的是手机上的软件开发,所有这些都是和C离不开的。虽然我不得不说,C++是一门很好的语言,但是它的编译速度,代码效率,编译后的代码大小都限制了它在嵌入式上的应用。(但现在的嵌入式CPU越来越快,内存容量变大。我觉得用C++也应该没有什么问题。这使我觉得似乎是嵌入式编译器的限制。虽然菲利普和TI好像都有C++的编译器,但是似乎没人用这个。难道是太贵了? 但不管怎么说,嵌入式应用中,C语言的普遍使用是肯定的)那么在面向过程的时代产生的C语言能否使用面向对象的思想呢?我认为是肯定可以的,C++不过是在语言级别上加入了对对象的支持,同时提供了丰富的对象库。而在C语言下,我们只好自力更生了。

一、面向对象思想的目的是框架化,手段是抽象

相信很多人都明白面向对象讲了什么:类,抽象类,继承,多态。但是是什么原因促使这些概念的产生呢?
打个比方说:你去买显示器,然而显示器的品牌样式是多种多样的,你在买的过程中发生的事情也是不可预测的。对于这样的事情,我们在程序语言中如何去描述呢。面向对象的思想就是为了解决这样的问题。编写一个程序(甚至说是一个工程),从无到用是困难的,从有到丰富是更加困难的。面向对象将程序的各个行为化为对象,而又用抽象的办法将这些对象归类(抽象),从而将错综复杂的事情简化为几个主要的有机组合(框架化)。
其实我们的身边很多东西都是这样组成的:比如说电脑:电脑是由主板,CPU加上各种卡组成的。这就是一个框架化。而忽略不同的CPU,不同的主板,不同的声卡,网卡,显卡的区别,这就是抽象。再比如说现在的教育网:是由主核心节点:清华,北大,北邮等几个,然后是各个子节点,依次组成了整个教育网网络。
所以我觉得面向对象的编程思想就是:一个大型工程是分层次结构的,每层又由抽象的结构连接为整体(框架化),各个抽象结构之间是彼此独立的,可以独立进化(继承,多态)。层次之间,结构之间各有统一的通讯方式(通常是消息,事件机制)。

二、以前C语言编程中常用的“面向对象”方法

其实C语言诞生以来,人们就想了很多办法来体现“面向对象”的思想。下面就来说说我所知道的方法。先说一些大家熟悉的东东,慢慢再讲诡异的。呵呵
1、宏定义:
   有的人不禁要问,宏定义怎么扯到这里来了,我们可以先看一个简单的例子:
#define MacroFunction  Afunction
   然后在程序里面你调用了大量的AFunction,但是有一天,你突然发现你要用BFunction了,(不过AFunction又不能不要,很有可能你以后还要调用),这个时候,你就可以#define MacroFunction  Bfunction来达到这样的目的。
   当然,不得不说这样的办法是too simple,sometime na?ve的,因为一个很滑稽的问题是如果我一般要改为BFunction,一半不变怎么办? 那就只好查找替换了。
2.静态的入口函数,保证函数名相同,利用标志位调用子函数:
   这样的典型应用很多,比如说网卡驱动里面有一个入口函数Nilan(int FunctionCode,Para*)。具体的参数是什么记不清楚了。不过NiLan的主体是这样的:
Long Nilan(int FunctionCode,Para*){
Switch(FunctionCode){

       Case SendPacket:         send(….)

       Case ReceivePacket:      receive(…)

       …..

}
写到这里大家明白什么意思了吧。保证相同的函数名就是说:网卡驱动是和pNA+协议栈互连的,那么如何保证pNA+协议栈和不同的驱动都兼容呢,一个简单的办法就是仅仅使用一个入口函数。通过改变如果函数的参数值,来调用内部的各个函数。这样的做法是可以进化的:如果以后想调用新的函数,增加相应的函数参数值就好了。如果我们将网卡驱动和pNA+协议栈看作两个层的话,我们可以发现:
层与层之间的互连接口是很小的(这里是一个入口函数),一般是采用名字解析的办法而不是具体的函数调用(利用FunctionCode调用函数,Nilan仅仅实现名字解析的功能)――!接口限制和名字解析
接口限制:层与层之间仅仅知道有限的函数
名字解析:层与层之间建立共同的名字与函数的对应关系,之间利用名字调用功能。

 

 

类模拟的性能分析
       类模拟中使用了大量的函数指针,结构体等等,有必须对此进行性能分析,以便观察这样的结构对程序的整体性能有什么程度的影响。
 
1.函数调用的开销
#define COUNTER XX
void testfunc()
{
       int i,k=0;
       for(i=0;i<YY;i++){k++;}
}
 
       在测试程序里面,我们使用的是一个测试函数,函数体内部可以通过改变YY的值来改变函数的耗时。测试对比是 循环调用XX次函数,和循环XX次函数内部的YY循环。
       结果发现,在YY足够小,X足够大的情况下,函数调用耗时成为了主要原因。所以当一个“简单”功能需要“反复”调用的时候,将它编写为函数将会对性能有影响。这个时候可以使用宏,或者inline关键字。
       但是,实际上我设置XX=10000000(1千万)的时候,才出现ms级别的耗时,对于非实时操作(UI等等),即使是很慢的cpu(嵌入式10M级别的),也只会在XX=10万的时候出现短暂的函数调用耗时,所以实际上这个是可以忽略的。
 
2.普通函数调用和函数指针调用的开销
void (*tf)();
tf=testfunc;
 
       测试程序修改为一个使用函数调用,一个使用函数指针调用。测试发现对时间基本没有什么影响。(在第一次编写的时候,发现在函数调用出现耗时的情况下(XX=1亿),函数指针的调用要慢(release版本),调用耗时350:500。后来才发现这个影响是由于将变量申请为全局的原因,全局变量的访问要比局部变量慢很多)。
 
3.函数指针和指针结构访问的开销
struct a {
       void (*tf)();
};
 
       测试程序修改为使用结构的函数指针,测试发现对时间基本没有什么影响。其实使用结构并不会产生影响,因为结构的访问是固定偏移量的。所以结构变量的访问和普通变量的访问对于机器码来说是一样的。
 
测试结论:使用类模拟的办法对性能不会产生太大的影响。