The mslu loader code is Copyright Jeremy Gordon.
Introduction
This help file touches on two inter-related subjects:-
For more information see my article "Writing Unicode programs" at www.GoDevTool.com.
Using codepages and DrawTextA
DrawTextA is available on both W9x/ME and on NT/2000/XP machines. It can be used to
display non-Roman characters if those characters are available in a character set
in a codepage available on the machine. The character set or codepage is a component of
the font which is selected into the device context. So you need to obtain a font
handle for the font and character set you want to use and then select the font handle
into the device context used by DrawTextA.
Note that DrawTextW (which would accept a Unicode string) is not available in W9x/ME unless the Microsoft Layer for Unicode (mslu) is also used.
In this demonstration, if no writeable character is available in the chosen character
set for the value given to DrawTextA, Windows will draw a default character, usually a
question mark, box or line.
You may find that you get only a line of default characters for some of the character
sets. That is probably because the chosen character set is not installed on the machine
you are using.
To watch this demonstration on the debugger set the breakpoint to ANSILANG_TEST or
ANSILANGPAINT. When coming into ANSILANG_TEST the value in AL will depend on the menu
item chosen.
Single-step in ANSILANGPAINT (which is called on the WM_PAINT message) and see the
font handle being obtained and selected into the device context before the draw.
Here is the code which is used in Testbug:-
DATA ; WNDCLASS DD 10D DUP 0 LOGFONT DD 7 DUP 0 ;pitch and widths etc. DB 32 DUP 0 ;typeface LF_FACESIZE=32 in ansi version PAINTSTRUCT DD 16D DUP 0 CLIENT_RECT DD 4 DUP 0 ;structure to hold client area size inf ;******************** Window message table ANSILANGMESSAGES DD (ENDOF_ANSILANGMESSAGES-$-4)/8 ;=number to be done DD 1h,ANSILANGCREATE,2h,ANSILANGDESTROY,0Fh,ANSILANGPAINT ENDOF_ANSILANGMESSAGES: ;label used to work out how many messages ;***************************************** ANSILANG_MESS DB 'Here is a string of random characters between 128 and 255:-',0Dh,0Ah DB 0F8h,0DBh,0D0h,0B4h,0A0h,0FEh,0ACh,0BAh,0EEh,0EBh,0A4h,0A5h DB 0F9h,0DCh,0D1h,0B5h,0A1h,0FFh,0ADh,0BBh,0EFh,0ECh,0A5h,0A6h,0 PROPOSED_CHARSET DB 0 ;******************** CODE ; GET_ANSILANGFONT: PUSH EDI ;edi is already being used MOV EDI,ADDR LOGFONT PUSH EDI ;push that for CreateFontIndirectA MOV EDX,EDI ;and save in edx XOR EAX,EAX MOV ECX,7 REP STOSD ;clear main parts of LOGFONT MOV D[EDX],16 ;use font height of 16 MOV AL,[PROPOSED_CHARSET] MOV B[EDX+17h],AL ;insert in LOGFONT MOV B[EDX+1Ch],0 ;any typeface will do CALL CreateFontIndirectA MOV ESI,EAX ;keep handle in esi, if found POP EDI RET ; ANSILANGCREATE: ;one of the few messages dealt with by this prog XOR EAX,EAX ;return zero to make window RET ; ANSILANGDESTROY: ;one of the few messages dealt with by this prog PUSH 0 CALL PostQuitMessage ;exit via the message loop STC ;go to DefWindowProc too RET ; ANSILANGPAINT: PUSH ADDR PAINTSTRUCT,[EBP+8h] ;EBP+8h=hwnd CALL BeginPaint ;get device context to use, initialise paint MOV EDI,EAX ;keep device context in edi ;******************* MOV EBX,ADDR CLIENT_RECT PUSH EBX,[EBP+8h]? CALL GetClientRect ;get client area of window in CLIENT_RECT ;******************* centre the text .. MOV EAX,[EBX+8h] ;get available ?idth SHR EAX,2 ;get a quarter of it MOV [EBX],EAX ;give that to x-pos SUB [EBX+8h],EAX ;shorten rectangle on right hand side MOV EAX,[EBX+0Ch] ;get available height SHR EAX,2 ;get a quarter of it MOV [EBX+4h],EAX ;give that to y-pos SUB [EBX+0Ch],EAX ;reduce height of rectangle ;******************* set the font and colour PUSH 0CC3333h,EDI ;colour, device context CALL SetTextColor CALL GET_ANSILANGFONT ;get, in esi, the font to try ;****** in fact CreateFontIndirect always returns one font or another PUSH ESI,EDI ;esi=handle to font, edi=device context CALL SelectObject ;select the font into the device context ;*************** push now to deselect later PUSH EAX,EDI ;eax=previous font handle, edi=device context ;******************************* PUSH 0 ;no formatting options PUSH 1h+10h+100h ;DT_CENTER+DT_WORDBREAK+DT_NOCLIP PUSH EBX ;formatting rectangle PUSH -1 ;Windows to calculate length of message PUSH ADDR ANSILANG_MESS ;address of message PUSH EDI ;device context CALL DrawTextExA ;******************************** CALL SelectObject ;deselect font handle in dc PUSH ESI CALL DeleteObject ;delete the font (handle in esi) ;******************* PUSH ADDR PAINTSTRUCT,[EBP+8h] ;EBP+8h=hwnd CALL EndPaint XOR EAX,EAX RET ; AnsiLangWndProc: MOV EDX,ADDR ANSILANGMESSAGES ;give edx the list of messages to deal with CALL GENERAL_WNDPROC ;call the generic message handler RET 10h ;restore the stack as required by caller ; AT44: ;esi=0=enable, 1-disable MOV EBX,970 L0: PUSH ESI,EBX,[hMenu] CALL EnableMenuItem ;enable/disable the correct menu items INC EBX CMP EBX,983 JNZ L0 RET ; ANSILANG_TEST: MOV [PROPOSED_CHARSET],AL ;keep character set to try XOR ESI,ESI ;0=enable, 1-disable INC ESI CALL AT44 ;enable/disable the correct menu items CALL INITIALISE_WNDCLASS ;get ready to register window class ;********** now add things specific to the window to be made MOV D[EBX],1h+2h+20h ;CS_VREDRAW+CS_HREDRAW+CS_OWNDC (window class style) MOV D[EBX+4],ADDR AnsiLangWndProc ;window procedure MOV D[EBX+24h],ADDR ANSILANG_CLASSNAME ;window class name PUSH EBX ;address of structure with window class data CALL RegisterClassA ;register the window class PUSH 0,[hInst],0,0 ;owner=desktop PUSH 200D ;height PUSH 320D ;width PUSH 50D,50D ;position y then x PUSH 90000000h +0C00000h+40000h +80000h +20000h +10000h ;window style ;(POPUP+VISIBLE)+CAPTION+SIZEBOX+SYSMENU+MINIMIZEBOX+MAXIMIZEBOX PUSH 'ANSI language test' ;window title PUSH ADDR ANSILANG_CLASSNAME ;window class name PUSH 0 ;extended window style CALL CreateWindowExA ;make window, returning handle in EAX ;************************ now enter the main message loop (ANSI version) L1: PUSH 0,0,0 PUSH ADDR MSG CALL GetMessageA ;wait for message from Windows OR EAX,EAX ;see if it is WM_QUIT JZ >L2 ;yes PUSH ADDR MSG CALL TranslateMessage ;no so convert message to character if necessary PUSH ADDR MSG CALL DispatchMessageA ;and send the message to the window procedure JMP L1 ;after message dealt with, loop back for next one L2: PUSH [hInst],ADDR ANSILANG_CLASSNAME ;message was WM_QUIT CALL UnregisterClassA ;ensure class is removed XOR ESI,ESI ;0=enable, 1-disable CALL AT44 ;enable/disable the correct menu items RET ;return to caller ; INITIALISE_WNDCLASS: ;get ready for all windows MOV EBX,ADDR WNDCLASS MOV EAX,9 L30: MOV D[EBX+EAX*4],0 ;fill it with zeroes (may have been fille? with other) DEC EAX JNS L30 MOV EAX,[hInst] ;obtained ?n another part of the prog MOV [EBX+10h],EAX MOV EAX,[hIcon] ;obtained ?n another part of the prog MOV [EBX+14h],EAX MOV EAX,[hCurs] ;obtained in another part of the prog MOV [EBX+18h],EAX ;and give to WNDCLASS MOV D[EBX+1Ch],6D ;COLOR_WINDOW+1 RET ; GENERAL_WNDPROC: ;eax can be used to convey information to the call PUSH EBP ;use ebp to avoid using eax which may hold information MOV EBP,[ESP+10h] ;uMsg MOV ECX,[EDX] ;get number of messages to do * 8 (+4) ADD EDX,4 ;jump over size dword L33: DEC ECX JS >L46 CMP [EDX+ECX*8],EBP ;see if its the correct message JNZ L33 ;no MOV EBP,ESP PUSH ESP,EBX,EDI,ESI ;save registers as required by Windows ADD EBP,4 ;allow for the extra call to here ;now [EBP+8]=hWnd, [EBP+0Ch]=uMsg, [EBP+10h]=wParam, [EBP+14h]=lParam, CALL [EDX+ECX*8+4] ;call the correct procedure for the message POP ESI,EDI,EBX,ESP JNC >L48 ;nc=return value in eax - don't call DefWindowProc L46: PUSH [ESP+18h],[ESP+18h],[ESP+18h],[ESP+18h] ;allowing for change of ESP CALL DefWindowProcA L48: POP EBP RET
Displaying Unicode with TextOutW
TextOutW is available on both W9x/ME and on NT/2000/XP. It accepts Unicode strings
as input for display.
It is therefore useful when making applications which can run on both platforms, since
the source code does not have to be different. However, if you want to display
non-Roman characters, W9x/ME requires some extra work to make sure that
the correct font is loaded. This is because an API can only display characters which are available within the font it is using. With XP this is all easier since XP is very smart at finding the correct font to use to display non-Roman characters.
With W9x/ME, as with DrawTextA, it is necessary to select a font containing the correct codepage/character set into the device context. Once this is done, the characters in the codepage are mapped to the Unicode characters correctly. In the code below, GetVersionA discovers which platform is being used and calls GET_ANSILANGFONT if necessary (the code for this is
given above). Note how in XP the size of string must include the null terminator. This seems to assist Windows to find the correct font automatically.
To see this test in the debugger use the breakpoint DRAW_UNICODE_TEXTOUT, then single-step through the code. Here is the source code used in this test:-
DATA SECTION ; UNICODE_STRING DUS 'Cheers! in Russian: За здоровье!',0 ; CODE SECTION ; UNICODE_TEXTOUT: PUSH 0,[hInst],951,[hWnd] ;parent=main window, id=951 PUSH 100,260 ;size (height, width) PUSH 50,50 ;position PUSH 50002C01h ;(CHILD+VISIBLE)+1=default push button PUSH 'Click me to see text drawn using TextOutW' ;text PUSH 'BUTTON' ;button class PUSH 0 ;extended window style CALL CreateWindowExA ;make button window, returning handle in EAX RET ; DRAW_UNICODE_TEXTOUT2: ;******************************* PUSH ADDR UNICODE_STRING CALL lstrlenW ;get length of unicode string in chars MOV EBX,EAX INC EBX ;make sure string includes the terminating null ;************** ebx now holds number of characters in the string ;************** now arrange for the text to be centered ;************** by getting size of string in SIZE structure PUSH ADDR SIZE,EBX,ADDR UNICODE_STRING,EDI CALL GetTextExtentPoint32W ;an api available on all platforms ;**** first the x-pos MOV EAX,[RECT+8h] ;get width of draw rectangle less 4 ADD EAX,4 ;allow for border removed earlier SUB EAX,[SIZE] ;less width of the characters SHR EAX,1 ;halve result for correct x-pos to centre the text ;**** and now the y-pos MOV EDX,[RECT+0Ch] ;get bottom of draw rectangle less 4 ADD EDX,4 ;allow for border removed earlier SUB EDX,[SIZE+4h] ;less height of the characters SHR EDX,1 ;halve result for correct x-pos to centre the text ;************** PUSH EBX ;number of characters in string PUSH ADDR UNICODE_STRING ;address of string PUSH EDX ;y coord PUSH EAX ;x coord PUSH EDI ;handle to device context CALL TextOutW ;write string to screen ;************** RET ; DRAW_UNICODE_TEXTOUT: PUSH [EBP+14h] ;handle to button CALL GetDC MOV EDI,EAX ;save handle of device context ;*************** rub out existing text MOV EBX,ADDR RECT PUSH EBX,[EBP+14h] ;handle to button CALL GetClientRect ;get in RECT the client area of pane MOV EDX,4 ADD [EBX],EDX ;left allow for border ADD [EBX+4h],EDX ;top allow for border SUB [EBX+8h],EDX ;right allow for border SUB [EBX+0Ch],EDX ;bottom allow for border ;*************** get ready to draw new text PUSH 15D ;COLOR_3DFACE CALL GetSysColorBrush ;get brush of face colour of 3 dimension objects PUSH EAX,ADDR RECT,EDI CALL FillRect ;fill child window with background colour (brush) PUSH 1,EDI ;transparent CALL SetBkMode ;*************** W9x and ME need special attention here MOV ESI,ADDR BUFFER MOV D[ESI],148 ;OSVERSIONINFO structure size PUSH ESI CALL GetVersionExA CMP D[ESI+10h],1 ;see from platform id, if NT,2000,XP and above JA >L10 ;yes, so no need to get font correct ;*************** W9x and ME version - ensure font with Cyrillic codepage is used MOV B[PROPOSED_CHARSET],204 ;Russian CALL GET_ANSILANGFONT ;get, in esi, the font to use PUSH ESI,EDI ;esi=handle to font, edi=device context CALL SelectObject ;select the font into the device context ;*************** push now to deselect later PUSH EAX,EDI ;eax=previous font handle, edi=device context CALL DRAW_UNICODE_TEXTOUT2 CALL SelectObject ;deselect font handle in dc PUSH ESI CALL DeleteObject ;delete the font (handle in esi) JMP >L12 ;*************** NT,2000,XP and above do not need special font handling L10: CALL DRAW_UNICODE_TEXTOUT2 L12: ;***************************************************************************** PUSH EDI,[EBP+14h] CALL ReleaseDC PUSH 4000 CALL Sleep RET
Displaying Unicode with ExtTextOutW
ExtTextOutW is also available on both W9x/ME and on NT/2000/XP. The following code is
similar to the code for TextOutW except that we can allow ExtTextOutW to fill-in the
background for us.
To see this test in the debugger use the breakpoint DRAW_UNICODE_EXTTEXTOUT, then single-step through the code. Here is the source code used in this test:-
DRAW_UNICODE_EXTTEXTOUT2: PUSH ADDR UNICODE_STRING CALL lstrlenW ;get length of unicode string in chars MOV EBX,EAX INC EBX ;make sure string includes the terminating null ;************** ebx now holds number of characters in the string ;************** now arrange for the text to be centered ;************** by getting size of string in SIZE structure PUSH ADDR SIZE,EBX,ADDR UNICODE_STRING,EDI CALL GetTextExtentPoint32W ;an api available on all platforms ;**** first the x-pos MOV EAX,[RECT+8h] ;get width of draw rectangle less 4 ADD EAX,4 ;allow for border removed earlier SUB EAX,[SIZE] ;less width of the characters SHR EAX,1 ;halve result for correct x-pos to centre the text ;**** and now the y-pos MOV EDX,[RECT+0Ch] ;get bottom of draw rectangle less 4 ADD EDX,4 ;allow for border removed earlier SUB EDX,[SIZE+4h] ;less height of the characters SHR EDX,1 ;halve result for correct x-pos to centre the text ;************** PUSH 0 PUSH EBX ;number of characters in string PUSH ADDR UNICODE_STRING ;address of string PUSH ADDR RECT ;clipping rectangle PUSH 2 ;options ETO_OPAQUE=2 PUSH EDX ;y coord PUSH EAX ;x coord PUSH EDI ;handle to device context CALL ExtTextOutW ;write string to screen RET ; DRAW_UNICODE_EXTTEXTOUT: PUSH [?BP+14h] ;handle to button CALL GetDC MOV EDI,EAX ;save handle of device context ;*************** rub out existing text MOV EBX,ADDR RECT PUSH EBX,[EBP+14h] ;handle to but?on CALL GetClientRect ;get in RECT the client area of pane MOV EDX,4 ADD [EBX],EDX ;left allow for border ADD [EBX+4h],EDX ;top allow for border SUB [EBX+8h],EDX ;right allow for border SUB [EBX+0Ch],EDX ;bottom allow for border ;************** set up background colour PUSH 15D ;COLOR_3DFACE CALL GetSysColor ;get face colour of 3 dimension objects PUSH EAX,EDI CALL SetBkColor ;insert new correct background colour in case writing opaque ;*************** W9x and ME need special attention here MOV ESI,ADDR BUFFER MOV D[ESI],148 ;OSVERSIONINFO structure size PUSH ESI CALL GetVersionExA CMP D[ESI+10h],1 ;see from platform id, if NT,2000,XP and above JA >L20 ;yes, so no need to get font correct ;*************** W9x and ME version - ensure font with Cyrillic codepage is used MOV B[PROPOSED_CHARSET],204 ;Russian CALL GET_ANSILANGFONT ;get, in esi, the font to use PUSH ESI,EDI ;esi=handle to font, edi=device context CALL SelectObject ;select the font into the device context ;*************** push now to deselect later PUSH EAX,EDI ;eax=previous font handle, edi=device context CALL DRAW_UNICODE_EXTTEXTOUT2 CALL SelectObject ;deselect font handle in dc PUSH ESI CALL DeleteObject ;delete the font (handle in esi) JMP >L22 ;*************** NT,2000,XP and above do not need special font handling L20: CALL DRAW_UNICODE_EXTTEXTOUT2 L22: ;***************************************************************************** PUSH EDI,[EBP+14h] CALL ReleaseDC PUSH 4000 ;4 second delay CALL Sleep RET
Using the Microsoft Layer for Unicode
The Microsoft Layer for Unicode ("mslu") is one way to make just one version of your
application which will run both under Windows 9x/ME ME platforms and also under NT/2000/XP.
The mslu is contained in a Dll called "unicows.dll". This is redistributable, so the
intention is that you will ship this with your executable for placement in the same folder
as your executable.
Basically unicows.dll acts as a wrapper around the ANSI APIs so that they can be called
as Unicode APIs under Windows 9x/ME. This helps reduce the complexity of your source code,
since you can call the Unicode APIs from your application directly.
For example, using mslu you can call CreateWindowExW under W9x/ME, passing Unicode strings.
The strings will be converted by unicows.dll to ANSI strings, and then unicows.dll will
call CreateWindowExA instead.
Under Windows NT/2000/XP, if your application is properly linked, unicows.dll will not
get involved at all and will not even be loaded into memory. A call to CreateWindowExW
will be a direct call to that API.
To achieve the above, at link-time special code is added to the application for
execution at load-time. When the application loads, this code identifies the platform
and if it is W9x/ME, it will divert all relevant API calls to unicows.dll.
The way Microsoft gets this loader code into your application is via unicows.lib at link-time. Unfortunately not all tools support this and applications written with some tools will still have to call unicows.dll even when running under NT/2000/XP. In this case the switching is done inside unicows.dll itself. Unicows.dll knows the version of Windows being used and will simply pass on the call to the appropriate system Dll. The disadvantage of this method is that when unicows.dll loads, it also loads a number of other Dlls on which it relies. This, and the extra switching involved, slows down the application.
Using GoLink you can add the mslu loader code very simply. Just add the /mslu switch to the GoLink command line or file. GoLink does not use unicows.lib but has its own code instead. Since GoLink is written wholly in assembler, this code works fast and is small, at less than 800 bytes plus a dword for each API within unicows.dll which has to be dealt with. This therefore provides a simple way to add an mslu loader to your application.
Testbug's unicows.dll test
In this test we make two calls to the API MessageBoxIndirectW. The first call is
made within Testbug.exe and the second within Testbug1.dll. Testbug.exe is linked
normally (no mslu loader), whereas Testbug1.dll contains an mslu loader. This loader
is called by the main executable (Testbug.exe) soon after it starts (see
below for an explanation of this).
If you are running
under Windows NT, 2000 or XP you will see a message box each time MessageBoxIndirectW
is called. This is because a working version of the API exists on those platforms within
User32.dll. If you are running under Windows 9x or ME, only the second message box will
appear. This is because the first call MessageBoxIndirectW (within Testbug.exe) simply
returns ERROR_CALL_NOT_IMPLEMENTED. This is because there is no mslu loader in Testbug.exe.
In the W9x/ME version of User32.dll the API MessageBoxIndirectW does exist, but it does
nothing except return with that value. The second call, however, is different. That
is made from Testdll1.dll, and that dll was linked to include the mslu loader. This
time the call is diverted to unicows.dll first, where the strings are translated to
ansi, and then MessageBoxIndirectA is called.
To single-step through the Unicows test in Testbug set the breakpoint to UNICODE_TEST1.
Here is the code you will see in Testbug.exe:-
UNICODE_TEST1: MOV ESI,ADDR MB_PARAMS ;structure for MessageBoxIndirectW MOV D[ESI],40D ;size of structure MOV EAX,[hWnd] MOV [ESI+4],EAX MOV EAX,[hInst] MOV [ESI+8],EAX MOV EAX,ADDR UTMESS1 ;message strings MOV [ESI+0Ch],EAX MOV EAX,ADDR UTMESS2 ;message strings MOV [ESI+10h],EAX MOV D[ESI+14h],40h ;information+ok button only PUSH ESI CALL MessageBoxIndirectW ;wait till ok pressed PUSH ADDR MB_PARAMS ;push on stack for next JMP UNICODE_TEST1AThen here is the code you will see in Testdll1.dll:-
UNICODE_TEST1A: POP ESI ;get structure into ESI MOV EAX,ADDR UTMESS1 ;message strings (in the dll) MOV [ESI+0Ch],EAX MOV EAX,ADDR UTMESS2 ;message strings (in the dll) MOV [ESI+10h],EAX PUSH ESI CALL MessageBoxIndirectW ;wait till ok pressed RET
START: PUSH 0 CALL GetModuleHandleA MOV [hInst],EAX CALL InitCommonControls ;ensure common control library is loaded CALL MSLU_LOADER