I'm new to MASM, but I have experience in high level languages (VB, C++).
If you haven't read my intro in the Soap Box, I am teaching myself Win32 programming in C++ and assembly. A program that I thought would be interesting to write is one that solves Sudoku puzzles. I have completed the C++ version, and I'm now working on the assembly version.
My program creates a 9x9 grid of EDIT boxes. As numbers are entered in, the font size changes to fit the box appropriately via the EN_CHANGE notification code. This works correctly in MASM. In the process of troubleshooting an issue I had with assigning the control identifiers to the EDIT boxes, I populated the identifiers into their respective boxes. I realized then that the font size wasn't being changed on every other box. I don't know why this is. See the OnCreate proc. I do not have this problem in C++. I have tried switching between using registers (very new to me) and local variables. I have read the "Register Preservation Convention" help topic in MASM. I have limited my use of the registers to eax, ecx, and edx.
I was having the same problem in my OnClearBtn proc when I was using bx. I changed it to use a local variable, and it works correctly now. I've tried to do the same thing with my OnCreate proc, but it still does not work. I'm stumped as to what's causing it to work only half the time.
Is there a way to step through with MASM and watch variable/register values?
I am including a zip file with the full source code file and a screenshot of the window with inconsistent font sizes.
Thanks in advance!
Ryan
SetFont proc hEdit:HWND,iSize:dword
; From MSDN help on CreateFont function
; nHeight = -MulDiv(PointSize, GetDeviceCaps(hDC, LOGPIXELSY), 72);
; Works with CreateButton and OnClearBtn procs
; Fails on every other call from OnCreate
invoke GetDC,hEdit
invoke GetDeviceCaps,eax,LOGPIXELSY
invoke MulDiv,iSize,eax,72
neg eax
invoke CreateFont,eax,0,0,0,0,0,0,0,0,0,0,0,0,0
invoke SendMessage,hEdit,WM_SETFONT,eax,TRUE
ret
SetFont endp
OnCreate proc hWnd:HWND
; Called from WM_CREATE msg
local coords[9]:dword
local txtID:word
local hEdit:dword
; Code to initialize coords array removed for brevity
; ch register is the row counter, cl is the column
; cx is used for the control identifier
; txtID local variable is used to preserve cx around API calls
mov ch,9
rowloop:
mov cl,9
colloop:
mov txtID,cx
; Array indexes need to be full 32-bit registers?
; Is there a better way?
; Copy ch and cl into eax and edx respectively
xor eax,eax
mov al,ch
xor edx,edx
mov dl,cl
invoke CreateWindowEx,0, addr EditClass, 0,\
WS_VISIBLE or WS_CHILD or WS_TABSTOP or WS_BORDER or ES_CENTER or\
ES_AUTOHSCROLL or ES_AUTOVSCROLL or ES_NUMBER or ES_MULTILINE,\
coords[edx*4-4], coords[eax*4-4], BOX_SIZE, BOX_SIZE, hWnd, txtID, 0, 0
mov hEdit,eax ; hEdit stores the handle to the EDIT box
invoke SetFont,hEdit,8 ; Fails on every other EDIT box. No idea why.
invoke SendMessage,hEdit,EM_LIMITTEXT,9,0
invoke GetDlgCtrlID,hEdit ; Verify that the ID was set correctly
invoke SetDlgItemInt,hWnd,eax,eax,TRUE ; Display it in the box
mov cx,txtID
dec cl
jnz colloop
dec ch
jnz rowloop
invoke CreateButton,hWnd,addr SolveButtonText,btnSolve,15
invoke CreateButton,hWnd,addr ClearButtonText,btnClear,100
ret
OnCreate endp
OnClearBtn proc hWnd:HWND
local txtID:word
; ch register is the row counter, cl is the column
; cx is used for the control identifier
; txtID local variable is used to preserve cx around API calls
; I was using the bx register with the 'uses' clause in the proc header
; The SetFont proc (CreateFont) was working consistently on every other EDIT box
; Now works correctly every time with local variable :)
mov ch,9
clearRloop:
mov cl,9
clearCloop:
mov txtID,cx
invoke SetDlgItemText,hWnd,txtID,0
invoke GetDlgItem,hWnd,txtID
invoke SetFont,eax,14
mov cx,txtID
dec cl
jnz clearCloop
dec ch
jnz clearRloop
ret
OnClearBtn endp
Hi Ryan,
Have a look at the attached executable, and especially at the result (eax) of the WM_SETFONT call.
What exactly is the problem? The font size looks ok to me. The odd thing is that the SendMessage WM_SETFONT returns zero every now and then, but then, according to MSDN, it does "not return a value" ::)
Changed one thing to get this result:
(http://www.gunnerinc.com/images/sudoku.png)
Is this what you are after? The parameters of most API calls are DWORD, if it expects a WORD size, it will tell you.
Change txtID to a DWORD and use ecx instead of cx
Yep, Gunner got it :U
Still, check if you cannot create 4 or 5 fonts once, instead of recreating them every time.
Great start for a first day project, Ryan :U
Thank you Gunner!
It's ironic because the control identifier is a 16-bit word in wParam in the Windows procedure. It didn't have anything to do with setting the font size, but the CreateWindowEx function. DOH!
Any idea why it worked on half, but not the other?
Thanks for the compliment jj, but I've been working on it on and off for a couple weeks.
For creating the fonts, do you mean to have them defined in the .data section so I don't have to create them each time, just reference the global instance? That sounds like a good idea! Thanks!
Yeah... in C++ I had to cast the identifier to an HMENU.
you can declare them in the uninitialized data section (.DATA?) as type HFONT
.DATA?
hFont1 HFONT ?
hFont2 HFONT ?
hFont3 HFONT ?
hFont4 HFONT ?
hFont5 HFONT ?
notice that MASM isn't as picky about types as C is :P
you could declare them as DWORD's and it would make little difference
.DATA?
hFont1 dd ?
hFont2 dd ?
hFont3 dd ?
hFont4 dd ?
hFont5 dd ?
I was able to correct the font size problem by replacing the SetFont procedure with this:
SetFont proc hEdit:HWND,iSize:dword
invoke GetDC, 0
invoke GetDeviceCaps, eax, LOGPIXELSY
mul iSize
xor edx, edx
mov ecx, 72
div ecx
invoke CreateFont,eax,0,NULL,NULL,FW_NORMAL,0,NULL,NULL,
DEFAULT_CHARSET,OUT_TT_PRECIS,CLIP_DEFAULT_PRECIS,
PROOF_QUALITY,DEFAULT_PITCH or FF_DONTCARE,
NULL
ret
SetFont endp
And no other changes.
Michael,
You don't think that could cause a resource leak, creating fonts many times without ever deleting them? Just curious...
Hi Jochen,
I didn't even consider that, because I would create the font only once.
Thanks Michael.
I see you expanded the MulDiv function into assembly. I'm curious about one thing: The example I copied from the MSDN help negated the result before passing it to CreateFont. Is that completely unnecessary?
Per the CreateFont documentation, the function interprets the font height as:
> 0 The font mapper transforms this value into device units and matches it against the cell height of the available fonts.
0 The font mapper uses a default height value when it searches for a match.
< 0 The font mapper transforms this value into device units and matches its absolute value against the character height of
So the height is matched against cell height or matched against character height. I think that could make a difference under some circumstances, but for my test code there was no apparent difference. For a character in a fixed-size control matching against the cell height seems like a better choice to me.
Please forgive my ignorance, but what does cell height mean in this context, and more to the point, how does it affect my font size that I am using? I assume I will have to adjust my numbers since the scaling is different. I tried to research it on my own, but Google searches are leading to many topics regarding Excel.
also...
the user has selected a set of fonts to use for display
you can use SystemParametersInfo to fill a LOGFONT structure with the selected font
well, acutally, the NONCLIENTMETRICS structure has 5 LOGFONT structures :P
the lfMessageFont member is probably the one you want
LOCAL ncm:NONCLIENTMETRICS
mov ecx,sizeof NONCLIENTMETRICS
mov ncm.cbSize,ecx
INVOKE SystemParametersInfo,SPI_GETNONCLIENTMETRICS,ecx,addr ncm,0
once you have the LOGFONT filled in, you can set the point size as Jochen suggested, then use CreateFontIndirect
mov ncm.lfMessageFont.lfHeight,8
INVOKE CreteFontIndirect,addr ncm.lfMessageFont
mov hFont8,eax
now you have a handle to a font that the user selected, but the size that you chose
when you are done using the font, delete the handle
INVOKE DeleteObject,hFont8
you can use GetDeviceCaps to get the "pixels per logical inch" setting
then use a little formula to convert between point size and height
normally, the LOGPIXELSY value is 96 dpi - but don't count on it
LOCAL hdcDesktop :HDC
LOCAL uLogPixelsY :UINT
INVOKE GetDC,HWND_DESKTOP
mov hdcDesktop,eax
INVOKE GetDeviceCaps,eax,LOGPIXELSY
mov uLogPixelsY,eax
INVOKE ReleaseDC,HWND_DESKTOP,hdcDesktop
once you have the "pixels per logical inch" value...
lfHeight = -(PointSize * uLogPixelsY) / 72
i find it easier to stick a constant into the lfHeight member, like 8 :P
if i want it larger, i stick a larger number in there - lol
in the end, what you are after is a font that will allow your text to fit in a box
the other approach is to use GetTextExtentPoint32 to measure the font and make your boxes fit the text
Per dedndave and jj's suggestion, I declared global variables in the data? section to hold the font handles that I use in the program, instead of creating a new font for each change.
InitFont proc fontSize:dword
local hdcDesktop:HDC
invoke GetDC,0
mov hdcDesktop,eax
invoke GetDeviceCaps, eax, LOGPIXELSY
invoke MulDiv,fontSize,eax,72
neg eax
invoke CreateFont,eax,0,0,0,0,0,0,0,0,0,0,0,0,0
invoke ReleaseDC,0,hdcDesktop
ret
InitFont endp
This gets executed once at the beginning for each font size I use. I took into account dedndave's last example of invoking ReleaseDC at the end of the proc. Releasing the DC appears to void my fonts that I create. If I comment out the invoking of ReleaseDC, things work as expected.
How crucial is it that I release the DC?
hmmmm....
something is amiss :P
the only reason you need the DC is for GetDeviceCaps
releasing it should have no affect on the font handles
but - try this, anyways
InitFont proc fontSize:dword
local hdcDesktop:HDC
invoke GetDC,0
mov hdcDesktop,eax
invoke GetDeviceCaps, eax, LOGPIXELSY
push eax
invoke ReleaseDC,0,hdcDesktop
pop eax
invoke MulDiv,fontSize,eax,72
neg eax
invoke CreateFont,eax,0,0,0,0,0,0,0,0,0,0,0,0,0
ret
InitFont endp
EDIT: i see what was happening
the font handle you returned in EAX was being destroyed by the ReleaseDC call :P
API calls may (and usually do) trash EAX, ECX, and EDX
so - this would also work
InitFont proc fontSize:dword
local hdcDesktop:HDC
invoke GetDC,0
mov hdcDesktop,eax
invoke GetDeviceCaps, eax, LOGPIXELSY
invoke MulDiv,fontSize,eax,72
neg eax
invoke CreateFont,eax,0,0,0,0,0,0,0,0,0,0,0,0,0
push eax
invoke ReleaseDC,0,hdcDesktop
pop eax
ret
InitFont endp
Ah. That makes sense. Thank you!