在线咨询
eetop公众号 创芯大讲堂 创芯人才网
切换到宽版

EETOP 创芯网论坛 (原名:电子顶级开发网)

手机号码,快捷登录

手机号码,快捷登录

找回密码

  登录   注册  

快捷导航
搜帖子
芯片精品文章合集(500篇!)    创芯人才网--重磅上线啦!
楼主: ucgui

ucos的GUI图形系统ucgui的模似器源码设计文档及各种资料

[复制链接]
 楼主| 发表于 2005-6-27 10:02:48 | 显示全部楼层

ucos的GUI图形系统ucgui的模似器源码设计文档及各种资料

UCGUI的模拟器UCGUISim的设计文档
作者: ucgui
日期: 2005-06-25
来源: http://www.ucgui.com
文档版本: v1.0.0.0
一、问题的由来-----模拟器完成了什么
在官方发布的UCGUI的源码包当中,附有很多的示例,源码与示例都打包与一个VC工程,在这个工程中我们可以编译和运行UCGUI的示例,进行UCGUI的图形编程,非常方便,这个工程结构下:
Application--------UCGUI应用程序目录。
Config-------------UCGUI配制文件目录。
GUI----------------UCGUI源码文件。
Simulation---------模拟器库文件、所有头文件目录,主要有GUISim.lib这个提供模拟器的库文件。
System-------------应用程序的接口调用主文件,即调用用户的MainTask函数的main函数。
在这个官方提供的包中,我们进行UCGUI的图形编程时, 大多都是将要写的程序源文件放加到Application目录中, 其中必不可少的一个函数是MainTask(), 其实好奇的朋友会发觉, 我们程序并没创建窗口, 为什么一运行就有窗口界面并有一个LCD显示屏显示出自己的UCGUI程序的运行效果出来, 其实这些我们看不到的代码都是写在GUISim.lib这个库文件当中的,为了了解模拟器具体做了些什么,我通过反编译,还原了官方模拟器的源码,下面详细介绍模拟器的构成[以下均为官方模拟器采用的方法]。
二、进一步入了解-----模拟器的基本实现原理

-----lCD模拟显示器的实现原理。
1、在WIN环境下,实现这个模拟器的原理比较简单,首先必须完成的是将GUI的结果显示给用户看,即做出一个模拟LCD显示器,将GUI画图的结果呈现给用户,使用位图是一种比较好的办法,在一个LCD窗口中,将GUI画图的结果以一定时间刷新的速度显示到窗体当中,虽然这样做不这下在速度上非常准确,但基本上可以满足要求了,只要我们设定一个重画定时器,定时刷新,可以满足要求。
2、在解决了模拟LCD显示的问题后,还有要弄清的问题是,显示器的一屏象素的存放位置,也即画图时所读写象素的内存,其实只要开一块足够大的内存来存放一屏LCD的象素,读写象素均在此块内存中,我们称此此块内存为显存。如LCD宽XSize,高YSize, 显存起始地址为pFix, 那么象素点(x,y)的位置即为 x + y * YSize + pFix;如果是四个字节表示一个象素,则为(x + y * YSize)*4 + pFix。显存中象素是按行存放的,如果为了查看器(ucguiview)可以同样显示出LCD显存的东西,那么这个显存可以映象文件的方式来实现内存共享。
3、关于一个象素用多少位来表示的问题,其实这个问题与几位位图是一个意思;位图中,8位及位以下的均含有一个调色板,位图中的数据即为调色板中颜色的索引值,没有调色板是无法解析显示此位图的,所以8位及位以下位图会多出一个调色板;8位以上的位图,其位图数据即为RGB值,每一个象素均占四个字节,对于24位位图,我们只用到RGB的三个字节,另外一个字节这里不用。
[注意:关于位图文件,这时就不多介绍了,介绍大家下载这篇文章看一下,"BMP檔案結構結構及平滑縮放"]
4、LCD_BITSPERPIXEL是UCGUI中定义的一个象素用多少位来表示的宏定义,可以表示的颜色值有1L<<LCD_BITSPERPIXEL种,但是RGB分别占多少位,则其可表示的颜色的范围是不同的,模拟器中以LCD_FIXEDPALETTE表示BGR(不是RGB)分别所占位数,主要有444(12)、555(15)、565(16),一个象素是由RGB三值来描述,RGB三值各值范围是0~255;所以除非每种颜色都用(r|g|b)8位表示,否则不可能把0~255的值均表示到;另外,对于每一种可显示的颜色模拟器中均以索引值来表示,最基本的画象素的LCDSIM_SetPixelIndex(int x, int y, int Index)中用的是索引值,这个索引值可以通过一种转换转为RGB值。
5、颜色索引值到RGB值的转换,索引转换成RGB的基本原理------首先, 对于RGB三值, 由FixedPalette指定其每个位所占的位数, 如为444则各4位. FixedPalette中指定能表示的RGB的范围分别为(R,G,B)1111/1111/1111,即16(R)/16(G)/16(B). 则当索引值Index为444时, 则是RGB各值索引最大的时候, 由此即可分析, R,G,B三值要表示的颜色范围是0x00-0xff, 为了让有限的0-16的索引来表示0x00-0xff这些颜色值, 那么当然只能是跳隔着来表示, 拿R来说, 最好是取值为(17*0, 17*1, 17*2, 17*3,...17*15=248), 如此的话, 才能差不多在有限的16个索引值下将0xff种颜色最大程度的表示到(比较平均)。
同理, 对于FixedPalette为555时, 则R取值最好为(8*0, 8*1, 8*2, 8*3,...8*31=243)...
同理, 对于FixedPalette为565时, 则B取值最好为(4*0, 4*1, 4*2, 4*3,...4*63=252)知道这一点, 则对于以下函数的理解就非常容易...
6、在LCD_BITSPERPIXEL小于等于8时,颜色索引值则无须通过运算来转换,因为它所能表示的颜色数不多于256种,此时我们就会用一个调色板来表示这不多于256种的颜色,取用也非常方便,通过调色板数组就可以取得索引对应的RGB值。对FixedPalette为111时, 调色板中只有八种颜色, 不须要256个选项, 8种为(0x000000[0], 0x0000ff[1], 0x00ff00[2], 0x00ffff[3], 0xff0000[4], 0xff00ff[5], 0xffff00[6], 0xffffff[7]),即调色板索引为(0-7)其余对于FixedPalette为111时并未用于;初始化调色板时, 要先调用LCD_L0_Index2Color(i)将Index转为RGB表示的颜色, 再与黑白成比例综合. 这样了就可对负所有实际使用调色板为2项,4项,8项,16项,256项的所有用到调色析的情况。
以上六点是LCD模拟显示器中用到的几点核心要点,要理解LCD模拟显示器,就必然先理解以上几点。
-----实现模拟LCD的内存布局。
[1]、首先是创建一个名为“emWinLCDMap”的大小为0x00400000(4M)的可读可写及其它所有权限的内存映象文件,如果已存在则是打开该内存映象文件, 设其分配所得初始地址为pSMemFix,类型字节。
[2]、pSMemFix+0x20开始,分别存放XSize、YSize、VXSize、VYSize、FixedPalette、BPP、NumColors,其计28个字节,这里存入这些信息是为ucguiview查看器使用的,所有的对于查看器要使用的信息均可以在这里存放。
[3]、pSMemFix + 0x100开始,用于存放位图结构信息40(0x28)个字节及1000个字节的调色板信息。即如下结构:
typedef struct tagBITMAPINFO { // bmi
    BITMAPINFOHEADER bmiHeader;
    RGBQUAD          bmiColors[1];
} BITMAPINFO;
[4]、pSMemFix + 0x1000开始,用作LCD的显存,存放LCD屏幕的象素点。

