X Window研究笔记(11)
转载时请注明出处和作者联系方式 作者联系方式:李先静 <xianjimli at hotmail dot com>11.X Window扩展机制--对象装饰 Decorator模式是一个非常重要的模式,它在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。X Server是用C开发的,不方便使用正统的装饰模式,但大量使用了类似装饰模式的扩展方式。可以被装饰的对象有:
屏幕: ScreenRec
窗口:WindowRec
图片: PixmapRec
绘图上下文: GC
颜色映射: Colormap
客户端:ClientRec
其实现过程可以这样理解:
初始化时,把原始对象的部分函数指针保存下来,并用自己的函数去替代它。
调用时,装饰之后的函数被调用,在该函数中,完成一些装饰性功能,并适当的位置调用原始的函数。
其中,用得最多的是对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 -> x = 0 ; pPriv -> y = 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; }
这个函数有点长,但我们只需要理解关键几点:
AllocateScreenPrivateIndex分配私有数据空间,用于保存原始函数指针等信息。
pPriv->PaintWindowBackground = pScreen->PaintWindowBackground; 之类的语句用于保存原始的函数指针。
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) 弄清楚了这两个宏,上面的程序不难理解了。这种扩展方式的好处在于,运行时动态为对象添加功能,同时又避免了扩展功能与框架的耦合,这是通过子类继承父类,然后重载部分虚函数无法实现的。 (待续)