Designing a snap for handling Windows window messages
The Fort language seems to the majority to be the least suitable for programming on it, and even under Windows. After all, there is no graphics in it, only a dull black text console.
Let's try to overcome this myth.
First, programming under Windows is very easy, just open any WinAPI instruction.
Secondly, Windows itself controls all its graphics, we just need to call the necessary functions and process messages correctly.
')
Before creating a window, you must create your own class In the WNDCLASS structure, there is a WNDPROC field lpfnWndProc, which contains a link to the procedure for processing messages coming from windows of this class.
Windows requirements for this procedure are simple:
1) If the message is not processed by the procedure, you must call the DefWindowProc function
2) Save the contents of the registers rdi rsi rbx
We do the assembler insert. We need a matching stub that will call the procedure written in Forte. Conversely, if a signal is received from a high-level procedure that a message has not been processed, call DefWindowProc.
winprocHEADER winproc HERE CELL+ , push_rcx push_rdx push_r8 push_r9 push_rbx push_rsi push_rdi mov_rax,# hwnd , mov_[rax],rcx mov_rax,# wmsg , mov_[rax],rdx mov_rax,# wparam , mov_[rax],r8 mov_rax,# lparam , mov_[rax],r9 mov_rax,# ' inWinProc , mov_r11,# ' Push @ , call_r11 mov_r11,# ' EXECUTE @ , call_r11 mov_r11,# ' Pop @ , call_r11 test_rax,rax jne forward> pop_rdi pop_rsi pop_rbx pop_r9 pop_r8 pop_rdx pop_rcx ret >forward pop_rdi pop_rsi pop_rbx pop_r9 pop_r8 pop_rdx pop_rcx push_rcx push_rdx push_r8 push_r9 push_rbx push_rsi push_rdi mov_r11,# ' DefWindowProcA CELL+ @ , sub_rsp,b# 0x 20 B, call_r11 add_rsp,b# 0x 20 B, pop_rdi pop_rsi pop_rbx pop_r9 pop_r8 pop_rdx pop_rcx ret
The logic of this piece is clear without unnecessary comments.
1) Save parameters to variables
2) Call the high-level procedure
3) Call DefWindowProc if non-zero is received.
Now let's do the high-level part.
The word Fort itself is a procedure.
Example WORD: Messages do_something ;WORD
What something we have to do, now and we will find out.
In the assembler box, we see the use of the wmsg variable. It takes the uMsg parameter, the Windows message number. We need to compare the contents of wmsg with the number of the message we need, and if this is the number, process the message. Return zero so that DefWindowProc is not called.
Try WORD: Messages wmsg @ hex, 201 (( WM_LBUTTONDOWN ) = If 1 Else do_lbuttondown 0 Then ;WORD
Has the right to exist. But this is acceptable when you need to process one or two messages. But uncomfortable, ugly, poorly followed and does not solve the problem. After all, you have to write nested If Then constructs, and this is horror-horror in the listing.
Ugly WORD: Messages wmsg @ hex, 201 (( WM_LBUTTONDOWN ) = If wmsg @ hex, 202 (( WM_LBUTTONUP ) = If 1 Else do_lbuttonup 0 Then Else do_lbuttondown 0 Then ;WORD
Only two messages, and you have to strain to be sure that it is written correctly.
Fortunately, the Case ... Of ... EndOf ... EndCase construction is implemented quite easily and greatly decorates the code.
Rewrite: WORD: Messages Case wmsg @ hex, 201 (( WM_LBUTTONDOWN ) = Of do_lbuttondown 0 EndOf wmsg @ hex, 202 (( WM_LBUTTONUP ) = Of do_lbuttonup 0 EndOf EndCase ;WORD
It is much more pleasant to read and, if anything, add more handlers. But still can be better.
Firstly, wmsg @ and = are constantly repeated here.
Second, inserting numerical hexadecimal constant values is somehow unaesthetic. In addition, you have to write a comment about what this value means.
Let WM_LBUTTONDOWN, WM_LBUTTONUP, etc. will be constants.
A wmsg @ and = combine in one word. WORD: (?wm) wmsg @ = ;WORD WORD: Messages Case WM_LBUTTONDOWN (?wm) Of do_lbuttondown 0 EndOf WM_LBUTTONUP (?wm) Of do_lbuttonup 0 EndOf EndCase ;WORD
It has become much more beautiful and clearer. But still too many extra words in the listing.
If you could write Messages{{ WM_LBUTTONDOWN{{ do_lbuttondown }} WM_LBUTTONUP{{ do_lbuttonup }} }}Messages
We solve this problem.
The simplest thing is to implement the word}}. It is almost the equivalent of the word EndOf, we’ll just add the word 0 to it.
And no. Word EndOf immediate execution. Instead of compiling, it will be executed. Executed during compilation of the word}}. And we need it to be executed during the compilation of the message processing module.
Take a look at the EndOf implementation WORD: EndOf COMPILE BRANCH HERE >R COMPILE 0 THEN R> ;WORD
We will use His Majesty Kopipaste and write ... But first, let us take into account that the word}} should be immediately executed.
so IMMEDIATES CURRENT ! WORD: EndOf COMPILE 0 COMPILE BRANCH HERE >R COMPILE 0 THEN R> ;WORD FORTH32 CURRENT !
Insert the word}} in place 0 EndOf, and make sure it works.
Let's deal with the word}} Messages
It should:
1) compile non zero
2) perform endcase
3) complete the compilation, similarly; WORD
Note, this word is immediate execution.
Writing it is quite simple: IMMEDIATES CURRENT ! WORD: }}Messages COMPILE 1 (EndOf) ;Word quit ;WORD ;WORD FORTH32 CURRENT !
And now we construct opening words. Let's start with Messages {{
What should it do?
4) run compilation
3) compile Case
2) make the address of the start of the procedure available for inserting winproc
1) mark the address from which the procedure for processing messages will begin
Automatic compilation is started by the word immediator. It fills the parameters field of the compiled word according to the source text. The parameter field is preceded by a code field, which in the case of a high-level definition must contain a reference to the address interpreter. It is given to us by the interpret constant #. The word Case is synonymous with the word 0. Just Case is an immediate execution, and 0 is the usual compiled word.
Write WORD: Messages{{ HERE ['] inWinProc CELL+ ! 0 interpret
Call inWinProc we meet in an assembly insertion. This is the so-called vector word. It is almost an ordinary constant, but instead of putting a value on the stack, it executes it.
Now the most interesting
Define the words WM_LBUTTONDOWN {{and WM_LBUTTONUP {{ IMMEDIATES CURRENT ! WORD: WM_LBUTTONDOWN{{ COMPILE WM_LBUTTONDOWN COMPILE (?wm) COMPILE ?OF HERE COMPILE 0 ;WORD WORD: WM_LBUTTONUP{{ COMPILE WM_LBUTTONUP COMPILE (?wm) COMPILE ?OF HERE COMPILE 0 ;WORD FORTH32 CURRENT !
Do you really have to copy-paste this code for each message, correcting only the constant? Let's take a closer look. The code in each definition is the same; they differ only in the name and the constant used. This constant is a parameter for the following code. Schematically looks like x do_something_with_x.
Fortunately, there is the concept of a defining word in Fort. Which are intended for such cases.
Write WORD: WM: CREATE , DOES> @ COMPILE (?wm) COMPILE ?OF HERE COMPILE 0 ;WORD
How to use IMMEDIATES CURRENT ! WM_LBUTTONDOWN WM: WM_LBUTTONDOWN{{ WM_LBUTTONUP WM: WM_LBUTTONUP{{ FORTH32 CURRENT !
Uh ... Why do we repeat the same text on the left and right? And even thrice. (We have defined constants earlier). It may be worth not to define constants, but to immediately identify words?
Like this 0d 513 WM: WM_LBUTTONDOWN{{ 0d 514 WM: WM_LBUTTONUP{{
And ... It does not work. Let's take a closer look. First, all these words should be immediate execution. That is, they must compile the code after DOES> into the body of Messages {{.
This part: COMPILE (? Wm) COMPILE? OF HERE COMPILE 0 does everything right. But immediately after DOES> we get the value compiled during the creation of the word WM_L ... And we need it during the execution of the word Messages {{.
We just need to compile this value as a literal already in the body of Messages {{.
Correct code WORD: WM: CREATE , DOES> @ LIT, COMPILE (?wm) COMPILE ?OF HERE COMPILE 0 ;WORD
To summarize It is convenient to select the common, header part into a separate file.
winuser.f WORD: Messages{{ HERE ['] inWinProc CELL+ ! 0 interpret# , immediator ;WORD WORD: (?wm) wmsg @ = ;WORD WORD: WM: CREATE , DOES> @ LIT, COMPILE (?wm) COMPILE ?OF HERE COMPILE 0 ;WORD IMMEDIATES CURRENT ! FORTH32 CONTEXT ! WORD: }}Messages COMPILE 1 (EndCase) ;Word quit ;WORD WORD: }} COMPILE 0 COMPILE BRANCH HERE >R COMPILE 0 THEN R> ;WORD 0d 513 WM: WM_LBUTTONDOWN{{ 0d 514 WM: WM_LBUTTONUP{{ 0d 512 WM: WM_MOUSEMOVE{{ 0d 15 WM: WM_PAINT{{ 0d 16 WM: WM_CLOSE{{ FORTH32 CURRENT !
File
test.f INCLUDE: winuser.f WORD: do_on_lbuttondown do on left button down ;WORD WORD: do_on_lbuttondup do on left button up ;WORD do someting else Messages{{ WM_LBUTTONDOWN{{ do_on_lbuttondown }} WM_LBUTTONUP{{ do_on_lbuttondup }} }}Messages EXIT
Afterword
Notice, despite the use of the Fort language, we have never remembered the stack and did not encounter a single word of manipulation of the stack. And we hid not the most nightmarish management structure. It is not visible, although it is. the code is more descriptive than procedural. Everything else, the code does not require comments, he reads like a comment. The fort system written in Forte is its own directory. Another nuance. To develop your program, you can use tools of any level. From inline assembler, even lower, you can add missing opcodes and mnemonics to inline assembler to creating high-level, generalizing tools that allow you to create compact expressive code.