Table of contents

In this article, I will explain how to create a simple application — a template for a Windows desktop toolbar. Along the way, we will revisit the Win32 API, learn how to use it in assembly language, and get familiar with Flat Assembler, which will become our main development tool.

THERE IS NO PERFECTION IN THE WORLD

Popular belief holds that the law of nastiness is the foundation of the universe, from which all other fundamental laws of nature follow. One such law is the law of non-decreasing entropy, according to which material objects deteriorate over time. On my monitor, this law manifested itself as horizontal stripes at the top of the screen, which look like a musical staff when there is a radically black background beneath them, and like an interference pattern in a double-slit experiment when the background is light.

After reviewing similar issues through videos on YouTube, I formed a general understanding of this defect. It could be caused either by a poor connection of the cable linking the monitor’s matrix to the controller or by a malfunction in the electronic components of the controller itself. Since the warranty has expired, I disassembled the monitor and concluded that, lacking experience and proper tools, my chances of a successful repair are about 50-50: either I’ll fix the defect or completely ruin it.

What other options are there? From experience, I know that the official service will most likely quote an exorbitant price, and I can’t bring myself to entrust my trusty companion to the shaky hands of some amateur repairman. The most reasonable idea seems to be to go to the nearest store, buy a new monitor, and not bother myself or my readers. That’s what I would do if the monitor were completely broken. But it almost works, and it’s simply a shame to throw it away.

When you think about it, repair doesn’t necessarily mean restoring the original quality of the product — often, some loss of functionality is acceptable. For example, what does a hacker do when, one fine day, they discover a hole… no, not in security, but in their favorite pair of home pants? Do they pull out their thick wallet and rush to the nearest boutique for a new pair? No, they remember the lessons from Maryvanna and, applying the “forward, needle” method, patch them up! Or another example. On another fine day, NASA engineers discovered that the main communication antenna of a probe launched to Jupiter didn’t fully deploy. Did they abandon the faulty device to fate and ask the government for funding for a new one? No, they showed resourcefulness and technical ingenuity, and as a result, despite some difficulties, they successfully carried out a multi-year research mission.

When you think about it, repair doesn’t necessarily mean restoring the original quality of the product — often, some loss of consumer properties is acceptable. For example, what does a hacker do when, one fine day, they discover a hole… no, not in security, but in their favorite pair of home pants? Do they pull out their thick wallet and rush to the nearest boutique for a new pair? No, they remember Maryvanna’s lessons and, applying the “forward, needle” methodology, patch them up! Or another example. On another fine day, NASA engineers discovered that the main communication antenna of the probe sent to Jupiter hadn’t fully deployed. Did they abandon the faulty device to fate and ask the government for funding for a new one? No, they showed resourcefulness and technical ingenuity, and as a result, despite some effort, they successfully completed a multi-year research mission.

CHOICE OF GOAL AND MEANS

The situation discussed in the article is somewhere between these two extremes. The biggest inconvenience caused by the described defect occurs when using full-screen programs because it either overlaps with the browser’s address bar or the main menu of the application. On the other hand, using programs in windowed mode, adjusting their position after each launch, is likely to lead to a nervous breakdown. I am willing to sacrifice some useful screen space as long as the image doesn’t overlap with the defective area.

The primary operating system on my computer is Windows. How can I exclude the strip at the top of the screen from the available desktop space so that application windows don’t overlap it when maximized? The idea came to me from the taskbar: it monopolizes the bottom part of the screen and is never overlapped by program windows. Maybe it would be enough to attach it to the top of the screen? No, first of all, it doesn’t quite fit in size, and secondly, it looks unsightly because of the defect. Is it possible to create a “patch” with similar properties, but one that would allow the user to control its size and color?

It turns out that it is possible, and the answer was quickly found in the Win32 API reference — it’s the desktop toolbar. The direction of work became clear, and now I just had to choose the right tool to implement it. The main development tool using the Win32 API is the C compiler. However, to call a few operating system functions, I wanted to use something simpler and more elegant. So, I went into the dark cupboard of my memory and found a dusty box with a flat assembler. If anyone hasn’t figured it out yet, this is how “Flat Assembler” sounds in Yandex Translator’s Russian version. Surprisingly, the product I got acquainted with back in the mid-2000s is still alive and well.

