Active Context及XP主题支持

博主按:

在对HTMLUI中HTML插入Windows的ListView控件时发现,IE8页面中的控件无法获得主题效果。用ResHacker打开iexplore.exe发现没有嵌入manifest清单,所以IE8不支持主题控件效果。

然而Internet选项的对话框是却有主题效果的!用Process Explorer查看iexplore.exe进程,发现5和6两个版本的comctl32.dll均被加载,即同进程中实现了site-by-site部署。

很显然,这使得EXE没有XP风格,但在DLL或ActiveX控件中实现XP风格有了机会。STFW很久后发现此文,讲的简单到位,转载以收藏。

Windows下的动态链接库可能存在多个版本,比如系统提供的基本控件所在的Comctl32.dll 就包括版本6.0和之前的版本。在新版本之前编写的程序能否保证绝对和新版本的动态链接库兼容呢?只能说,一般情况下可以,但是不能绝对保证。 Windows提供了解决办法,那就是设定程序执行的Active Context。对于客户自己编写的动态链接库,同样也存在这样的问题。这里说的是动态链接库,如果考虑动态链接库的其他应用,就涉及到更广阔的应用范围 了,可能是普通的动态链接库,可能是COM服务器等,这些都是可以通过配置程序执行环境来改变具体使用哪个动态链接库。
配置程序执行上下文,实际就是配置程序执行的组件配置,这里的组件只要是指动态链接库。对组件进行配置,简单来说,就是配置程序使用什么动态链接库。这种 配置具体是怎么实现的呢?其实就是在Windows程序真正启动之前增加了一层处理。这层处理就是把程序执行所需要的环境先建立好,直接点说就是把需要的 动态链接库准备好。

上面说到,配置组件就是在程序真正执行之前准备好需要的动态链接库,这项工作可以由Windows系统的Module Loader或者应用程序自己完成。具体的实现方式有三种:
1.文件级的配置:在应用程序所在的目录下创建一个.exe.manifest文件,这个文件中说明组件配置的具体信息,具体的信息格式可以查询MSDN。在VS2003及以上的版本,新建工程时,会自动生成一个manifest文件在res目录下。
2.资源级的配置:创建资源,类型:RT_MANIFEST(值为24),ID:CREATEPROCESS_
MANIFEST_RESOURCE_ID(值 为1),内容为上面的manifest文件的内容。Module Loader在加载EXE之后,执行程序之前,会检测到程序的资源表中是否有类型为24,ID为1的资源,如果有这项配置信息,就会按照配置信息中的配 置,加载对应的动态链接库了。
3.资源和代码混合的配置:首先也需要添加资源,资源类型RT_MANIFEST(值为24),ID为 ISOLATIONAWARE_MANIFEST_RESOURCE_ID(值为2),内容也为上述符合格式的manifest文件;然后再 在#include之前增加一句
#define ISOLATION_AWARE_ENABLED 1。这样编译器在编译时,会把一些API重定向到另外一个API,这个API先进行当成程序Active Context的检测,如果程序执行Context没有配置成资源文件所描述,会调用相应的API配置好Context,然后再从目的API所在的动态链 接库中获得函数地址(GetProcAddress)后,通过函数指针调用目标API,最后执行完毕后恢复执行环境。这可以保证在系统Module Loader没有程序Active Context支持时,配置好程序执行环境。但是采用这种方法时需要注意,这是一种代码级的支持,所以对MFC/ATL并不支持,因为MFC/ATL库编 译时,并不是在ISOLATION_AWARE_ENABLED已经定义的情况下编译的。
4.代码级的配置:除了以上几种方法之外,你还可以通过调用相应的API,自己通过代码来控制当前的执行环境,还可以做到一段代码调用这个版本动态链接库 中的代码,另外一段代码调用另外一个版本的动态链接库代码。程序执行Context是线程有效的。这可能在什么情况下有应用呢?比如说,你要开发某个应用 程序的插件,你的插件依赖与系统提供的某个版本的动态链接库,但是应用程序在不断的升级,不能保证应用程序的执行环境始终与最初发布版本一致。这时你就可 以自己通过代码来限定执行环境了。需要注意的是,如果程序已经采用了第三种方式进行执行环境的管理了,你就不能使用代码级的环境管理了。
前面说的都是程序执行Context,实际准确的说应该是模块执行Context。对于动态链接库,同样可以进行类似的配置。这些配置都有相应的API及manifest文件格式,详见MSDN。

上面说了这么多,下面我们来做几个简单的试验吧。
1.使用VS2003,新建一个MFC对话框程序,随便拖几个控件进去,编译。到Debug目录下运行,你会发现界面并不是XP风格的。在Res目录下, 你会发现一个manifest文件,这个manifest文件是VC自动生成的,里面包含使用XP风格控件动态链接库的信息。把这个动态链接库拷贝到 Debug目录下,并重命名为.exe.manifest,再运行程序,程序界面是否是XP风格了?这是第一种方式的实现。
2.新建一个Win32工程,在about对话框中随便添加几个控件,编译运行,对话框中的控件不是XP风格的。添加ID为1的RT_MANIFEST类 型资源,内容为例1中的manifest文件内容,再编译运行,可以看见对话框中控件为XP风格。这是第二种方式的实现。
3.把上述工程中的RT_MANIFEST资源ID修改为2,然后在stdafx.h中#include之前增加#define ISOLATION_AWARE_ENABLED 1,编译运行,对话框控件也是XP风格的。这是第三种方式的实现。
4.在上述最原始Win32工程中,把WinMain函数修改如下:

ACTCTX actctx;
HANDLE hActCtx = INVALID_HANDLE_VALUE;
static const TCHAR szManifest[] = TEXT("Win32.manifest");
ULONG_PTR ulpActivationCookie;
DWORD dwLastError;

// Set up the activation context structure
memset(&actctx, 0, sizeof(actctx));
actctx.cbSize = sizeof(actctx);
actctx.lpSource = szManifest;

// Create the activation context, then delete the string - we don''t need it
// anymore.
hActCtx = CreateActCtx(&actctx);
dwLastError = GetLastError();

// Did we fail creating the activation context?
if (hActCtx == INVALID_HANDLE_VALUE)
...{
     SetLastError(dwLastError); // operator delete[] may have cleared it
 return 0;
}

// Activate the context and make use of it

if (ActivateActCtx(hActCtx, &ulpActivationCookie))
...{
     ……
     之前WinMian函数的代码
     ……
 // Use SearchPath to find DLLs mentioned in the application''s manifest,
 // use COM to find isolated components, or use CreateWindow to find isolated
 // window classes.
 if (!DeactivateActCtx(0, ulpActivationCookie))
 return 0;;
}

ReleaseActCtx(hActCtx);

保证增加以上代码后可以编译通过,同时保证EXE所在目录下有Win32.manifest文件,内容为之前的XP主题配置信息。运行程序,会看见控件为XP主题,注释掉ActivateActCtx,控件不再是XP主题。

以上的例子主要是如何实现XP主题,但是程序执行环境的配置不仅仅实现XP主题,还有很多更广阔的应用。

2 Replies to “Active Context及XP主题支持”

  1. 你好博主,我使用了你所写的方法在我的bho中,可是弹出的对话框依然没有采用系统样式。可否给我你的QQ能够即时联系?

    1. 检查manifest文件是否指定正确,BHO我也没做过,参考http://social.msdn.microsoft.com/Forums/en-NZ/ieextensiondevelopment/thread/99daf906-b13a-47f6-acac-c2e8d3f79256

Leave a Reply

Your email address will not be published. Required fields are marked *