-----实现LCD功能的几个基本函数。
[1]LCDSIM_SetPixelIndex--------画点
///////////////////////////////////////////////////////////////////////
//
// 函数名       : LCDSIM_SetPixelIndex
// 功能描述     : 最基本的画点函数...
// 参数         : int x[点x坐标]
// 参数         : int y[点y坐标]
// 参数         : int Index[颜色索引值]
// 返回值       : void  
//
///////////////////////////////////////////////////////////////////////
void  LCDSIM_SetPixelIndex(int x, int y, int Index)
{
int pixPos = 0;
char* lptemp = 0;
if(paaPixel == 0) return;
if(BPP <= 8){
  pixPos = y * BytesPerLine + x;
  lptemp = (char*)paaPixel + pixPos;
  *lptemp = Index;
}
else{
  pixPos = y * BytesPerLine + x * 4;
  lptemp = (char*)paaPixel + pixPos;
  *(int*)lptemp = Convert_Index16IntoIndex32(Index); //转索引为RGB颜色值...
}
ModifyCnt++; //表明LCD显示屏已有变化,在LCD窗口中的定时器会检测此值以决定是否重画窗口,即更新显示LCD...
if(pFix != 0){ //映象是否成功申请...
  lptemp = (char*)((char*)pFix + 0x3c);
  *lptemp = ModifyCnt;
}
}
这个函数是最基本的画点函数,是LCD模拟显示器的最基本与最核心的函数,其余画线、画矩形、画位图等等功能均由以此为基础。
[paaPixel]-------------LCD显存起始地址。
[BytesPerLine]---------LCD显示屏一行象素之字节数。
[ModifyCnt]------------表示LCD显示是否有更新,每画一个点加1。
Convert_Index16IntoIndex32----转换象素位数大于8位时的颜色索引为RGB颜色值。
当象素位数 LCD_BITSPERPIXEL 不大于8位时,直接写索引值到显存中;大于8位时,必须将索引值转换为RGB颜色值后再写入。 其实在我们这个模拟LCD显示屏当中,我们往其中画点的过程就是构造位图数据的过程,但没有BMP文件头、位图信息頭、调色板,在BMP位图文件中,这些都辅助用于解析显示位图用的,我们这里的LCD调色板是放在另外一块内存中,并已经初始化; 当构造好位图数据后,我们调用win的API SetDIBitsToDevice或StretchDIBits来将位图画到LCD窗口中,下面说明一下这两个API:
[SetDIBitsToDevice]----是将设备无关位图中的颜色数据画到与指定的设备场景相关的设备上的指定矩形中,这个函数不会进行缩放,即原矩形与目标矩形大小相同,它有一个参数为位图数据,还有一个参数为位图信息及调色板(BITMAPINFO),其余为指定源矩形与目标矩形及设备场景句柄。
[StretchDIBits]--------除完成以上功能还会对位图进行缩放显示,此种情况即为模拟器中LCD_YMAG、LCD_XMAG不为1的情况,即放大显示。                                                                                                                       
调用这两个函数之前必须先调用SetStretchBltMode位图映射模式。   
[LCDSIM_GetPixelColor]----取点RGB颜色值
int LCDSIM_GetPixelColor(int x, int y)
{
int Color = 0;
if(paaPixel == 0) return Color;
if(BPP > 8){
  Color = *((int*)paaPixel + y * BytesPerLine + x * 4);  //直接在显示内存中取, 其值即为该点RGB值.
}
else if(BPP <= 8){
  Color = *((char*)paaPixel + BytesPerLine * y + x * 4);
  Color = LCDSIM_Index2Color(LCDSIM_GetPixelIndex(x, y));
}
return Color;
}
获取指定象素点的RGB颜色值,当LCD_BITSPERPIXEL大于8时,此点RGB直接在LCD显存中取即是;当不在于8时,则须根据从LCD显存中取回的索引值再取调色板中RGB值,即下面的函数:转换索引为颜色值。
[LCDSIM_Index2Color]------根据索引转换成RGB值。
int LCDSIM_Index2Color(int Index)
{
int getColor = 0;
if(BPP == 0) return getColor;
else if(BPP > 8){
  getColor = Convert_Index16IntoIndex32(Index);
}
else if(BPP <= 8){
  getColor = *((char*)pBitmapInfo + 0x28 + Index*4) << 16 | *((char*)pBitmapInfo + 0x29 + Index*4) << 8 |       *((char*)pBitmapInfo + 0x2a + Index*4);
}
return getColor;
}
[pBitmapInfo]--------位图信息起始地址,其中位图结构大小0x28.
[pBitmapInfo+0x28]---调色板起始址址。
Convert_Index16IntoIndex32----转换LCD_BITSPERPIXEL大于8时的颜色索引值为RGB颜色值
LCD_COLOR Convert_Index16IntoIndex32(int Index)
{
// 2005-6-4 12:33:06
return INDEX2COLOR(Index);  //此一句可以底上前面的N句,编译器将由预定义值来选定具体的转换函数。
/* LCD_COLOR convertColor = 0;
switch(FixedPalette){
case 444:
  convertColor = LCD_Index2Color_444(Index);
  break;
case 555:
  convertColor = LCD_Index2Color_555(Index);
  break;
case -555:
  convertColor = LCD_Index2Color_M555(Index);
  break;
case 565:
  convertColor = LCD_Index2Color_565(Index);
  break;
case -565:
  convertColor = LCD_Index2Color_M555(Index);
  break;
case -444: //无此项转换...
  break;
}
return convertColor;
*/
}
在这个函数中,具体的转换的功能是由GUI\ConvertColor目录下的几个文件中提供的,具体参看UCGUI源码。
[LCDSIM_SetLUTEntry]------设置调色板信息。
void  LCDSIM_SetLUTEntry(U8 Pos, LCD_COLOR color)
{
char* lptemp = 0;
if(BPP == 0) return;
color = FilterColor(color, LCDSIM_aLCDColorBlack[0], LCDSIM_aLCDColorWhite[0]);
lptemp = (char*)pBitmapInfo + 0x28 + Pos * 4;
*(char*)lptemp++ = (color & 0xff0000) >> 16;
*(char*)lptemp++ = (color & 0xff00) >> 8;
*(char*)lptemp = color & 0xff;
ModifyCnt++;
LUT_ModifyCnt++;
if(pFix != 0){
  lptemp = (char*)pFix + 0x3c;
  *lptemp = ModifyCnt;
  lptemp = (char*)pFix + 0x40;
  *lptemp = LUT_ModifyCnt;
}
}
[LUT_ModifyCnt]----记录调色信息的更改次数。
[pBitmapInfo]------位图信息起始地址。
[FilterColor]------此函数负责将指定颜色值转换为在背景色与前景色之间的值。转换的方法是(colorWhite & 0xff - colorBlack &            0xff) * (color & 0xff) / 0xff; colorWhite为有景色,colorBlack为背景色,取相应的RGB位,用前景色减去背景     色之值再乘以255, 所得值再除以255得商即为调整后颜色值,这样做目的是将指定颜色值中和到前景色与背景色之间。
                                                                                                                             -----模拟器的输入模拟。
在UCGUI当中有专门的接收输入的接口,有MOUSE、KEY、TOUCH的,分别为GUI_MOUSE_StoreState、GUI_StoreKeyMsg、GUI_TOUCH_StoreState这三个函数,三UCGUI中关于这些消息的处理都比较简易,并没有什么队列,都是接收一个处理一个,这对于简单的嵌入式应用来说已经足够,可以减少内存占用,但有可以造成消息的丢失。
[MOUSE]
[LCDSIM_SetMouseState]--------传送MOUSE及TOUCH(触摸屏)消息到UCGUI。
void NotifyMouseState(LCD_tMouseState mouseState)
{
if(mouseState.KeyStat == 0){
  GUI_TOUCH_StoreState(-1, -1);
}
else{
  GUI_TOUCH_StoreState(mouseState.x, mouseState.y);
}
GUI_MOUSE_StoreState((const GUI_PID_STATE*)&mouseState);
}
MOUSE消息处理中,包括WM_MOUSEMOVE、WM_LBUTTONDOWN、WM_LBUTTONUP这三种,NotifyMouseState的功能就是内存消息至ucgui.
[LCDSIM_GetMouseState]--------获取MOUSE消息。
[KEY]
[HandleKeyEvents]-------------传送KEY消息到UCGUI。
void HandleKeyEvents(UINT message, WPARAM wParam)
{
int key = 0, keyCount = 0;
switch(message){
case WM_KEYDOWN:
  key = VirtKey2Key(wParam);
  if(key == 0) key = Keydown2ASCII(wParam);
  keyCount = 1;
  break;
case WM_KEYUP:
  key = VirtKey2Key(wParam);
  keyCount = 0;
  break;
}
if(key != 0) GUI_StoreKeyMsg(key, keyCount);
}
[VirtKey2Key]----主要处理一些特殊键,如SHIFT、DELTE、BACKSPACE、INSERT、CRTL、ENTER、方向键等。
[Keydown2ASCII]--将键盘虚拟码转换为ASCII码,大小写区别的。     
在键盘处理当中,如上所说的特殊键是有特别用途的,UCGUI中已重定义这些特殊键所对应的KEY值,可以在GUI.h当中查找GUI_KEY_UP来找到这些特殊键在UCGUI中的KEY值。在传送键盘消息时,这里只在WM_KEYDOWN时处理普通键,WM_KEYUP中并未处理,WM_KEYUP只处理了特殊键。其它人可以根据自己人须求要改写这个传送键盘消息到UCGUI的函数。
HandleKeyEvents在LCD的窗口消息函数中,当LCD窗口有键盘消息时,即传送至UCGUI内部,以驱动UCGUI的键盘处理。
二、拔去见日-----UCGUISim模拟器的模块划分。
在上面介如了模拟器的基本原理,差不多将核心的东西都说出来了,这时简要的说明一下模拟器的几个构成模块。
emWin.c----------创建UCGUISim模器主窗口及LCD显示窗口,处理KEY、MOUSE消息传送,开启、暂停UCGUI程序,输出LOG等。
LCDSIM.c---------实现模拟LCD显示屏。
Branding.c-------显示版权。
GUI_X_SIM.c------实现UCGUI的临界代码锁及实际的LOG输出及延时功能。

具体可以能见源码,这里就不就代码作详细解说了。   
 楼主| 发表于 2005-7-1 22:59:46 | 显示全部楼层

ucos的GUI图形系统ucgui的模似器源码设计文档及各种资料

                    修改UCGUI源代码以支持多个独立窗体的说明
作 者: ucgui
email: ucgui@163.com
home: http://www.ucgui.com
版 本: v1.0.0.1

修改后的源代码下载:
http://www.ucgui.com/ucgui/GUISim1004_MultDialog.rar

问题的提出:
-----[求助]关于对话框对话框处理程序中,ok按钮按下后想出现一个消息框,该怎么做?直接加在程序中好像不行,请版主帮帮忙.
[独立窗体]------指该窗体的父窗体不是用户创建窗体,在UCGUI中其父窗体为句柄为1.
一,消息处理的流程.
   ucgui是采用的消息驱动的. 它专门有对外的一套采集消息的接口, 我在模似器中, 就是通过LCD窗口的MOUSE消息,将MOUSE移动, 点击消息传入到这个接口中, 以驱动UCGUI中的事件的..
   UCGUI中的消息驱动, 其实与WINDOWS的是类似的, 几种基本的消息与WINDOWS是一样的, 但UCGUI的更简单, 消息更少, 没有WINDOWS那么多的消息种类. 在WINDOWS中, 我们最简单处理按钮事件的是在WM_COMMAND消息中, 通过按钮的标志ID来处理不同的按钮, 所以, 我们的按钮的标志ID都是不同的. 要不然无法区别开的.
   要处理点击OK这个按钮的事件, UCGUI的处理方法有些不同, 是在WM_NOTIFY_PARENT消息中处理:
    case WM_NOTIFY_PARENT:
      Id    = WM_GetId(pMsg->hWinSrc);    /* Id of widget */
      NCode = pMsg->Data.v;               /* Notification code */
      switch (NCode) {
        case WM_NOTIFICATION_RELEASED:    /* React only if released */
          if (Id == GUI_ID_OK) {          /* OK Button */
            GUI_EndDialog(hWin, 0);
          }
          if (Id == GUI_ID_CANCEL) {      /* Cancel Button */
            GUI_EndDialog(hWin, 1);
          }
          break;
      }
      break;
   ucgui中的消息非常的少, 只有差不多不到二十种, 其实这对于嵌入式系统来说, 已经完全足够了, 用户可以自定义消息, WM_NOTIFY_PARENT这个消息是由你窗体传送过来的,  是由函数WM_NotifyParent(hObj, Notification)实现的.
void WM_NotifyParent(WM_HWIN hWin, int Notification) {
  WM_MESSAGE Msg;
  Msg.MsgId   = WM_NOTIFY_PARENT;
  Msg.Data.v  = Notification;
  WM_SendToParent(hWin, &Msg);
}
   这个函数相当简单, 其主要还是WM_SendToParent这个函数的调用, 这个函数又调用void WM_SendMessage(WM_HWIN hWin, WM_MESSAGE* pMsg), 这个函数是最基本的一个消息处理函数, 它的第一个参数指定了接受这个要处理的消息的句柄, 第二个指定了是什么消息.这个函数的主要作是, 是调用相就窗口的消息处理函数来处理消息.
   比如说, 在这里,我们这个消息的处理过程是这样的, 首先, 你左键点击OK按钮, 那么在GUI_Exec()这个窗体消息LOOP处理当中, 首先将此消息(WM_TOUCH)传给OK按钮(注意这也是一个窗体), 然后, OK按钮的消息处理函数将此消息再以WM_NOTIFY_PARENT传送到你窗体---对话框当中,  这样, 对话框就可以处理到点击OK按扭这个事件了...
   其实这个消息处理的流程与WINDOWS也是类似的. 原理一样..
   理解了这个过程, 那么, 我们就可以这个消息中处理很多东西了, 只要是在对话框中的子控件, 一般的消息都可以如此处理.

