转贴, 使用MSLU

href="../techartical.css" type="text/css" rel="stylesheet"/>

* 本文选自:COM集中营


使用MSLU(Microsoft Layer for Unicode)

lostall

一、什么是MSLU

    Microsoft Layer for Unicode on Windows 95/98/ME Systems(简称MSLU)是微软新提供的一个开发包。原本,Windows 95/98/ME不支持Unicode,Unicode程序在这些系统下运行都会出错。但是MSLU使得即使在这些系统下,Unicode程序仍然可以正常运行。对Windows程序来说这是非常重要的,因为大部分商用应用程序都不希望只能在NT/2000以上的系统上运行。当然,ANSI程序可以在所有Windows平台上跑,但是在Windows NT/2000/XP下,所有的ANSI函数都是先转换为Unicode函数运行的,这意味着效率的下降。所以让Unicode程序能在Win95/98/ME下跑,比让ANSI程序在WinNT/2000/XP上跑好,毕竟未来的Windows都将是基于Unicode的。这就是MSLU的目的。

    MSLU并不复杂,MSDN里也有关于它的文档,篇幅不大,但也足以说明它的原理和使用方法。但在实际使用MSLU里,大多数初学者都会倍感困扰。困难来自于MSLU本身存在的Bug,以及MFC库并没有支持MSLU。要注意到,MSLU出现在VC6甚至VC7之后,MS并没有在当前的VC版本中支持MSLU。但相信在下一个版本的VC中,这个问题将不复存在。

    我很早以前,在2001年11月份的时侯就已经使用了MSLU,当时很顺利,觉得它很好用。但直到最近才发现原来还有这么多的困难。一个重要原因就是以前我用的只是Win32 SDK程序,而现在是MFC程序。 MSLU应用在MFC程序时问题很多。为了解决这些问题,我花了很多时间在网上查找资料和实验,现在终于小有所成。天外有天人外有人,每个人的前进都是站在别人的肩膀上,现在我也让出我的肩膀:)

二、MSLU的原理

    MSLU的原理很简单,它把Unicode参数先转换成非Unicode字符,调用非Unicode版本的函数,运行完后再把运行结果转换回Unicode字符。因为实际调用的是非Unicode版本的函数,所以在Win95/98/ME这些操作系统上也一样可以正确运行。

    不用担心使用了MSLU的程序在WinNT/2000/XP中的执行效率,MSLU不是为这些系统设计的,它根本不能在这上面使用,所以效率丝毫不受影响。

    Unicows.dll中所有的API函数,都可以重载,包括Unicows.dll的装载过程。比如:

// overriding of the loading of unicows.dll
static HMODULE __stdcall MyLoadUnicowsProc( void )
{
    return (LoadLibraryA("unicows.dll")) ;
}
extern "C" FARPROC _PfnLoadUnicows = (FARPROC) &MyLoadUnicowsProc ;


// overriding SetWindowTextW
static BOOL __stdcall MySetWindowTextW( HWND hWnd, LPCWSTR lpString )
{
    int nBytes = WideCharToMultiByte( CP_ACP, 0, lpString, -1, NULL, 0, NULL, NULL ) ;
    char *lpStringA = (char*)_alloca( nBytes ) ;	// alloc memory on statck

    if ( 0 == WideCharToMultiByte( CP_ACP, 0, lpString, -1, lpStringA, nBytes, NULL, NULL ) )
	return FALSE ;

    return SetWindowTextA( hWnd, lpStringA ) ;
}
extern "C" FARPROC Unicows_SetWindowTextW = (FARPROC) &MySetWindowTextW ;
    MSDN中的这部分文档有两个遗漏,首先必须是 __stdcall方式,其次必须用 extern "C"

三、Win32程序

    在Win32中应用MSLU是最容易的了,对VC6和VC7来说是一样的,按照MSDN里的指示做就可以了,具体步骤如下:
    (1)新建一个Win32程序,不使用MFC库。
    (2)设置为Unicode程序。在C++/Preprocessor definitions/中去除_MBCS,加上_UNICODE,UNICODE;在Link/Output/Entry-point symbol中设为wWinMainCRTStartup。
    (3)加上MSLU支持。在Link/ Object/Library Modules中加上下面几行:
