深入Brew编程之一接口函数替换技术及其应用

来源:互联网 发布:哈尔博熊牌啤酒 知乎 编辑:程序博客网 时间:2024/05/19 19:58

 天我在这里要讲的我这几天工作的一个总结,希望对大家有帮助。不过我这个人有个毛病,在开宗明义之前总喜欢发点满腹牢骚。请大家满足一下我这个小小癖好。
   Brew实际上是一个再简单不过的系统了,只不过搭上手机开发这个比较新鲜的事物,所以有那么一点神秘感。各位新人们,我要告诉你们,开发Brew比开发Win32程序要简单的多,容易得多(当然,要想通过那个有点变态的UBT测试是另外一回事),完全无需心虚害怕。如果你对VC恶熟,程序写得很溜,那么恭喜你,你只需要最多一周的时间,就可以把Brew玩得很溜了。大家切忌有那种学了Brew就是捡了金矿的想法。
   好了,牢骚发完,进入正题。我们发帖子的,要讲的东西当然就不能讲那些Program Guide、API Refrence上能看到的东西,怎么也得是类似于深入XX编程这一层次的东西才对。我今天要讲的主题,是Brew接口函数替换技术和一个实际的例子:怎么为 IHtmlViewer 控件加上背景图片。我的方法可能不是唯一的方法,更不是最好的方法,但是应该有一些独特的地方。如果这个方法能给大家一点启示,作为抛砖引玉,让大家能更深入地挖掘Brew的玩法,然后贡献出来共同讨论,那就是最好不过的了。
   IHtmlViewer这个东西是一个好东西,但是也有很多让人出离愤怒的地方,包括不支持背景色和背景图片,令人郁闷不已。解决这个问题,我用到了两个关键技术:
(1) 接口函数替换技术
(2) 直接修改显示缓冲区技术

   接口函数替换类似于一种钩子方法(Hook),我通过这种方法用一个自定义的函数(IDISPLAYER_MyUpdateEx)替换IDisplay接口的IDISPLAY_UpdateEx函数。大家知道所有显示输出操作最后都需要调用IDISPLAY_UpdateEx函数刷新屏幕,IHtmlViewer也不例外。使用IDISPLAYER_MyUpdateEx替换IDISPLAY_UpdateEx函数后,IHtmlViewer在刷新屏幕的时候就会首先调用IDISPLAYER_MyUpdateEx,在这个函数中我修改显示缓冲区,添加背景图片,然后再调用真正的IDISPLAY_UpdateEx函数刷新屏幕,这样,IHtmlViewer控件就有背景图片了。
   首先看看怎么替换IDISPLAY_UpdateEx接口函数。打开AEEDisp.h,可以看到IDISPLAY_ UpdateEx的定义如下。

#define IDISPLAY_UpdateEx(p, bDefer)  AEEGETPVTBL(p,IDisplay)->Update((p), (bDefer))

再把AEEGETPVTBL宏定义展开,实际的定义应该是这样的:

#define IDISPLAY_UpdateEx(p, bDefer)  (*((IDisplayVtbl**)p))-> Update((p), (bDefer))

   从这里我们可以看到很多问题。我们知道,每一个应用都需要创建一个IDisplay接口的实例指针(假设是pIDisplay),那么这个指针指向怎么样一个数据结构呢?在AEEDisp.h中我们可以看到IDisplay的定义:

typedef struct _IDisplay IDisplay;

   可以看到,IDisplay实际上是一个伪数据类型,没有任何意义(所有的Ixxxxx数据类型定义都是如此),我们无从知道其具体数据结构定义。幸运的是,从接口函数的定义中我们可以知道两个事实:
(1) pIDisplay指针所指向的数据的前四个字节的值是一个指针
(2) 这个指针的类型为IDisplayVtbl(记为pIDisplay->pIDisplayVtbl)。

   IDisplayVtbl数据结构实际上是IDisplay接口的函数表(所有其它接口也都一样),在AEEDisp.h中定义。

AEEINTERFACE(IDisplay)
{
  INHERIT_IBase(IDisplay);
  …
  void (*Update)(IDisplay * po, boolean bDefer);
  …
}

把宏定义展开就是这样:

typedef struct IDisplayVtbl IDisplayVtbl;
struct IDisplayVtbl
{
  uint32  (*AddRef) (IDisplay*);
  uint32  (*Release) (IDisplay*);
  …
  void (*Update)(IDisplay * po, boolean bDefer);
  …
}

   其它函数我们就不关心了,跟这篇文章有关的就是这个 Update 函数指针。很显然,我们只要把这个指针替换为IDISPLAY_MyUpdateEx就可以了。但是且慢,这里还有一个问题。pIDisplay->pIDisplayVtbl这个指针指向的地址位于代码段,是不能修改的。因此我们还需要做一些处理。方法是重新构造一个函数表,用于替换原来的函数表。

函数替换的代码如下。

IDisplay* pIDisplay = ((AEEApplet*) GETAPPINSTANCE() )->m_pIDisplay;
IDisplayVtbl* pIDisplayVtbl = (IDisplayVtbl*)MALLOC(sizeof(IDisplayVtbl*));
MEMCPY(pIDisplayVtbl, *(( IDisplayVtbl**) pIDisplay), sizeof(IDisplayVtbl));
//在这里保存原来的Update函数指针
SaveOldUpdate(pIDisplayVtbl->Update);
//替换
pIDisplayVtbl->Update = IDISPLAY_MyUpdateEx;
*(( IDisplayVtbl**) pIDisplay) = pIDisplayVtbl;

   这样IDISPLAY_UpdateEx就替换成自己定义的函数了。下面看一下IDISPLAY_ MyUpdateEx这个函数怎么实现。直接修改显示缓冲区的方法在过去已经有人讨论过,在这里就不详细说明了,直接给出IDISPLAY_ MyUpdateEx函数的参考实现。在参考实现中没有做任何错误处理,请注意。

void IDISPLAY_MyUpdateEx(IDisplay * po, boolean bDefer)
{
   IBitmap* pScrBitmap;
   IDIB* pScrDIB;
   IBitmap* pBgBitmap;
   IDID* pBgDIB;
   IImage* pBgImage;

   // 获取显示器位图
   IDISPLAY_GetDeviceBitmap(po, &pScrBitmap);
   // 获取显示缓冲区
   IBITMAP_QueryInterface(pScrBitmap, AEECLSID_DIB, (void**)& pScrDIB);
   // 创建背景位图
   IBITMAP_CreateCompatibleBitmap(pScrBitmap, &pBgBitmap,pDIB->cx,pDIB->cy);
   // 获取背景位图的缓冲区
   IBITMAP_QueryInterface(pBgBitmap, AEECLSID_DIB, (void**)&pBgDIB);
   // 载入背景图片
   pBgImage = ISHELL_LoadImage(
                          ((AEEApplet*)GETAPPINSTANCE() )->m_pIShell,
     “bg.png”
      );
   // 替换IDisplay接口的目标位图
   IDISPLAY_SetDestination(po, pBgBitmap);
   IBITMAP_Release(pScrBitmap); // 引用计数减1
   // 贴背景图
   IIMAGE_Draw(pBgImage);
   // 还原IDisplay接口的目标位图
   IDISPLAY_SetDestination(po, pScrBitmap);
   IBITMAP_Release(pBgBitmap); // 引用计数减1

   IIMAGE_Release(pBgImage);

   // 这时,pScrDIB里面是原来IHtmlViewer载入的页面的位图数据,pBgDIB里是背景图片
   // 的位图数据,我们pScrDIB里白色的点用pBgDIB对应的点来替换。这里假设设备的颜色
   // 深度为16bit
   {
       uint16* pixel = (uint16*)pScrDIB->pBmp;
       uint16* bgpixel = (uint16*)pBgDIB->pBmp;
       int pcount = pScrDIB->cx * pScrDIB->cy;

       for(; pcount; --pcount) {
           if(*pixel == 0xffff) *pixel = *bgpixel;
++pixel;
++bgpixel;
       }
   }

   // 调用真正的屏幕刷新函数
   (GetOldUpdate())(po, bDefer);

   // 释放资源
   IDIB_Release(pBgDIB);
   IBITMAP_Release(pBgBitmap);

}

   好了,到此这篇文章也该结束了。最好我想狗尾续貂一下。上面讲的我想应该是一种较具普篇性的,应该不只限于为IHtmlViewer控件添加背景图(包括背景色),通过举一反三,还可以用于其它目的。希望大家可以共同探讨一下。