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.
Article based on information from habrahabr.ru

Комментарии

Популярные сообщения из этого блога

Wikia Search — first impressions

Emulator data from GNSS receiver NMEA

mSearch: search + filter for MODX Revolution