/nod:kernel32.lib /nod:advapi32.lib /nod:user32.lib /nod:gdi32.lib /nod:shell32.lib /nod:comdlg32.lib /nod:version.lib /nod:mpr.lib
/nod:rasapi32.lib /nod:winmm.lib /nod:winspool.lib /nod:vfw32.lib /nod:secur32.lib /nod:oleacc.lib /nod:oledlg.lib /nod:sensapi.lib
unicows.lib kernel32.lib advapi32.lib user32.lib gdi32.lib shell32.lib comdlg32.lib version.lib mpr.lib rasapi32.lib winmm.lib 
winspool.lib vfw32.lib oleacc.lib oledlg.lib
之所以要这样设置是希望能保证正确的链接顺序,要保证unicows.lib在任何其他lib之前被链接。
   OK!就这么多,Win32程序就这么简单。

四、准备知识

   在开始复杂的MFC之旅前,先要了解一些基本知识:
(1)几个预处理定义:
_WINDLL : 表明这是个DLL
_USRDLL : 表明这是个Regular DLL
_AFXDLL : 表明是动态链接MFC库

Regular DLL : 普通的正常的DLL,以extern "C"的方式输出函数和类。它本身可以使用MFC类,但绝不能输出MFC类。它的客户程序不一定是MFC程序
AFXDLL : 也可以把它看作“Extension DLL”,即扩展的DLL。它输出MFC类,(比如派生的窗口类),所以其客户程序必须是MFC程序。
	MFC DLL自己就是一种特殊的“Extension DLL”。

(2)有几个设置是和运行时库相关的:

/MD : 对应Mutlithreaded DLL。定义_MT,_DLL。链接MSVCRT.LIB
/MDd: 对应Debug Multithreaded DLL。定义_DEBUG,_MT,_DLL。链接MSVCRTD.LIB
/MT : 对应Mutlithreaded。定义_MT。链接LIBCMT.LIB
/MTd: 对应Debug Multithreaded。定义_DEBUG,_MT。链接LIBCMTD.LIB
/ML : 对应Single Threaded。链接LIBC.LIB
/MLd: 对应Debug Single Threaded。定义_DEBUG。链接LIBCD.LIB

(3)MFC 6.0的组成:

静态链接MFC
Debug : nafxcwd.lib
Release : nafxcw.lib
Unicode Debug : uafxcwd.lib
Unicode Release : uafxcw.lib

动态链接MFC
Debug : MFC42D.DLL (core), MFCO42D.DLL (OLE), MFCD42D.DLL (database), MFCN42D.DLL (network), MFCS42D.LIB (static)
Release : MFC42.DLL (combined), MFCS42.LIB (static)
Unicode Debug : MFC42UD.DLL (core), MFCO42UD.DLL (OLE), MFCD42UD.DLL (database), MFCN42UD.DLL (network), MFCS42UD.LIB (static)
Unicode Release : MFC42U.DLL (combined), MFCS42U.LIB (static)

(4)MFC 7.0的组成:

静态链接MFC
Debug : nafxcwd.lib
Release : nafxcw.lib
Unicode Debug : uafxcwd.lib
Unicode Release : uafxcw.lib

动态链接MFC
Debug : MFC70D.DLL, MFCS70D.LIB (static)
Release : MFC70.DLL, MFCS70.LIB (static)
Unicode Debug : MFC70UD.DLL, MFCS70UD.LIB (static)
Unicode Release : MFC70U.DLL, MFCS70U.LIB (static)

五、MFC程序,静态链接

    静态链接的MFC程序并不复杂,和Win32程序基本一样,只是要多加上MFC的lib和运行时库,如下:
