Hi all
I need to draw some vertical lines and i am using MoveToEx and LineTo to do it but the lines dont appear in the screen. It may be a problem with the color of the lines i dont know. How to specify it ? Any help about this would be appreciated. Thanks
RuiLoureiro
Be sure that you are setting the pen colour and not the brush colour when drawing lines. You can get the current pen colour as follows...
invoke GetCurrentObject,[LineDC],OBJ_PEN
mov [hp],eax
invoke GetObject,[hp],SIZEOF LOGPEN,ADDR lpen
You would generally create a pen using CreatePen or CreatePenIndirect or choose one of the stock objects, I prefer creating my own pens it allows more control over the type and colour...
invoke CreatePen,PS_SOLID,1,0FFh ; red pen width of 1 pixel
mov [hp], eax
invoke SelectObject, [LineDC], eax
Be sure to destroy the pen with DeleteObject when done with it.
Donkey
I had used this macro in an old project to draw lines. It seemed to work to do some graphical drawing work.
drawLine MACRO DC,colr,xx1,yy1,xx2,yy2
  pushad
  invoke CreatePen,0,1,colr
  mov esi,eax  ;; hPen in esi
  invoke SelectObject,DC,esi
  mov edi,eax  ;; hPenOld in edi
  invoke MoveToEx,DC,xx1,yy1,NULL
  invoke LineTo,DC,xx2,yy2
  invoke SelectObject,DC,edi
  invoke DeleteObject,esi
  invoke DeleteObject,edi
  popad
ENDM
Some folk don't like macros, but then I'm not a purist! :bg
Donkey,
Thank you so much. Your advice is very good ! I like to prepare all kind of things at the begining of the program and then use it. Very good. Thank you
CrisLeslie,
Thank you for your example. There is no problem about macros or not because i transform macros into procs and vice versa. It help me too. Thank you.
Now it is time to work ! When my problem was solved i will be here to say something.
Have a good night/day.
Rui
Rui,
Just remember that if you are drawing lines on the client area of a window which is normal, you need to do it from the WM_PAINT message processing in your WndProc for your main window. This ensures that even when the window has been covered by another window that the lines you draw will still show after the window has been sized or overlapped by another.
Hutch,
     I didnt forget of WM_PAINT. Thank you for your help too.
My first window program has now 1Mb of code and data and it runs correctly ... till now !
At one point, when the program start, i call one proc to print on the client area. The same proc is under WM_PAINT and so, there is no problem when we maximize the window or when we move the mouse etc.
My work was this:
$PENWIDTHÂ Â Â equ 3
.data
_hdc       dd 0    ; Device context handleÂ
_hPen      dd 0    ; Pen handle
_hPenOld     dd 0    ; Pen handle
;-----------------------------------------------------------------------
.code
; Input:
;      _hdc  - Device context handle
;
CriaPen      proc   Cor:DWORD
         pushad
         ;
         ; color: Cor, width: $PENWIDTH pixels
         ; -----------------------------------
         invoke  CreatePen, PS_SOLID, $PENWIDTH, Cor   ; eax
         mov    _hPen, eax
         invoke  SelectObject, _hdc, eax
         mov    _hPenOld, eax
         ;
         popad
         ret
CriaPen      endp
; ----------------------------------------------------------------------------
; Input:
;      _hdc  - Device context handle
;
SetPenColor    proc   Cor:DWORD
         invoke  SetDCPenColor, _hdc, Cor
         ret
SetPenColor    endp
; -----------------------------------------------------------------------------
DestroiPen    proc
         pushad
         cmp    _hPen, 0
         je    _eDestroiPen
         ;
         invoke  SelectObject, _hdc, _hPenOld                Â
         invoke  DeleteObject, _hPen
         ;
_eDestroiPen:Â Â Â popad
         ret
