;----< see the UTF-8 byte order mark in an ordinary text editor ; ; HelloUnicode3 - copyright Jeremy Gordon 2004 ; ; SIMPLE WINDOWS GDI PROGRAM DEMONSTRATING UNICODE ; SWITCHING TECHNIQUES (and also MSLU) - for GoAsm ; ; Important! View this file using Internet Explorer, help ; or a Unicode editor (copy and paste it into the editor). ; ; This file is in UTF-8 format, but if it wasn't for the short ; "Hello World" in Russian (see BYE_MESS below) it could be ; in ordinary text format. It can produce an ANSI or a Unicode ; executable. It makes a window with a button entitled "Click ; me". When this is clicked the program then informs the user ; whether it is the ANSI, or the Unicode version of the program. ; If it is the Unicode version, it attempts to write the short ; Russian string. ; The Unicode version does not run on Windows 9x or ME unless ; you use the Microsoft Layer for Unicode (MSLU). Then, at ; run-time the Unicode APIs are thunked to their ANSI versions. ; To use the MSLU just add the /mslu switch to GoLink's commands ; and comment out the code in this file as mentioned at "START". ; You must ensure Unicows.dll (available from Microsoft) or from ; my website, is in GoLink's folder and that it is also in the ; same folder as the final program. ; ; -------------------------------------------------- ; ; Assemble using GoAsm HelloUnicode3 to produce the ANSI version. ; Assemble using GoAsm /d UNICODE HelloUnicode3 to produce the ; Unicode version (note that the word UNICODE is case sensitive ; because it is a word defined). ; This produces a PE COFF file. ; Then link using:- ; GoLink HelloUnicode3.obj user32.dll kernel32.dll gdi32.dll ; (add /debug coff if you want to watch the program in the debugger) ; (add /mslu if you want to use the Microsoft Layer for Unicode) ; ;------------------------------------------------------------------ ; #ifdef UNICODE ;if making UNICODE version STRINGS UNICODE ;all quoted strings to be Unicode DSS=DUS ;DSS is always to mean DUS (declare unicode sequence) AW=W ;make all switched APIs the Unicode version S=2 ;switched type and size indicator #else ;in other words, if ANSI STRINGS ANSI ;all quoted strings to be ANSI DSS=DB ;DSS is always to mean DB (declares a byte sequence) AW=A ;make all switched APIs the ANSI version S=1 ;switched type and size indicator #endif ; ;------------------------------------------------------------------ ; DATA SECTION ; hInst DD 0 ;to keep the handle to the process itself CLIENT_RECT DD 4 DUP 0 ;structure to hold rectangle PAINTSTRUCT DD 16 DUP 0 ;structure to hold stuff from Windows on WM_PAINT MSG DD 7 DUP 0 ;structure to hold messages from Windows as follows:- ;hwnd, +4=message, +8=wParam, +C=lParam, +10h=time, +14h/18=pt WNDCLASS DD 10D DUP 0 ;structure to send to RegisterClass holding data:- ;+0 window class style (CS_) ;+4 pointer to Window Procedure ;+8 no. of extra bytes to allocate after structure ;+C no. of extra bytes to allocate after window instance ;+10 handle to instance of this window class ;+14 handle to the class icon ;+18 handle to the class cursor ;+1C identifies the class background brush ;+20 pointer to resource name for class menu ;+24 pointer to string for window class name ; ;******************** LOGFONT structure and data initialisation LOGFONT STRUCT DD 0 ;height DB 19 DUP 0 ;attributes which can be zero DB 0 ;+17h charset DD 0 ;precision attributes DB S*32 DUP 0 ;+1Ch typeface LF_FACESIZE=32 (switched size) ENDS LF LOGFONT <16,?,204,?,'Microsoft Sans Serif'> ;height=16, 204=Russian character set, switched string for typeface ;specifying the character set can be omitted for Windows XP only version ; ;******************** Window message table ; (in a real program this would deal with many more messages) MESSAGES DD (ENDOF_MESSAGES-$-4)/8 ;=number to be done DD 1h,CREATE,2h,DESTROY,0Fh,PAINT,5h,SIZE DD 111h,PROCESS_COMMAND ENDOF_MESSAGES: ;label used to work out how many messages ;****************************************** ; ;this string holds a unique name (which can be anything) but it must be ;null terminated - the ANSI version requires just one null byte, Unicode ;requires two null bytes, so it is switched using DSS (you don't need to ;use DSS, anything can be used) WINDOW_CLASSNAME DSS 'WC',0 ;string to hold name of window class ; ;and here is a split string; the first part is switched using DSS and ;the second using conditional assembly ;and the third part using DSS again BYE_MESS: #ifdef UNICODE DUS '"Здравствуй Мир" from the Unicode' #else DB ' "Guten Tag, Welt" from the ANSI' #endif DSS ' version' ;the following gives the number of characters (not the number of bytes) BYE_MESSCHARS DD $-BYE_MESS/S ; ;this string and its null terminator will always be in Unicode, since it is ;used with MessageBoxW which is available both on Windows NT,2000,XP and W9x ;DUS (declare Unicode sequence) is a GoAsm special operator and is very ;useful when declaring Unicode sequences in for example, dialog templates ;in the data section SORRYWX_STRING DUS 'Sorry, your Windows platform is cannot use ' DUS 'the Unicode versions of the APIs deployed ' DUS 'by this program, unless you use the Microsoft ' DUS 'Layer for Unicode (MSLU) - see the GoLink manual',0 ; ;------------------------------------------------------------------ ALIGN 4 hButton DD 0 ;handle to the button CLICKMESS DSS 'Click ' ;Unless you use brackets, GoAsm does arith from left to right .. BUFFER DB SIZEOF CLICKMESS+3*S DUP 0 ;buffer size depends on switch ;size of this structure suits GetVersionExA not GetVersionExW ;(W not available on W9x/ME) .. OSVERSIONINFO DD 148 ;size of this simple structure DB 144 DUP 0 ;and remainder of the structure ; CODE SECTION ; INITIALISE_WNDCLASS: ;get ready for window MOV EBX,ADDR WNDCLASS MOV EAX,9 L1: MOV D[EBX+EAX*4],0 ;fill it with zeroes DEC EAX JNS L1 ;***** add things to window class for all windows in the program .. MOV EAX,[hInst] ;get handle to the process MOV [EBX+10h],EAX ;make it the window class PUSH 32512 ;IDC_ARROW common cursor PUSH 0 CALL LoadCursorA ;get in eax, handle to arrow cursor MOV [EBX+18h],EAX ;and give to WNDCLASS MOV D[EBX+1Ch],6D ;set background brush to COLOR_WINDOW+1 RET ; PREPARE_TEXT: ;prepare the correct button text (either ANSI or Unicode) MOV EDI,ADDR BUFFER MOV ESI,ADDR CLICKMESS MOV ECX,SIZEOF CLICKMESS ;this reports size of string as switched REP MOVSB ;put "Click me " string into buffer ;******* lets add the word "me" and then a null by hand .. ;******* in these sequences S is either 1(byte) or 2(word) ;******* and the character is also switched MOV S[EDI],'m' ADD EDI,S MOV S[EDI],'e' ADD EDI,S MOV S[EDI],0 ;null terminator (either 1 or 2 bytes) MOV EAX,ADDR BUFFER ;get the address of the text in eax RET ; ;******************* ;This procedure is called on the WM_SIZE message. SIZE: ;adjust position of button MOV EAX,[EBP+14h] ;get lParam into EAX AND EAX,0FFFFh ;get low word = new width of window client area MOV EDX,64 ;desired width of button SUB EAX,EDX ;get window width excluding button SHR EAX,1 ;get distance to x-pos to centre the button ;************ x-pos of button now in eax, width in edx MOV EBX,[EBP+14h] ;get lParam into EAX SHR EBX,16 ;get high word = new height of window client area MOV ECX,40 ;desired height of button SUB EBX,ECX ;get window height excluding button SHR EBX,1 ;get distance to y-pos to centre the button ;************ y-pos of button now in ebx, height in ecx PUSH 1,ECX,EDX ;repaint, height in pixels, width PUSH EBX,EAX,[hButton] ;y-pos, x-pos, button handle CALL MoveWindow ;only one version of this API XOR EAX,EAX ;return zero RET ; ;******************* ;This procedure is called on the WM_COMMAND message. PROCESS_COMMAND: MOV EAX,[EBP+14h] ;get handle of thing clicked OR EAX,EAX ;see if this is from a control JZ >.ignore ;no, don't know what it is then MOV EDX,[EBP+10h] ;get wParam SHR EDX,16D ;get hiword and see if BN_CLICKED (0) JNZ >.ignore ;no CMP EAX,[hButton] ;see if the button was clicked JNZ >.ignore ;no, don't know what it is then PUSH EAX CALL DestroyWindow ;destroy the button - be ruthless MOV D[hButton],0 ;rub out its handle PUSH 1,0,[EBP+8h] ;EBP+8h=handle to main window CALL InvalidateRect ;and we must cause repaint of whole window .ignore XOR EAX,EAX ;return zero RET ; ;******************* ;This procedure is called on the WM_CREATE message. CREATE: ;one of the few messages dealt with by this prog PUSH 0,[hInst],6D,[EBP+8h] ;id=6, parent=main window (handle in EBP+8h) PUSH 0,0 ;size done later (on WM_SIZE) PUSH 0,0 ;position done later (varies with WM_SIZE) PUSH 50000000h ;(CHILD+VISIBLE) PUSH 0 ;text (if written now would be wrong font) PUSH 'BUTTON' ;button class (switched format null-terminated string) PUSH 0 ;extended window style CALL CreateWindowEx##AW ;make button window, returning handle in EAX MOV [hButton],EAX ;keep button handle ;******************* get appropriate font to use PUSH 12D CALL GetStockObject ;get handle to ANSI_VAR_FONT held by windows PUSH 0,EAX,30h,[hButton] ;0=no redraw, ansifont, WM_SETFONT, handle CALL SendMessage##AW ;use it to write the text following (switched API) ;******************* CALL PREPARE_TEXT ;prepare the correct button text (either ANSI or Unicode) PUSH EAX,0,0Ch,[hButton] ;eax=address of text, 0Ch=WM_SETTEXT CALL SendMessage##AW ;add the button text now (switched API) XOR EAX,EAX ;return zero to make window RET ; ;******************* ;This procedure is called on the WM_DESTROY message. DESTROY: ;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 ; ;This procedure is called by the PAINT procedure PAINT_TEXT: ;******************* 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 width SUB EAX,258 ;assumed width of text ;in practice you would measure this JNC >L0 XOR EAX,EAX ;window too thin make eax=0 L0: SHR EAX,1 ;halve the difference MOV ESI,EAX ;give that to esi for x-pos MOV EAX,[EBX+0Ch] ;get available height SUB EAX,16 ;height of text JNC >L1 XOR EAX,EAX ;window too narrow make eax=0 L1: SHR EAX,1 ;halve the difference MOV EBX,EAX ;give that to ebx for x-pos ;******************* set the font and colour PUSH ADDR LF ;see LOGFONT initialisation in data CALL CreateFontIndirect##AW ;get font handle in eax (switched API) ;*** now, you can't do this in "C" .. PUSH EAX ;push font handle now for DeleteObject later ;******************* PUSH EAX,EDI ;font handle, device context (dc) CALL SelectObject ;select font handle into dc PUSH EAX,EDI ;push old font, dc, for SelectObject later ;******************* PUSH 0CC3333h,EDI ;colour, device context CALL SetTextColor ;******************* PUSH [BYE_MESSCHARS] ;length of message in characters PUSH ADDR BYE_MESS ;address of string PUSH EBX,ESI,EDI ;y coord, x coord, dc CALL TextOut##AW ;write string to screen (switched API) ;******************* CALL SelectObject ;deselect font from dc CALL DeleteObject ;and delete it ;******************* RET ; ;This procedure is called on the WM_PAINT message. PAINT: PUSH ADDR PAINTSTRUCT,[EBP+8h] ;EBP+8h=handle of window CALL BeginPaint ;get device context to use, initialise paint CMP D[hButton],0 ;see if button is still there JNZ >.donothing ;yes, do nothing having removed WM_PAINT from queue MOV EDI,EAX ;keep device context in edi CALL PAINT_TEXT .donothing PUSH ADDR PAINTSTRUCT,[EBP+8h] ;EBP+8h=handle of window CALL EndPaint XOR EAX,EAX ;don't go to DefWindowProc too RET ; ;********** this is a general window procedure which in an ordinary ;********** program deals with all messages sent to the window 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 ADD EDX,4 ;jump over size dword L2: DEC ECX JS >L3 CMP [EDX+ECX*8],EBP ;see if its the correct message JNZ L2 ;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 >L4 ;nc=return value in eax - don't call DefWindowProc L3: PUSH [ESP+18h],[ESP+18h],[ESP+18h],[ESP+18h] ;allowing for change of ESP CALL DefWindowProc##AW ;(switched API) L4: POP EBP RET ; ;******************* This is the actual window procedure WndProcTable: MOV EDX,ADDR MESSAGES ;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 ; ;******************************************************************* START: ;-------------- to use /mslu comment out this code up to the #endif ;-------------- specify /mslu to the linker (GoLink) ;-------------- ensuring Unicows.dll is in GoLink's folder ;-------------- and is also shipped with the program #ifdef UNICODE ;only assemble this if UNICODE version (to #endif) MOV ESI,ADDR OSVERSIONINFO ;simple structure for GetVersionExA PUSH ESI CALL GetVersionExA ;ANSI version of the API (works on all platforms) CMP D[ESI+10h],1 ;see from platform id if NT,2000,XP and above JA >L6 ;yes, it's Unicode version on NT,2000,XP so proceed ;************************ no its Unicode version on W95/98/ME .. PUSH 40h ;information + ok button PUSH 'HelloUnicode3' ;string is Unicode because of STRINGS UNICODE PUSH ADDR SORRYWX_STRING PUSH 0 ;make it child of desktop CALL MessageBoxW ;wait till ok pressed PUSH 0 ;exit code = FALSE (failure) CALL ExitProcess ;return to Windows in the manner it prefers L6: #endif ;************************ Either ANSI version, or Unicode version on NT,2000,XP PUSH 0 CALL GetModuleHandle##AW ;get handle to the process (switched API) MOV [hInst],EAX ;record the handle CALL INITIALISE_WNDCLASS ;get ready to register window class, WNDCLASS=ebx ;********** now register the window class to be used by the program MOV D[EBX],1h+2h+40h ;CS_VREDRAW+CS_HREDRAW+CS_CLASSDC (window class style) MOV D[EBX+4],ADDR WndProcTable ;window procedure MOV D[EBX+24h],ADDR WINDOW_CLASSNAME ;window class name PUSH EBX ;address of structure with window class data CALL RegisterClass##AW ;register the window class (switched API) ;********** now make the window PUSH 0,[hInst],0,0 ;owner=desktop PUSH 200D ;height PUSH 360D ;width PUSH 50D,50D ;position y then x ;***** get the window style - you could use equates in include file here PUSH 90000000h +0C00000h+40000h +80000h +20000h +10000h +2000000h ;(POPUP+VISIBLE)+CAPTION+SIZEBOX+SYSMENU+MINIMIZEBOX+MAXIMIZEBOX+CLIPCHILDREN ;the window title sent here is switched - using the STRINGS directive PUSH 'HelloUnicode3 window made by GoAsm' ;window title (switched format) PUSH ADDR WINDOW_CLASSNAME ;window class name (switched) PUSH 0 ;extended window style CALL CreateWindowEx##AW ;make window, returning handle in EAX (switched API) ;************************ now enter the main message loop .messageloop PUSH 0,0,0 PUSH ADDR MSG CALL GetMessage##AW ;wait for message from Windows (switched API) OR EAX,EAX ;see if it is WM_QUIT JZ >.quit ;yes PUSH ADDR MSG CALL TranslateMessage ;no so convert message to character if necessary PUSH ADDR MSG CALL DispatchMessage##AW ;send the message to window procedure (switched API) JMP .messageloop ;after message dealt with, loop back for neMt one .quit ;message was WM_QUIT PUSH [hInst],ADDR WINDOW_CLASSNAME CALL UnregisterClass##AW ;ensure class is removed (switched API) PUSH [MSG+8h] ;exit code (send contents of wParam) CALL ExitProcess ;return to Windows in the manner it prefers