/nod:kernel32.lib /nod:advapi32.lib /nod:user32.lib /nod:gdi32.lib /nod:shell32.lib /nod:comdlg32.lib /nod:version.lib /nod:mpr.lib
/nod:rasapi32.lib /nod:winmm.lib /nod:winspool.lib /nod:vfw32.lib /nod:secur32.lib /nod:oleacc.lib /nod:oledlg.lib /nod:sensapi.lib
unicows.lib kernel32.lib advapi32.lib user32.lib gdi32.lib shell32.lib comdlg32.lib version.lib mpr.lib rasapi32.lib winmm.lib 
winspool.lib vfw32.lib oleacc.lib oledlg.lib uafxcw.lib libcmt.lib
    如果是Debug版,当然就是 uafxcwd.liblibcmtd.lib了。
    要注意的是,这两个文件必须放在最后面。

六、MFC程序(VC6),动态链接

    (1)按照" Rebuild MFC 6.0 and CRT with MSLU"中所讲的方法重编译MFC和CRT DLLs,并将新生成的Lib和DLL文件拷到各自的目录下。
    (2)库链接设置如下:
/nod:kernel32.lib /nod:advapi32.lib /nod:user32.lib /nod:gdi32.lib /nod:shell32.lib /nod:comdlg32.lib /nod:version.lib /nod:mpr.lib
/nod:rasapi32.lib /nod:winmm.lib /nod:winspool.lib /nod:vfw32.lib /nod:secur32.lib /nod:oleacc.lib /nod:oledlg.lib /nod:sensapi.lib
unicows.lib kernel32.lib advapi32.lib user32.lib gdi32.lib shell32.lib comdlg32.lib version.lib mpr.lib rasapi32.lib winmm.lib 
winspool.lib vfw32.lib oleacc.lib oledlg.lib mfc42u.lib msvcrt.lib
    如果是Debug版,则是 mfc42ud.libmsvcrtd.lib

七、MFC程序(VC7),动态链接

    (1)按照" Rebuild MFC 7.0 and CRT with MSLU"中所讲的方法重编译MFC和CRT DLLs,并将新生成的Lib和DLL文件拷到各自的目录下。
    (2)库链接设置如下:
/nod:kernel32.lib /nod:advapi32.lib /nod:user32.lib /nod:gdi32.lib /nod:shell32.lib /nod:comdlg32.lib /nod:version.lib /nod:mpr.lib
/nod:rasapi32.lib /nod:winmm.lib /nod:winspool.lib /nod:vfw32.lib /nod:secur32.lib /nod:oleacc.lib /nod:oledlg.lib /nod:sensapi.lib
unicows.lib kernel32.lib advapi32.lib user32.lib gdi32.lib shell32.lib comdlg32.lib version.lib mpr.lib rasapi32.lib winmm.lib 
winspool.lib vfw32.lib oleacc.lib oledlg.lib mfc70u.lib msvcrt.lib
    如果是Debug版,则是 mfc70ud.libmsvcrtd.lib

八、MSLU 3665中的一个重大的Bug

    (Bug本不应在这里列出,只怪这个Bug太过严重。)
    MSLU的当前最新版本1.0.3665中有个非常严重的Bug( VC7及老版本中没这问题),就是对话框是不可见的!一个基于对话框的MFC程序根本是看不见的,File/Open对话框也不可见。
    不过幸好也有大侠研究出了解决方案,方法如下:

    (1)在InitInstance()前面加上如下两句话:
extern "C" BOOL InitMultipleMonitorStubs( void ) ;
extern "C" BOOL (WINAPI* g_pfnGetMonitorInfo)( HMONITOR, LPMONITORINFO ) ;
    (2)在InitInstance()中加上如下语句:
if (InitMultipleMonitorStubs())
{
   if (HMODULE hUser32 = GetModuleHandle(_T("USER32")))
      *(FARPROC*)&g_pfnGetMonitorInfo = GetProcAddress( hUser32, "GetMonitorInfoA" ) ;
}
    这种方法只适合于静态链接MFC的情况,如果是动态链接的话,还是直接改源码得了,把multimon.h中的第124行改为:
        (*(FARPROC*)&g_pfnGetMonitorInfo      = GetProcAddress(hUser32,"GetMonitorInfoA")) )
然后再重新编译MFC就行了。当然也可以重新编译MFC静态库版本,这样即使是静态链接MFC,也不需要在程序中加上如上的额外代码了。
    至于这个Bug的产生源由,对比一下VC6中multimon.h的第124行和VC7中multimon.h的第194行,就能猜出个大概了。
    有消息称,新版本的MSLU即将推出,届时这个Bug必将不复存在。