二,发现存在的问题----两个独立的窗口并立时, 当关闭其中一个, 另外一个不再有响应.
   在如下代码中处理弹出消息框, 点击对话框的OK后弹出消息框, 会出现当按对话框的Cancel关闭对话框后, 弹出的消息框就没有任何响应的情况. 或者是关闭掉弹出的消息框, 对话框就没有任何响应的情形.
    case WM_NOTIFY_PARENT:
      Id    = WM_GetId(pMsg->hWinSrc);    /* Id of widget */
      NCode = pMsg->Data.v;               /* Notification code */
      switch (NCode) {
        case WM_NOTIFICATION_RELEASED:    /* React only if released */
          if (Id == GUI_ID_OK) {          /* OK Button */
//弹出消息框...
   GUI_MessageBox("This text is shown\nin a message box",
                 "Caption/Title", GUI_MESSAGEBOX_CF_MOVEABLE);
           GUI_EndDialog(hWin, 0);
          }
          if (Id == GUI_ID_CANCEL) {      
            GUI_EndDialog(hWin, 1);
          }
          break;
      }
   经分析, 粗步的原因是, 调用MainTask的主线程已经退出了, 这个主线程是在模拟器中开启的, 它的主线程函数是Thread(), Thread函数里调用main, main再调用MainTask; 所以主线程退出后, 消息框再也没有任何反应了.这是从模拟器的角度来分析, 现在我们分析一下, 为什么MainTask的主线程会这么早退出呢?
   深入UCGUI的源码, 可以知道, 对话框的消息处理函数是在如下的函数中调用的.
int     GUI_ExecDialogBox(const GUI_WIDGET_CREATE_INFO* paWidget,
                          int NumWidgets, WM_CALLBACK* cb, WM_HWIN hParent,
                          int x0, int y0)
{
  _cb = cb;
  GUI_CreateDialogBox(paWidget, NumWidgets, _cbDialog, hParent, x0, y0);
  while (_cb) {
    if (!GUI_Exec())
      GUI_X_ExecIdle();
  }
  return _r;
}

   在如上的代码中我们可以看到, 当UCGUI中有一个独立的窗体退出后, _cb会被清为0, 此时退出GUI窗口LOOP. 即结束了UCGUI窗口消息处理.
   其实, GUI_MessageBox弹出的消息框其实也是一种对话框, 在这里我们就可以发觉UCGUI的窗口支持系统还不是非常的完备, 我们期待的结果是, 点击对话框的OK, 弹出消息框, GUI_EndDialog关闭掉对话框, 消息框继续有反应, 如果没有在OK按钮中调用GUI_EndDialog关闭对话框, 则对话框与消息框都要能够正常反应.
   关于这个问题的解决, 我想还要进一步了解UCGUI窗口处理的细节.
三.寻找问题的解决办法.
   在我们发现这个问题, 我们已经粗步分析了, 问题不是出在我们编写程序上, 而上UCGUI的内部, 那么要解决这个问题, 我们就要进一步了解UCGUI的窗口体系.其实换一句话说, 在嵌入式应用中, 窗口的强大直接决定到GUI系统的体积大小, 并不是所有的情况都要有这种支持, 也不一定说是UCGUI的BUG, 当然我们希望在下一版本不再有这个问题.
下面是详细分析:
   1. 对话框
void MainTask(void) {
  GUI_Init();
  WM_SetDesktopColor(GUI_RED);      /* Automacally update desktop window */
  WM_SetCreateFlags(WM_CF_MEMDEV);  /* Use memory devices on all windows to avoid flicker */
  GUI_ExecDialogBox(_aDialogCreate, GUI_COUNTOF(_aDialogCreate), &_cbCallback, 0, 0, 0);
}
   上面是我们创建对话框的程序, 是我们编写的代码, 具体看GUI_ExecDialogBox()这个函数代码如下, 它主要用以下两个作用:
   [1].创建对话框中的的所有子窗体.
   
   [2].进入消息LOOP, 收集并转发消息到对应窗体进行处理. 消息如WM_TOUCH及WM_KEY, 这些消息被分发到对话框中各子窗体中去处理, 要理解如下的尽, 父窗体(对话框)之所以能够处理其上子控件消息, 全都是因为子控件在传递, 父窗体中对子窗体的消息进行处理, 也可以不处理, 我们编程者要处理时, 则要清楚哪些消息是可以处理的, 不然无法编写程序, 所以子窗体中的消息一直都在往父窗体中发送, 与父窗体有无处理无关系.
int     GUI_ExecDialogBox(const GUI_WIDGET_CREATE_INFO* paWidget,
                          int NumWidgets, WM_CALLBACK* cb, WM_HWIN hParent,
                          int x0, int y0)
{
  _cb = cb;
  GUI_CreateDialogBox(paWidget, NumWidgets, _cbDialog, hParent, x0, y0);
  while (_cb) {
    if (!GUI_Exec())
      GUI_X_ExecIdle();
  }
  return _r;
}
2. 消息框
   其实消息框本身就是一个只含静态文框及OK按钮的对话框, 在对话框的WM_NOTIFY_PARENT消息中弹出消息框:
    case WM_NOTIFY_PARENT:
      Id    = WM_GetId(pMsg->hWinSrc);    /* Id of widget */
      NCode = pMsg->Data.v;               /* Notification code */
      switch (NCode) {
        case WM_NOTIFICATION_RELEASED:    /* React only if released */
          if (Id == GUI_ID_OK) {          /* OK Button */
            GUI_EndDialog(hWin, 0);
          }
          if (Id == GUI_ID_CANCEL) {      /* Cancel Button */
            GUI_EndDialog(hWin, 1);
          }
          break;
      }
      break;
   在关闭对话框之前弹出一个消息框, 当我们都不点击消息框与对话框的OK按钮来结束时, 他们都有反应也可以前后台切换, 不存在什么问题. 问题就出在关闭其中一个后, 其它的一个也无任何的反应.
   这个原因, 在上面有过过粗步的分析, 但没有进入到UCGUI的实现机制内, 现在我们来看一下.
  _cb = cb;
  while (_cb) {
    if (!GUI_Exec())
      GUI_X_ExecIdle();
  }
   这个LOOP, 其实它就是类似我们非常熟悉的WIN下面的消息LOOP, 其原理是一致的. _cb是什么东西呢? 这里我们可以看到, 它其实就是对话框的窗口消息处理函数, 这里面有一个判断, 就是_cb非空时, 才进行消息LOOP, _cb在Dialog.c中的定义如下:
   
static WM_CALLBACK* _cb;
   _cb是一个全局变量! 我们程序中创建对话框与弹出消息框时两次调用了GUI_ExecDialogBox, 后一次的_cb将会把前面的值冲掉, 我们程序中, 可以知道它是消息框的窗口消息处理函数地址. 由引可知道谁在后面调用, 则另外一个对话框的窗口消息处理函数根本就是无用的, 它的消息其实是被后来创建的对话框消息处理函数所处理的.

   在while中有判断, 那么证明_cb是在GUI_Exec所产生的调用中有使用的.进一步看, 可以看到, 调用的其实是如下面的箭头方向所指, 一步一步深入的, 其中最明显的一点就是, 窗口消息处理函数是在WM_SendMessage中通过函数指针的调用中, 注意[]内部的就是真正被调用来处理消息的函数, 它是是将消息一步一步的传到真正要处理此消息的窗体中, 在这里其实就是后面弹出的消息框窗体.
GUI_Exec--->GUI_Exec1-->WM_Exec--->WM_Exec1-->WM_HandlePID-->WM_SendMessage-->(*pWin->cb)(pMsg)[_FRAMEWIN_Callback]-->_OnTouch()--->(*cb)(pMsg)[_cbDialog]--(*_cb)(pMsg)[_MESSAGEBOX_cbCallback]

WM_HandlePID()----------专门处理MOUSE消息的函数.
WM_SendMessage()--------基层的分派消息的函数, 决定谁应该被调来来处理消息.

   这里面有些人会提出一个问题, 为什么消息框与对话框的消息都在后来创建的消息处理函数里面处理而不会引发什么问题呢?
   是的, 表面上看去, 消息框与对话框都好好的, 没什么异常, 但其实如果你在对话框中进行过消息的处理, 那么你就可以很清楚的看到这些消息是从来不会发生的, 即使你在它的消息处理函数里下断点它也不会有任何中断.
   至于其它的一些常用的消息的处理, 对话框与消息没有什么区别, 最重要的消息就是重画, 重画的时候, 比如消息框与对话框有重叠的话, 当进行窗口前后切换将其中一个切换到前面来时, 会产生重画消息. 这样就会重新计算一次该画到屏慕上的东西, 不光要发关重画消息到各窗口, 还会将整个屏慕的东西都重新计算一次, 并画出来. 这一点还是很复杂的, 须要进一步学习.是GUI图形的一个难点和重点. 以后再详细分析.

   进行到这里, 我们就可以比较明白的知道, 在点击OK后, 无论是消息框还是对话框, 哪一个先被关掉, 都会掉用下面的GUI_EndDialog. 这里, _cb被清为零, 也就意味着消息LOOP到此结束了. 所以后面另外一个未被关掉的当然不会再有任何响应了.
void GUI_EndDialog(WM_HWIN hWin, int r) {
  _cb = NULL;
  _r = r;
  WM_DeleteWindow(hWin);
}

   原因我们分析出来了. 现在我们要解决这个问题, 那还差一大步, 还有很多问题要进一步弄清, 为什么UCGUI是这样的机制.要如何解决. 将会在下一步仔细学习分析.
   楼主再仔细读一下我的分析, 结合实际自己也来分析一下, 问题虽然小, 但其实隐藏着的问题不小. 你的所言其实还是没有理解清楚问题, 因为你所说的HIDE对话框. 其实没有点着问题实际, 我们现在所讲的是UCGUI对于多个独立窗体的消息处理支持. UCGUI不完善.对这个目前没有支持. 你要深入到这一点来理解问题.
