Qt-Forth - учебный forth с графикой Qt для Windows и Linux
Учиться никогда не поздно,
тем более классическим алгоритмам.
Главная
Учебник
SPF 4.20 и QT
Алгоритмы
Исходники
DownLoad
Об авторе

  1. Вызов функций из внешних загружаемых библиотек .dll и .so
  2. Управление Qt-Forth из .dll и .so (выполнение слов форта по вызову из внешней библиотеки)
  3. Вызов из Qt-Forth конструкторов и методов из C++ dll
  4. Создание DLL (SO) для работы с Qt из SPF

Вызов функций из внешних загружаемых библиотек .DLL и .SO

Немного теории.

Функции - это основной строительный материал программы. В любой ОС их необъятное множество. Сами функции сосредоточены в динамически загружаемых библиотеках (основа ОС так сказать).DLL для Windows и .SO для Linux , или в статических библиотеках (входят в состав компиляторов, или поставляются отдельно) .LIB для Windows и .A для Linux. Положение функции в программе может быть:

  1. В самом исполняемом файле, статическая компоновка. Производиться во время компиляции.
  2. Во внешнем, загружаемом во время выполнения файле - динамическая компоновка. В свою очередь она делиться на:
    • Неявная. Компилятор автоматически формирует код для загрузки динамических библиотек.
    • Явная. Программа сама выдает команды на загрузку и выгрузку библиотек.

В Qt-Forth применяется последний способ. Программа явно может загружать и выгружать динамические библиотеки. Делает она это при помощи функций API:

Когда библиотека загружена в память, следующим этапом надо найти адреса нужных функций. Динамическая библиотека содержит таблицу имен функций. Есть много утилит которые показывают эту таблицу. Для Linux : objdump -t имя.so например. Адрес функции, это адрес куда надо передать выполнение для выполнения функции. Ищем этот адрес:

Если вызов прошел удачно, то на выходе будет адрес нужной нам функции. По этому адресу можно её вызвать. Перед вызовом нужно положить в стек параметры, а после вызова забрать результат выполнения, если он есть. Как растолковать компилятору (например С++) данную задачу? Через предварительное описание функции. Например:


typedef     int (*ИмяФункции)(int);      //    int ИмяФункции(int);  
ИмяФункции =  GetProcAddress("Имя функции"...);  // для Windows
int rezultat = (ИмяФункции))(int);       // Вызов на выполнение

Когда функции больше не нужны, то библиотеку можно выгрузить, вернее попросить ОС выгрузить. Будет ли она физически выгружена зависит от того, есть ли еще пользователи для этой библиотеки.

Как быть если заранее не известно имя функции, количество и тип параметров.

Как быть, если нужно вызвать функцию по адресу не зная заранее ни имени, ни количества, ни типа параметров. Компилятору C++ все равно надо сообщить данную информацию. В Qt-Forth я использую следующий способ:

Таким образом при компиляции Qt-Forth из исходников на C++ не известно ни имя будущей вызываемой функции, ни количество и тип её аргументов. Из практических соображений я сделал CASE на 10 параметров. В основном из за функции sprintf(), так как на практике количество параметров у неё может быть довольно большим.

Реализация загрузки динамических библиотек в Qt-Forth.

Версии Qt-Forth для Windows и Linux отличаются блоком функций для загрузки динамических библиотек. Подробнее об этом в документации. Рассмотрим пример вызова API функций для Windows:


0 var h_user32   // Переменная для указателя на библиотеку
: load_user32     // ( -- ) Загрузим user32.dll При успехе в h_user32 будет записан адрес библиотеки
      "user32.dll" 1+ llibrary dup not if "Ошибка загрузки" w drop else h_user32 ! then ;  
load_user32      //  Выполним загрузку библиотеки
h_user32 @  "GetWindowTextA" 1+ gpaddres : GetWindowText  3 1 lit, acall ;
h_user32 @  "SetWindowTextA" 1+ gpaddres : SetWindowText   2 1 lit, acall ;

Определили две функции API . Фактически компилируются новые слова (функции). Определение немного хитрое. После выполнения gpaddres /* GetProcAddress() */ на стеке адрес вызываемой функции. Правда здесь нет проверки, правильно найдена функция или нет. Предполагается, что адрес правильный (не нулевой). Далее : делает новое слово. А вот дальше параметры для CASE в С++, а именно два или три входных параметра и один или ноль выходных. Слово lit, еще во время компиляции скомпилирует со стека адрес найденной функции в тело вновь создаваемого слова и в конце вызов acall (фактически тот самый CASE С++). Вот мы и оформили вызов двух функций из API Win32.