九、MSLU以外...

    MSLU并非全部,在Win95/98/ME上已存在一些别的Unicode的支持:
    (1)有些函数的Unicode版本在这些系统下已经实现了,比如常用的MessageBox、TextOut等。MSLU没有重载这些函数,但我们自己可以重载它,就象重载一个MSLU实现的API一样。
    (2)以下技术也已经支持Unicode了:
  • Common Control (commonctrl32), 必须需要comctl32.dll 5.80 (IE5.0)以上
  • Input Method Editor (IME)
  • MultiLanguage Object (MLang)
  • Rich Edit, 需要Rich Edit 2.0以上
  • Uniscribe
    MSLU与它们不重叠。

    (a)关于Common Control
    我起始并不知道这些。我在Win98第一版下做了一个MDI的测试程序,一切运行正常,但是Tooltip有问题,Debug下有ASSERT错误,原因是Tooltip的几个消息处理失败。后来我才明白是因为我的comctl32.dll的版本太低,我换到Win98第二版下,comctl32.dll的版本是5.81,Tooltip就能正常显示了。    

    (b)关于RichEdit
    Richedit目前有3个版本,1.0(riched32.dll),2.0(riched20.dll),3.0(riched20.dll)。在VC6中缺省地使用的是RichEdit 1.0,如果想在VC6中使用Richedit 2.0以上,以支持Unicode,可以参考下面的代码:
// AfxInitRichEdit()里调用的是LoadLibraryA("RICHED32.DLL"),这不行,要改掉它:
_AFX_RICHEDIT_STATE* pState = _afxRichEditState;
if (pState->m_hInstRichEdit == NULL)
	pState->m_hInstRichEdit = LoadLibraryA("RICHED20.DLL");

HWND hWnd = CreateWindowEx( 0, RICHEDIT_CLASSW, NULL, // L"RichEdit20W"
	WS_CHILD|WS_VISIBLE|WS_BORDER|ES_SUNKEN|ES_MULTILINE,
	100, 100, 200, 200, m_hWnd, NULL, AfxGetInstanceHandle(), NULL ) ;

::SetWindowText( hWnd, _T("test") ) ;
    在richedit.h里有如下定义:
#define RICHEDIT_CLASSA		"RichEdit20A"
#define RICHEDIT_CLASSW		L"RichEdit20W"
#define RICHEDIT_CLASS10A	"RICHEDIT"

#if (_RICHEDIT_VER >= 0x0200)
#ifdef UNICODE
#define RICHEDIT_CLASS		RICHEDIT_CLASSW
#else
#define RICHEDIT_CLASS		RICHEDIT_CLASSA
#endif /* UNICODE */
#else
#define RICHEDIT_CLASS		RICHEDIT_CLASS10A
#endif /* _RICHEDIT_VER >= 0x0200 */
    所以如果觉得用RICHEDIT_CLASSW过于死板,想用RICHEDIT_CLASS的话,自己定义_RICHEDIT_VER吧:
#undef _RICHEDIT_VER
#define _RICHEDIT_VER	0x0300
注意必须加在#include <afxwin.h>后面。
    如果用CRichEditCtrl创建的话,CRichEditCtrl自己内部调用了LoadLibraryA("RICHED32.DLL"),所以用同样的办法把它改掉就行了。

    以上都说的是6.0,在7.0里没这些问题了。7.0里多了个AfxInitRichEdit2(),它装载的是 RICHED20.DLL; CRichEditCtrl里缺省也是Load RICHED20.DLL。

参考资料

1、 http://www.microsoft.com/globaldev/articles/mslu_announce.asp,简单的介绍。
2、 http://www.trigeminal.com/usenet/,本文大部分内容来源于此。
3、 MSLU的新闻组
 

下载

msluexample.cpp(Overriding an API)
6.0示例代码(80KB) 7.0示例代码(80KB)
MSLU 1.0.3665(257KB) MSLU 1.0.3590(224KB)


* 本文选自:COM集中营

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值