再仔细看下我的分析. 有什么问题再提, 结合实际, 进行源码调试, 对于问题会有更多的了解....
四. 进一步分析找到解决办法.
   点击对话框的OK按钮后, 其实这个时候, 由于消息框是对话框后弹出来的,那么这个消息框的消息处理函数_MESSAGEBOX_cbCallback则成为_cb的值了, 那么以后对话框与消息框的消息处理都是在消息框的_MESSAGEBOX_cbCallback里面处理的, 如果你是在点击对话框的OK按扭之后弹出消息框.如下:
    case WM_NOTIFY_PARENT:
      Id    = WM_GetId(pMsg->hWinSrc);    /* Id of widget */
      NCode = pMsg->Data.v;               /* Notification code */
      switch (NCode) {
        case WM_NOTIFICATION_RELEASED:    /* React only if released */
          if (Id == GUI_ID_OK) {          /* OK Button */
            GUI_MessageBox("This text is shown\nin a message box",
      "Caption/Title", GUI_MESSAGEBOX_CF_MOVEABLE);
            GUI_EndDialog(hWin, 0);
          }
          if (Id == GUI_ID_CANCEL) {      
            GUI_EndDialog(hWin, 1);
          }

   那么, 程序执行到弹出消息框之前, 消息 WM_NOTIFY_PARENT 都是由对话框的消息处理函数_cbCallback处理的, 在弹出消息框之后, 如我们在(二)上面的分析, 就知道以后的消息都是由  消息框的_MESSAGEBOX_cbCallback来处理的, 此时, 在消息LOOP中马上又会发现上一次的MOUSE消息, 因为这个点击OK产生的消息还在, 并没有产生新的消息, [说来其实UCGUI中的消息是没有什么队列的, 是接收一个处理一个, 即时处理那种, 这样就简单了很多,] 那么又一次进行WM_NOTIFY_PARENT的GUI_ID_OK按钮的处理, 只不过这个处理是在消息框的_MESSAGEBOX_cbCallback函数中进行的, 在这个函数里面默认的对于GUI_ID_OK的处理就是调用GUI_EndDialog(hWin, 1); 一旦调用这个函数, 那么产生的结果就如同先前分析的, 会导致WM_DeleteWindow的调用, 而将_cb的值清为空, 并清除hWin及其所有了窗体. 从而结束了LOOP消息处理.
   所以, 我们可以看到, 在点击对话框的OK后, 弹出消息框, 之后即使我们不调用清除对话框的函数, 什么也不做. 也会导到消息LOOP结束, 退出所有窗口消息的处理.
   其实, 须要进一步说明的是, 在UCGUI中, 似乎在设计时只是支持单窗口的消息处理, 如果要多窗口的支持, 可以如同示例中一样, 启用多任务支持, 不然, 在单任务下, 一个MainTask中只能支持一个独立窗体, 但是, 显然我们如果只是为了要弹出一个消息框而启动一个任务, 这未免太不实际.
   解决的办法, 还是有的, 那就是了解UCGUI, 自己来修改. 可以如下做-------
   [如上创建一对话框及消息框, 对话框与消息框即为如下所讲的外部独立窗体]
   1. 经过详细的分析, 认识到在消息处理中, 创建一个独立窗体, 则其窗口消息处理函数就会成为整个UCGUI中的消息的外部接收口, 用户所能处理到所有消息均在此处接收到.
   2. 如果在后来在创建独立窗体, 则后来的窗口的消息处理函数代替了先前创建的窗口的消息处理. 那么, 如果不使它替代, 而是用一个数组将所有消息处理函数都存放起来, 然后在内部将消息往外传的地方(即外部独立窗口消息函数被调用处)修改成调用所有非空的外部独立窗口消息处量函数(而不是单个).这样就可以达到每个独立窗口消息处理函数都被调用, 各自独立运行. 但要注意和解决的问题是, 消息该如何分配到各独立窗口.
   3.在清除独立窗体时, 只是将此独立窗体对应的消息处理函数清零, 并清除该窗体及其的所有子窗体. 使其不再处理消息.

   那么. 我们在点击OK关闭对话框, 也就是清除对话框并让其退出消息接收. 对于弹出的消息框, 我们希望它作为一个新的独立窗体而进入窗体消息接收与处理. 就达到了我们的目的.

   这是理论上的分析. 我将把实际修改后的东东也发上来, 做一示例.

五. 对UCGUI源码做出部分修改以实现多独立窗口支持.
[2005-6-30 2:30]
   在第三节当中, 我们通过进一步的分析源码, 大致找到了解决问题的办法, 但那只是理论上的指导, 实际上的修改其实还会带来其它的很多问题, 因为在UCGUI体系中, 对其源码作出改动, 一定都会影响到其它的地方, 现在我们就实际的源码修改说明几点要注意的问题:

   1、将原来的_cb一个变量, 修改成一个结构为new_cb的结构数组,有10个元素,
//static WM_CALLBACK* _cb;
typedef struct win_cb{
WM_CALLBACK* _cb;
WM_HWIN hwin; //_cb消息函数对应的窗口..
WM_HWIN hclient;//_cb消息函数对应的窗体窗户区...
}new_cb,*lpnew_cb;
static int dialog_pos = 0;//在数组中当前可用作存窗体消息函数的元素.
static int MAX_DIALOG = 10;//最多可支持打开的独立窗口数, 其实可以改成支持无数个,但这里作简单处理
int checkHasDialog(); //检查是否还有独立窗体存在, 以决定是否退出消息LOOP...
int getDialogIndex(lpnew_cb lp_cb);//获取当前可用于存放独立窗体的位置索引, 创建新独立窗体时调用.
static new_cb _cb[10];  //独立窗口数组创建一个新的独立窗体, 将此窗体加入到独立窗体数组当//中时, 必须注意几个问题,在如下的新旧代码对比中说明:
//新修改后的创建对话框的函数...
int     GUI_ExecDialogBox(const GUI_WIDGET_CREATE_INFO* paWidget,
                          int NumWidgets, WM_CALLBACK* cb, WM_HWIN hParent,
                          int x0, int y0)
{
//  _cb = cb;
dialog_pos = getDialogIndex(_cb);
if(dialog_pos != -1) _cb[dialog_pos]._cb = cb;
else return _r;
GUI_CreateDialogBox(paWidget, NumWidgets, _cbDialog, hParent, x0, y0);
//  while (_cb) {
/* 2005-6-30 0:20:16
while (checkHasDialog()) {
  if (!GUI_Exec())
   GUI_X_ExecIdle();
}*/
return _r;
}
//旧的创建对话框的函数...
int     GUI_ExecDialogBox(const GUI_WIDGET_CREATE_INFO* paWidget,
                          int NumWidgets, WM_CALLBACK* cb, WM_HWIN hParent,
                          int x0, int y0)
{
  _cb = cb;
  GUI_CreateDialogBox(paWidget, NumWidgets, _cbDialog, hParent, x0, y0);
  while (_cb) {
    if (!GUI_Exec())
      GUI_X_ExecIdle();
  }
  return _r;
}
WM_HWIN GUI_CreateDialogBox(const GUI_WIDGET_CREATE_INFO* paWidget, int NumWidgets, WM_CALLBACK* cb, WM_HWIN hParent,
                            int x0, int y0)
{
  WM_HWIN hDialog = paWidget->pfCreateIndirect(paWidget, hParent, x0, y0, cb);     /* Create parent window */
  WM_HWIN hDialogClient = WM_GetClientWindow(hDialog);
  _cb[dialog_pos].hwin = hDialog;
  _cb[dialog_pos++].hclient = hDialogClient; //加到GUI_CreateDialogBox中的,其余不变...
  WIDGET_OrState(hDialog, paWidget->Flags);
  ........
}
   [1]、getDialogIndex(_cb),创建新的独立窗体前,首先就要在独立窗体数组中查找空位置,如果独立窗体已达最大数,则不可再加入独立窗体,这里我做的是简单处理,没有用到动态内存分配,将独立窗体数组扩大,主要是因为是演示,读者自己可以试度支持无限独立窗体。
   [2]、_cb[dialog_pos]._cb = cb 独立窗口的窗口消息处理函数必须在GUI_CreateDialogBox之前赋值,因为在GUI_CreateDialogBox中就会用到这个窗口消息处理函数。
   [3]、_cb[dialog_pos]中的hwin等窗口句柄的处理加到创建对话框当中,千万不要在创建完对话框后再根据返回的对话框句柄初始化,因为在创建对话的子窗体时就会调用到对话框消息处理函数,如果hwin此时未初始化,则在_cDialog()中就无法分发消息,这样对话框中的子窗体都无法正确显示的。
   [4]、GUI_ExecDialogBox中的窗口消息LOOP被注掉了,改为放到MainTask中调用,这一点与我们WIN下面就很类似了,而且事实证明,这个改进是非常有用的,因为当我们把窗体都创建了之后,再来执行消息LOOP的话,可以避免前面创建独立窗体的消息LOOP被后面创建的消息LOOP中断的作用,只有后面的窗体关闭掉后,才会返回到先前的窗体消息LOOP当中,在我们一直谈论的话题当中,点OK按钮弹出一个消息框,这个流程其实是这样的:
   ------点击OK的消息处理流程: 在没有弹出消息框之前,是在一直进行着对话框的窗口消息LOOP,当点击对话框上的OK按扭时(消息LOOP中的点击处理在WM_HandlePID()进行),WM_HandlePID()调用WM_SendMessage向OK按扭窗体发送WM_TOUCH消息,再到BUTTON的消息处理函数_BUTTON_Callback中处理WM_TOUCH(_OnTouch)消息,然后OK按钮中还要调用WM_NotifyParent转发WM_NOTIFY_PARENT消息到父窗体,此时父窗体才开如执行弹出消息框的代码,所以此时我们可以了解到,如果窗口消息LOOP在GUI_ExecDialogBox当中进行的话,那么表明原来的对话框中进行的窗口消息LOOP就会被新创建的消息框挂起来了,一直要等到消息框中的窗口消息LOOP结束返回为止,这当中会造成在对话框窗口消息LOOP中的WM_LOCK调用后,WM_UNLOCK的调用一直要等到消息框的窗口消息LOOP结束为止。
void MainTask(void) {
GUI_Init();
WM_SetDesktopColor(GUI_RED);      /* Automacally update desktop window */
WM_SetCreateFlags(WM_CF_MEMDEV);  /* Use memory devices on all windows to avoid flicker */
GUI_ExecDialogBox(_aDialogCreate, GUI_COUNTOF(_aDialogCreate), &_cbCallback, 0, 0, 0);
//  while (_cb) {
while (checkHasDialog()) {
  if (!GUI_Exec())
   GUI_X_ExecIdle();
}
}
   在上面的流程说明中,认识到是有必要将窗口消息LOOP放到创建对话框这外的,因为我们是要创建多个独立窗体,那么LOOP当中要分发消息的对象也就是多个窗体,而不是其中的一个,所以要从创建对话框函数中拿出, 由用户来写,如同WIN下面一样。
   2、在第三节中第2点还说到,要分别调用各个独立窗体的窗口消息函数,必须注意消息的分发,下面我们看一下具体如何分发:
static void _cbDialog(WM_MESSAGE* pMsg)
{
char buf[100];
/* 2005-6-28 0:53:23
if (_cb) {
  sprintf(buf, "the hwin = %d, msgid = %d", pMsg->hWin, pMsg->MsgId);
  SIM_Log(buf);
  (*_cb[0]._cb)(pMsg);
}*/
int i = 0;
WM_LOCK();
for(i = 0; i < MAX_DIALOG; i++){
  if(_cb._cb != (WM_CALLBACK*)0){
   if (WM__IsWindow(pMsg->hWin))
   {
    if(pMsg->hWin == _cb.hwin || _cb.hclient == pMsg->hWin)
    {
     sprintf(buf, "has call the i = %d hwin = %d, msgid = %d _cb.hwin = %d _cb.hclient = %d \n", i, pMsg->hWin, pMsg->MsgId, _cb.hwin, _cb.hclient);
     (*(_cb._cb))(pMsg);
    }else
    {
     sprintf(buf, "not call the i = %d hwin = %d, msgid = %d _cb.hwin = %d _cb.hclient = %d \n", i, pMsg->hWin, pMsg->MsgId, _cb.hwin, _cb.hclient);
    }
    SIM_Log(buf);
   }
  }
  
}
WM_UNLOCK();
}
   _cbDialog是在对话框的FRAMEWIN__cbClient中调用的,如下形式:
  if (cb) {
    pMsg->hWin = hParent;
    (*cb)(pMsg);
  }
   每个独立对话框窗体均是这样,通过其FRAMEWIN来调用用户指定的窗口消息处理函数,在分发消息时,其实只须要根据消息中的窗体句柄来分发,因为我们对于每个独立窗体,均记载了它的窗体句柄及客户句柄。所有创建的独立窗体的消息均是在_cbDialog中顺序进行处理的,如果任何一个窗体的消息处理函数有问题,那么就会导致知所有窗体均没有反应。

   3、第三节中所说的第3点,独立窗体退出的处理:
void GUI_EndDialog(WM_HWIN hWin, int r) {
//  _cb = NULL;
int i = 0;
char buf[255];
if (!hWin)
  return;
WM_LOCK();
if (WM__IsWindow(hWin))
{
  for(i = 0; i < MAX_DIALOG; i++){
   if(hWin == _cb.hwin || _cb.hclient == hWin){
    _cb._cb = NULL;
    _cb.hwin = 0;
    _cb.hclient = 0;
    sprintf(buf, "not call the i = %d hWin = %d,  _cb.hwin = %d _cb.hclient = %d \n", i, hWin, _cb.hwin, _cb.hclient);
    SIM_Log(buf);
   }
  }
  
}
WM_UNLOCK();
_r = r;
WM_DeleteWindow(hWin);
}
   同样也是根据窗口句柄来找到要清除的独立窗体。
   4、关于MOUSE点击消息处理的WM_HandlePID()函数
   这个函数是专门负责处理MOUSE消息的,即WM_TOUCH消息,当你点击或是在触摸屏上点击上,产生此消息;它当中有两个变量,一个静态的旧消息变量,一个是局部新消息变量,每次均从MOUSE的接口(GUI_PID_GetState())中取新的,每次处理一次最新消息就将最新消息更新到旧消息变量上,下一次处理消息时会比较新旧消息,以避免对相同消息的处理。
   说明上面的问题,我们是为了说明这样一个问题,如果我们没有将窗口消息LOOP放到GUI_ExecDialogBox函数之外,那么存在一个问题就是在点击OK一次后会重复创建N多的消息框,原因其实是相同的消息进行了重复处理,不是每处理完一次消息就更新了旧消息吗? 为什么还会重复处理?这里面根本的问题就是旧消息还未更新,是因为在一创建消息框后,每一个消息框都进行自己的消息LOOP,而将上面一次的消息LOOP挂起来了,那么一直要等到结束返回到上一次消息LOOP,才会更新旧消息。如果将消息LOOP放出GUI_ExecDialogBox之处,即MainTask当中,即不存在此问题,如果硬是不把消息LOOP放出来,也可以在WM_SendMessage之前就更新旧消息,这样也可以避免由于旧消息未更新而重复处理同一消息。


附言:
   一般情况下, 小型的GUI体系, 都很少会有打个多个独立窗口的要求, UCGUI还是在发展中, 而且并不成熟, 所以有很多的问题, 不完善是肯定的.
   我们要学习UCGUI, 首先一定要动手写UCGUI的程序, 阅读源码,  这样才以发现更多的问题, 才能更了解UCGUI...
   我本人就是经常有问题就读源码, 在实际编程中, 发现的问题是最多的, 而且现在模拟器已经还原成源码. 底下不再有任何的秘密, 所有的问题都摆在源码之下, 所以只要花心思研究. 应该可以解决很多问题. 学习到很多图形处理的深层知识.

35_383_11.jpg
 楼主| 发表于 2005-7-1 23:06:03 | 显示全部楼层

ucos的GUI图形系统ucgui的模似器源码设计文档及各种资料

UCGUI的动态内存分配的原理深入分析
作者: ucgui
日期: 2005-06-25
来源: http://www.ucgui.com
文档版本: v1.0.0.0
了解UCGUI的朋友,一定知道UCGUI中的窗口体系,窗口一般都是由程序动态创建的,那么这当中当然要用到动态的内存申请,现在我们就来就这个话题进行深入分析,了解UCGUI中的动态内存分配,是了解其窗口体系统的基础,这一点非常的重要。
先说明一下本文中用到的一些关键下词:
[内存分配信息节点]------------记录一块已分配内存块信息的tBlock结构体,可简称分配节点。
[内存分配信息节点数组]--------内存分配信息节点的数组。
[内存句柄]--------------------是指分配内存块数组中的元素位置索引值。
[最小粒度对齐]----------------是指内存分配大小应该为最小粒度的整数倍。

一,打开动态分配的预定义选项。
在GUIConf.h配制文件当中,有这样一个定义。
#define GUI_ALLOC_SIZE            12500  /* Size of dynamic memory ... For WM and memory devices*/
GUI_ALLOC_SIZE定义的即是整个UCGUI中可用于动态分配的内存大小,这个大小且不能为0,也只有当这个预定义打开后,才能使用UCGUI提供的动态内存分配的功能在GUIAlloc.c文件中提供。
二,动态内存分配的基本功能。
在GUI.h中一段提供了如下一段定义,即:
/*********************************************************************
*
*         Dynamic memory management
*
**********************************************************************
*/
#if !defined(GUI_ALLOC_ALLOC)
  void        GUI_ALLOC_Init(void);
  void*       GUI_ALLOC_h2p     (GUI_HMEM  hMem);
  void        GUI_ALLOC_Free    (GUI_HMEM  hMem);
  void        GUI_ALLOC_FreePtr (GUI_HMEM *phMem);
  GUI_HMEM    GUI_ALLOC_Alloc(int size);
  /* diagnostics */
  int         GUI_ALLOC_GetUsed(void);
  int         GUI_ALLOC_GetNumFreeBytes(void);
  int         GUI_ALLOC_GetMaxSize(void);
  /* macros */
  #define GUI_ALLOC_ALLOC(size)     GUI_ALLOC_Alloc(size)
  #define GUI_ALLOC_H2P(h)          GUI_ALLOC_h2p(h)
  #define GUI_ALLOC_FREE(handle)    GUI_ALLOC_Free(handle)
  #define GUI_ALLOC_LOCK(handle)    GUI_ALLOC_h2p(handle)
  #define GUI_ALLOC_UNLOCK(handle)
#endif
总的来说,动态内存分配提供了如下几组功能:
1,动态内存初始化。
[GUI_ALLOC_Init]
2,动态内存分配、释放、加解锁;以及碎片整理。
[GUI_ALLOC_Free/GUI_ALLOC_Alloc]、[GUI_ALLOC_LOCK/GUI_ALLOC_UNLOCK]
3,动态内存使用情况统计。
[GUI_ALLOC_GetUsed]、[GUI_ALLOC_GetNumFreeBytes]、[GUI_ALLOC_GetMaxSize]
三 动态内存分配的实现原理。
1,首先介绍几个有关动态内存分配的常量及结构。
----常量
GUI_ALLOC_SIZE------------------可用于分配的大小,如开启动态内存分配,在预定义中已经规定必须大于0,否则编译无法通。
GUI_ALLOC_AUTDEFRAG-------------是否进行碎片整理,只有在请求在内存不能满足时才须要将碎片整理,须将所有已分配内存数据前移,例如总共大小为12500,当内配到最后剩200字节,但请求800字节,此时如果定义了碎片整理,则会将之前未用碎片整理出来,将所有已分配的内存都往前移,将碎片整到后面合成一个大的剩余空间。
GUI_BLOCK_ALIGN-----------------内存分配的对齐值,是为保证每块分配的内存均从对齐粒度开始,其值为4个字节。如要求29~31字节则实得32字节,即(29+3)&0xfffffffc,这是在Size2LegalSize完成的。
GUI_MAXBLOCKS-------------------最多可分的内存块数,是内存分配信息记录数组的大小,它决定了将内存正好分配完时每块的最小数值,这个最小数值为32,在后面中我们称其每一元素为[内存分配信息节点]
tALLOCINT-----------------------记录每块内存偏移内存起始点的依稀的变量类型,2字节还是4字节,GUI_ALLOC_SIZE大于32767时,要用四字节类型。
HANDLE--------------------------内存块句柄类型,1字节还是2字节,当GUI_MAXBLOCKS大于256时要用2字节。
#if GUI_ALLOC_SIZE <32767
  #define tALLOCINT I16
#else
  #define tALLOCINT I32
#endif
#if GUI_MAXBLOCKS >= 256
  #define HANDLE U16
#else
  #define HANDLE U8
#endif
tALLOCINT,HANDLE的定义会影响到用记载每一块内存的信息结点的大小,即用于动态内存分配的开消。
----结构
记录每个内存块信息的节点结构。
typedef struct {
  tALLOCINT Off;/* Offset of memory area */
  tALLOCINT Size;/* usable size of allocated block */
  HANDLE Next;/* next handle in linked list */
  HANDLE Prev;
} tBlock;
[Off]-----------------------记录此块内存相对整个内存起始点的偏移。
[Size]----------------------记录此块内存大小。
[Next]----------------------记录此块内存之一下一块内存之指针,其实为这里是指下一块内存在内存分配信息记录数组中的第几个元素。
[Prev]----------------------记录此块内存之上一内存之指针。
[下面这个结构的定义在GUI.h当中]
typedef union {
  int aintHeap[GUI_ALLOC_SIZE/4];     /* required for proper alignement */
  U8  abHeap[GUI_ALLOC_SIZE];
} GUI_HEAP;
GUI_HEAP GUI_Heap;       /* Public for debugging only */
static tBlock aBlock[GUI_MAXBLOCKS];
从上面,可以知道,UCGUI中的内存分配,其实质是通过一大块全局数组的空间来实现的,这个内存是在编译程序后分配的。它的大小是即是在GUIConf.h中预定义的GUI_ALLOC_SIZE个字节,但同时通过GUI_HEAP这个联合,以abHeap来访问是基于1字节,[aintHeap]则是基于4字节。
aBlock是用于记录所有内存分配块的数组,大小是GUI_MAXBLOCKS,GUI_MAXBLOCKS=(2+GUI_ALLOC_SIZE/32),每一个元素记录一个内存分配块的信息,只要知道了内存分配块的位置就可以从数组中取出该分配块的信息。其实所有的内存分配块不光记录在这一数姐中,而且已经构成了一个双链表,这对于遍历所有已经分配的内存块非常的方便。
struct {
  int       NumUsedBlocks, NumFreeBlocks, NumFreeBlocksMin;        /* For statistical purposes only */
  tALLOCINT NumUsedBytes,  NumFreeBytes,  NumFreeBytesMin;
} GUI_ALLOC;
以上的结构记录每次进行内存分配后的[已用块]、[空闲块]、[最小空闲块]、[已分配字节]、[剩余字节]、[最小空闲字节]。其实最小空闲块与最小空闲字节与空闲块、剩余字节用处差不多,其值是相等的。
2,实现动态内存分配的函数详解。
-----初始化
void GUI_ALLOC_Init(void);
主要初始化GUI_ALLOC这个整体内存分配信息结构,并置已经初始化状态,初始化了第一个内存分配结点:
  aBlock[0].Size = (1<<GUI_BLOCK_ALIGN);  /* occupy minimum for a block */
  aBlock[0].Off  = 0;
  aBlock[0].Next = 0;
  
注意这个结点其实是用于双链表的头结点,其大小为最小分配对齐粒度,偏移是0即从整个动态内存的起始。它一直存在,并不会被释放或者改变大小,总之是不会有任何变化,当所有块都分配完或是释放掉,它都是头结点。其实它的作是就是为了维护内存分配信息节点双链表的。
-----分配
GUI_HMEM GUI_ALLOC_Alloc(int size);
这个函数,它实际是调用_Alloc进行内存分配的,这个函数主要完成以下所做的。
[1]、调用Size2LegalSize将要分配内存大小调整至最小粒度对齐。
[2]、寻找可用于记录此块分配信息的节点,在FindFreeHandle中完成。
[3]、寻找在整个内存分配空间中从低至高可满足此次分配的区域,在FindHole中完成,有几种情况,将在FindHole说明。
[4]、如果剩余字节不够分配,当预定义碎片整理时,调用CreateHole进行整理,CreateHole反回分配信息节点。
[5]、将分配所取得的内存分配信息节点加入双链表、初始化此次分配内存为0值、更新GUI_ALLOC这个整体分配结构体信息。
                                                                           [Size2LegalSize]
                                                                           size = (size + ((1<<GUI_BLOCK_ALIGN)-1)) & ~((1<<GUI_BLOCK_ALIGN)-1);
此函数主要是将要分配的大小调整为最小粒度对齐, 这种对齐所用的方法很普遍,即将要调整的值加上一个值产生一个最小粒度的进位,再将余位通过与的法清除。所加之值即为最小粒度减一,所与之值即为最小料度减一求反。
[FindFreeHandle]
这个函数很简单,即从内存分配信息节点数组aBlock中找出未用的节点(即.sise为0),注意是从节点1开始找,节点0已经使用了。如果找不到返回GUI_HMEM_NULL(0), GUI_ALLOC_Alloc检测到此值时即返回已分配内存句柄为0。GUI_ALLOC_H2P中转换此内存句句柄时,如检测到内存句柄为0,则会返回此内存句柄真内对应内存地址值为0。
[FindHole]
遍历双链表中的所有已分配节点,以找到此次要分配的内存的区域,有两种情况:
[1]、所找到的区域在已分配节之间;这种情况在最开始分配内存之时不会发生,发生这种情况是指在释放过内存之后再分配新的内存之时,查找时其实是根据后一分配结点偏移减去前一分配结点偏移加上大小之值,即 aBlock[iNext].Off- (aBlock.Off+aBlock.Size), 看后一结点与前一点之间有无间隙,且此间隙是否满足此次分配。这种间隙其实就是由释放内存块后引起的。
[2]、所找到的区域在所有已分配节点之后,在已分配节点之间找不到合适的区域,那么就只能从剩余的空间中取,取时要判断剩余空间是否足够,不够才返回-1。
FindHole找到可满足分配的区域时,其返回值是可分配区域的最邻近区域的内存句柄。即分配节点的在节点数中的位置索引。
[CreateHole]
FindHole中如果找不到合适的区域可满足分配的话,返回-1,此时我们遇到的情况是,在全部内存中没有一整块如此大的内存能满足此次分配,无法满足分配的原因可能是由于过多的小的内存分配释放后形成了碎片,这些碎版夹杂在整个内存之间,所以可采取的解决办法就是将这些碎片合在一处,形成一块大的内存,在整理碎版,CreateHole要完成如下两件事:
[1]、首先要从已分配节点中找出间隙,找出间隙的方法就是 space = aBlock[iNext].Off- (aBlock.Off+aBlock.Size), 当space小于要分配的内存大小, 经将是整理的对象。
[2]、整理的方法,要整理出碎片空间,要保证已分配内存的数据和正常访问,所以在将有间隙的两个节点的后一节点数据前移,并调整后一节点的偏移,这是注意点的地方。
[3]、最后此次内存是在所有已分配节点之后的,当 GUI_ALLOC_SIZE - (aBlock.Off+aBlock.Size) >= Size 这个条件满足,即调整碎片后所得的剩余空间满足此次分配,那么就返回i值,i值即为双链表中最后一结点;如果调整碎片后还是无法满足些次分配,那上面那个条件不成立,那么还是返回-1,即此次分配失败。
总结:关于碎片整理,是比较花时间的,这个时间也每次可以都不确定。
-----释放
释放与分配比起来,所做的工作少多了。
[GUI_ALLOC_Free]与GUI_ALLOC_FreePtr,两者完成同样功能,参数不同而已。
[1]、根据参数中指定中的内存句柄,将些内存句柄指对应分配节点size清零,对应内存清为0xcc,并将节点从双链表中清除。
[2]、更新GUI_ALLOC中记录的整体内存使用情况信息。
-----整体内存使用情况获取
这一组函数比较简单,只作简短说明,它的信息基本上从GUI_ALLOC这个结构中取得。
GUI_GetUsedMem---------------获取已用内存字节数NumUsedBytes。
GUI_ALLOC_GetNumFreeBytes----获取剩余内存字节数NumFreeBytes。
GUI_ALLOC_GetMaxSize---------遍历所有已分配节点找出分配节点之间最大剩余一个区域的字节数,并与最后一节点后剩余的内存比较,找出最大的剩余一块内存字节数。

                                                                                                                           
                                                                                                                           
                                                                                                                           
                                                                                                                           
                                                                                                                           
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
 楼主| 发表于 2005-7-19 10:30:41 | 显示全部楼层

ucos的GUI图形系统ucgui的模似器源码设计文档及各种资料

[2005/07/14]
在上一文中已经提供了一种自定义小型字库的说明, 现在再提出另外一种建立小型字库的方法, 这两种差别不大, 但构造方法还是有区别的, 希望大家可以对自定义字库(其实也就是自定义自己的字体)有本质的认识, 很多朋友对于UCGUI当中字体文件的.C文件是如何产生的不清楚原理, 也有许多朋友不不清楚自定义汉字字体的原理与方法, 这里我就如何自定义小型汉字库做了很清楚的说明, 须要的朋友可以看看....
从HZK16中取出所须的汉字, 组成新的字库文件, 再变成UCGUI中所须的源文件. 这样讲是非常抽象的, 首先, 你取出的汉字, 其机内码不是连续的, 那么这样一来, 如果还是使用汉字的标准机内码的话, 则必须为每一个要用到的汉字都建立一个GUI_FONT_PROP结构, 一个汉字一个, 在将他们链接成链表, 这样做唯一好处是可以直接以如下方法:
const unsigned char helloworld[]="世界你好";
GUI_DispString((const char*)helloworld);
依*编译器来将"世界你好"转变成汉字机内码的字符串. 但我们亦可手工构造要显示的汉字如下:
const unsigned char helloworld[]={0xca, 0xc0, 0xbd, 0xe7, 0xc4, 0xe3, 0xba, 0xc3, 0x00};
GUI_DispString((const char*)helloworld);

如上两种比较, 结果是一样的,唯一不同的是, 第一种方法显示汉字时, 无须使用知道汉字的机内码, 而是由编译器来转换的, 但我要说的是, 其实两者本质是一样的, 只是对使用者来说有表面上的不同.比较以上的两种显示汉字时构造字符串的方法, 我们可以得到一个启示, 对于第二种, 我们可以采取自定义汉字机内码, 然后直接通过自定义机内码来显示汉字, 这一点我已经在"建立自定义小型汉字库说明"一文当中说明了, 现在我想说的是对于采用第一种方法显示汉字的建立自定义小型字库的方法:

使用如下:
void MainTask(void)
{
char helloworld[] = "世界你好";
GUI_Init();
GUI_DispString((const char*)helloworld);
while(1){
  GUI_Delay(800);
}
}
构造SmallHZK12_2.c如下:
/***********************************************************************
  REVISION LOG ENTRY
  Revision By: ucgui
  Revised on  2005-6-17 19:09:52
  QQ:106719880
  Email:ucgui@163.com
  Home:http://www.ucgui.com
  Comments: SmallHZK12_2.c
  这时是第二种自定义小型汉字库的方法,在这种方法下可以采用
  char hellworld[]="世界你好";这种方法来方便的显示汉字, 比
  第一种方法容易使用, 不过缺点是每一个字对应一个GUI_FONT_PROP结构
  体, 明显占用了多的内存, 减慢查找显示的速度.
***********************************************************************/
#include "GUI.H"
#ifndef GUI_FLASH
   #define GUI_FLASH
#endif
extern GUI_FLASH const GUI_FONT GUI_FontHZ12;
//世
GUI_FLASH  const unsigned char acFontHZ12_cac0[24] = {
0x04,0x40, 0x24,0x40, 0x24,0x40, 0x24,0x40, 0xff,0xf0, 0x24,0x40, 0x24,0x40, 0x24,0x40,
0x27,0xc0, 0x24,0x40, 0x20,0x00, 0x3f,0xf0
};
//界
GUI_FLASH  const unsigned char acFontHZ12_bde7[24] = {
0x3f,0xc0, 0x24,0x40, 0x3f,0xc0, 0x24,0x40, 0x3f,0xc0, 0x04,0x00, 0x0b,0x00, 0x38,0xf0,
0xc9,0x20, 0x09,0x00, 0x11,0x00, 0x61,0x00
};
//你
GUI_FLASH  const unsigned char acFontHZ12_c4e3[24] = {
0x12,0x00, 0x12,0x00, 0x27,0xf0, 0x24,0x20, 0x69,0x40, 0xa1,0x00, 0x25,0x40,
0x25,0x20, 0x29,0x10, 0x31,0x10, 0x25,0x00, 0x22,0x00
};
//好
GUI_FLASH  const unsigned char acFontHZ12_bac3[24] = {
0x20,0x00, 0x27,0xe0, 0x20,0x40, 0xf8,0x80, 0x48,0x80, 0x48,0xa0, 0x57,0xf0, 0x50,0x80,
0x30,0x80, 0x28,0x80, 0x4a,0x80, 0x81,0x00
};
GUI_FLASH   const GUI_CHARINFO GUI_FontHZ12_CharInfo[4] = {
    {  12,  12,  2, (void GUI_FLASH *)&acFontHZ12_cac0 },
    {  12,  12,  2, (void GUI_FLASH *)&acFontHZ12_bde7 },
    {  12,  12,  2, (void GUI_FLASH *)&acFontHZ12_c4e3 },
    {  12,  12,  2, (void GUI_FLASH *)&acFontHZ12_bac3 }
};
GUI_FLASH  const GUI_FONT_PROP GUI_FontHZ12_Propa4= {
      0xbac3,
      0xbac4,
      &GUI_FontHZ12_CharInfo[3],
      0
};
GUI_FLASH  const GUI_FONT_PROP GUI_FontHZ12_Propa3= {
      0xc4e3,
      0xc4e4,
      &GUI_FontHZ12_CharInfo[2],
      (void *)&GUI_FontHZ12_Propa4
};
GUI_FLASH  const GUI_FONT_PROP GUI_FontHZ12_Propa2= {
      0xbde7,
      0xbde8,
      &GUI_FontHZ12_CharInfo[1],
      (void *)&GUI_FontHZ12_Propa3
};
GUI_FLASH  const GUI_FONT_PROP GUI_FontHZ12_Propa1= {
      0xcac0,
      0xcac1,
      &GUI_FontHZ12_CharInfo[0],
      (void *)&GUI_FontHZ12_Propa2
};

GUI_FLASH const GUI_FONT GUI_FontHZ12 = {
      GUI_FONTTYPE_PROP_SJIS,
      12,
      12,
      1,  
      1,  
      (void GUI_FLASH *)&GUI_FontHZ12_Propa1
};
SmallHZK12_2.C自定义小字库及说明下载:
http://www.ucgui.com/ucgui/SmallHZK12_2.rar
 楼主| 发表于 2005-7-19 10:31:36 | 显示全部楼层

ucos的GUI图形系统ucgui的模似器源码设计文档及各种资料

建立自定义小汉字库的说明
作者: ucgui
日期: 2005-06-25
来源: http://www.ucgui.com
文档版本: v1.0.0.0
要高效的利用汉字, 最好的办法是建立自己的小汉字库,要做到这一点, 就必须理解汉字库的构成原理及汉字的显示原理.否则常规的汉字库太大了, 网上流传的HZK12.C文件, 是一个完全的汉字库源码文件,是按照UCGUI中处理文字显示的基本原理来显示汉字的----即以点阵位图来显示文字. 但是这个文件很大, 不可能直接在嵌入式当中使用的..
我认为一种可行的比较灵活的办法是, 将可能用到的汉字集中在一块编一个序号,比如说"世界你好", 则编号分别为0xa1a1,0xa1a2,0xa1a3,0xa1a4, 请注意这些编码与机内码没有任何关联, 是我们自定义的文字机内码.
世-------->0xa1a1
界-------->0xa1a2
你-------->0xa1a3
好-------->0xa1a4
构建SmallHZK12.c如下:显示"世界你好".

调用时如下:
void MainTask(void)
{
// ToD  Make sure hardware is initilized first!!
//自定义小字库时用.
const unsigned char  helloworld[]={0xa1, 0xa1, 0xa1, 0xa2, 0xa1, 0xa3, 0xa1, 0xa4, 0x00};
//标准字库时用
//const unsigned char helloworld[]={0xca, 0xc0, 0xbd, 0xe7, 0xc4, 0xe3, 0xba, 0xc3, 0x00};
GUI_Init();
GUI_DispString((const char*)helloworld);
while(1){
  //GUI_DispStringHCenterAt("TEST汉字支持 ", 50, GUI_GetDispPosY());
  GUI_Delay(800);
}
}
/***********************************************************************
  REVISION LOG ENTRY
  Revision By: ucgui
  Revised on  2005-6-17 19:09:52
  QQ:106719880
  Email:ucgui@163.com
  Home:http://www.ucgui.com
  Comments: SmallHZK12.c
***********************************************************************/
#include "GUI.H"
#ifndef GUI_FLASH
   #define GUI_FLASH
#endif
extern GUI_FLASH const GUI_FONT GUI_FontHZ12;
//世
GUI_FLASH  const unsigned char acFontHZ12_cac0[24] = {
0x04,0x40, 0x24,0x40, 0x24,0x40, 0x24,0x40, 0xff,0xf0, 0x24,0x40, 0x24,0x40, 0x24,0x40, 0x27,0xc0, 0x24,0x40, 0x20,0x00, 0x3f,0xf0
};
//界
GUI_FLASH  const unsigned char acFontHZ12_bde7[24] = {
0x3f,0xc0, 0x24,0x40, 0x3f,0xc0, 0x24,0x40, 0x3f,0xc0, 0x04,0x00, 0x0b,0x00, 0x38,0xf0, 0xc9,0x20, 0x09,0x00, 0x11,0x00, 0x61,0x00
};
//你
GUI_FLASH  const unsigned char acFontHZ12_c4e3[24] = {
0x12,0x00, 0x12,0x00, 0x27,0xf0, 0x24,0x20, 0x69,0x40, 0xa1,0x00, 0x25,0x40, 0x25,0x20, 0x29,0x10, 0x31,0x10, 0x25,0x00, 0x22,0x00
};
//好
GUI_FLASH  const unsigned char acFontHZ12_bac3[24] = {
0x20,0x00, 0x27,0xe0, 0x20,0x40, 0xf8,0x80, 0x48,0x80, 0x48,0xa0, 0x57,0xf0, 0x50,0x80, 0x30,0x80, 0x28,0x80, 0x4a,0x80, 0x81,0x00
};
GUI_FLASH   const GUI_CHARINFO GUI_FontHZ12_CharInfo[4] = {
    {  12,  12,  2, (void GUI_FLASH *)&acFontHZ12_cac0 },
    {  12,  12,  2, (void GUI_FLASH *)&acFontHZ12_bde7 },
    {  12,  12,  2, (void GUI_FLASH *)&acFontHZ12_c4e3 },
    {  12,  12,  2, (void GUI_FLASH *)&acFontHZ12_bac3 }
};

GUI_FLASH  const GUI_FONT_PROP GUI_FontHZ12_Propa2= {
      0xa1a1,
      0xa1fe,
      &GUI_FontHZ12_CharInfo[0],
      0
};
GUI_FLASH const GUI_FONT GUI_FontHZ12 = {
      GUI_FONTTYPE_PROP_SJIS,
      12,
      12,
      1,  
      1,  
      (void GUI_FLASH *)&GUI_FontHZ12_Propa2
};
不过目前有一个问题, 就是显示结果挤在一块了.只是显示半个字节.这个问题下一步解决..
呵呵.....先吃钣...
[2005/07/14]
昨天晚上已经解决了半个汉字的问题, 写了一篇详细些的文档, 不过由于拷错文件, 没持拷过来, 所以暂且只贴了代码, 没有大多的详细说明.
演示代码下载:
http://www.ucgui.com/ucgui/GUISim1005_SmallHZK12.rar
截图下载:


 楼主| 发表于 2005-8-2 14:27:17 | 显示全部楼层

ucos的GUI图形系统ucgui的模似器源码设计文档及各种资料

[这个贴子最后由ucgui在 2005/08/02 02:35pm 第 2 次编辑]

UCDOS这个中文DOS平台, 想必知道的人不少了....
在网上, 也流传了UCDOS下的转换后的HZK12.C这个专用于UCGUI的字体文件下载, 但是没有见到hzk16,hzk24等的
宋体/黑体/楷体的.C文件下载..
这里我制作了他们的. C文件, 并且提供转换代码下载:
.c字体文件特点:
1.对于字体文件有注解, 可以让大家更清晰的认清UCGUI下所用的字体文件的结构.
2.只取汉字当中有汉字的区, 从0xb0a1~0xb0fe, 0xb1a1~0xb1fe, 0xb2a1~0xb2fe....0xf7a1~0xf7fe, 不包含其
它无用的区域, 在hzk12.c当中包含了0xa1a1~0xa1ff等一些无用区域.
[1]. 转换好的字库文件下载:
hzk24h.c--------24号黑体字
hzk24s.c--------24号宋体字
hzk24k.c--------24号楷体字
hzk16s.c--------16号宋体字
hzk16fs.c-------16号仿宋字
hzk12s.c--------12号宋体字

[包含24号黑体/宋体/楷体, 16号宋体/仿宋体, 12号宋体]
http://www.ucgui.com/ucgui/font/font_hzk.rar

[2]. 转换后的字体文件使用示例下载:
http://www.ucgui.com/ucgui/GUISim1004_HZDisplay.rar

[3]. 转换字库文件工具代码下载:
http://www.ucgui.com/ucgui/GUISim1004_HZK24.rar


35_383_15.jpg
发表于 2005-8-2 16:28:55 | 显示全部楼层

ucos的GUI图形系统ucgui的模似器源码设计文档及各种资料

支持你
 楼主| 发表于 2005-8-31 17:45:39 | 显示全部楼层

ucos的GUI图形系统ucgui的模似器源码设计文档及各种资料

uC-GUI-FontConvert-Demo.exe字体转换工具修改版, 可正确导出字体.c文件[2005/08/30更新]
[更新记录] 2005/08/30
UCGUIFontTool.rar
http://www.ucgui.com/ucgui/UCGUIFontTool.rar
  1. 完成字体生成工具动态库, 调用接口为ShowGenFontHZDlg(), 弹出字体生成界面,
     导出汉字GBK的汉字字体.c文件. 专门用于UCGUI.
  2. 可以自定义导出汉字.C文件, 输入自己要使用的汉字及ASCII字符.
  3. 跳过0xaaa1~0xafff这几分区, 因为这几个分区没对有对应任何的汉字.
[更新记录] 2005/08/22
16:36 2005-8-21
关于转换字体中的2bpp及4bpp.
1.创建字体时, 可以有三种选择:
[1]标准字体,无光滑处理.
[2]二位光滑处理, 即字符点阵中每个象素占2位.
[3]四位光滑处理, 即字符点阵中每个象素占4位.
在有光滑处理的候, 由于点阵中一个象素可以有多位来表示, 也即表明每个象素点可以有多种颜色. 在2bpp的情况下, 每个象素点可以有4种颜色. 4bpp中, 每个象素点可以16种颜色; 在进行光滑处理时, 是为了防止锯齿, 所以为了平滑过渡, 在产生锯齿的地方填充上一些相近颜色, 即可以合锯齿不太明显. 所以2bpp的可以认为是4级灰度. 4bpp可以认为是16级灰度光滑处理.
当选择了光滑处理后, 可以在对每个字符的点阵进行填色, 人为的进行光滑处理, 但如果在选择光滑处理时选择Internal的话, 转换工具自制为对字符进行光滑处理. 导出即可使用.

[更新记录] 2005/08/15
字体转换工具放下很久了, 一直没弄了, 这个周未弄了一下, 唉, 有时候有些东西要做完还是须要坚持, 开始把最难的弄出来了, 后面就没想再继续做简单的修改了...拖到现在....
2:17 2005-08-14
1.修改导出每个字符宽度所占用字节数. 即指GUI_CHARINFO结构中的BytesPerLine.以后将陆续加入如下功能:
1.可以直接转换出UNICODE的. 也可以选择GBK码.
2.可以转换指定范围, 这个官方已经支持的, 很方便于自定义字库.
3.可以根据输入的指定字符来产生.C文件,方便自定义字库.
4.更文件的支持UNICODE的显示处理.
2:35 2005-7-22 v1.0.0.0
1.发布1.0.0.0, 正确导出点阵, 但导出字符宽度所占字节数老是1, 不能正确使用.C文件.
一. 字体转换工作修改版下载:
http://www.ucgui.com/ucgui/ucGUI-FontConvert-Modiy.rar
二. 网友的ucgui_font.exe字体.C生成工具, 04年10月份就有了, 可惜现在才露面...
http://www.ucgui.com/ucgui/ucgui_font.rar
三. 我自己生成的UCGUI 汉字GBK码到UNICODE转换码表
http://www.ucgui.com/ucgui/GBKConvertTableUnicode.rar
四. 以下是修改版的字体转换工具导出的.C文件的示例:
英文字体elephant 72号字体导出.c文件
http://www.ucgui.com/ucgui/Elephant123.rar
英文字体elephant 72号字体显示示例
http://www.ucgui.com/ucgui/GUISim1005_BigFontView.rar
1..C文件中每个字符的BytesPerLine, 即每行字数是不对的, 默认是1,要正确使用请自行修改.
2.目前导出的.C文件的每个字符的点阵当中的BytesPerLine亦不对, 正确的暂不公开, 稍后修改完整即行公开,有什么问题请与我联系:ucgui@163.com
三. 字体转换工具调试日志:

2:50 2005-7-24
这个周未来了一个朋友, 本来可以早点弄完的, 不过弄完了没在公司, 不能上网, 还是一样的.开始, 我的hiew用不了, 是demo版的, 不能存修改后的文件, 没有办法, 又不能上网下载, 所以就只能用OLLDBG来一句一句的写代码, 然后直接将汇编的二进制数据加到PE文件代码空间未用的部分.比较麻烦, 但代码不多, 还好, 写错一句就要重头来, 惨!
2:35 2005-7-22
今天心情有点郁闷, 在olldbg中调试追踪及IDA中静态调试时精神不是特别集中...
今天的事太郁闷了...还是不能做到心无旁物, 居然有这样的大学四年的同学, 不知是我的
悲哀, 还是他的悲哀, 这事太郁闷了....

关于从TRUETYPE字体中如何提取一个字符的点阵数据?
以下步骤中, 如果选中字体是UNICODE时, 所有函数尾部加(GetPixel除外).
1. 首先通过这个字符的的代码来获取这个字符的宽高(单位为象素), 用GetTextExtentPoint函数.
BOOL GetTextExtentPoint(
  HDC hdc,           // handle to device context
  LPCTSTR lpString,  // pointer to text string
  int cbString,      // number of characters in string
  LPSIZE lpSize      // pointer to structure for string size
);
2. 再将这个字符通过TextOut函数输出到指定的HDC当中(此HDC可以任一指定窗体的), 位置是(0,0).
BOOL TextOut(
  HDC hdc,           // handle to device context
  int nXStart,       // x-coordinate of starting position
  int nYStart,       // y-coordinate of starting position
  LPCTSTR lpString,  // pointer to string
  int cbString       // number of characters in string
);
3. 再通过GetPixel来从点(0,0)开始, 一行一行的获取指定字符的点阵信息.
COLORREF GetPixel(
  HDC hdc,   // handle to device context
  int XPos,  // x-coordinate of pixel
  int nYPos  // y-coordinate of pixel
);
以上就是基本的原理, 非常的简单, 也是大多数软件采取的从TRUETYPE字体中提取字符点阵信息的原理.  剩下的破解工作其实就是查看输出的那个函数. 代码中已经有现成的读取指定字符真正点阵的函数. 直接调用就可以了...所做的工作其实不多了....

1:00 2005-7-21
1.获取到取一个字符点阵的原理, 是显示字符后对过GetPixel来取该字符的点阵信息.对于任何的字体, 无论是点阵字体还是TRUETYPE字体, 显示之后都是点阵的集合, 所以这是提取TYUETYPE字体对应点阵字体的基本原理.
2.但问题是显示该字符时, 显示在什么位置, 是否显示一个字符取一个字符点阵??
3.进一步了解到生成字体时, 选择标准字体与光滑处理的字体时的区别.

2:25 2005-7-20
1.明白字符显示出来来用的SetPixel, 但不明白是为何不用TEXTOUT来显示字符呢? 显示字符前必须先取到字符的点阵信息.
2.初步理解程序的数据结构, 但还是有好多细节不明.
1:40 2005-7-19
1.理解到点阵信息的获取是通过TRUETYPE字体来提取的, 并非先前所想的提取WIN下面的点阵字体的数据.
2.初步理解到取点阵信息时并不是直接从TYUETYPE字体文件中取, 而是通过显示后提取象素.

1:50 2005-7-18
1.选中一个字符时, 在下面的方格中显示, 是通过先画矩形, 再填充块来实现的, 根据点菜来画块.
2.通过上面的大框选中字符, 下面的框显示字符的点阵, 是在下面框的ondraw当中实现的.

0:45 2005-7-17
静态反汇编工具: IDA4.5版, 静态查看方便, 跳转到指地址方便, 亦可进行动态调试, IAD还有一个很方便的功能是可以在其当中为函数写注解, 这一点非常的重要, 调试时很多的函数都是要反复读的, 由于没有函数, 很难记起, 所以要上注解会很方便阅读代码(注间注解的时候先输入";"符号才弹出填写注解的框), 当然也可以自己为已经清楚功能的函数重命名, 把sub_XXX这一类名改成可读性强的.
  动态调试工具: olldbg1.09版, 这个工具动态调试时, 有两个很有用的优点.
  1.可以设定读写指定内存时断点.这一点可以用于很方便追踪到读写某一块内存的代码.
  2.可以在调用指定WIN的API时在汇编中标明参数, 并且在堆栈中标明. 还有就是当前存写地址的数据也会自动标出.

1.发现是被压缩过的, 用的是UPX工具, 版本1.93的即可成功解压.
2.初步发觉是MFC写的程序, 其中大量的MFC类, 是静态连接MFC库的.
3.MFC写的程序, 由于比SDK多了一层类的封装, 很多的虚函数调用都没有名字可看, 对于调试难度非常大.
4.大至决定调试程序时, 只能根据特征字符串来追踪, 这也是大多数破解才有的办法, 在程序中有字符串等信息, 可以大至了解到字符串所在函数的功能. 比如说, 如果要找输出.C字体文件的东东, 由于有一堆的特征串, 很容易找到.一般来说很少程序将字符串加密的.
5.初步决定首先要调试理解程序, 必顺先理解TRUETYPE字体转换的原理.
发表于 2005-9-6 09:31:05 | 显示全部楼层

ucos的GUI图形系统ucgui的模似器源码设计文档及各种资料

ddddddddd
 楼主| 发表于 2005-9-26 09:51:21 | 显示全部楼层

ucos的GUI图形系统ucgui的模似器源码设计文档及各种资料

近日整理修改了几篇以前写的有关UCGUI的技术文章,提供给大家下载.
希望大家可以对文章中表达不准备及不到位有错误的地方指出来, 以在下一版中修正.

一."UCGUI的模拟器UCGUISim的设计文档", 6100余字,主要介绍了UCGUI的模拟器的构成及使用,并着重讲角了模拟器的实现原理,理解模拟器的原理有助加深对UCGUI的理解;了解UCGUI在图形驱动模块的层次结构,对于UCGUI的移植会有更加清晰的认识。

二."UCGUI的动态内存分配的原理深入分析", 5500余字,了解UCGUI的朋友,一定了解UCGUI中的窗口体系,窗口是动态创建的,其中当然要用到动态的内存申请,现在我们就这个话题在UCGUI3.24版源码基础上进行深入分析,了解UCGUI中的动态内存分配功能,这是了解其窗口系统及内存设备场景的基础,这一点非常的重要。文中并深入讲解了此动态内存是从何处得来的问题,描述了程序代码段及数据段以及程序连接加载运行的一些相关知识。

三."UCGUI窗体管理及消息处理机制分析",12500余字,本文主要介绍了UCGUI中的对话框的消息处理机制,并指出在现有UCGUI上如何增加多窗体支持,并在分析解决问题时着重介绍了其输入设备消息WM_TOUTCH及WM_KEY两类消息处理方法,并同时初步指出一种在UCGUI中实现模态对话框以及透明窗体的原理说明,不还有窗体重画消息WM_PAINT消息处理原理。

四."UCGUI简介", 3000余字,从整体上介绍了UCGUI的基本功能/设计思想/架构/设计目标.让学习UCGUI的人可以从大体上把握UCGUI的一些基本概念, 了解其模块化的设计架构及模块的划分.

五."UCGUI的性能与资源占用", 1000余字,从资源占用的角度分析了UCGUI各个模块的资源占用特性,UCGUI各个模块均是可配制的,了解它对于安排硬件及软件功能支持均非常大的意义.
六.有关嵌入式图形系统学习的大体导向
文档下载:
http://www.ucgui.com/ucgui/ucgui_pdf.rar
您需要登录后才可以回帖 登录 | 注册

本版积分规则

关闭

站长推荐 上一条 /2 下一条

小黑屋| 关于我们| 联系我们| 在线咨询| 隐私声明| EETOP 创芯网
( 京ICP备:10050787号 京公网安备:11010502037710 )

GMT+8, 2024-4-27 03:02 , Processed in 0.053857 second(s), 7 queries , Gzip On, Redis On.

eetop公众号 创芯大讲堂 创芯人才网
快速回复 返回顶部 返回列表