DestroiPen    endp
;-----------------------------------------------------------------------------
; Input:
;      _hdc  - Device context handle
;
DrawVLine    proc  XIni:DWORD, YIni:DWORD, YFim:DWORD, Cor:DWORD
        pushfd
        pushad
        ;
        invoke  SetDCPenColor, _hdc, Cor       Â
        ;
        ; eax = X, ebx = Y
        ; ----------------
        mov   eax, XIni
        mov   ebx, YIni
        invoke MoveToEx, _hdc, eax, ebx, NULL
        ;
        mov   eax, XIni
        mov   ebx, YFim       Â
        invoke LineTo, _hdc, eax, ebxÂ
        ;
        popad
        popfd
        ret
DrawVLine    endp
All is running well !
Thank you Donkey, ChrisLeslie, Hutch for your help
Have a good night/day
RuiLoureiro
Hi RuiLoureiro
Just a little factoid, there is nothing really wrong with your code. When you use SelectObject to select an object into a DC it returns the handle of the object that has been replaced. Since you are only creating a pen for one-shot draws you have no need to save it's handle. You simply have to destroy the handle returned when you select in the old pen...
invoke CreatePen, PS_SOLID, $PENWIDTH, Cor ; eax
invoke SelectObject, _hdc, eax
mov _hPenOld, eax
...
invoke SelectObject, _hdc, _hPenOld
invoke DeleteObject, eax
And by the way why all the pushad/popad & pushfd/popfd stuff ? You are not using any registers that need preserving so it's not really necessary and pretty much a waste of cycles.
Donkey
Hi donkey,
        Thank you for the reply
Quote from: donkey on December 07, 2007, 05:02:53 AM
You simply have to destroy the handle returned when you select in the old pen...
  I think i saw it in an example somewhere. But Ok. We are always learning.
Quote from: donkey on December 07, 2007, 05:02:53 AM
why all the pushad/popad & pushfd/popfd stuff ? You are not using any registers that need preserving so it's not really necessary and pretty much a waste of cycles.
  I have a lot of procs that assume values in the registers. In this particular case i need to preserve some registers. But also i have one rule to myself: all procs that print stuff preserve all registers and flags. In this way i can use/test it nearly everywhere in the program. Also, many times i make the version1, version2,... etc. of the same proc. To test or to use it, i comment one and paste another. And i learn with it.
  I have another rule to myself: i dont want to know what registers the system procedures preserve (
except ebp)
  Another version of DrawVLine may be this:
; Input:
;      _hdc  - Device context handle
;
DrawVLine    proc  XIni:DWORD, YIni:DWORD, YEnd:DWORD, Cor:DWORD
        ;pushfd
        pushad
        ;
        invoke  SetDCPenColor, _hdc, Cor       Â
        ;
        invoke MoveToEx, _hdc, XIni, YIni, NULL
        ;
        invoke LineTo, _hdc, XIni, YEnd         Â
        ;
        popad
        ;popfd
        ret
DrawVLine    endp
Thanks
Rui
Quote from: RuiLoureiro on December 07, 2007, 03:41:59 PMI have another rule to myself: i dont want to know what registers the system procedures preserve (
except ebp)
Hi,
The Windows API procedures preserve ESI, EDI and EBX. Your procedures are also expected to preserve those registers by Windows. If you do not use those registers they will not be modified and therefore do not have to be explicitly preserved. EAX, ECX and EDX are considered volatile registers and your procedure is not expected to do anything to preserve them. EBP and ESP are used for the stack and should be returned unaltered. For flags you must preserve the state of the direction flag Windows also guarantees that that flag will be preserved, all others are volatile.
Donkey
Rui,
> I have another rule to myself: i dont want to know what registers the system procedures preserve (except ebp)
This works against you in a number of ways. By using PUSHAD / POPAD is slower because it has to push and pop all of thew general purpose registers where in many instances you don't have to do it to ANY registers if the proc only uses EAX ECX and EDX.
The standard convention is very reliable, vary from that and you have to write dangerous unreliable code or slow code. As Donkey mentioned, freely modify EAX ECX EDX by ALWAYS preserve EBX ESI EDI and if you write stack frame free procedures ESP and EBP. If ALL of the necessary registers are not the same on procedure exit, your code will not run on all versions of Windows.
Just a note of support- I regularly use pusha/popa in non-time critical code for simplicity, and you are calling enough slow api's that the extra cycles will be lost in the noise.
Quote from: Jimg on December 08, 2007, 02:57:51 PM
Just a note of support ...
Hi Jimg
      Thank you
