利用扩展内存快速显示汉字


当今,许多的图形软件为了能处理汉字,都采用直接读取汉字库文件或小汉字库技术来显示汉字。这种方法虽有效但总有许多的不足:直接读取汉字库文件,显示速度太慢而且对磁盘磨损太大;而小汉库技术虽提高了一定的显示速度,但并没有从根本上解决"快速显示"这一问题,而且也仅限于处理少量汉字。其实,现在微机不仅仅只有1M的内存空间,只要我们将HZK16读入扩展内存,再从扩展内存中取字模,便能从根本上解决"快速显示"。

  最初的PC机,采用8086/8088为CPU,由于它有20位地址总线,因而最多能管理1MB的地址空间,以后的一系列80X86,地址总线已不止20位,CPU采用两种工作模式:实地模式和保护虚地模式。在实地模式下,象80286只用到24位地址总线中的20位,因而寻址在1M以内,DOS即是工作在这种模式下。而在保持模式下,80286则可达16M。

  在保护模式下访问存储器,比实地模式下复杂得多,实地模式下只需指出段址和偏移地址即可,而在保护模式下,还需建立全局描述符GDT表,它由6种描述符组成,结结构及说明如下:


  而每一种描述符又由8B组成,各意义为:

  0-1B: 该字指定段长的字节数
  2-3B: 基地址低位字。
  4B    :基地址高位字节。

  在保护模式下80286可访访问16M(224)空间,此字节为基地址的高8位,而低16位,则有基地址低位字的16位决定。

  5B :这一字节称为存储段存取权字,一般将其置成93H。
  6-7B:这两字节均保留,它总是置成0。

  为了以后说明方便,我们用C语言将其结构定义如下:

  #define  WORD           unsigned int
  #define  BYTE           unsigned char
  #define  LONG           unsigned long
  typedef struct Des{
  WORD size;       /*数据段长  */
  WORD BaseLow16; /*基地址低16位*/
  BYTE BaseHigh8; /*基地址高8位*/
  BYTE attr;     /*存取权或属性值*/
  WORD NoUse;    /*保留*/
  } DES;
  typedef struct GDTHead
  { DES BlankDsc; /*空白描述符*/
  DES GDTDsc;    /*该GDT的描述符*/
  DES SrcDsc;   /*源数据块描述符*/
  DES DstDsc;   /*目标数据块描述符*/
  DES BiosCs;   /*BIOS代码段描述符*/
  DES BiosSs;   /*BIOS堆栈段描述符*/
  } GDT;

  在我们的程序中,要访问1M以上的扩展内存,则用到了BIOS INT15H中断中的87H子功能。该子功能可将0 ̄崐16M之间各个存储器的数据相互交换,使用方法如下:

  入口参数:AH=87H
  CX=以字为单位一次传送的数据块数
  ES:SI=全局描述表GDT段址:偏移地址。

  用C语言实现则为:

  void EmsMoveData(GDT *mcb,WORD size)
  {  /*作用: 利用BIOS INT 15H 实现源数据到目
  的数据之间的数据传送*/
  struct REGPACK r;
  r.r_es=FP_SEG(mcb);
  r.r_si=FP_OFF(mcb);
  r.r_cx=size>>1;
  r.r_ax=0x8700;
  intr(0x15,&r);
  }

  懂得以上扩展内存的使用方法,我们需要访问扩展内存,只需改变源和目标块的传输段长和基地址,然后调用87H号功能。其中,改变源和目标块的描述符源程序如下:

  void SetSrcAddr(GDT *e,LONG addr,WORD size)
  { /**********************************
  作    用:置源数据块描述符值
  入口参数:e     指向GDT的指针
  addr   基地址
  size   字节大小
  ***********************************/
  e->SrcDsc.BaseLow16=addr & 0x0ffff;
  /*置基地址的低16位*/
  e->SrcDsc.BaseHigh8=addr>>16;
  /*置基地址的高8位*/
  e->SrcDsc.size=size;
  }
  void SetDstAddr(GDT *e,LONG addr,WORD size)
  { /*********************************
  作    用:置目标数据块描述符值
  入口参数:e    指向GDT的指针
                addr 基地址
                 size 字节大小
  *********************************/
  e->DstDsc.BaseLow16=addr & 0x0ffff;
  e->DstDsc.BaseHigh8=addr >> 16;
  e->DstDsc.size=size;
  }

  实际生活中,并不是所给的扩展内存大小都符合要求,因此,我们有必要在程序中获取扩展内存的大小,灵活地处理各种出现的现象,这时,则要用到INT 15H的88H号子功能。该功能表示取1M以上扩展内存容量(以1K为单位),调用为:

  入口参数:AH= 88H
  返 回 值:AX=扩展内存的块数(每块=1K)

  值得说明的是,若用户在DOS的config.sys文件中配制了emm386.exe文件,则此功能号被屏蔽,但可用EMM386.exe文件提供的INT87H 42号功能取可用页数(每页16K)。

  WORD   GetEMSSize(void)
  /*取得当前扩展内存大小,返回多少K值*/{ _AH=0x88;
  geninterrupt(0x15);
  if (_AX==0)/*若当前功能号被EMM386程序占用*/
  { _AH=0x42;/*调用EMM38642H号功能,取可用页数*/
  geninterrupt(0x67);
  return(_BX*16);
  }
  else
  return(_AX);}

  因此,我们要利用扩展内存快速显示汉字,则必须做以下几项工作。

  ⑴、将HZK16读入扩展内存中。我们不防将HZK16读入由HZK16ADDR指定的扩展内存中,则实现方法如下:

  int LoadHZK16ToEms(void)
  { /*************************************
  函数作用:将文件HZK16的内容送入指定的扩展内存中
  ***********************/
  FILE *fp;
  LONG FileSize,EmsSize,SrcAddr;
  WORD size,num;
  char *buf;
  fp=fopen("HZK16","rb" ;/*打开HZK16文件*/
  if(fp==NULL)/*文件不能打开,返回打开文件出错*/
  return(OpenFileError);
  EmsSize=((LONG)GetEMSSize())*1024;
  /*取当前扩展内存的大小并化为以字节为单位*/
  fseek(fp,0L,SEEK_END); /*将文件指针移到文件尾,
  测试文件的大小*/
  FileSize=ftell(fp);
  rewind(fp);
  if (EmsSize<FileSize)
  /*若文件的大小超过当前扩展内存有大小,
  则返回文件太大的错误信息*/
  { fclose(fp);
  return(FileToBigger);}
  if(EmsSize-(HZK16ADDR-0x10000)<0) /*若保存的起始地址到最大扩展内存之间的空间不够装入该文件,则返回指出的基地址太大错误*/
  { fclose(fp);
  return(AddrToBigger);}
  buf=(char *)malloc(0x8000); /*为传输文件内容分配内存*/
  if (buf==NULL)  /*内存分配失败,返回内在内存分配错误*/
          { fclose(fp);
  return(MallocError);}
  SrcAddr=FP_SEG(buf); /*计算源数据块地址*/
  SrcAddr=(SrcAddr<<4)+FP_OFF(buf);
  SetSrcAddr(&mes,SrcAddr,0x8000);
  do /*每次读取8000H字节内容到基本内存,交将传到扩展内存中*/
          { num=fread(buf,1,0x8000,fp);
            SetDstAddr(&mes,addr,num);
            EmsMoveData(&mes,num);
            addr+=0x8000;
            } while(num==0x8000);
  free(buf);
  fclose(fp);
  return(NoError);/*成功返回无错误*/
  }

  以上程序中用到了一些宏定义和一个结构为GDT的全局变量mes,它们可以定义在头文件中,初始化代码如崐下:

  #define  HZK16ADDR       0x110000L
  /*汉字库在扩展内存在起始地址*/
  enum ErrMsg{  /*错误信息定义*/
          NoError          ,    /*没有错误*/
          MallocError      ,    /*内存分配错误*/
          FileToBigger     ,    /*文件太大,无法装入扩展内存*/
          OpenFileError    ,     /*打开文件错误*/
          AddrToBigger    };


  GDT mes={{0,0,0,0   ,0},/*伪描述符初始为0*/
  {0,0,0,0   ,0},/*GDT的描述符*/
  {0,0,0,0x93,0},/*源和目标块描述符*/
           {0,0,0,0x93,0},/*存取权均置为93H*/
           {0,0,0,0   ,0},/*BIOS代码段描述符置为0*/
  {0,0,0,0,0}};/*BIOS堆栈段描述符置为0*/

  ⑵、将汉字库读入扩展内存后,我们则要根据给定的汉字区位码,取出该汉字对应的字模。这一过程较简单,首先算出该汉字在汉字库中的偏移地址,然后加上汉字库在扩展内存中的起始地址,即是该汉字在扩展内存中的存储地址。接下来,就利用INT 15H的87H子功能传到需要的基本内存中。

  void GetHzMode(WORD c,WORD *buf)
  { /********************************
  函数作用:取汉字的字模到BUF处,
  入口参数:c   全角字符的区位码
  buf 字模存放的地址
  ********************************/
  LONG addre,offset;
  register int i;
  addre=FP_SEG(buf);/*将存放字模的地址化成符合要求的基地址*/
  addre=(addre<>8)-0xa1)*94L+((c&0xff)-0xa1))*32l;
  /*计算该字符在整个汉字库中的偏移地址*/
  offset+=HZK16ADDR;
  /*将基偏移地址加上汉字库装入时的起始地 址,即是该字符在扩展内存的地址*/
  SetSrcAddr(&mes,offset,32);
  /*从扩展内存中读32个字节,存放入由buf 指定的内存中*/
  SetDstAddr(&mes,addre,32);
  EmsMoveData(&mes,32);
  }

  ⑶、根据取出的字模,在屏幕上显示汉字有许多方法,常见的有描点法、画线法、修改IMG结构法、直接写屏等等。这几种方法各有千秋,笔者在此仅给出用画线法显示汉字。用画线方法显示汉字巧妙地利了画线类型定义函数setlinestyle(int linestyle,usigned upatten,int thickness)。当linstyle为USERBIT_LINE(用户定义的崐线型)时,其参量patten是一个无符号整数,若某一位为1,则表示在屏幕上描点。而一般用于屏幕显示的是16×16点阵的汉字,因此我们可以将其看成16条不同线型的线组成的图形。

  /********************************************
  入口参数:cwords 显示的字符串
  x,y    显示时的坐标
  color  显示的色彩
  *********************************************/
  void  ListCstring(unsigned char  *cwords,
  int x,int y,int color)
  { WORD      mode[16];
  BYTE      w[2];
  WORD      c;
  register int i;
  int      xx,yy;
  struct linesettingstype OldLineSetting;
  getlinesettings(&OldLineSetting);
  /*保存原线型设置值*/
  setcolor(color);
  xx=x;yy=y;
  for(;*cwords
  if(*cwords<0x7f) /*若为英文字符,则用C语提供的outtextxy函数输出它*/
          { w[0]=*(cwords++); w[1]='\0';
            outtextxy(xx,yy,w);
            xx+=8;}   else /*是全角字符*/
  { c=(*cwords<<8) | *(cwords+1);
           GetHzMode(c,mode);/*取字符的字模*/
  for(i=0;i>8&0x00ff )
  |(mode <<8&0xff00);
           for(j=0;ibase=SaveScreenAddr;
  addr=0xa0000L+s->y1*80L+s->x1/8;
  for(y=s->y1;yy2;y++,addr+=80L)
  for(i=0;ix2-s->x1)/8+3);
  SetSrcAddr(&mes,addr,(s->x2-s->x1)/8+3);
  EmsMoveData(&mes,(s->x2-s->x1)/8+3);
  SaveScreenAddr+=(s->x2-s->x1)/8+3;
  }
  outportb(0x3ce,4); /*允许第0个位面读*/
  outportb(0x3cf,0);
  }
  
  void RestoreMsgToScreen(SSH *s)
  /*将640*480*16色模式下的屏幕内容从基地址
  addr开始的扩展内存处恢复到屏幕上*/
  { BYTE i;
  WORD y;
  LONG addr,ToAddr;
  addr=s->base;
  ToAddr=0xa0000L+s->y1*80L+s->x1/8;
  for(y=s->y1;yy2;y++,ToAddr+=80L)
  {for(i=1;i<=8;i<x2-s->x1)/8+3);
  SetDstAddr(&mes,ToAddr,
  (s->x2-s->x1)/8+3);
  EmsMoveData(&mes,(s->x2-s->x1)/8+3);
  addr+=(LONG)((s->x2-s->x1)/8+3);
  }
  }
  outportb(0x3c4,2); /*允许第4个位面写*/
  outportb(0x3c5,0x0f);
  SaveScreenAddr=s->base;
  }

  虽然此方法能有效的处理大屏幕区域内容的保存与恢复,但在使用时,请一定要注意这一点:后保存的区域一定要先恢复,这一点与栈是相同的。若不需后面保存的区域,可将其置之不理。

  用户在使用这些函数时,将它们全部放入一文件中,然后在自己的程序头加入#include 即可。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值