News:

MASM32 SDK Description, downloads and other helpful links
MASM32.com New Forum Link
masmforum WebSite

Scrolling issues

Started by NoCforMe, October 13, 2011, 06:15:11 PM

Previous topic - Next topic

NoCforMe

I'm learning how to scroll in Windows, and have successfully created a program that uses that capability; it's a simple (so far) hex file viewer.

It works, but there are issues. I was really happy, of course, when I got my little hex-view window to scroll correctly (for the most part). I tried it out with several files, and it seemed to work fine, so I thought "Hey, I can be the next high-tech millionaire!". (Not really, just joking.)

Then I tried it out with a big (<2MB) file. Its performance sucks.

The program is attached here (with standard MASM32 pathnames). I'd appreciate any look-sees and suggestions.

Here are the issues:

  • Scrolling is extremely sluggish on large files. I think this is due to a couple of things:
    • I took my scroll code largely from this MSDN page. Normally, I don't like to do this, as I really don't completely understand what they're doing there, but I was having problems trying to implement this on my own, so I copied their code, more or less. I simplified it somewhat; there's no horizontal scrolling, and I changed the way I compute the starting position. Plus I'm not using their method of only painting the invalidated rect; I redraw all the text in the window. More below.
    • I did the simplest possible thing with memory allocation: I get the size of the file, then allocate a buffer this size using HeapAlloc(). Obviously, this isn't the most efficient way to manage memory.
  • It scrolls correctly. However, sometimes there's an extra blank line at the end of the window.
  • It's easy to crash it with a large file, for instance by trying to drag the thumb. More below.
The MSDN sample code is actually pretty clever: what they do is to get the current scroll position (GetScrollInfo() ), calculate what they think should be the new scroll position and set it (SetScrollInfo() ), then call GetScrollInfo() again and use the adjusted new position that Windows has calculated. Then then compare the original saved positions with the new calculated ones, and use the new one to actually scroll the window, if the position has changed.

One problem is that this involves a lot of overhead: 4 calls to scrolling functions, plus UpdateWindow(), for each scroll message received. This may also explain wiy it's easy to crash: on a large file, the messages pile up as the system grinds away, leading to conflicting conditions. Is there a simpler (or better) way to do this?

I notice that the MASM32 programs that handle text (like the "quick editor") scroll text very quickly and nicely, as does Notepad. The large files I was using are the MASM listing files from assembling my programs, which come out to just over 2 megabytes.

I originally thought that I could handle the scroll position internally by keeping track of a variable (say, ScrollPos) in my message handler, incrementing or decrementing it as needed in response to scroll up/down by line/page requests. Maybe this is still the way to go.

I'm confused, at this point, by what appears to be two measurement systems in use here. As I understand it (not sure if I do), the scroll position (for text) is an integer quantity, specified in numbers of lines. At least it seems to behave that way; if you look at my code, you'll see that I'm using a line count as the scroll position. (Of course, for a window where an image is scrolled, the scroll position is in terms of pixels, or display units.) In the example MSDN code, they derive the scroll amount per line from the height of a line of text (from GetTextMetrics() ).

While I can track scroll up/down requests pretty easily, the one request that's not so easy to handle is SB_THUMBTRACK, where you have to read the current scroll position from the SCROLLINFO structure and use it to position the scrollbar.

Another thing: the example uses ScrollWindow() to scroll, rather than SetScrollInfo(). Good? Bad?