WHAT CAN FLAT ASSEMBLER DO?

Let’s go ahead and figure out the environment we’ll be working in. On the download page, choose the archive with the latest build for Windows, download it, and extract it somewhere on your disk. In the three megabytes of the FASMW folder, there’s everything we’ll need.

Create an empty folder called “Appbar” for the project and copy the source text of the standard Windows application template from FASMW\EXAMPLES\TEMPLATE\TEMPLATE.ASM into it. Launch the integrated development environment FASMW\FASMW.EXE and, using the menu option File → Open…, load this file. Note that we have a text-based multi-window editor with assembler syntax highlighting at our disposal.

photo 2024 11 19 10 02 12

The text editor of the FASMW integrated environment.

The template of a Windows application in assembler consists of the following main parts:

  • A header specifying the target executable file format and the entry point of the application.
  • The .text code section, where the start label indicates the command with which the program execution should begin.
  • The .data section, containing the global variables of the program.
  • The .idata import section, listing the dynamic libraries used by the program and declaring the functions contained within them.

In general, the program text should be understandable to anyone who has used the Win32 API. Firstly, the API itself is extremely simple. The parameters of all functions expect 32-bit argument values, and if the data doesn’t fit within this size, a 32-bit pointer to an array or structure of 32-bit values is passed. The only exception is probably strings. The return value of a function (such as an exit code) is passed through the EAX register or, if it exceeds 32 bits, through a structure pointed to by one of the arguments.

Secondly, Flat Assembler, based on its set of macros, provides syntactic sugar that makes using the API as close as possible to high-level programming languages. For example, a fairly complex function call to create a window can be described in a single line:

invoke  CreateWindowEx,0,_class,_title,WS_VISIBLE+WS_DLGFRAME+WS_SYSMENU,128,128,256,192,NULL,NULL,[wc.hInstance],NULL

Here, invoke is the command to call a subroutine according to the STDCALL calling convention, CreateWindowEx is the name of the called API function, and then the arguments follow, separated by commas, in the order they are described in the documentation. C programmers might consider that the names of all variables here are pointers (_class, _title), for dereferencing which square brackets are used ([wc.hInstance]). Note the familiar “dot” notation for accessing structure members.

The description of the window procedure WindowProc should also not pose any difficulties:

proc WindowProc uses ebx esi edi, hwnd,wmsg,wparam,lparam
   ...
   ret
endp

The Win32 API convention requires that after returning from callback procedures, the values of the EBX, ESI, and EDI registers remain the same as they were before the call. To achieve this, the header includes a directive to save these registers, written as uses ebx esi edi. Following that, the list of formal procedure parameters is provided, which corresponds to the documentation.

Now, you can compile and run this program. But first, specify the path to the folder containing the include files in the menu option Options → Compiler setup so that it matches the actual location of FASMW\INCLUDE. After that, execute the Run → Run option or simply press the F9 key. If everything is done correctly, a freshly compiled TEMPLATE.EXE file will appear in the working folder Appbar, and a tiny window displaying “Win32 program template” will appear on the screen.

photo 2024 11 19 10 20 18

Setting the path to the include files

CREATING A WM_CREATE MESSAGE HANDLER

Now that we’ve got the development environment set up, let’s start working on the program. If you’ve never programmed Windows desktop panels before, now is the perfect time to study the documentation. But before that, I’d like to highlight the key points. The desktop panel is not a unique object of the operating system. Any window created using the CreateWindowEx function can serve as the panel. All the Windows tools that support the panel’s functionality are concentrated in a single function, SHAppBarMessage, which allows you to:

  • Determine the boundaries of the desktop area where a new toolbar can be placed.
  • Reserve a section of that area for the new panel.
  • Specify a window handle (handle) for the panel window, to which system notifications related to changes in the desktop environment will be sent.

