Windows Forth +

    Конструирование оснастки для обработки оконных сообщений Windows


    Язык Форт большинству кажется наименее подходящим, чтобы программировать на нем, да еще и под Windows. Ведь в нем нет никакой графики, только унылая черная текстовая консоль.
    Попробуем преодолеть этот миф.

    Во-первых, программировать под Windows оказывается очень легко, достаточно открыть любую инструкцию по WinAPI.

    Во-вторых, сама Windows управляет всей своей графикой, нам достаточно лишь вызывать нужные функции и правильно обрабатывать сообщения.

    Перед созданием окна необходимо создать свой класс. В структуре WNDCLASS есть поле WNDPROC lpfnWndProc, которое содержит ссылку на процедуру обработки сообщений, поступающих от окон данного класса.

    Требования у Windows к данной процедуре несложные:

    1) Если сообщение не обрабатывается процедурой, необходимо вызвать функцию DefWindowProc
    2) Сохранить содержимое регистров rdi rsi rbx

    Сделаем ассемблерную вставку. Нам нужна согласующая заглушка, которая будет вызывать процедуру, написанную на Форте. Обратно, если из высокоуровневой процедуры приходит сигнал о том, что сообщение не было обработано, вызвать 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 

    Логика данного куска понятна без лишних комментариев.

    1) Сохраним параметры в переменные
    2) Вызовем высокоуровневую процедуру
    3) Вызовем DefWindowProc, если получен не ноль

    Теперь займемся высокоуровневой частью

    Слово Форта само по себе является процедурой.

    Пример
    WORD: Messages  
       do_something   
    ;WORD

    Какое something мы должны do, сейчас и будем выяснять.

    В ассемблерной вставке мы видим использование переменной wmsg. Она принимает параметр uMsg — номер сообщения Windows. Нам необходимо сравнить содержимое wmsg с номером нужного нам сообщения, и если это тот номер, обработать сообщение. Вернуть ноль, чтобы DefWindowProc не вызывалась.

    Проба
    WORD: Messages   
     wmsg @   hex, 201 (( WM_LBUTTONDOWN )   =  If 
                       1 Else 
        do_lbuttondown 0 Then 
    ;WORD 
    

    Имеет право на существование. Но это приемлемо, когда надо обработать одно-два сообщения. Но неудобно, некрасиво, плохо сопровождаемо и не решает поставленную задачу. Ведь придется писать вложенные конструкции If Then, а это ужас-ужас в листинге.

    Некрасиво
    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
    

    Всего два сообщения, а приходится напрягаться чтобы быть уверенным что написано правильно.
    К счастью, конструкция Case… Of… EndOf… EndCase реализуется довольно легко и существенно украшает код.

    Перепишем:
    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

    Гораздо приятнее читать и, если что, добавлять еще обработчики. Но все же можно лучше.

    Во-первых, здесь постоянно повторяется wmsg @ и =.

    Во-вторых, вставлять численные шестнадцатеричные значения констант как-то неэстетично. К тому же приходится писать комментарий, что это значение обозначает.

    Пусть WM_LBUTTONDOWN, WM_LBUTTONUP и т.п. будут константами.

    A wmsg @ и = объединим в одно слово.
    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
    

    Стало гораздо красивее и понятнее. Но все равно слишком много лишних слов в листинге.

    Если бы можно было писать
    Messages{{
        WM_LBUTTONDOWN{{ do_lbuttondown }}
        WM_LBUTTONUP{{   do_lbuttonup   }}
    }}Messages
    

    Решим эту задачу.
    Самое простое — это реализовать слово }}. Оно — почти эквивалент слова EndOf, просто к нему мы присовкупим слово 0.

    И...
    WORD: }}
       0 EndOf
    ;WORD
    

    А вот и нет. Слово EndOf немедленного исполнения. Вместо того, чтобы скомпилироваться, оно будет исполнено. Исполнено во время компиляции слова }}. А нам надо, чтобы оно исполнялось во время компиляции модуля обработки сообщений.

    Взглянем на реализацию EndOf
    WORD: EndOf	
       COMPILE BRANCH   HERE >R   COMPILE 0   THEN R> 
    ;WORD
    

    Воспользуемся его Величеством Копипастом и напишем… Но для начала учтем, что слово }} должно быть немедленного исполнения.

    Итак
    
    IMMEDIATES CURRENT !
    
    WORD: EndOf	
       COMPILE 0  COMPILE BRANCH   HERE >R   COMPILE 0   THEN R> 
    ;WORD
    
    FORTH32 CURRENT ! 
    

    Вставим слово }} на место 0 EndOf, и убедимся в его работоспособности.

    Разберемся со словом }}Messages

    Оно должно:

    1) компилировать не ноль
    2) выполнять EndCase
    3) заканчивать компиляцию, аналогично ;WORD

    Заметим, это слово немедленного исполнения.

    Написать его довольно просто:
    
    IMMEDIATES CURRENT !
    
    WORD: }}Messages
       COMPILE 1   (EndOf)    ;Word   quit  ;WORD
    ;WORD
    
    FORTH32 CURRENT ! 
    

    А сейчас сконструируем открывающие слова. Начнем с Messages{{

    Что оно должно делать?

    4) запускать компиляцию
    3) компилировать Case
    2) делать адрес начала процедуры доступным вставке winproc
    1) отмечать адрес, с которого начнется процедура обработки сообщений

    Автоматическая компиляция запускается словом immediator. Оно заполняет поле параметров компилируемого слова соответственно исходному тексту. Полю параметров предшествует поле кода, которое в случае высокоуровневого определения должно содержать ссылку на адресный интерпретатор. Её нам дает константа interpret#. Слово Case суть синоним слова 0. Просто Case — немедленного исполнения, а 0 — обычное компилируемое слово.

    Напишем
    WORD: Messages{{
       HERE   ['] inWinProc CELL+  !   0    interpret# ,    immediator  
    ;WORD
    

    Вызов inWinProc мы встречаем в ассемблерной вставке. Это так называемое векторное слово. Оно почти обычная константа, но вместо того, чтобы положить значение на стек, исполняет его.

    Теперь самое интересное

    Определим слова WM_LBUTTONDOWN{{ и 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 ! 
    

    Неужели придется для каждого сообщения копипастить этот код, исправляя лишь константу? Посмотрим повнимательнее. Код в каждом определении один и тот же, они различаются лишь именем и используемой константой. Эта константа является для последующего кода параметром. Схематично выглядит как x do_something_with_x.

    На наше счастье в Форте существует понятие определяющего слова. Которые и предназначены для таких случаев.

    Напишем
    WORD: WM:
        CREATE ,  DOES> @  COMPILE (?wm)   COMPILE ?OF  HERE    COMPILE 0 
    ;WORD
    

    Как пользоваться
    
    IMMEDIATES CURRENT !
    
     WM_LBUTTONDOWN  WM: WM_LBUTTONDOWN{{
     WM_LBUTTONUP    WM: WM_LBUTTONUP{{
    
    FORTH32 CURRENT !
    

    Эээ… А зачем мы повторяем один и тот же текст слева и справа? И даже трижды. (Мы же определили ранее константы). Может быть стоит не определять константы, а сразу определять слова?

    Вот так
    
     0d 513  WM: WM_LBUTTONDOWN{{
     0d 514  WM: WM_LBUTTONUP{{
    

    И… Не работает. Посмотрим повнимательнее. Во-первых, все эти слова должны быть немедленного исполнения. То-есть они должны скомпилировать код после DOES> в тело Messages{{.

    Эта часть: COMPILE (?wm) COMPILE ?OF HERE COMPILE 0 все делает правильно. Но сразу после DOES> мы получаем значение, скомпилированное во время создания слова WM_L… А оно нам нужно во время исполнения слова Messages{{.

    Нам надо всего-лишь скомпилировать это значение как литерал уже в тело Messages{{.

    Верный код
    WORD: WM:
        CREATE ,  DOES> @ LIT,   COMPILE (?wm)   COMPILE ?OF   HERE   COMPILE 0 
    ;WORD
    

    Подытожим. Удобно выделить общую, заголовочную часть в отдельный файл.

    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 ! 
    

    Файл
    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
    

    Послесловие


    Заметьте, несмотря на использования языка Форт, мы ни разу не вспомнили про стек и не встретили ни одного слова манипуляции со стеком. А еще мы спрятали не самую кошмарную структуру управления. Её не видно, хоть она и есть. код получился более описательный, чем процедурный. Ко всему прочему, код не требует комментариев, он сам читается как комментарий. Форт-система, написанная на Форте является сама себе справочником. Еще один нюанс. Для разработки своей программы вы можете использовать средства любого уровня. От встроенного ассемблера, даже ниже, вы можете внести во встроенный ассемблер недостающие опкоды и мнемоники и до создания высокоуровневых, обобщающих инструментов, позволяющих создавать компактный выразительный код.
    Поделиться публикацией
    Похожие публикации
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 13
    • 0
      Знаете, мне и с графикой Forth кажется не слишком подходящим для программирования. Не в этом веке и не для современных машин и задач.
      • +1
        Для микроконтроллеров вполне себе подходит
        • 0
          собственно, идеи форта живут практически во всех плк. яркий пример — сименсовские s7 + stl в step7. всё через стек. то есть идея-то одна — постоянное изменение некоторого потока данных или некоторого состояния, некоторой переменной. стековые языки очень просты в плане реализации.
          я несколько лет использовал именно stl (+ lad и fbd — тоже, в принципе, потоковая обработка переменных на стеке) для задач автоматизации.
          но тот же сименсовский scl («обычный» яп, похожий на паскаль) в случаях не очень тривиальных — более или менее сложная логика, циклы (открытые, конечно!) — гораздо читабельнее. его гораздо проще поддерживать, анализировать глазами.
          так что такие яп имеют право на жизнь именно там, где нужна простота, где нет сложных задач.
          да, можно на форте писать сложные системы. можно также написать аналог офиса на баше. но зачем и кто это будет развивать?
          … всё-таки не нужно.) уже нет.
          • +1
            Понимаете, в чем дело. Идея Форта — не стек. Основное в Форте — расширяемость. Вы создаете сами инструменты для расширения системы под решаемую задачу. Причем можете создавать их на любом уровне, от машинного кода до самого верхнего уровня, причем на всех одновременно. Это и мощь и слабость. Чтобы писать на Форте, надо вывернуть мышление наизнанку. Это пугает «традиционалистов».
            А на вечный вопрос «зачем?» ответ — хочу! :)
        • 0
          removed
          • 0
            На современных машинах он может обрести второе дыхание. С нынешней модой на виртуализацию.
          • 0
            Я бы выделил слово {{ отдельно
            • 0
              Можно. Но… не нужно. Нет такого функционала, ради которого стоит заводить еще одно слово. Да еще с ничего не говорящим именем. {{ в конце слов, определенных через WM: в первую очередь украшательство. Можно вообще обойтись без них. Например сделать такой интерфейс: WM_LBUTTONDOWN do_smth ;WM
              В предыдущей версии скобки были одинарные, но сразу выяснилось что их легко потерять или перепутать с круглыми.
              Кроме того, код после DOES> в WM: существует в системе в единственном экземпляре, а не копируется в каждое слово, определенное через WM:. То-есть экономии никакой нет. Но я прям чувствую, что отдельно стоящие скобки так и будет тянуть или потерять, или прилепить.

            • 0
              Прекрасно!
              • 0
                Приятно видеть, что форт продолжает жить.

                А вообще форт намного удобней для интерактивной разработки. Например для тюнинга ядра ОС. Написал разбор таблицы — и сразу запомнил как функцию.
                • +2
                  В результате мы в статье так и не увидели никакой графики, а только унылая черная текстовая консоль чёрный текст на белом фоне.
                  • 0
                    Хотелось бы узнать, какую реализацию форта автор использовал для данной статьи.
                    • 0
                      Авторская реализация openforth с минимальным ядром. Подробности лучше, наверное, в личку.

                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.