Пример использования в Windows.

Версии Qt-Forth для Windows и Linux отличаются блоком функций для загрузки динамических библиотек. Подробнее об этом в документации. Рассмотрим пример вызова API функций для Windows:


def форма:CQWidget       // Определим форму
форма.show                  
форма.hwnd "Наш новый заголовок окна" 1+ SetWindowText    // Поставим на неё новый заголовок
drop      // Сбросим единичку, признак того, что функция отработала правильно.

Управление Qt-Forth из .dll и .so (выполнение слов форта по вызову из внешней библиотеки)

Интерфейс вызова.

Qt-Forth предоставляет адреса трех функций, вызывая которые, вы получаете возможность класть на стек форта параметры, забирать со стека параметры и выполнять произвольное слово из уже определенных. Эту возможность можно использовать для управления Qt-Forth из внешней DLL. Для организации этого взаимодействия в Qt-Forth определены следующие функции:

>
Прототип C++
Описание
Qt-Forth
void PopSD(void *adr) Положить на стек 2 syslink
void *PushSD(void) Взять со стека 3 syslink
void ExecZIM(void *adr) Вызвать слово форт по адресу 1 syslink

Для получения адресов этих функций используется слово форта syslink которое по числу на стеке (см таблицу) выкладывает адрес необходимой функции.

Разберем пример в Windows. Предположим, что у вас есть DLL, в которой определены некие внешние переменные (C++ Builder 6.0)

extern "C"  void * __export   adrExecForth;    //  Сохраним здесь адрес  функции вызова  слова Qt-Forth 
extern "C"  void * __export   adrForthWord;   //   а  здесь адрес выполняемого  слова форта

Из Qt-Forth загрузим DLL в память (смотри выше) и запишем адреса Qt-Forth в переменные DLL

:   onCallFromDLL                    // ( -- ) Это слово вызовем из DLL	
     "Вызвано из DLL ...." w       //   напишем эту фразу в консоли
 	 mstop
	 ;
: initC++         // ( -- ) Инициализация глобальных переменных  в DLL
     lit onCallFromDLL  adrForthWord !      // Запишем  адрес  onCallFromDLL в  adrForthWord С++
     1 syslink  adrExecForth !                          //  Запишем  адрес  выполнителя форт слов в adrExecForth
    ;	 initC++
	

Слово lit во время выполнения выкладывает адрес следующего за ним слова на стек. Таким образом, вызвав initC++подготовим вызов из DLL. А теперь вызовем из DLL Qt-Forth:

  typedef void (*ExecZIM)(void *);    //  определим  тип  для вызываемой функции
((ExecZIM)adrExecForth)((void *)adrForthWord);    //   Вывод в консоль фразы "Вызвано из DLL ...."
  

Вызов из Qt-Forth конструкторов и методов из C++ dll

Вызов конструкторов и методов классов из dll довольно сложный процесс, но возможный! Первое, что мешает, это искажение имен в C++ библиотеке. Хотя найти нужное имя конструктора или метода реально. Помогают утилиты tdump например, котороя показывает список глобальных (экспортируемых) функций. А утилита UNDNAME с ключом -f осуществляет перекодировку имен в понятный вид.

C:\>undname -f ?resize@QWidget@@QAEXHH@Z
>> ?resize@QWidget@@QAEXHH@Z == public: void __thiscall QWidget::resize(int,int)

Таким образом становиться возможно найти нужный метод или конструктор. Второй моиент, это неявная подстановка адреса данных экземпляра, при вызове методов. Передача этого адреса осуществляеться через регистр. Для реализации этой возможности пришлось ввести еще один case в алгоритм вызова внешних функций (смотри выше) для возможности записи этого регистра. В результате получилась приблизительно такая конструкция:

     switch (typecall) {
      case 1 : ((ccall_0_0)uk)(); break;                                            // тип __cdecl
      case 2 : ((call_0_0)uk)(); break;                                             // тип __stdcall
      case 11 : _asm { mov ecx, gadr } ((ccall_0_0)uk)(); break;       // тип __thiscall
      case 12 : _asm { mov ecx, gadr } ((call_0_0)uk)(); break;         // тип __thiscall
      }
	

где gadr - просто переменная получающая своё значение со стека форта.