Once the new area is reserved, the operating system prevents other windows from using that space when maximized, clears any desktop icons (if any were present), and that’s pretty much it. The appearance of the panel is controlled by the associated window, which, in theory, should fill the reserved space and adopt the appropriate style. However, in reality, this may not always be the case.

Use the menu item File → Save as… to save the open file in the editor under the name appbar.asm. In our program, the panel will be the main window of the application. We will reserve a strip at the top of the desktop for it. This can be done in the WM_CREATE message handler, which is sent by the operating system to the application window just before it is displayed on the screen. To do this, at the beginning of the window procedure WindowProc, we will insert the following lines to handle the message:

cmp     [wmsg],WM_CREATE

je      .wmcreate

and let’s write the handler itself before the label .wmdestroy:

.wmcreate:

  stdcall wmCreateProc, [hwnd]

  jmp     .finish

INFO

Labels that begin with a dot are local to the procedure where they are used (in this case — WindowProc). This is another feature of Flat Assembler, which allows you not to worry about label names being repeated in different procedures.

Our handler calls a procedure wmCreateProc, which doesn’t exist yet, and passes it the value of the handle to the created window. This procedure should describe actions for reserving the window’s title bar and positioning the main window, then clear the EAX register to signal the successful completion of the procedure. If after processing the WM_CREATE message, the value of the EAX register is (-1), the operating system will interpret this as a signal of an issue, leading to the termination of the program. The text for the wmCreateProc procedure can be placed after the endp operator that closes the description block of the WindowProc procedure.

proc wmCreateProc,hwnd

  invoke MessageBox,[hwnd],_title,_class,MB_OK+MB_ICONINFORMATION

  xor eax,eax

  ret

endp

INFO
Here is an intermediate version of the program that outputs a debug message: appbar-ver1.asm.

The wmCreateProc procedure in this form doesn’t do anything useful, but when the program is run, it displays a window with an informational message, indicating that everything is working as expected at this stage.

INFO
Be careful to ensure that the number and order of arguments in the procedure call always match the number and order of parameters in its definition. FASMW does not check this, and if you are careless, it can lead to hard-to-detect errors.

PREPARATION FOR USING THE SHAPPBARMESSAGE FUNCTION

The program and the operating system agree on reserving space for the toolbar using the SHAppBarMessage function through the APPBARDATA structure. The definition of this structure is missing from the include files provided with FASMW, so you will need to create it yourself based on the documentation. We will place it in a separate file. To do this, select the menu item File → New, and in the newly opened text editor tab, type the following:

struct APPBARDATA

  cbSize                  dd ?

  hWnd                    dd ?

  uCallbackMessage        dd ?

  uEdge                   dd ?

  rc                      RECT

  lParam                  dd ?

ends

This is how the structure type definition looks in Flat Assembler. After the field name, there is a type specifier (dd means a 4-byte double word, and the question mark indicates an undefined value), which can, in turn, be the name of a known structure (e.g., RECT). To avoid revisiting this issue later, you can also add the definitions of constants used by the SHAppBarMessage function in the same file:

ABM_NEW = 0x0

ABM_REMOVE = 0x1

ABM_QUERYPOS = 0x2

ABM_SETPOS = 0x3

ABE_LEFT = 0

ABE_TOP = 1

ABE_RIGHT = 2

ABE_BOTTOM = 3

MSG_ABNOTIFY = WM_USER+1

APPBAR_THICKNESS = 64

Constants with the ABM_ prefix represent service codes requested from the operating system, while those with the ABE_ prefix represent the boundaries of the desktop where the panel is supposed to be placed. The constant MSG_ABNOTIFY corresponds to a “custom” (i.e., non-standard, chosen by the programmer) message identifier, which we will suggest using for the operating system when sending notifications to the toolbar window. Here, we will also define the constant APPBAR_THICKNESS with the desired panel width value.

Save the text you typed in a file named appbar.inc using the File → Save As… menu option.

INFO
The include file with the declaration of the APPBARDATA structure and constant definitions: appbar.inc.

