X Window研究笔记(11)

X Window研究笔记(11)

转载时请注明出处和作者联系方式
作者联系方式:李先静 <xianjimli at hotmail dot com>

11.X Window扩展机制--对象装饰

Decorator模式是一个非常重要的模式,它在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。X Server是用C开发的,不方便使用正统的装饰模式,但大量使用了类似装饰模式的扩展方式。可以被装饰的对象有:

  1. 屏幕:  ScreenRec
  2. 窗口:WindowRec
  3. 图片:  PixmapRec
  4. 绘图上下文: GC
  5. 颜色映射: Colormap
  6. 客户端:ClientRec

其实现过程可以这样理解:
  1. 初始化时,把原始对象的部分函数指针保存下来,并用自己的函数去替代它。
  2. 调用时,装饰之后的函数被调用,在该函数中,完成一些装饰性功能,并适当的位置调用原始的函数。

其中,用得最多的是对ScreenRec的装饰,下面我们以sprite模块中对ScreenRec的装饰为例,分析一下它的实现原理:

sprite指像鼠标指针一类的屏幕精灵,它的特点是,形状可能不规则,可以在屏幕上移动,会覆盖当前位置的图像,当它移动到新的位置时,自动恢复先前位置的图像。

sprite的初始化是在miSpriteInitialize中完成的:

Bool
miSpriteInitialize (pScreen, cursorFuncs, screenFuncs)
    ScreenPtr               pScreen;
    miSpriteCursorFuncPtr   cursorFuncs;
    miPointerScreenFuncPtr  screenFuncs;
{
    miSpriteScreenPtr   pPriv;
    VisualPtr           pVisual;
#ifdef RENDER
    PictureScreenPtr    ps 
= GetPictureScreenIfSet(pScreen);
#endif
   
    
if (miSpriteGeneration != serverGeneration)
    
{
        miSpriteScreenIndex 
= AllocateScreenPrivateIndex ();
        
if (miSpriteScreenIndex < 0)
            
return FALSE;
        miSpriteGeneration 
= serverGeneration;
        miSpriteGCIndex 
= AllocateGCPrivateIndex ();
    }

    
if (!AllocateGCPrivate(pScreen, miSpriteGCIndex, sizeof(miSpriteGCRec)))
        
return FALSE;
    pPriv 
= (miSpriteScreenPtr) xalloc (sizeof (miSpriteScreenRec));
    
if (!pPriv)
        
return FALSE;
    
if (!miPointerInitialize (pScreen, &miSpritePointerFuncs, screenFuncs,TRUE))
    
{
        xfree ((pointer) pPriv);
        
return FALSE;
}

for (pVisual = pScreen->visuals;
         pVisual
->vid != pScreen->rootVisual;
         pVisual
++)
        ;
    pPriv
->pVisual = pVisual;
    pPriv
->CloseScreen = pScreen->CloseScreen;
    pPriv
->GetImage = pScreen->GetImage;
    pPriv
->GetSpans = pScreen->GetSpans;
    pPriv
->SourceValidate = pScreen->SourceValidate;
    pPriv
->CreateGC = pScreen->CreateGC;
    pPriv
->BlockHandler = pScreen->BlockHandler;
    pPriv
->InstallColormap = pScreen->InstallColormap;
    pPriv
->StoreColors = pScreen->StoreColors;

    pPriv
->PaintWindowBackground = pScreen->PaintWindowBackground;
    pPriv
->PaintWindowBorder = pScreen->PaintWindowBorder;
    pPriv
->CopyWindow = pScreen->CopyWindow;
    pPriv
->ClearToBackground = pScreen->ClearToBackground;

    pPriv
->SaveDoomedAreas = pScreen->SaveDoomedAreas;
    pPriv
->RestoreAreas = pScreen->RestoreAreas;
#ifdef RENDER
    
if (ps)
    
{
        pPriv
->Composite = ps->Composite;
        pPriv
->Glyphs = ps->Glyphs;
    }

#endif

    pPriv
->pCursor = NULL;
    pPriv
->= 0;
    pPriv
->= 0;
    pPriv
->isUp = FALSE;
    pPriv
->shouldBeUp = FALSE;
    pPriv
->pCacheWin = NullWindow;
    pPriv
->isInCacheWin = FALSE;
    pPriv
->checkPixels = TRUE;
pPriv
->pInstalledMap = NULL;
    pPriv
->pColormap = NULL;
    pPriv
->funcs = cursorFuncs;
    pPriv
->colors[SOURCE_COLOR].red = 0;
    pPriv
->colors[SOURCE_COLOR].green = 0;
    pPriv
->colors[SOURCE_COLOR].blue = 0;
    pPriv
->colors[MASK_COLOR].red = 0;
    pPriv
->colors[MASK_COLOR].green = 0;
    pPriv
->colors[MASK_COLOR].blue = 0;
    pScreen
->devPrivates[miSpriteScreenIndex].ptr = (pointer) pPriv;
    pScreen
->CloseScreen = miSpriteCloseScreen;
    pScreen
->GetImage = miSpriteGetImage;
    pScreen
->GetSpans = miSpriteGetSpans;
    pScreen
->SourceValidate = miSpriteSourceValidate;
    pScreen
->CreateGC = miSpriteCreateGC;
    pScreen
->BlockHandler = miSpriteBlockHandler;
    pScreen
->InstallColormap = miSpriteInstallColormap;
    pScreen
->StoreColors = miSpriteStoreColors;

    pScreen
->PaintWindowBackground = miSpritePaintWindowBackground;
    pScreen
->PaintWindowBorder = miSpritePaintWindowBorder;
    pScreen
->CopyWindow = miSpriteCopyWindow;
    pScreen
->ClearToBackground = miSpriteClearToBackground;

    pScreen
->SaveDoomedAreas = miSpriteSaveDoomedAreas;
    pScreen
->RestoreAreas = miSpriteRestoreAreas;
#ifdef RENDER
    
if (ps)
    
{
        ps
->Composite = miSpriteComposite;
        ps
->Glyphs = miSpriteGlyphs;
    }

#endif

    
return TRUE;
}


