Windows 95/98/Me系统上的Microsoft® Layer for Unicode
多年来,Microsoft一直在大力宣传创建支持Unicode的程序带来的好处。在Windows NT/2000/XP,这些好处是显而易见的,包括:
改进国际和多语言支持;
与NT平台特性更好地集成;
只要发行单一的可执行程序,而不需为每个区域单独编译;
对Windows 2000/XP里的多语种用户界面(MUI)提供更好支持;
支持很多新的仅能通过Unicode才能用的语言,譬如印地语、格鲁吉亚语。
问题是在Windows 95/98/ME里支持Unicode应用程序的难度;开发者也要照顾到那些客户的需求。由于这些平台并没有NT家族操作系统所提供的天生的Unicode支持,因此只有开发非Unicode的程序才是容易的。
然而,时移世易……
从Windows XP RC1版本的Platform SDK开始,Windows 95/98/Me系统上的Microsoft Layer for Unicode(简写为MSLU)可以帮助你迎接这个重要的挑战。这个部件在Windows 5/98/ME上提供位于Win32 API之上的一层,这样你就可以只开发一个Unicode版本的应用程序,让它在所有平台上都能正确运行。
特性概述
l 容易集成-你需要做的一切,仅仅是把你的程序编译为Unicode版本,在链接你所需要的其它库时加上unicows.lib即可。
l 在WinNT/2K/XP上不减慢程序速度-为unicows.dll定制的加载器,内建于unicows.lib中,编译进你的应用程序。如果运行在Win95/98/ME之外的机器上,根本就不会加载MSLU。这个技术和VC++的delay load机制类似,但它是一个独立的解决方案,并不依赖于任何特定的编译器。
l 容易定制-使用MSLU的开发者可以重载他们想要的任何API,并且仍然利用定制加载器带来的好处。
l 对Win32 API涵盖全面-MSLU为440多个API调用提供了完全实现的包装,还有一组“占位”包装可以由开发者重载,来实现自己的版本。
l 轻量级,易使用-只有大约160KB的大小,没有任何依赖关系,不需要注册,不需要特殊的文件位置,很仔细地避开了DLL噩梦问题,API和Unicode平台上的高度一致,使得MSLU出人意料地容易添加到程序中。
l 和常用的库配合工作-你可能正在使用Microsoft基础类库(MFC)、VC++运行时库(CRT)或者AvtiveX模板库(ATL)。如果你静态链接了这些DLL,后来又集成进去MSLU,那么所有这些库都会恰当地使用MSLU提供的API。
l 良好的互操作性-不论你是要和当前进程中使用MSLU的其它部分一起工作,还是当前进程之外的应用程序,甚至根本不支持Unicode的方案,MSLU都表现正常。
l 全面的文档-Platform SDK将会在相关主题里为MSLU支持的每一个API提供信息,包括对某些特殊的API要特别注意的事项。除此之外,还有很多特定的主题有助于如何集成MSLU。
l 开发者新闻组支持-你可以访问MSLU的专题新闻组。在这里你可以收到Microsoft产品支持专家和使用这项激动人心的新技术进行开发的其它开发者提供的帮助。
l 完整开发包的一部分-Microsoft还开发了很多别的技术,譬如RichEdit、Windows通用控件、MLang、Uniscribe和GDI+等等,可以和MSLU一起使用。这可以帮助开发者提供完整的跨平台体验,在程序运行的每一个平台上都挖掘出最好的特性来。
当然,MSLU并不是用来创建在Windows 95/98/Me上运行得像Windows NT/2000/XP上一样好的良好全球化应用的万能药。在基于Windows代码页的平台上创建多语种应用,由此与生俱来的架构上的挑战仍然存在。首先,即使实现了MSLU,在Windows 95/98/Me上的多语种支持比起Windows NT/2000/XP的差距仍然很可观,譬如在韩文Windows98下输入阿拉伯文。其次,MSLU并没有让基于代码页的平台在内部支持Unicode。再次,MSLU并没有被设计成支持现存的为基于代码页平台提供各自的Unicode功能的其它方案和部件,譬如MLang、Uniscribe、RichEdit、公用控件等等。第四,你仍然需要遵循和全球化相关的那些最好的惯例。最后,从国际的观点来看,MSLU和那些真正的Unicode平台比起来还是有很明显的缺点。在MSLU的确为你的程序提供了Unicode的“接口”的情况下,它还是要跟非Unicode的操作系统内部进行交谈。这样,当你的Unicode应用程序运行在Windows 95/98/ME上的时候,就被局限在某个代码页,这样保证那个程序表现正常就很重要了。一个好的Unicode程序不会被预期有太多的问题。然而,你必须在这三个平台上都测试你的程序,以确保不会出现意外,绝对不能让用户丢失任何数据。通过查看你的程序用在系统支持能力之外的情况下出现的情况,你可以在发布之前解决掉问题。
MSLU是如何工作的
MSLU实际上包括了两部分。第一部分是一个动态链接库(DLL)——Unicows.dll——可以和用户创建的程序一起再分发。第二部分是一个库文件(LIB)——Unicows.lib——编译进同一个程序中。这个LIB包含了MSLU加载器,用来判断是要装入DLL并调用它,还是调用操作系统原来版本的API。前一种情况发生在Windows95/98/Me上,后一种情况发生在Windows NT/2000/XP上。这个加载器还包含了像操作系统本身实现一样的所有必需的错误占位,这样,在DLL找不到的时候,你可以在你的代码中很斯文地处理API的错误。从技术观点来看,你的代码仅在使用Windows 95/98/Me的时候才会调用Unicows.dll里面的函数。即使你绕过加载器(譬如通过自己调用LoadLibrary/GetProcAddress,或者使用Microsoft .NET框架中的P/Invoke语法)来直接使用DLL,在WindowsNT/2000/XP平台上,DLL还是仅仅会把调用转发给真正的API,而不是调用MSLU的“包装”函数。这是因为包装比起操作系统提供的支持Unicode的完全版来,功能还是不够。如果你用的是C或C++,那么通过调用一个你不需要的DLL仅仅来访问操作系统,由此故意减慢程序运行速度,实在不是个好主意。反之,你可以自己决定是调用操作系统版本或是MSLU版本地函数,以此避免性能损失。与MSLU中每一个Unicode函数相伴随的是,显然一定有一次转换到非Unicode字符串的过程。通常这是用计算机的系统默认代码页(CP_ACP)来完成的,这个代码页在Windows 95/98/Me上不可更改。有一些特定的API并不使用CP_ACP,因为它们在别的参数,譬如区域ID值(LCID)或者设备上下文中可以找到更合适的代码页。
如何(以及何时)处理重载
在MSLU的典型应用中,并不需要写任何代码,只有两个例外:
l 需要重载MSLU加载过程的时候
l 需要单独重载某个API的时候
注意这两种情况下都是假设你使用MSLU加载器。如果你用的是Microsoft Visual C#或者Microsoft Visual Basic P/Invoke机制而不是MSLU加载器,你可以直接调用Unicows.dll里的函数,DLL里的代码会判断是自己处理函数还是调用操作系统版本。
重载MSLU的加载过程
重载MSLU的加载过程需要两步:1.设置好钩子来通知MSLU加载器它需要调用什么函数。2.提供加载器首次载入DLL的时候需要调用的函数。下面演示的代码里的回调函数试图先不指定路径加载DLL。用这种方式,如果这个进程中别的部分已经加载了这个DLL,就可以共享同一个实例。如果这个函数还是没法找到DLL,进程就退出。即使加载器不重载的时候也遵循同样的逻辑。理想情况下的重载应该根据注册表或者其它程序定义的信息来构建一个路径。对Microsoft基础类库(MFC)来说,退出是很重要的,因为MFC对Unicode API的出错处理并不很好。MFC假设某些特定的函数可以正常使用。这样,由于在这种情况下不检查出错,那么在调用API失败之后,MFC程序就会由于对空指针做反引用而崩溃。
extern FARPROC _PfnLoadUnicows = (FARPROC) &LoadUnicowsProc;
HMODULE LoadUnicowsProc(void)
{
HMODULE hMod = LoadLibraryA("unicows.dll");
if(hMod == 0)
{
// Replace with your specific path.
hMod = LoadLibraryA("\\unicows.dll");
}
if(hMod == 0)
{
// If the load still failed, then exit.
MessageBoxA(0,
"Unicode wrapper not found",
"My Company",
MB_ICONSTOP | MB_OK);
_exit(-1);
}
return(hMod);
}
在回调函数中不能调用任何Unicode API,因为载入的第一个Unicode API将会让加载器调用这个函数。如果你调用Unicode API,就会间接调用自己,造成递归,直到堆栈空间耗尽。这样,被MSLU包装的任何API都不能在这个函数里调用。
单独重载某个API
单独重载某个API的语法和重载MSLU的加载过程类似。只要设置一个钩子通知加载器应该调用哪个函数,然后提供实际的函数。位于http://msdn.microsoft.com 的Microsoft Windows Platform SDK文档包含了一个重载LoadCursorW的例子。然而,这里提供的例子重载的是MSLU中仅包含了存根形式的API。这个API是OleUIInsertObjectW,调用很多应用程序都支持的InsertObject方法来支持Microsoft ActiveX对象的插入。
static UINT __stdcall
MyOleUIInsertObjectW (LPOLEUIINSERTOBJECTW lpouiiow)
{
UINT result = OLEUI_CANCEL;
OLEUIINSERTOBJECTA ouiioa;
memcpy(&ouiioa, lpouiiow, sizeof (OLEUIINSERTOBJECTA));
ouiioa.lpszFile = (char *)alloca (ouiioa.cchFile + 1);
Microsoft Layer for Unicode (MSLU) Chapter 18
ouiioa.lpszFile [0] = '\0';
result = OleUIInsertObjectA(&ouiioa);
if (result == OLEUI_SUCCESS)
{
memcpy(lpouiiow,
&ouiioa,
sizeof(OLEUIINSERTOBJECTW));
MultiByteToWideChar(CP_ACP,
0,
ouiioa.lpSzFile,
ouiioa.cchFile,
lpowiiow->lpszFile,
lpowiiow->cchFile);
}
return result;
}
extern "C" FARPROC Unicows_OleUIInsertObjectW =
(FARPROC)&MyOleUIInsertObjectW;
注意前面的重载只能在Windows 95/98/Me上调用。这种重载的一个局限性就是,它是由MSLU加载器完成的,因此只能用于C和C++。然而,在这些语言里,你可以用这种语法重载你想要重载的任何数量的API。实际上,你可以重载所有的API,而仅仅在你的程序中使用MSLU加载器。你甚至可以通过在先调用LoadLibrary("unicows.dll")再GetProcessAddress("")这种特殊手段来调用MSLU API。