struct APPBARDATA
        cbSize                  dd ?
        hWnd                    dd ?
        uCallbackMessage        dd ?
        uEdge                   dd ?
        rc                      RECT
        lParam                  dd ?
ends
ABM_NEW = 0x0
ABM_REMOVE = 0x1
ABM_QUERYPOS = 0x2
ABM_SETPOS = 0x3
ABE_LEFT = 0
ABE_TOP = 1
ABE_RIGHT = 2
ABE_BOTTOM = 3
MSG_ABNOTIFY = WM_USER+1
APPBAR_THICKNESS = 64

Now, return to editing the appbar.asm file with the program text (you can do this by pressing the keyboard shortcut Ctrl-Tab) and insert the following include line at the beginning of the file, before the code section:

include 'appbar.inc'

INFO
After making the final changes to the program text (as we have done now), always check if it compiles. Make sure that the tab with the main program text is active in the text editor, not the included module. If FASM reports an error, recheck the last changes and fix the error before proceeding further.

Now that we have the APPBARDATA type declaration, we can define a global variable abd of this type. To do this, add the following line in the data section:

abd APPBARDATA sizeof.APPBARDATA, 0, MSG_ABNOTIFY, ABE_TOP, <0, 0, 0, 0>, 0
ASM

This line combines the variable definition (which reserves memory) with its initialization (which assigns initial values to the fields). Note that the initializer for the field of the nested structure abd.rc is enclosed in angle brackets. FASM macro definitions allow specifying the size of the APPBARDATA structure using the sizeof.APPBARDATA construct.

The SHAppBarMessage function is located in the system dynamic library Shell32.dll. Therefore, you will need to add lines in the import section, resulting in the following format:

section '.idata' import data readable writeable

  library kernel32,'KERNEL32.DLL',\

          user32,'USER32.DLL',\

          shell32,'SHELL32.DLL'

  include 'api\kernel32.inc'

  include 'api\user32.inc'

  include 'api\shell32.inc'
ASM

INFO
To find out which dynamic library contains a Win32 API function, you need to open the official Microsoft documentation page for that function and scroll toward the end to find the Requirements section. This section provides information on the minimum supported version of Windows (Minimum supported client/server), the name of the header file for the C programming language that declares the function (Header, *.h), the static library that must be linked to the executable to use the function (Library, *.lib), and finally, the name of the dynamic library file (DLL, *.dll).

Make sure that the program compiles and works.

INFO
This is the intermediate version of the program with all preliminary declarations and definitions: appbar-ver2.asm.

; Template for program using standard Win32 headers

format PE GUI 4.0
entry start

include 'win32w.inc'
include 'appbar.inc'

section '.text' code readable executable

  start:

        invoke  GetModuleHandle,0
        mov     [wc.hInstance],eax
        invoke  LoadIcon,0,IDI_APPLICATION
        mov     [wc.hIcon],eax
        invoke  LoadCursor,0,IDC_ARROW
        mov     [wc.hCursor],eax
        invoke  RegisterClass,wc
        test    eax,eax
        jz      error

        invoke  CreateWindowEx,0,_class,_title,WS_VISIBLE+WS_DLGFRAME+WS_SYSMENU,128,128,256,192,NULL,NULL,[wc.hInstance],NULL
        test    eax,eax
        jz      error

  msg_loop:
        invoke  GetMessage,msg,NULL,0,0
        cmp     eax,1
        jb      end_loop
        jne     msg_loop
        invoke  TranslateMessage,msg
        invoke  DispatchMessage,msg
        jmp     msg_loop

  error:
        invoke  MessageBox,NULL,_error,NULL,MB_ICONERROR+MB_OK

  end_loop:
        invoke  ExitProcess,[msg.wParam]

proc WindowProc uses ebx esi edi, hwnd,wmsg,wparam,lparam
        cmp     [wmsg],WM_CREATE
        je      .wmcreate
        cmp     [wmsg],WM_DESTROY
        je      .wmdestroy
  .defwndproc:
        invoke  DefWindowProc,[hwnd],[wmsg],[wparam],[lparam]
        jmp     .finish
  .wmcreate:
        stdcall wmCreateProc, [hwnd]
        jmp     .finish
  .wmdestroy:
        invoke  PostQuitMessage,0
        xor     eax,eax
  .finish:
        ret