Пример. Осуществим прямое взаимодействеие с библиотекой QtGui4.dll для создания объекта типа QWidget и его отображения на экране.

 // Проверка прямых вызовов конструкторов и методов с Qt.DLL
// Модификаторы для слова acall+ (новая версия с поддержкой вызова методов и классов)
1  const _cdecl
2  const _stdcall
10 const _c++

// Загрузим QtGuid4.dll
0 var h_qt
: load_qt    // Загрузка в память QtGuid4.dll
     "QtGuid4.dll" 1+ llibrary dup
      not if "error loading" w drop else "Ok" w h_qt ! then ; load_qt

// Это конструктор см документацию по QWidget.
// Если это имя подставить в утилиту UNDNAME пакета MSVC 6.0 то получим
// public: __thiscall QWidget::QWidget(class QWidget *,class QFlags)
//
h_qt @  "??0QWidget@@QAE@PAV0@PBDV?$QFlags@W4WindowType@Qt@@@@@Z" 1+
   gpaddres : QWidget  // ( Awidget Aqflags Afreebuf -- ) Создать QWidget по адр Afreebuf
            >r 2 0 lit, r> _cdecl _c++ + acall+ ;

// Это метод show QWidget.
// Если это имя подставить в утилиту (UNDNAME -f имя) пакета MSVC 6.0 то получим
// public: void __thiscall QWidget::show(void)
h_qt @  "?show@QWidget@@QAEXXZ" 1+ gpaddres 
   : show >r 0 0 lit, r> _cdecl _c++ + acall+ ;

// Это метод resize QWidget.
// Если это имя подставить в утилиту (UNDNAME -f имя) пакета MSVC 6.0 то получим
// public: void __thiscall QWidget::resize(int,int)
h_qt @ "?resize@QWidget@@QAEXHH@Z" 1+ gpaddres 
   : resize >r 2 0 lit, r> _cdecl _c++ + acall+ ;

// Подготовим буфер под объект QWidget
here@ 1000 + const buf  // Указатель на свободное место
: bufc 100 0 do 0 buf i + c! loop ; bufc // Забъем нулями этот участок
: bufd buf dump ; // Распечатаем на консоле дамп

// Работаем с функциями ...
: test
  bufd "Память по указателю buf до создания объекта" w
  0 0 buf QWidget              //  QWidget *buf = new QWidget(NULL, NULL);
  bufd "Создан объект QWidget по адресу buf" w
  200 100 buf resize          // buf->resize(200, 100);
  buf show                        // buf->show();
  ;
 

Создание DLL (SO) для работы с Qt из SPF

Ниже описана простая графическая библиотека для SPF, использующая вызовы QT для построения графического интерфейса. Так как QT написана на C++, то прямое взаимодействие SPF с ней затруднено. Нам придется написать обертки (wrapping) для объектов С++. В качестве таких обёрток будет выступать DLL (SO для Linux), которую мы напишем на C++.

Для Windows будем использовать MSVC 6.0, для Linux GCC. Саму QT возьмем версии 4.3 — хотя это не принципиально. Форт будем использовать SPF, т.к. он открыт и существует в реализациях для Windows и для Linux.

Предполагается, что вы знакомы с основами Форт и C++.

Инициализация QT.

Рассмотрим самую простую программу C++ с использованием QT.

 

#include                  // Включить в программу файлы заголовков

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);     // Инициализация Qt
	....................
    return app.exec();                // Цикл опроса графических событий
}

Для этого надо:

Данный пример демонстрирует минимально необходимый «скелет» программы работающей с Qt. Визуальные объекты при этом не рисуются, а только происходит вызов цикла обработки графических сообщений.

Пишем загружаемую библиотеку на C++ и программу на SPF для работы с ней.

Создадим и будем использовать собственную DLL. Задача этой DLL скрыть от нас момент создания объекта и обеспечить интерфейс вызова из SPF. Форт, SPF в частности, легко может организовать вызов внешних С процедур из DLL. Таким образом наша DLL должна предоставить форту четкое имя процедуры, забрать входные параметры для конструктора или метода и вернуть ссылку на созданный объект.

Кусочек кода нашей DLL для создания объекта QApplication.


#ifdef LINUXF
  #define FQT_API 
#endif

// На вход параметры инициализации, на выход указатель на объект
extern "C" FQT_API void *QT_App(int qargc, char *qargv[]) {
	return  (void *)new QApplication(qargc, qargv);
}

