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.

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 thestart
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.

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
ASMThis 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'
ASMINFO
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'
ASMRESERVING 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
@@:
ASMHere 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
ASMAfter 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
ASMAt 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
ASMThis 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'
ASMNow 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
ASMSecondly, 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
ASMAfter 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
ASMINFO
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'
ASMRun 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.