Windows Forth +
Design tooling for the processing of window messages in Windows
Forth language, the majority seems the least suited to programming it, and even under Windows. Because it has no graphics, just dull black text console.
Try to overcome this myth.
First, programming for Windows is very easy just open any manual on WinAPI.
Secondly, Windows itself manages all of its graphics, we have only to call up the desired function and properly process the message.
Before you create the window you need to create your class. In the structure WNDCLASS lpfnWndProc WNDPROC has a field that contains a reference to the processing of messages coming from the Windows of this class.
Requirements Windows this procedure is simple:
1) If the message is not processed by the procedure, you must call the function DefWindowProc
2) Save the contents of registers rdi rsi rbx
Will make Assembly insert. We need a matching plug, which can call a procedure written in Forte. Back if from top-level procedures to get a signal that the message was not processed, call DefWindowProc.
winproc
HEADER 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 further comment.
1) Save the settings in variables
2) Call the top-level procedure
3) Call DefWindowProc if received not zero
Now for top level part of the
The word Fort is in itself a procedure.
Example
WORD: Messages
do_something
;WORD
What is something we should do, are going to find out.
In Assembly the insert we see the use of variable wmsg. It takes a parameter uMsg — the message number Windows. We need to compare the contents of the wmsg with the number of the desired message, and if the number to process the message. Return zero to DefWindowProc is not called.
Sample
WORD: Messages
wmsg @ hex, 201 (( WM_LBUTTONDOWN ) = If
1 Else
do_lbuttondown 0 Then
;WORD
Has a right to exist. But it is acceptable when it is necessary to process one or two messages. But uncomfortable, ugly, badly maintained and not solves the problem. I will have to write nested constructs, If-Then, and this is the horror, the horror in 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 have to strain to be sure that is written correctly.
Fortunately, the design of Case... Of... EndOf... EndCase is fairly easy and greatly beautified code.
Rewrite:
WORD: Messages
Case
wmsg @ hex, 201 (( WM_LBUTTONDOWN ) = 0 EndOf Of do_lbuttondown
wmsg @ hex, 202 (( WM_LBUTTONUP ) = 0 EndOf Of do_lbuttonup
EndCase
;WORD
It is much nicer to read and, if anything, add more handlers. But still can be better.
First, there is a constantly repeated wmsg @ and =.
Second, insert the hexadecimal numerical values of the constants somehow unaesthetic. Besides, you have to write the review that this value represents.
Let WM_LBUTTONDOWN, WM_LBUTTONUP, etc. are constants.
A wmsg @ and = combine in one word.
WORD: (?wm)
wmsg @ =
;WORD
WORD: Messages
Case
WM_LBUTTONDOWN (?wm) Of do_lbuttondown 0 EndOf
EndCase
;WORD
It became much prettier and clearer. But still too many unnecessary words in the listing.
If you could write
Messages{{
WM_LBUTTONDOWN{{ do_lbuttondown }}
WM_LBUTTONUP{{ do_lbuttonup }}
}}Messages
To solve this problem.
The simplest way is to implement the word }}. It is almost the equivalent of EndOf, just for him we prisovetuet word 0.
...
WORD: WORD }}
0 EndOf
;WORD
And here and there. The word EndOf immediate execution. Instead of having to compile, it will be executed. Performed at compile-time words }}. And we need to ensure it is followed at compile time of the module processing messages.
Look at the implementation of EndOf
WORD: EndOf
COMPILE BRANCH HERE >R COMPILE 0 THEN R>
;WORD
Use his Majesty copy-paste and write... But first consider that the word }} should be immediate execution.
so
IMMEDIATES CURRENT !
WORD: EndOf
COMPILE BRANCH HERE 0 COMPILE >COMPILE R 0 THEN R>
;WORD
FORTH32 CURRENT !
Insert word }} in place of 0 EndOf, and make sure it is working.
Let's deal with the word }}Messages
It must:
1) compile not null
2) perform EndCase
3) finish the compilation, the same ;WORD
Note the word immediate execution.
Write its quite simple:
IMMEDIATES CURRENT !
WORD: }}Messages
COMPILE 1 (EndOf) ;Word quit ;WORD
;WORD
FORTH32 CURRENT !
Now construct the opening words. Let's start with the Messages{{
What they should do?
4) run compilation
3) compile Case
2) make the address of the start procedures are available insert the winproc
1) to mention the address at which to start processing messages
Automatic compilation starts with the word immediator. It populates the field with parameters of compiled words accordingly to the original text. Field parameters are preceded by a field code, which in the case of a high-level definition should contain a reference to the address interpreter. It gives us constant interpret#. Case the essence a synonym for 0. Just Case immediate execution, and 0 is the usual word being compiled.
Write
WORD: Messages{{
HERE ['] inWinProc CELL+ ! Interpret 0# , immediator
;WORD
Call inWinProc we meet in Assembly code insert. This so-called vector word. It is almost a regular constant, but instead put a value on the stack, executes it.
Now the most interesting
Define the word WM_LBUTTONDOWN{{ WM_LBUTTONUP{{
IMMEDIATES CURRENT !
WORD: WM_LBUTTONDOWN{{
WM_LBUTTONDOWN COMPILE COMPILE (?wm) COMPILE ?OF HERE 0 COMPILE
;WORD
WORD: WM_LBUTTONUP{{
WM_LBUTTONUP COMPILE COMPILE (?wm) COMPILE ?OF HERE 0 COMPILE
;WORD
FORTH32 CURRENT !
Really it is necessary for every message to copy-paste this code, just correcting the constant? Take a closer look. The code in each definition is the same, they differ only in name and used a constant. This constant is for subsequent code parameter. Schematically looks like x do_something_with_x.
Luckily in the Fort there is the concept of defining words. Which is designed for such cases.
Write
WORD: DOS:
CREATE , DOES> @ COMPILE (?wm) COMPILE ?OF HERE 0 COMPILE
;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 left and right? And even three times. (We have previously determined constants). It may be worth it to define constants, and to define words?
Here
0d 513 WM: WM_LBUTTONDOWN{{
0d 514 WM: WM_LBUTTONUP{{
And... Not work. Take a closer look. First, all these words should be immediate execution. That is, they must compile the code after DOES > in the body of Messages{{.
This part: COMPILE (?wm) COMPILE ?OF HERE 0 COMPILE does the right thing. But immediately after DOES> we get the value that was compiled during the creation of the word WM_L... AND we need it during the execution of speech Messages{{.
We need only just to compile the value as a literal already in the body of Messages{{.
Correct code
WORD: DOS:
CREATE , DOES > @ LIT, COMPILE (?wm) COMPILE ?OF HERE 0 COMPILE
;WORD
To summarize. It is convenient to identify a common header part into a separate file.
winuser.f
WORD: Messages{{
HERE ['] inWinProc CELL+ ! Interpret 0# , immediator
;WORD
WORD: (?wm)
wmsg @ =
;WORD
WORD: DOS:
CREATE
DOES> @ LIT, COMPILE (?wm) COMPILE ?OF HERE 0 COMPILE
;WORD
IMMEDIATES CURRENT ! FORTH32 CONTEXT !
WORD: }}Messages
1 COMPILE (EndCase) ;Word quit
;WORD
WORD: WORD }}
COMPILE BRANCH HERE 0 COMPILE >COMPILE R 0 THEN R>
;WORD
0d 513 WM: WM_LBUTTONDOWN{{
0d 514 WM: WM_LBUTTONUP{{
0d 512 WM: WM_MOUSEMOVE{{
0d 15 WM: WM_PAINT{{
WM 0d 16: 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
the
Epilogue
Note that despite the use of the forth language, we never remembered the stack and have not met one of the word manipulation with the stack. And we hid the most dreadful management structure. It is not visible, though it is. code is more descriptive than procedural. Everything else, the code does not require comments, he reads like a review. Fort system written in Forte is itself a directory. One more thing. Developing your program you can use at any level. From the built-in assembler, even lower, you can make the integrated assembler the missing opcodes and mnemonics and to create a high-level, generalizing tools to create compact, expressive code.
Комментарии
Отправить комментарий