Так как указатель у нас хранится в хипе, он сохраняется после выхода из функции в DLL.

В Qt-Forth применяется последний способ. Программа явно может загружать и выгружать динамические библиотеки. Делает она это при помощи функций API:

Когда библиотека загружена в память, следующим этапом надо найти адреса нужных функций. Динамическая библиотека содержит таблицу имен функций. Есть много утилит которые показывают эту таблицу. Для Linux : objdump -t имя.so например. Адрес функции, это адрес куда надо передать выполнение для выполнения функции. Ищем этот адрес:

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

В SPF организуем загрузку библиотеки в память и работу с этими двумя функциями. Следующая программа на SPF для Linux грузит в память нашу библиотеку и определяет адреса процедур.

VARIABLE aDLL          \ Для хранения ссылки на библиотеку
VARIABLE aQT_App       \ для адреса функции  QT_App
VARIABLE aapp_exec     \ для адреса функции  aapp_exec
VARIABLE APP

: init  \ Загрузить библиотеку и найти точки входа в процедуры
S" fqt.so.2.0.0" DROP dlopen aDLL !  \ Загрузить DLL. Если aDLL<>0 значит ОК
S" QT_App"   DROP aDLL @ dlsym aQT_App !   \ Точка входа в QT_App(.....)
S" app_exec" DROP aDLL @ dlsym aapp_exec ! \ Точка входа в app_exec(...)
   ;

: main     \  Основная программа
  init    
  ARGV ARGC 2 aQT_App @ CDECL-CALL APP ! \ Создать объект QApplicatin
  APP @ 1 aapp_exec @ CDECL-CALL         \ Вызвать Qapplicatin.exec()
  ;
main \ Запуск программы

Здесь, в этом примере, специально не используются расширения SPF для работы с динамическими библиотеками (типа USE, WINAPI и т.д.) , а так же не делаются проверки на правильность работы функций API. По правильному, надо организовать проверку возвращаемых значений, для обнаружения возможных ошибок. При запуске слова main происходит вход в цикл обработки графических событий. Так как мы пока не нарисовали ни одного виджета (окна), то на экране ни чего не должно отражаться.

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

Определяем визуальный объект (widget) и работаем с ним.

Теперь можно начать определять визуальные компоненты. Опеделим Qwidget. Это базовый класс окон. Однако есть трудность. Она заключается в том, каким образом обрабатывать события из QT в SPF. В QT определено несколько способов взаимодействия объектов. Например СИГНАЛ—СЛОТ. Мы немного позже рассмотрим эту возможность. А сейчас попробуем задействовать механизм событий. Каждый класс в QT содержит (или может содержать) виртуальные функции обработки событий (onevent). Например:

void QWidget::resizeEvent ( QResizeEvent * event )   [virtual protected]

Это событие вызывается автоматически из QT при изменение размера виджета (окна). Когда произошел вызов, виджет уже имеет новую геометрию. На примере этого события, мы определим механизм взаимодействия QT и SPF.

Суть метода. Пишем на C++ функцию, задача которой вызвать SPF по CALLBACK и передать параметры в форт. Для этого надо запомнить адрес callback (адрес слова форта, которое будет выполнено при вызове из QT). Для каждого экземпляра объекта необходимо запоминать свой уникальный адрес обработки данного события в форт. Таким образом, мы сможем вызывать разные слова форта в ответ на разные события QT. Сделаем собственный класс QWidget в нашей DLL.

// Определяем собственный класс с возможностью хранения адреса обраб события
zQWidget::zQWidget( QWidget* parent ) : QWidget( parent )
{
	aOnResize = NULL;  // Адрес слова SPF для обработки события OnResize
}
void zQWidget::resizeEvent( QResizeEvent *a )
{
      // Если aOnResize не нулевой, то вызываем функцию (прототип
      // с количеством входных и выходных параметров
      // описаны в ExecSPF_1_0 (один параметр на вход и ноль парам. на выход)
      // и передачей в SPF аргумента, а именно  QResizeEvent *a. 
	if (aOnResize!= NULL) ((ExecSPF_1_0)aOnResize)((void *)a);
} 
// Установить обработчик OnResizeEvent
extern "C" FQT_API void QT_QWidget_onresize(zQWidget* qw, void *uk) {
	qw->aOnResize = uk;  // Запомнить указатель на слово SPF в объекте
}