endp

proc wmCreateProc,hwnd
     invoke MessageBox,[hwnd],_title,_class,MB_OK+MB_ICONINFORMATION
     xor eax,eax
     ret
endp

section '.data' data readable writeable

  _class TCHAR 'FASMWIN32',0
  _title TCHAR 'Win32 program template',0
  _error TCHAR 'Startup failed.',0

  wc WNDCLASS 0,WindowProc,0,0,NULL,NULL,NULL,COLOR_BTNFACE+1,NULL,_class

  msg MSG

  abd APPBARDATA sizeof.APPBARDATA, 0, MSG_ABNOTIFY, ABE_TOP, <0, 0, 0, 0>, 0

section '.idata' import data readable writeable

  library kernel32,'KERNEL32.DLL',\
          user32,'USER32.DLL',\
          shell32,'SHELL32.DLL'

  include 'api\kernel32.inc'
  include 'api\user32.inc'
  include 'api\shell32.inc'
ASM

RESERVING A SPACE FOR THE PANEL

The preparatory work is finished, and we are in the home stretch. Do not run the program until I tell you to. For syntax checking, you can periodically compile the source code into an executable file via the menu option Run → Compile, but do not run the program!

Let’s go back to the block with the wmCreateProc procedure, remove the debug message output line, and give the application some purpose. According to the documentation, we will first declare the intent to create a desktop toolbar by calling the SHAppBarMessage function with the ABM_NEW message and an APPBARDATA structure, in which the fields cbSize, uCallbackMessage (during variable initialization), and hWnd (immediately before the function call) are filled:

proc wmCreateProc, hwnd

  mov eax, [hwnd]

  mov [abd.hWnd], eax

  invoke SHAppBarMessage, ABM_NEW, abd

  cmp eax, TRUE

  je @f

  mov eax, -1

  ret

@@:
ASM

Here we encounter an assembly limitation that does not allow direct transfer of data from one memory cell to another, which is why we had to use the EAX register as an intermediate storage. After calling the function, we check the value returned by it. If the operating system has no objections to using the window as a panel, the EAX register will contain TRUE.

Flat Assembler offers a convenient system of local labels for short jumps, allowing for “skipping” over several commands during such checks. For example, the command je @f will jump to the nearest following label @@ if equality is detected in the previous cmp eax, TRUE comparison. If you need to jump to the nearest preceding label @@, you should use @b in the jump command.

Thus, in the case of inequality, the value (-1) will be stored in the EAX register and returned to the operating system. As noted earlier, the OS will interpret this as a problem when processing the WM_CREATE message, which will cause the application to terminate.

We will query Windows for the boundaries of the desktop within which the panel can be placed. Without undue modesty, we will request the entire screen size via abd.rc, and the operating system will adjust the values in the RECT structure to fit the actual setup. To do this, we will inquire with the system and then store in abd.rc.right the screen width reduced by one, and in abd.rc.bottom the screen height reduced by one. After that, we will call the SHAppBarMessage function with the ABM_QUERYPOS message.

invoke GetSystemMetrics, SM_CXSCREEN

dec eax

mov [abd.rc.right], eax

invoke GetSystemMetrics, SM_CYSCREEN

dec eax

mov [abd.rc.bottom], eax

invoke SHAppBarMessage, ABM_QUERYPOS, abd
ASM

After returning, the abd.rc structure will contain the boundaries of the allowed area. However, we only need a strip of width APPBAR_THICKNESS at the top edge of the desktop. Therefore, we will pull up the lower coordinate and request the resulting strip from the operating system by calling the SHAppBarMessage function with the ABM_SETPOS message:

mov eax, [abd.rc.top]

add eax, APPBAR_THICKNESS

dec eax

mov [abd.rc.bottom], eax

