News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests
NB: Posting URL's See here: Posted URL Change

Main Menu

Dialog box programming considerations

Started by kkurkiewicz, October 23, 2023, 02:18:34 AM

Previous topic - Next topic

NoCforMe

Wellll, it could be a global resource; or maybe it could be a shared local resource? ISTR there are such things within Win32.

Where is Raymond Chen when you need him?
Assembly language programming should be fun. That's why I do it.

jj2007

See Memory Leaks 'r Us' Started by Raistlin, May 31, 2018

Interesting:
QuoteThe following values are obsolete, but are provided for compatibility with 16-bit Windows. They are ignored.
...
GMEM_DDESHARE
GMEM_SHARE

SetClipboardData:
QuoteIf SetClipboardData succeeds, the system owns the object identified by the hMem parameter. The application may not write to or free the data once ownership has been transferred to the system, but it can lock and read from the data until the CloseClipboard function is called. (The memory must be unlocked before the Clipboard is closed.) If the hMem parameter identifies a memory object, the object must have been allocated using the function with the GMEM_MOVEABLE flag.

Re "the clipboard is a can of worms":
QuoteBOOL OpenClipboard(
  [in, optional] HWND hWndNewOwner
);
hWndNewOwner: A handle to the window to be associated with the open clipboard. If this parameter is NULL, the open clipboard is associated with the current task.
...
If an application calls OpenClipboard with hwnd set to NULL, EmptyClipboard sets the clipboard owner to NULL; this causes SetClipboardData to fail.

caesay:
QuoteThere is lots of mis-information here. If you are reading from the clipboard, you can provide an empty handle to OpenClipboard, but if you intend to write (ex, SetClipboardData) then you must open the clipboard with a handle to a window you own.

NoCforMe

I'm a firm believer in the way of Win32 programming that Hutch apparently hated. Even more firm after re-reading some of R. Chen's comments on this topic in those posts you linked.

Look, the guy said: when your program exits, it basically frees everything that it allocated. So stop being so anal about unwinding every little allocation you made. If he said it, it's like the word of God, right?

I do do deallocation of GDI objects in my WM_PAINT handlers, just to keep the number of objects flying around in my program's space to a minimum. But even this isn't necessary.
Assembly language programming should be fun. That's why I do it.

TimoVJL

#48
https://devblogs.microsoft.com/oldnewthing/20041101-00/?p=37433

QuoteEven in Win32, you have to be careful not to confuse the local heap from the global heap. Memory allocated from one cannot be freed on the other. The functional differences have largely disappeared; the semantics are pretty much identical by this point. All the weirdness about near and far pointers disappeared with the transition to Win32. But the local heap functions and the global heap functions are nevertheless two distinct heap interfaces.
May the source be with you

jj2007

Quote from: NoCforMe on October 28, 2023, 11:56:42 AMLook, the guy said: when your program exits, it basically frees everything that it allocated. So stop being so anal about unwinding every little allocation you made.

Correct, but check for global resources, such as atoms.

QuoteI do do deallocation of GDI objects in my WM_PAINT handlers, just to keep the number of objects flying around in my program's space to a minimum. But even this isn't necessary.

If you think it isn't necessary in a WM_PAINT handler, then don't do it. You will have unpleasant surprises, though :biggrin:

Greenhorn

#50
From Petzold's "Windows Progamming":
https://www-user.tu-chemnitz.de/~heha/petzold/ch12b.htm

QuoteSimple Use of the Clipboard
(...)
Memory Allocation

When your program transfers something to the clipboard, it must allocate a memory block and essentially hand it over to the clipboard. When we've needed to allocate memory in earlier programs in this book, we've simply used the malloc function that is supported by the standard C run-time library. However, because the memory blocks stored by the clipboard must be shared among applications running under Windows, the malloc function is inadequate for this task.

Instead, we must dredge up memory allocation functions that were designed back in the dark ages of Windows, in the days when the operating system ran in a 16-bit real-mode memory architecture. These functions are still supported and you can still use them, but they are not often needed.

To allocate a memory block using the Windows API, you can call

hGlobal = GlobalAlloc (uiFlags, dwSize) ;

The function takes two parameters: a possible series of flags and a size in bytes of the allocated block. The function returns a handle of type HGLOBAL, called a "handle to a global memory block" or a "global handle." A NULL return value indicates that sufficient memory was not available for the allocation.

Although the two parameters to GlobalAlloc are defined a bit differently, they are both 32-bit unsigned integers. If you set the first parameter to zero, you effectively use the flag GMEM_FIXED. In this case, the global handle that GlobalAlloc returns is actually a pointer to the allocated memory block.

You can also use the flag GMEM_ZEROINIT if you'd like every byte in the memory block to be initially set to zero. The succinct GPTR flag combines the GMEM_FIXED and GMEM_ZEROINIT flags as defined in the Windows header files:

    #define GPTR (GMEM_FIXED | GMEM_ZEROINIT)

There is also a reallocation function:

hGlobal = GlobalReAlloc (hGlobal, dwSize, uiFlags) ;

You can use the GMEM_ZEROINIT flag to zero out the new bytes if the memory block is being enlarged.

Here's the function to obtain the size of the memory block:

dwSize = GlobalSize (hGlobal) ;

and the function to free it:

GlobalFree (hGlobal) ;

In the early 16-bit versions of Windows, the GMEM_FIXED flag was strongly discouraged because Windows could not move the block in physical memory. In the 32-bit versions of Windows, the GMEM_FIXED flag is normal because it returns a virtual address and the operating system can move the block in physical memory by altering the page table. When programming for the 16-bit versions of Windows, using the flag GMEM_MOVEABLE in GlobalAlloc was instead recommended. (Note that most dictionaries prefer the spelling "movable" over "moveable," so that's how I'll spell the word otherwise.) There's also a shorthand identifier identified in the Windows header files to additionally zero out the movable memory:

#define GHND (GMEM_MOVEABLE | GMEM_ZEROINIT)

The GMEM_MOVEABLE flag allows Windows to move a memory block in virtual memory. This doesn't necessarily mean that the memory block will be moved in physical memory, but the address that the application uses to read and write to the block can change.

Although GMEM_MOVEABLE was the rule in 16-bit versions of Windows, it is generally less useful now. However, if your application frequently allocates, reallocates, and frees memory blocks of various sizes, the virtual address space of your application can become fragmented. Conceivably, you could run out of virtual memory addresses. If this is a potential problem, then you'll want to use movable memory, and here's how to do it.

First define a pointer (for example, to an int type) and a variable of type GLOBALHANDLE:

    int * p ;
    GLOBALHANDLE hGlobal ;

Then allocate the memory. For example:

    hGlobal = GlobalAlloc (GHND, 1024) ;

As with any Windows handle, don't worry too much about what the number really means. Just store it. When you need to access that memory block, call

    p = (int *) GlobalLock (hGlobal) ;

This translates the handle into a pointer. During the time that the block is locked, Windows will fix the address in virtual memory. It will not move. When you are finished accessing the block, call

    GlobalUnlock (hGlobal) ;

This gives Windows the freedom to move the block in virtual memory. To be really compulsively correct about this process (and to experience the torments of early Windows programmers), you should lock and unlock the memory block in the course of a single message.

When you want to free the memory, call GlobalFree with the handle rather than the pointer. If you don't currently have access to the handle, use the function

    hGlobal = GlobalHandle (p) ;

You can lock a memory block multiple times before unlocking it. Windows maintains a lock count, and each lock requires a corresponding unlock before the block is free to be moved. When Windows moves a block in virtual memory, it doesn't need to copy the bytes from one location to another—it needs only manipulate the page tables. In general, in the 32-bit versions of Windows the only real reason for allocating a movable block for your own program's use is to prevent fragmentation of virtual memory. When using the clipboard, you should also use movable memory.

When allocating memory for the clipboard, you should use the GlobalAlloc function with both the GMEM_MOVEABLE and the GMEM_SHARE flags. The GMEM_SHARE flag makes the block available to other Windows applications.
(...)
Kole Feut un Nordenwind gift en krusen Büdel un en lütten Pint.

jj2007

Quote from: Greenhorn on October 28, 2023, 11:42:52 PMAlthough GMEM_MOVEABLE was the rule in 16-bit versions of Windows, it is generally less useful now. However, if your application frequently allocates, reallocates, and frees memory blocks of various sizes, the virtual address space of your application can become fragmented.

Good find :thumbsup:

kkurkiewicz

I know it has been almost two weeks, but—can I have a follow-up question? After going through the source of fearless' CD utility, I tried implementing the accelerators in the same way, but no matter what I do, I can't even implement (or enable) the processing for TAB. Everything works fine when the application window is a modal dialog, but as soon as I switch from DialogBoxParamA to CreateDialogParamA, all the 'bundled' navigation keys, such as TAB or ENTER, basically become dead. I'm fully aware that in the case of modeless dialogs, the keys will not work unless the application calls IsDialogMessage, but it seems to me that they simply generate no messages. Following this blog post, I call the IsDialogMessage function as soon as I check the return value of GetMessage, and my message dispatch looks as follows, but it doesn't work. Please, do you notice anything wrong with this code? (Full code in attachment.)

; Get the application descriptor
    PUSH  0
    CALL  GetModuleHandleA@4
    MOV   HINST, EAX
;-----------------------------------
    PUSH  0
    PUSH  OFFSET DLGPROC
    PUSH  0
    PUSH  OFFSET PA
    PUSH  HINST
    CALL  CreateDialogParamA@20
    MOV   NEWHWND, EAX
; Message-processing loop
MSG_LOOP:
    PUSH  0
    PUSH  0
    PUSH  0
    PUSH  OFFSET MSG
    CALL  GetMessageA@16
    CMP   EAX, 0
    JE    END_LOOP
; Call IsDialogMessageA
    PUSH  OFFSET MSG
    PUSH  NEWHWND
    CALL  IsDialogMessageA@8
    CMP   EAX, 0
    JNE   MSG_LOOP
    PUSH  OFFSET MSG
    CALL  TranslateMessage@4
    PUSH  OFFSET MSG
    CALL  DispatchMessageA@4
    JMP   MSG_LOOP
END_LOOP:
    PUSH  0
    CALL  ExitProcess@4
Kamil

jj2007

Quote from: kkurkiewicz on November 13, 2023, 05:34:39 AMI can't even implement (or enable) the processing for TAB

Where exactly do you process for TAB? File, line number?

kkurkiewicz

Quote from: jj2007 on November 13, 2023, 08:03:58 AMWhere exactly do you process for TAB?

Isn't it enough to specify WS_TABSTOP in a resource script?
Kamil

jj2007

Right, I had not opened all your files. Btw prog1.asm builds fine and works fine when pressing TAB. In contrast, prog2.asm doesn't.

To find out why, check the differences, see attachment. I am not eager to read code that makes no use of the invoke macro, but don't take that personally, please :thup:

P.S.: Can you explain what you are doing here?
    PUSH  0
    PUSH  OFFSET DLGPROC
    PUSH  0
    PUSH  OFFSET PA
    PUSH  [HINST]
    CALL  DialogBoxParamA@20
    CMP   EAX, -1
    JNE   KOL
KOL:
...

NoCforMe

No. That only applies to tabbing behavior within the dialog, and not to any special handling of tabs. In other words, WS_TABSTOP when applied to a control in a dialog just tells the dialog manager that that control should be moved to in sequence when the user presses the Tab key. It doesn't allow for any other function of the tab key.

I know a lot about dialogs and have been able to make them do several non-standard tricks, but unfortunately I don't have an answer to your problem of handling tabs. If I discover anything I'll let you know.
Assembly language programming should be fun. That's why I do it.

NoCforMe

Quote from: jj2007 on November 13, 2023, 11:05:00 AMI am not eager to read code that makes no use of the invoke macro, but don't take that personally, please :thup:

What JJ said. That was my reaction on glancing at your code. Please learn to use INVOKE instead of push-push-call. It makes your code SO much more readable and generates exactly the same code. (And likewise, no disrespect intended.)
Assembly language programming should be fun. That's why I do it.

fearless

#58
Maybe handle the WM_GETDLGCODE message and return:

mov eax, DLGC_WANTTAB or DLGC_WANTALLKEYS
ret

https://learn.microsoft.com/en-us/windows/win32/dlgbox/wm-getdlgcode

I should also add and echo what was mentioned above, that I found it difficult to read and go through the code, I was attempting to add in the WM_GETDLGCODE check but found that its just too awkward to insert it without having to change many other labels.

From a code maintenance point of view for your own future reference and readability it might be handier to use the .IF .ELSE .ELSEIF and .ENDIF when processing the messages. The assembler will automatically create the appropriate low level code to handle those conditional branching statements, which makes it easier to read and to modify at a later stage.

Similarly the use of parameters in the procedure instead of manually referring to stack or base pointer offsets makes it easier to deal with variables and parameter values.

Its too easy to accidentally supply the wrong value and spend ages trying to debug something that is a typo or user provided error (wrong offset etc). Something to think about, and you dont need to change everything all at once, but you may find that it speeds up your development when its easier to read and maintain.


kkurkiewicz

Quote from: jj2007 on November 13, 2023, 11:05:00 AMCan you explain what you are doing here?
    PUSH  0
    PUSH  OFFSET DLGPROC
    PUSH  0
    PUSH  OFFSET PA
    PUSH  [HINST]
    CALL  DialogBoxParamA@20
    CMP  EAX, -1
    JNE  KOL
KOL:
...

Just instantiating the dialog... From the bottom up—HINST is the application descriptor retrieved by calling GetModuleHandleA, PA the identifier of the dialog, 0 a handle to the window that owns the dialog box (there isn't any), DLGPROC the dialog procedure, and the second 0 the pointer to be passed to the dialog box in the lParam parameter of the WM_INITDIALOG message (also NULL).
Kamil