这个函数有点长,但我们只需要理解关键几点:
  1. AllocateScreenPrivateIndex分配私有数据空间,用于保存原始函数指针等信息。
  2. pPriv->PaintWindowBackground = pScreen->PaintWindowBackground; 之类的语句用于保存原始的函数指针。
  3. pScreen->PaintWindowBackground = miSpritePaintWindowBackground; 之类的语句用于把原始的函数指针替换为装饰之后的函数。

这里要特别说明的是,所谓的原始函数指针,并非一定是原装正品,可能已经是被别的模块装饰之后的函数。

下面我们继续看函数调用的实现:

static void
miSpritePaintWindowBackground (pWin, pRegion, what)
    WindowPtr   pWin;
    RegionPtr   pRegion;
    
int         what;
{
    ScreenPtr       pScreen;
    miSpriteScreenPtr    pScreenPriv;

    pScreen 
= pWin->drawable.pScreen;

    SCREEN_PROLOGUE (pScreen, PaintWindowBackground);

    pScreenPriv 
= (miSpriteScreenPtr) pScreen->devPrivates[miSpriteScreenIndex].ptr;
    
if (pScreenPriv->isUp)
    
{
        
/*
         * If the cursor is on the same screen as the window, check the
         * region to paint for the cursor and remove it as necessary
         
*/

        
if (RECT_IN_REGION( pScreen, pRegion, &pScreenPriv->saved) != rgnOUT)
            miSpriteRemoveCursor (pScreen);
    }


    (
*pScreen->PaintWindowBackground) (pWin, pRegion, what);

    SCREEN_EPILOGUE (pScreen, PaintWindowBackground, miSpritePaintWindowBackground);
}


为了看明白这段程序,先得弄清楚两个宏:
SCREEN_PROLOGUE: 用于取出原始的函数指针,后面可以调用原始函数。
#define SCREEN_PROLOGUE(pScreen, field)/
  ((pScreen)->field = /
   ((miSpriteScreenPtr) (pScreen)->devPrivates[miSpriteScreenIndex].ptr)->field)
   
SCREEN_EPILOGUE:重新把装饰过的放回去,以便于下次再调。
#define SCREEN_EPILOGUE(pScreen, field, wrapper)/
    ((pScreen)->field = wrapper)

弄清楚了这两个宏,上面的程序不难理解了。这种扩展方式的好处在于,运行时动态为对象添加功能,同时又避免了扩展功能与框架的耦合,这是通过子类继承父类,然后重载部分虚函数无法实现的。

(待续)

 
原文地址:https://www.cnblogs.com/zhangyunlin/p/6167678.html