invoke SHAppBarMessage, ABM_SETPOS, abd

xor eax, eax

ret

endp
ASM

At the end of the procedure, we clear the EAX register with the xor eax, eax command and return this zero value to the operating system, which will interpret it as a successful completion of the WM_CREATE handler.

Let’s assume we have done everything correctly. The result of performing the listed actions should be the cutting of a strip from the top boundary of the desktop. If the program is run in this form, the reserved area will remain “idle” even after the program finishes, and the computer will need to be rebooted. We must explicitly return the strip obtained for temporary use to the operating system. To do this, we will add the following line to the WindowProc procedure, specifically in the block that handles the WM_DESTROY message (insert it right after the .wmdestroy label):

invoke SHAppBarMessage, ABM_REMOVE, abd
ASM

This ensures that the reserved strip is properly released, avoiding the need for a system restart.

INFO
This is an intermediate version of the program that is capable of reserving and freeing a section of the desktop: appbar-ver3.asm.

; Template for program using standard Win32 headers

format PE GUI 4.0
entry start

include 'win32w.inc'
include 'appbar.inc'

section '.text' code readable executable

  start:

        invoke  GetModuleHandle,0
        mov     [wc.hInstance],eax
        invoke  LoadIcon,0,IDI_APPLICATION
        mov     [wc.hIcon],eax
        invoke  LoadCursor,0,IDC_ARROW
        mov     [wc.hCursor],eax
        invoke  RegisterClass,wc
        test    eax,eax
        jz      error

        invoke  CreateWindowEx,0,_class,_title,WS_VISIBLE+WS_DLGFRAME+WS_SYSMENU,128,128,256,192,NULL,NULL,[wc.hInstance],NULL
        test    eax,eax
        jz      error

  msg_loop:
        invoke  GetMessage,msg,NULL,0,0
        cmp     eax,1
        jb      end_loop
        jne     msg_loop
        invoke  TranslateMessage,msg
        invoke  DispatchMessage,msg
        jmp     msg_loop

  error:
        invoke  MessageBox,NULL,_error,NULL,MB_ICONERROR+MB_OK

  end_loop:
        invoke  ExitProcess,[msg.wParam]

proc WindowProc uses ebx esi edi, hwnd,wmsg,wparam,lparam
        cmp     [wmsg],WM_CREATE
        je      .wmcreate
        cmp     [wmsg],WM_DESTROY
        je      .wmdestroy
  .defwndproc:
        invoke  DefWindowProc,[hwnd],[wmsg],[wparam],[lparam]
        jmp     .finish
  .wmcreate:
        stdcall wmCreateProc, [hwnd]
        jmp     .finish
  .wmdestroy:
        invoke SHAppBarMessage, ABM_REMOVE, abd
        invoke  PostQuitMessage,0
        xor     eax,eax
  .finish:
        ret
endp

proc wmCreateProc,hwnd
     mov eax, [hwnd]
     mov [abd.hWnd], eax
     invoke SHAppBarMessage, ABM_NEW, abd
     cmp eax, TRUE
     je @f
     mov eax, -1
     ret
@@:
     invoke GetSystemMetrics, SM_CXSCREEN
     dec eax
     mov [abd.rc.right], eax
     invoke GetSystemMetrics, SM_CYSCREEN
     dec eax
     mov [abd.rc.bottom], eax
     invoke SHAppBarMessage, ABM_QUERYPOS, abd
     mov eax, [abd.rc.top]
     add eax, APPBAR_THICKNESS
     dec eax
     mov [abd.rc.bottom], eax
     invoke SHAppBarMessage, ABM_SETPOS, abd
     xor eax,eax
     ret
endp

section '.data' data readable writeable

  _class TCHAR 'FASMWIN32',0
  _title TCHAR 'Win32 program template',0
  _error TCHAR 'Startup failed.',0

  wc WNDCLASS 0,WindowProc,0,0,NULL,NULL,NULL,COLOR_BTNFACE+1,NULL,_class

  msg MSG

  abd APPBARDATA sizeof.APPBARDATA, 0, MSG_ABNOTIFY, ABE_TOP, <0, 0, 0, 0>, 0

