X Windows Extension的编写和加载

 

Extension简介

 X Window的设计有一个优势,就是X Window在核心层之外提供一个扩展层,开发者可以开发相应扩展,来实现自己的扩展协议,比方说:标准的Window都是矩形的,如何用它来画一个圆形的窗口?X Window协议并未提供,但是通过“shape”这个扩展,X Window可以实现不规矩的窗所以,很多年来X Window除了继续完善核心协议、驱动以外,很大程度上,都是扩展使它保持“与时俱进”,比如说:

要多头显示支持,这个是由“Xinerama”扩展实现的;

要有多媒体视频回放的支持,这个是由“X Video”扩展实现的;

OpenGL的3D支持,则是通过“GL”扩展来实现的;

Keyboard的支持,都是通过“X Keyboard Extension”(也就是“XKB”)的!

X Window的核心,基本上就是在处理Server/Client、驱动之类的,而外部的那些支持,基本上全是通过“扩展”进行的。我们熟悉的Xrandr命令也是X Windows的一个扩展。

众所周知,X Windows采用了Server/Client,Extension是运行在Server端,Client端的程序通过X11协议与其通信。

X Window的核心服务是放在一个函数表中的,当客户端程序有请求到来时,X Window通过请求号从函数表中找到对应的服务函数,然后调用它,服务函数执行完成后返回结果。函数表其实也是一种抽象,它让框架不必知道实现的功能,扩展功能时,只要修改这个函数表,而不用修改框架

这个函数表在programs/Xserver/dix/table.c中,其声明如下:

int (* ProcVector[256]) (
        ClientPtr /* client */
    ) =
{
    ProcBadRequest,
    ProcCreateWindow,
    ProcChangeWindowAttributes,
    ProcGetWindowAttributes,
    ProcDestroyWindow,
    ProcDestroySubwindows,              /* 5 */
    ProcChangeSaveSet,

      ……………………

    ProcGetModifierMapping,
    0,                                  /* 120 */
    0,
    0,
    0,
    0,
    0,                                  /* 125 */
    0,
    ProcNoOperation
};

这个函数表的前0-127位是X Windows预留的,以后都是从128开始的。

还有一个函数表,叫作SwappedProcVector,它与ProcVector中的函数功能上基本是对应的,特殊之处在于,在调用真正的服务前,先预处理一下请求的数据,客户端程序可能运行在不同字节顺序的机器上,这时候要交换一下数据的字节顺序。我们可以看SProcCreateWindow的实现:

Int SProcCreateWindow(ClientPtr client)
{  
    char n;

    REQUEST(xCreateWindowReq);
    swaps(&stuff->length, n);
    REQUEST_AT_LEAST_SIZE(xCreateWindowReq);
    swapl(&stuff->wid, n);
    swapl(&stuff->parent, n);
    swaps(&stuff->x, n);
    swaps(&stuff->y, n);
    swaps(&stuff->width, n);
    swaps(&stuff->height, n);
    swaps(&stuff->borderWidth, n);
    swaps(&stuff->class, n);
    swapl(&stuff->visual, n);
    swapl(&stuff->mask, n);
    SwapRestL(stuff);
    return((* ProcVector[X_CreateWindow])(client));
}


有了这种机制,增加新的功能,只需要增一个处理函数就行了,处理函数并需要知道请求从何而来,何时被调用。相反,要裁剪某些功能,亦只需要从这里拿开这个函数就可以了。

如何编写Extension

XWindow中有大量扩展模块,每个扩展模块完成一组相关的功能,把扩展功能从核心功能中剥离出来,可以大大提高X Server的可配置性和扩展性。扩展模块的实现机制如下:每一个扩展模块都有一个初始化函数,这个函数在X Window起动时被调用,在这里面会初始化一些该模块的数据结构,然后会调用AddExtension把相关回调函数注册进去。

AddExtension的函数原型如下:

ExtensionEntry *
AddExtension(char *name, int NumEvents, int NumErrors,
             int (*MainProc)(ClientPtr c1),
             int (*SwappedMainProc)(ClientPtr c2),
             void (*CloseDownProc)(ExtensionEntry *e),
             unsigned short (*MinorOpcodeProc)(ClientPtr c3))

 

l       Name: 插件的名称。每个插件都有唯一的名称,Utility的名称为VIAGFX_API。

l       NumEvents: 为扩展保留的事件数。

l       NumErrors:为扩展保留的错误码数。

l       MainProc: 扩展的处理函数。

l       SwappedMainProc: 扩展的处理函数,在处理前先交换字节顺序。

l       CloseDownProc: 扩展的析构函数。

l       MinorOpcodeProc: 用来得到子处理号,一般没有什么用处,在出错时,设置到错误信息里。

从AddExtension的实现中,我们很容易看出,扩展其实也是前面所说的ProcVector来实现的。

   i = NumExtensions;
    newexts = (ExtensionEntry **) xrealloc(extensions,
                       (i + 1) * sizeof(ExtensionEntry *));
    if (!newexts)
    {
    xfree(ext->name);
    xfree(ext);
    return((ExtensionEntry *) NULL);
    }
    NumExtensions++;
    extensions = newexts;
    extensions[i] = ext;
    ext->index = i;
    ext->base = i + EXTENSION_BASE;
    ext->CloseDown = CloseDownProc;
    ext->MinorOpcode = MinorOpcodeProc;
    ProcVector[i + EXTENSION_BASE] = MainProc;
   SwappedProcVector[i + EXTENSION_BASE] = SwappedMainProc;

 

从理论上说,框架完全是独立于扩展的,增加增/删扩展不需要修改框架的代码。但实际情况往往不是这样的,有的扩展依赖框架提供一些特殊功能,有的扩展依赖另外一些扩展,所以在X Server的代码中,常常出现很多ifdef之类的宏,这些宏用来控制是否启用某些扩展。