Внутри класса определяем переменную для хранения адреса callback обработки данного события. Переопределяем виртуальную функцию, заставляя её вызвать функцию по адресу хранящемуся в объекте (если он не нулевой конечно). Поскольку при создании нового объекта все адреса забиваются NULL (нулем), то наш объект не будет реагировать на события, пока мы не запишем адрес нашего обработчика.

Обратите внимание, на функцию установки обработчика события. Функция оформлена в С стиле. На вход ей передается из форта адрес Qwidget и адрес callback форта для обработки данного события.

Для создания виджета (окна) напишем функцию конструктора.

// !!! Выдать QWidget на стек
extern "C" FQT_API void *QT_QWidget(QWidget* parent) {
	return  new zQWidget(parent);
}

Для визуализации виджета (окна) напишем функцию вызова метода show()

// !!! Показать QWidget
extern "C" FQT_API int QT_QWidget_show(zQWidget* qw) {
	qw->show();
	return 0;
}

Работа с событиями QT из SPF

У нас есть теперь возможность вызвать визуальный компонент (окно) и посмотреть обработку событий. Рассмотрим программу на SPF. Теперь в неё добавлена работа с нашим виджетом и обработка событий.

VARIABLE aDLL                \ Для хранения ссылки на библиотеку
VARIABLE aQT_App             \ для адреса функции  QT_App
VARIABLE aapp_exec           \ для адреса функции  aapp_exec
VARIABLE aQWidget            \ для адреса QWidget
VARIABLE aQWidget_show       \ для Qwidget.show()
VARIABLE aQWidget_onresize   \ для установки адреса callback обраб. события

VARIABLE APP                 \ под объект QApplicatin
VARIABLE QW                  \ под объект Qwidget №0
VARIABLE QW1                 \ под объект Qwidget №1

: dlsym1    \ Подготовка параметров и вызов dlsym
     DROP aDLL @ dlsym
   ;

: init  \ Загрузить библиотеку и найти точки входа в процедуры
S" fqt.so.2.0.0" DROP dlopen aDLL !  \ Загрузить DLL. Если aDLL<>0 значит ОК
S" QT_App"              dlsym1  aQT_App !   \ Точка входа в QT_App(.....)
S" app_exec"            dlsym1  aapp_exec ! \ Точка входа в app_exec(...)
S" QT_QWidget"          dlsym1  aQWidget !
S" QT_QWidget_show"     dlsym1  aQWidget_show !
S" QT_QWidget_resize"   dlsym1  aQWidget_resize !
S" QT_QWidget_onresize" dlsym1  aQWidget_onresize !
   ;

:NONAME \ ( a -- ) Обработка события OnResize из QT
  DUP                \ дублируем параметры (т.к. cdecl)
  S" Работает наше событие OnResize" TYPE CR
  0 \ возвращаемое значение
; 1 CELLS            \ 1 параметра - 4 байт
CALLBACK: Test       \ слово Test это callback для обработки события в QT

VARIABLE aTest       \ Переменная для хранения адреса обработчика
' Test aTest !

: cr_qt   \ Инициализация и подготовка виджетов
  init    
  ARGV ARGC 2 aQT_App @ CDECL-CALL APP ! \ Создать объект QApplicatin
  0 1 aQWidget @ CDECL-CALL QW  !        \ Создать объект QWidget
  0 1 aQWidget @ CDECL-CALL QW1 !        \ Создать объект Qwidget
  QW @ 1 aQWidget_show @ CDECL-CALL DROP \ Отобразим окно (виджет) на экране
  ;

: main     \  Основная программа
  cr_qt
  aTest @ QW @ 2 aQWidget_onresize @ CDECL-CALL  \ Установим обработчик события
  APP @ 1 aapp_exec @ CDECL-CALL         \ Вызвать Qapplicatin.exec()
  ;
main \ Запуск программы

Самое интересное в данной программе, это определение функции на форте, которая будет вызвана из QT при наступлении события OnResize (изменение размера окна). Это слово Test и предшествующие слово NONAME. Более подробно оформление функций в callback стиле описано в документации на форт. Сам обработчик устанавливается в слове main. При работе данной программы, мы увидем окно, изменяя размеры которого увидим надпись в консоли «Работает наше событие OnResize». Данная строка печатается на каждый вызов события.

Теперь нам ни чего не мешает расширять нашу DLL, для ввода туда новой функциональности, а именно добавления работы с другими виджетами. Аналогично можно построить работу с кнопками, списками и т.д. и т.п.



Hosted by uCoz