My program works: File--> Open to open a file (any type, binary, text, doesn't matter) to view its contents. You can select another file afterwards, and so on.

One nice thing: I hadn't planned on this, but somehow things work out so that if the file is smaller than will fit on one page, there's no scroll bar! I had actually wanted this but hadn't explicitly put anything in the code. I suspect what's happening is that in this case, the parameters I'm passing (to SetScrollInfo() in the main window proc) are invalid, so Windows doesn't put in a scroll bar at all.

Notes about my code: a little bit complicated. Most complex part is drawing the hex display lines. I use a formatted display line, into which I stuff the current character from the file, first as 2 hex digits, then as an ASCII character. The heavy lifting here is done by PutHexChar(), which receives its parameters in registers. That routine is a little ugly, since one thing it has to do is check each character  to see if it's beyond EOF, since it's always called 16 times per line, and blank out any display positions at an beyond EOF (this only happens on the last line). It works correctly, but there may be a better way to implement this. At any rate, someone in there has to continually monitor for EOF.

I'm using TextOut() to display the line of text. Would DrawText() or some other function be better?

That's enough for now.

dedndave

QuoteOne nice thing: I hadn't planned on this, but somehow things work out so that if the file is smaller than will fit on one page, there's
no scroll bar! I had actually wanted this but hadn't explicitly put anything in the code. I suspect what's happening is that in this case,
the parameters I'm passing (to SetScrollInfo() in the main window proc) are invalid, so Windows doesn't put in a scroll bar at all.

that happens automatically when you use "system scroll bars"
i.e. scrollbars created by adding style bits to the CreateWindow call
if you create the bars yourself, you have to turn them off and on

as for handling large files, the scroll code, itself, is probably not so much to blame
it is likely to be problems with how the file/memory is handled and the WM_PAINT handler - how you display text

NoCforMe

Regarding the WM_PAINT handler, yes, it's doing a little more than just plopping text onto the screen.

On the other hand, I've tried to optimize this code. It seems fairly minimal: some address calculations, a per-character loop, and a subroutine that does two hex conversions and stores (XLATB/STOSB), a simple character store, and that's it. For each WM_PAINT message, there are these initial system calls:


BeginPaint()
SelectObject() (to set the display font)
GetClientRect()
GetScrollInfo()

and of course

EndPaint()


There's only one Windows function (TextOut() ) called per line (16 bytes). Does that seem like little enough overhead? or could that be causing the sluggishness?

I think it's the memory issue that's really slowing things down.

How else to display a file? Read it into a small buffer and refresh the buffer as needed? Use a memory-mapped file?

NoCforMe

To anyone who downloads this and tries to put it together: I forgot to change the path in the #include in the resource file. Easily fixed. Sorry 'bout that.

Is the source code for the editor supplied with MASM32 ("Small Memory Footprint Editor"/Quick Editor 4.0") available? (I guess I'm directing this question at Hutch.) I'd be very curious to see how scrolling is done there. Also what memory-management scheme is used.

dedndave

it hangs pretty easily
i got it to hang by scrolling the ASM file
when i started learning about windows and scrolling text, i was told that ScrollWindow was sluggish
so, i have no experience with it
but, having looked at that part of the code, i don't see an issue
the crash error is good ole C0000005h - access violation
that generally means a pointer is out of whack - like trying to read or write to/from address = 00000000h, or some similar illegal address

NoCforMe

How do you retrieve the crash errors?

I tried looking for error logs (in Control Panel--> Administrative Tools--> Event Viewer--> Applicaton Log), but for some weird reason it reports 5,002 events but they're all BLANK?!?!?.

qWord

FPU in a trice: SmplMath
It's that simple!

dedndave

not sure how that works under win 2000 - maybe Steve or Michael can give you some tips
under XP and above, Dr Watson pops up and tells you where you went wrong

... or you can use Olly, like qWord said   :P

NoCforMe

OK, now I'm confused. This scrolling code works (vPos is the scrollbar position read from GetScrollInfo() before doing the scroll, scrlInfo.nPos is the position value read back after setting the position with SetScrollInfo()-GetScrollInfo() ):



MOV EAX, vPos
MOV EDX, scrlInfo.nPos
CMP EAX, EDX
JE hx_noscroll
; Scroll window if changed:
SUB EAX, EDX
MUL LineHeight
INVOKE ScrollWindow, hWin, 0, EAX, NULL, NULL
INVOKE UpdateWindow, hWin



(but results in crashes with larger files)

However, none of the following work, even though it seems that they should:


#1:
SUB EAX, EDX
MUL LineHeight
MOV scrlInfo.nPos, EAX
MOV scrlInfo.fMask, SIF_POS ;Change pos. only
INVOKE SetScrollInfo, hWin, SB_VERT, ADDR scrlInfo, TRUE
INVOKE UpdateWindow, hWin

#2:
SUB EAX, EDX
MUL LineHeight
INVOKE ScrollWindowEx, hWin, 0, EAX, NULL, NULL, NULL, NULL, SW_INVALIDATE
INVOKE UpdateWindow, hWin

#3:
SUB EAX, EDX
MUL LineHeight
MOV scrlInfo.nPos, EAX
MOV scrlInfo.fMask, SIF_POS ;Change pos. only
INVOKE SetScrollPos, hWin, SB_VERT, EAX, TRUE
INVOKE UpdateWindow, hWin



(I also tried using InvalidateRect() instead of UpdateWindow(), but it didn't help.)

I'm confused because the description of ScrollWindow() (and ScrollWindowEx() ) says that it scrolls the contents of the specified window. However, I'm already redrawing all the text in my window; it seems to me all I want is to have Windows redraw the scrollbar itself, not my text. And yet none of those last 3 do anything (the scrollbar become completely inoperative).

I may be making some dumb mistake elsewhere, but this is a puzzle. Maybe that's why it's crashing, because both Windows and my program are redrawing the text in the window.


dedndave

UpdateWindow only sends a WM_PAINT message if there is an update region
you have to tell the OS that you have invalidated the display area (or a portion of it)
you can use InvalidateRgn or InvalidateRect - or another method that i can't remember - lol
then, UpdateWindow

you have to read a lot of stuff   :P
http://msdn.microsoft.com/en-us/library/dd162759%28v=vs.85%29.aspx

you can see if that's the case by minimizing the window, then restoring it
or by running another window over it and away
you will see the updates occur then, because the OS tells it there is an update region

jj2007

Quote from: NoCforMe on October 13, 2011, 06:29:56 PM
Read it into a small buffer and refresh the buffer as needed? Use a memory-mapped file?

Yes and maybe. All you need is a small buffer with your binary "data window", and an edit control that displays the selection. Simple and blazing fast, and memory-mapped files would not change that, it's an overkill.

To give you an idea of the logic, here a console mode demo. Output:
0        18 00 00 00 EC 15 40 00 39 00 DA 00 18 00 00 00
1        E0 15 40 00 69 00 DE 00 18 00 00 00 EC 15 40 00
2        69 00 DA 00 18 00 00 00 E0 15 40 00 79 00 DE 00


For your app, instead of printing move the text into a small buffer, then use WM_SETTEXT to display it.

include \masm32\MasmBasic\MasmBasic.inc   ; download
   Init
   Let edi=
New$(4096)   ; make a buffer
   
Open "U", #1, "\masm32\bin\ml.exe"   ; you better not write to that one ;-)
   
Seek #1, 8192   ; put file pointer somewhere
   
Input #1, edi, 4096
   Close #1
   xor ecx, ecx
   mov esi, edi
   .Repeat
      Print Str$(ecx), Tb$   ; print the line counter
      inc ecx
      REPEAT 4
         lodsd   ; get the next 4 bytes from the data window
         xchg eax, ebx
         REPEAT 4
            movzx eax, bl   ; isolate lowbyte 4 times
            shr ebx, 8
            add eax, 100h   ; avoid trimming of bytes le 0Fh
            Print " ", Right$(Hex$(eax), 2)
         ENDM
      ENDM
      Print
   .Until ecx>20   ; 20 lines is enough for a demo
   

   Inkey "-- ok --"
   Exit
end start


NoCforMe

OK, but are you suggesting that I open and close the file for each page? Wouldn't that be really slow? Or does Windows optimize file access enough so that would actually be fast? (Also, would you mind un-translating that back to assembly language/WinAPI functions? I dunno what "Input #1" means to Windoze ...)

Regarding the scrolling issues, I got all those 3 cases above to work. The problem was that I was using the approach used by that MSDN example without really understanding it. They were scrolling the window (using ScrollWindow() ) in device units, when what I really need are just line units (as in scroll up/down by 1 or n lines). It's actually much simpler than the example. Now they all work fine.

Except that the thing still crashes on larger files. Must be something else wrong (out-of-bounds pointer somewhere).

[later that same day, in the pirates' grotto ...]

OK, problem solved. It was, once again, my fault. I was reading the whole file every time I displayed a page of text. I was actually lucky, in that my read pointer was going out bounds almost every time, but Windows (or Intel) was being forgiving, so apparently I hadn't strayed into forbidden territory, until Boom!. So that's all fixed.

And the scroll is now plenty fast. Large files no problem. Check out new version. Couple added controls; it's almost a usable tool.

So is it OK to allocate a lot of heap? I guess I could use a general-level tutorial on Windows memory management. (Still waiting to hear back from jj about that example you posted above.)

I eventually want to add editing capabilities.

jj2007

Quote from: NoCforMe on October 14, 2011, 02:52:18 AM
OK, but are you suggesting that I open and close the file for each page? Wouldn't that be really slow? Or does Windows optimize file access enough so that would actually be fast? (Also, would you mind un-translating that back to assembly language/WinAPI functions? I dunno what "Input #1" means to Windoze ...)

Assembly language is language that gets assembled using standard assemblers. Input #1 is assembly language - not by accident this forum is called Masm32, not Asm32 :bg

To answer your question, the macro uses ReadFile

Quote
(Still waiting to hear back from jj about that example you posted above.)

It's 7:00 am here. If you are impatient, next time just launch Olly

NoCforMe

On another subject, if I wanted to make my hex viewer able to edit, would it be easiest just to use an edit control? Seems like a minor nightmare trying to implement a mouse text-selection interface on my own.

NoCforMe

OK, here's a better working copy. Most bugs fixed.

Hey, Dave, I thought you might like this routine I cooked up to convert text input in hex to binary:



;=====================================================
; ConvertAddress()
;
; Converts a buffer to a binary number (decimal or hex input).
;
; On entry,
;  EAX--> string to convert (zero-terminated)
;
; On exit,
;  EAX: TRUE if valid characters found, FALSE otherwise
;  If valid, result is stored in NewAddress
;
; TBD: decimal conversion
;=====================================================

ConvertAddress PROC
LOCAL buffer[20]:BYTE

PUSH ESI
PUSH EDI
PUSH EBX
PUSH ECX
LEA EDI, buffer
XOR DL, DL ;Zero char. counter
; Select conversion based on setting of HexFlag:
; CMP HexFlag, TRUE
; JE cadHex
;
; TBD Do decimal conversion

cadHex: MOV ESI, EAX ;Address of string
cadH10: LODSB
OR AL, AL ;End o'string?
JZ cadHOK
CMP AL, '0'
JB cad777 ;No good.
CMP AL, '9' ;Numeric?
JBE cadH20 ;Yes, stuff char.
CMP AL, 'A'
JB cad777 ;No good.
CMP AL, 'f'
JA cad777
CMP AL, 'F'
JBE cadH18 ;'A'-'F'
CMP AL, 'a'
JB cad777
AND AL, 5FH ;Make uppercase.
cadH18: SUB AL, 7 ;Convert A-F--> '10-15'
cadH20: SUB AL, '0'
STOSB
INC DL ;Bump count.
JMP cadH10 ;Keep scanning.
cadHOK: CMP DL, 8 ;8 chars. max.
JA cad777 ;Too many.
OR DL, DL ;But need at least 1
JZ cad777
; Finally, we have 1-8 valid hex chars: let's convert 'em!
XOR EBX, EBX ;EBX is accumulator
XOR CL, CL ;Zero shift amount
cadH30: XOR EAX, EAX ;Clear entire reg.
MOV AL, BYTE PTR [EDI - 1] ;Next char. to left
SHL EAX, CL ;Shift nybble into position
OR EBX, EAX
ADD CL, 4
DEC DI
DEC DL
JNZ cadH30
MOV NewAddress, EBX ;Return w/new address.
MOV EAX, TRUE ;Indicate valid input.
JMP SHORT cad888

cad777: XOR EAX, EAX ;Indicate no valid input.
cad888: POP ECX
POP EBX
POP EDI
POP ESI
RET

ConvertAddress ENDP



I think it's pretty slick. Could be tweaked a bit, but it's pretty lean, robust (rejects any input that's not valid hex) and works.

One other thing: run the program and try the "goto" address field. It doesn't work the way I had intended it to (thought you would press Enter to make it go), but I actually like the way it works better: it jumps to the address as you type into it. I think I'll leave it that way. (I haven't yet put in the decimal-to-binary conversion; it'll do that depending on the setting of the radio buttons.)