Client端的编写

Extension是在Server层运行的,而对这些Extension的使用一般是在client端,client的请求处理过程如下。

1.         client调用Xlib或者扩展模块的客户端库函数,这些库函数把服务请求数据按X Protocol打包,然后通过XTransport层发送到 X Server端。

2.         X Server收到数据包后,根据请求号调用对应的服务函数。ProcVector是服务函数表,扩展也是通过这个表来扩展功能的。

3.         服务函数处理该请求,并调用WriteToClient把结果发送给客户端。

 其过程表示如下图

Client端和server端是通过X 协议来通信的,现在使用的是这个协议的第十一个版本,称为X11。X11的协议格式分为两种,一种是请求格式(request format),一种是响应格式(reply format)

X11协议对其通信的格式有其严格的要求,要求client和server端得request和reply的结构体必须一样,否则数据在传输的过程中会出错。

1.       请求格式(Request Format)

每一个请求由请求头和请求体组成。每个请求最小是4个字节。

在每一个请求的最前面,有一个4字节的请求头,其中第一个字节是8位的主操作码(major opcode),接下来的两字节是16位的请求长度(这个长度值也包括头在内),最后的一个字节在本协议里暂时不用。

跟在头后面的是请求体,格式是不固定的,依请求种类而不同。如果实际的请求体内容长度小于头里说明的长度,不必用0来补位。

++++++++ | ++++++++ ++++++++ | ++++++++ | ++++++++++++++++++++++++

m-opcode | length of request |  blank   |  request content ...

++++++++ | ++++++++ ++++++++ | ++++++++ | ++++++++++++++++++++++++

(每个"+"代表一位)

在本核心协议中,主操作码在0~127之间。128~255保留做扩展用。很多情况下,扩展的请求里可能包含多个操作码,这时请求的结构会有一些改变。比如,一种典型的用法是,在头的第二个字节里存放一个副操作码(minor opcode),把长度值移到后面两个字节去。

下面是utility定义的请求格式

typedef struct {

    CARD8   reqType;//主操作码

    CARD8   VIAGFXReqType;// 副操作码

    CARD16  length B16;

    CARD32  pad1 B32;

    CARD32  primary_id;//以下为utility扩展需求

    CARD32  secondary_id;

    CARD32  command;

    CARD32  parm[VIAGFX_PARM_NUM];

    CARD32  result_header;

    CARD32  result[VIAGFX_PARM_NUM];   

} xVIAGFXCommandReq;

 

2.         响应格式(Reply Format)

每一个响应由三部分组成:响应头,响应体以及扩展部分,每个响应最小是32个字节

响应头由4字节的组成,并没有特殊的标识,这4个字节中的内容是整个响应的长度。
接下来是32个字节的响应体。
如果响应的内容比较长,超过了32字节,可继续在响应体后接续,这是响应体的扩展部分,长度向字节对齐。
在响应体和扩展部分中如果有空位的话,server并不保证把空位置0。
在响应的内容中还包含所对应请求的序列号。(但协议没有指明哪几个字节是这一内容。)

下面是utility定义的响应格式

typedef struct {

    BYTE    type;               /* X_Reply */ /* Required */

    BOOL    pad1;              

    CARD16  sequenceNumber B16; /* Required */

    CARD32  length B32;         /* Required */

    CARD32  primary_id;

    CARD32  secondary_id;

    CARD32  command;

    CARD32  parm[VIAGFX_PARM_NUM];

    CARD32  result_header;

    CARD32  result[VIAGFX_PARM_NUM];   

} xVIAGFXCommandReply;

在client端使用的时候,需要先填写请求结构体,然后使用_XReply函数发送给Server端。在Server端,Extension会根据request填写reply结构体,client端接收到reply结构体以后,就可以取出数据来使用了,下面为utlity中代码

    BOOL VIAGFXCommand(Display *dpy, viagfxcommand *viacmd)

{       

    XExtDisplayInfo *info = find_display (dpy);

    xVIAGFXCommandReply rep;

    xVIAGFXCommandReq *req;

    int i;   

    VIAGFXCheckExtension(dpy, info, False); //检查是否有所需的Extension,如果没有,直接返回

    LockDisplay(dpy);

    GetReq(VIAGFXCommand, req);//填写request结构体的一些变量

    req->reqType = info->codes->major_opcode;//赋值主操作码

    req->VIAGFXReqType = X_VIAGFXCommand;//副操作码

    req->primary_id = viacmd->primary_id;

    req->secondary_id = viacmd->secondary_id;

    req->command = viacmd->command;   

 

    // Send Request

    for (i=0; i

    {

        req->parm[i] = viacmd->parm[i];

    }

   

    if(!_XReply(dpy, (xReply *)&rep, (SIZEOF(xVIAGFXCommandReply) - SIZEOF(xReply)) >> 2, xFalse))//通过_XReply函数来传递request

    {

        UnlockDisplay(dpy);

        SyncHandle();

   

        return False;

    }    //如果返回true,则说明client获得其请求的数据。

 

    // Get Reply

    viacmd->result_header = rep.result_header;

    if (rep.result_header)

    {

        for (i=0; i

        {       

            viacmd->result[i] = rep.result[i];

        }       

    }

 

    UnlockDisplay(dpy);

    SyncHandle();

 

    /* Don't forget to flush! */

    XFlush(dpy);

   

    return rep.result_header;

}

 

这种方法的益处是模块化,修改起来比较方便。不好的地方是如果ExtensionInit需要使用ScrnInfoPtr指针,而xxxProbe函数并没有提供得到ScrnInfoPtr指针的方法,就需要另外想办法给其传递ScrnInfoPtr指针。

你可能感兴趣的:(linux)