Hutch,
      Thanks for the advice.
Donkey,
      Ok, but is this code better to preserve all registers ?
; ----------------------------------------------------------------------------------------------
; Input:
;      _hdc  - Device context handle
; Info:
;Â Â Â Â Â Â Preserve all registers
;
DrawVLine    proc  XIni:DWORD, YIni:DWORD, YEnd:DWORD, Cor:DWORD
        push  eax
        push  ecx
        push  edx
        ;
        invoke  SetDCPenColor, _hdc, Cor       Â
        ;
        invoke MoveToEx, _hdc, XIni, YIni, NULL
        ;
        invoke LineTo, _hdc, XIni, YEnd         Â
        ;
        pop  edx
        pop  ecx
        pop  eax
        ret
DrawVLine    endp
Â
Rui
Hi RuiLoureiro,
If the procedure that calls the function expects those registers to be preserved then yes you will have to preserve them, however as a general rule I follow the Windows ABI (application binary interface) and only preserve the registers mentioned in my previous post. Is it better coding practice ? well not really in any significant way, it is just sticking to the set of rules that Microsoft created for Win32, yes it will make your application run marginally faster but as Jimg pointed out the difference is virtually unnoticeable in non-critical functions. In your case a GDI based line drawing function would not be particularly critical, if it was you would have implemented your own and stayed clear of the GDI, so there is little gained in not preserving them.
Now the argument for sticking to the ABI... If you later replace your internal function with one from the API you will be forced to modify all calls to the function because internally you expect all registers to be preserved. You may think that this will not happen but it does and in the case of line drawing may require alot of tweaking. However if you follow the ABI you know in advance that Windows API functions will react the exact same way your internal functions react. It is always better if everything reacts the same way every time, consistency reduces errors.
Also, you should be using EAX to return some sort of indication that the function was successful, for example NULL if successful/-1 if an error occurred, this facilitates error and exception handling being built into your program making it more robust. I have always believed that Microsoft/Intel made a critical error in judgment when they chose to preserve EBX over ECX, the counting register should have been preserved, the base register is more likely to be volatile. In your case preserving ECX is a good idea as this is a function that may be called in a counting loop and the x86 CPU has some loop and counting functions that use ECX. EDX however is the extended return register under the ABI and therefore there is no need to preserve it.
Donkey
Hi donkey,
        I read your "document" carefully and i agree with you completely. In the future i will go to follow ABI "plus ecx case".
Quote from: donkey on December 08, 2007, 05:39:18 PM
In your case a GDI based line drawing function would not be particularly critical, if it was you would have implemented your own and stayed clear of the GDI, so there is little gained in not preserving them.
      I would like to know what to do to implement my own without suport of GDI. How to begin.
Quote
In Also, you should be using EAX to return some sort of indication that the function was successful, for example NULL if successful/-1 if an error ...
      I generally use EAX=0 meaning NO (not found/no error/...) and EAX=1 YES and/or EAX=2 (error/...)
Quote
I have always believed that Microsoft/Intel made a critical error in judgment when they chose to preserve EBX over ECX, the counting register should have been preserved, the base register is more likely to be volatile.
     I agree. But in my case i have no problems with ECX because i use variables with the length at the back and tables the same way. Something like this (it waste memory):
      dd 10    (at -8 )                                         dd DimY
      dd 7     (at -4 )             dd 3                         dd DimX
_Var1  db "Example"           _Tbl  dd offset Var1        _ArrayXY   dd x dup (?)
      db 3 dup (?)                 dd offset Var2
      db 0   (null terminated too)      dd offset Var3
Thank you, donkey.
Rui
Hi RuiLoureiro,
You have the ability to write directly to a bitmap so any function of the GDI can be imitated at a much greater speed. I have posted a few here from time to time and have gotten in some cases 20-30x the speed of the GDI. For myself I use 32 bit DIB images internally in my graphics projects, this helps in that the pixel depth is natural. Here is an old example of a gradient fill that I wrote some time ago (it must be old, I was still using MASM then :P ). It extracts the handle to the bitmap from the DC passed in parameters then obtains a pointer to the actual image bits, at that point it performs a gradient fill, not sure how much faster this one is than the MSIMG one but it demonstrates the concept well enough. For lines you should look into the Bresenham line algorithm, it is a fairly fast and reliable one that is easy enough to find a suitable implementation of.
GradientFillD proc gradDC,gradTriVert,gradRect,gradSize,gradFillMethod
LOCAL gBitmap :DWORD
LOCAL gradDIBits :DWORD
LOCAL gbmp :DIBSECTION
LOCAL gradSteps :DWORD
LOCAL deltaRED :BYTE
LOCAL deltaGRN :BYTE
LOCAL deltaBLU :BYTE
;invoke GradientFill,hMemDC,ADDR vert,2,ADDR gRect,1,gFillMethod
; This algorithm is for a single TRIVERTEX structure for speed
; gradSize is ignored but included for compatibility
; Get the DIBits
invoke GetCurrentObject,gradDC,OBJ_BITMAP
mov gBitmap,eax
invoke GetObject,gBitmap,SIZEOF DIBSECTION,ADDR gbmp
mov eax,gbmp.dsBm.bmBits
mov gradDIBits,eax
; Calulate the steps for each color
; Find the number of steps
.IF gradFillMethod == GRADIENT_FILL_RECT_V
mov edi,gradRect
mov eax,[edi].RECT.bottom
mov ecx,[edi].RECT.top
sub eax,ecx
.ELSE
mov edi,gradRect
mov eax,[edi].RECT.right
mov ecx,[edi].RECT.left
sub eax,ecx
.ENDIF
mov gradSteps,eax
mov edi,gradTriVert
; Red step
movzx eax,[edi].TRIVERTEX.Red
movzx ecx,[edi+SIZEOF TRIVERTEX].TRIVERTEX.Red
sub eax,ecx
test eax,eax
.IF !SIGN?
xor edx,edx
div gradSteps
mov deltaRED,al
.ELSE
neg eax
xor edx,edx
div gradSteps
neg eax
mov deltaRED,al
.ENDIF
; Green step
movzx eax,[edi].TRIVERTEX.Green
movzx ecx,[edi+SIZEOF TRIVERTEX].TRIVERTEX.Green
sub eax,ecx
test eax,eax
.IF !SIGN?
xor edx,edx
div gradSteps
mov deltaGRN,al
.ELSE
neg eax
xor edx,edx
div gradSteps
neg eax
mov deltaGRN,al
.ENDIF
; Blue step
movzx eax,[edi].TRIVERTEX.Blue
movzx ecx,[edi+SIZEOF TRIVERTEX].TRIVERTEX.Blue
sub eax,ecx
test eax,eax
.IF !SIGN?
xor edx,edx
div gradSteps
mov deltaBLU,al
.ELSE
neg eax
xor edx,edx
div gradSteps
neg eax
mov deltaBLU,al
.ENDIF
ret
GradientFillD endp
There are times when I feel like a voice crying in the wilderness when it comes to writing reliable code and this among oher things means understanding the limitations of having 8 general purpose registers and understanding why the operating system comforms to the Intel convention of what registers to preserve and what registers you can trash within a procedure.
Volatile = EAX ECX & EDX.
System = EBX ESP EBP ESI EDI
There IS a reason for which registers are volatile, they are the first three registers in processor order. They were historically the Accumulator, Counter and Data registers. The rest are in their order, Base address register, Stack pointer, Base pointer, Source index, Destination index.
From the Intel manual this is the operation of PUSHAD / POPAD in the above mentioned order.
PUSHAD
Temp (ESP);
Push(EAX);
Push(ECX);
Push(EDX);
Push(EBX);
Push(Temp);
Push(EBP);
Push(ESI);
Push(EDI);
POPAD
EDI Pop();
ESI Pop();
EBP Pop();
increment ESP by 4 (* skip next 4 bytes of stack *)
EBX Pop();
EDX Pop();
ECX Pop();
EAX Pop();
These two old instructions are useful in temporary code for debugging, mainly to find mistakes that someone else has made in not preserving the correct registers in the first place but also for tasks like checking the value of a register in the middle of an algo while its running but they have no place in production code as they are far slower than preserving on the basis of need.
The simple rule is to do what you need to do to write safe code but don't do more so you write slow code.
Often we have had questions like "Why does my assembler code run slower than C code" and the answer is you have written SLOW assembler code. Then you get the perennial question "Why does my app run on win9(whatever) but crashes on XP" and the answer is the same as it always was, learn your register preservation rules and the problem will magically go away.
For Rui the situation is a simple one, learn your register preservation rules and write fast reliable code including if you wish later, code with no stack frame, keep listening to bad advice and you will write slow unreliable code.
Well said Steve, I can hear your voice from behind the tree in that same wilderness :bg
donkey,
      thank you. I am going to study your code.
Hi Hutch,
      i didnt see your post before. Ok, when i need fast code i preserve only the registers that need to be preserved. Another thing is the rule (more or less) i said it is to myself. It is to myself.
      There is a problem with DrawVLine: it doesnt change the color. It draws the first set of lines in blue but when i change to red it draws in blue. I am sure the parameter is for red 000000FFh. So «invoke  SetDCPenColor, _hdc, Cor» doesnt work.
      Now, i am using the following code and it works correctly. At the end of drawing each set i use DestroiPen.
; ------------------------------------------------------------------------------
; Input:
;      _hdc  - Device context handle
;
DrawVerLine   proc  XIni:DWORD, YIni:DWORD, YEnd:DWORD, Cor:DWORD
         ;
        cmp   _hPen, 0 Â
        jne   @F
        ;
        ; color: Cor, width: $PENWIDTH pixels
        ; --------------------------------------------------
        invoke  CreatePen, PS_SOLID, $PENWIDTH, Cor  Â
        mov    _hPen, eax
        invoke  SelectObject, _hdc, eax
        mov    _hPenOld, eax       Â
        ;
 @@:      invoke MoveToEx, _hdc, XIni, YIni, NULL
        ;
        invoke LineTo, _hdc, XIni, YEnd         Â
        ;
        ret
DrawVerLine   endp
; ------------------------------------------------------------------------------
DestroiPen    proc
         ;pushad
         ;
         cmp    _hPen, 0
         je    _eDestroiPen
         ;
         invoke  SelectObject, _hdc, _hPenOld                Â
         invoke  DeleteObject, eax           ;_hPen
         ;
         mov    _hPen, 0        Â
         ;
_eDestroiPen:Â Â Â ;popad
         ret
DestroiPen    endp
Rui
Hi Rui
Isn't this code a little bit dangerous if you leak away too much memory by a possible loop with millions of undestroyed Pens?
Chris
Quote from: ChrisLeslie on December 09, 2007, 09:06:46 PM
Isn't this code a little bit dangerous if you leak away too much memory by a possible loop with millions of undestroyed Pens?
Hi Chris,
      Why ? I am not seeing.
1. _hPen =0 -> Create -> Draw 1ª line
2.                  Draw 2ª line
3.                  Draw 3ª line
4.          Destroy -> _hPen=0
Where is the error ? Â
Rui
     Â
Hi Rui
I think that if your program draws millions of lines with a procedure that does not clean up after each call, and you forget to invoke your DestroiPen procedure then your computer might start to argue with you.
Chris
Quote from: ChrisLeslie on December 09, 2007, 11:14:54 PM
Hi Rui
I think that if your program draws millions of lines with a procedure that does not clean up after each call, and you forget to invoke your DestroiPen procedure then your computer might start to argue with you.
Chris
Actually, he can only create the pen once regardless of how many lines he draws, if _hPen contains a value the procedure will not create another until it is set to zero by the DestroiPen function. It seems like he has taken precautions though I would tend to create a pen at the start of the proggy and modify it when needed, destroying it on exit but that is my personal style, actually I think one app I did created 8 pens and only destroyed them just before ExitProcess.
You're right. I didn't see the anonymous jump. :U