今天我在这里要讲的我这几天工作的一个总结,希望对大家有帮助。不过我这个人有个毛病,在开宗明义之前总喜欢发点满腹牢骚。请大家满足一下我这个小小癖好。
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这个指针指向的地址位于代码段,是不能修改的。因此我们还需要做一些处理。方法是重新构造一个函数表,用于替换原来的函数表。