section '.idata' import data readable writeable

  library kernel32,'KERNEL32.DLL',\
          user32,'USER32.DLL',\
          shell32,'SHELL32.DLL'

  include 'api\kernel32.inc'
  include 'api\user32.inc'
  include 'api\shell32.inc'
ASM

Now it’s time to run the program you’ve created and see how it works. Try moving the application windows, maximizing them to full screen, and observe how they behave. Pay attention to the fact that the main window of the resulting program seems to exist separately from the reserved strip on the desktop. When the program is closed, the situation on the desktop should be restored. If there are any issues, restart the computer and carefully check the program.

WINDOW PANEL CONFIGURATION

The only thing left is to adjust the main window of our application to fit its purpose. To do this, first, we’ll modify the window styles in the line with the CreateWindowEx function call as follows:

invoke CreateWindowEx, WS_EX_TOPMOST + WS_EX_TOOLWINDOW, _class, _title, WS_POPUP, 128, 128, 256, 192, NULL, NULL, [wc.hInstance], NULL
ASM

Secondly, we will add lines to the wmCreateProc procedure that handle the window’s positioning on the reserved strip of the desktop. To do this, immediately after the procedure’s header, insert a line to define local variables:

local width:DWORD, height:DWORD
ASM

After the variable name, the type is specified after a colon (like in Pascal). Similarly, parameter types for procedures can be specified this way. We didn’t do that because we’re satisfied with the default 32-bit type. The lines for calculating the window size and moving it should be placed at the end of the procedure, just before the return value is formed in the EAX register:

mov eax, [abd.rc.right]
sub eax, [abd.rc.left]
inc eax
mov [width], eax

mov eax, [abd.rc.bottom]
sub eax, [abd.rc.top]
inc eax
mov [height], eax

invoke SetWindowPos, [hwnd], HWND_TOP, [abd.rc.left], [abd.rc.top], [width], [height], SWP_NOACTIVATE + SWP_SHOWWINDOW
ASM

INFO
Final version of the program: appbar.asm.

; Template for a program using standard Win32 header
format PE GUI 4.0
entry start

include 'win32w.inc'
include 'appbar.inc'

section '.text' code readable executable

  start:

        invoke  GetModuleHandle,0
        mov     [wc.hInstance],eax
        invoke  LoadIcon,0,IDI_APPLICATION
        mov     [wc.hIcon],eax
        invoke  LoadCursor,0,IDC_ARROW
        mov     [wc.hCursor],eax
        invoke  RegisterClass,wc
        test    eax,eax
        jz      error

        invoke  CreateWindowEx,WS_EX_TOPMOST+WS_EX_TOOLWINDOW,_class,_title,WS_POPUP,128,128,256,192,NULL,NULL,[wc.hInstance],NULL
        test    eax,eax
        jz      error

  msg_loop:
        invoke  GetMessage,msg,NULL,0,0
        cmp     eax,1
        jb      end_loop
        jne     msg_loop
        invoke  TranslateMessage,msg
        invoke  DispatchMessage,msg
        jmp     msg_loop

  error:
        invoke  MessageBox,NULL,_error,NULL,MB_ICONERROR+MB_OK

  end_loop:
        invoke  ExitProcess,[msg.wParam]

proc WindowProc uses ebx esi edi, hwnd,wmsg,wparam,lparam
        cmp     [wmsg],WM_CREATE
        je      .wmcreate
        cmp     [wmsg],WM_DESTROY
        je      .wmdestroy
  .defwndproc:
        invoke  DefWindowProc,[hwnd],[wmsg],[wparam],[lparam]
        jmp     .finish
  .wmcreate:
        stdcall wmCreateProc, [hwnd]
        jmp     .finish
  .wmdestroy:
        invoke SHAppBarMessage, ABM_REMOVE, abd
        invoke  PostQuitMessage,0
        xor     eax,eax
  .finish:
        ret
endp

proc wmCreateProc,hwnd
local width:DWORD, height:DWORD
     mov eax, [hwnd]
     mov [abd.hWnd], eax
     invoke SHAppBarMessage, ABM_NEW, abd
     cmp eax, TRUE
     je @f
     mov eax, -1
     ret
@@:
     invoke GetSystemMetrics, SM_CXSCREEN
     dec eax
     mov [abd.rc.right], eax
     invoke GetSystemMetrics, SM_CYSCREEN
     dec eax
     mov [abd.rc.bottom], eax
     invoke SHAppBarMessage, ABM_QUERYPOS, abd
     mov eax, [abd.rc.top]
     add eax, APPBAR_THICKNESS
     dec eax
     mov [abd.rc.bottom], eax
     invoke SHAppBarMessage, ABM_SETPOS, abd
     mov eax, [abd.rc.right]
     sub eax, [abd.rc.left]
     inc eax
     mov [width], eax
     mov eax, [abd.rc.bottom]
     sub eax, [abd.rc.top]
     inc eax
     mov [height], eax
     invoke SetWindowPos, [hwnd], HWND_TOP, [abd.rc.left], [abd.rc.top], [width], [height], SWP_NOACTIVATE+SWP_SHOWWINDOW
     xor eax,eax
     ret
endp

section '.data' data readable writeable

  _class TCHAR 'FASMWIN32',0
  _title TCHAR 'Win32 program template',0
  _error TCHAR 'Startup failed.',0

  wc WNDCLASS 0,WindowProc,0,0,NULL,NULL,NULL,COLOR_BTNFACE+1,NULL,_class

  msg MSG

  abd APPBARDATA sizeof.APPBARDATA, 0, MSG_ABNOTIFY, ABE_TOP, <0, 0, 0, 0>, 0

section '.idata' import data readable writeable

  library kernel32,'KERNEL32.DLL',\
          user32,'USER32.DLL',\
          shell32,'SHELL32.DLL'

  include 'api\kernel32.inc'
  include 'api\user32.inc'
  include 'api\shell32.inc'
ASM

Run the resulting application.
The reserved strip of the desktop should be replaced by a gray window without borders or a title — our patch. Do not try to terminate the program through the task manager, as the reserved part of the desktop will not be freed in this case. Instead, click the left mouse button on this strip and press the Alt-F4 key combination. The program will terminate, and the desktop will return to its original state.

Conclusion

This is how I was able to eliminate the main inconvenience caused by the monitor defect when using full-screen applications. Along the way, we learned how to create our own taskbar for the Windows desktop.

The developed application can serve as a template for a more functional panel, rather than one like the picture in the cartoon about Uncle Fyodor that “covers up a hole in the wallpaper.” The main window of the application is used as the panel, which allows adding controls, text, and drawings. But the first thing I would do is add protection against the program being run multiple times (for this, we need to use a named mutex, which is created using the CreateMutex function). It would also be helpful to respond to MSG_ABNOTIFY notifications, which the operating system might send to our window (for example, when a full-screen application is launched or when neighboring panels become active).

The chosen development tool, Flat Assembler, and assembler as the programming language leave mixed feelings. The positive aspects include:

  • Compact size and fast deployment on any Windows computer.
  • The ability to fully use the methodology of structured programming thanks to macro definitions, which practically removes any limitations on the complexity of projects.
  • The same behavior of applications across the entire Windows line from Windows 2000 to Windows 10 x86-64, thanks to the use of the Win32 API.
  • Small size of generated EXE files (the application developed in this article takes up 2.5 KB on disk).

The controversial aspects, in my opinion, are as follows:

  • Tight coupling to Intel processor architecture.
  • Weak “protection against mistakes” (virtually no type checking of variables and no verification of actual arguments against declared formal parameters when calling functions).

Nevertheless, using Flat Assembler, you can certainly study the basics of assembly language programming for the x86 architecture. This tool will help beginners master the syntax and core constructs of the language and is useful for anyone who has yet to encounter assembly but wishes to use it in the future to create their own applications.

Categorized in: