Практика мультипарадигмального программирования, 03 лекция (от 12 марта)
Материал из eSyr's wiki.
Предыдущая лекция | Следующая лекция
Tcl (продолжение)
Это хороший пример языка, который встраивается, и в него можно встроить. В прошлый раз был рассмотрен пример встраивания Tcl в C-шное приложение. В этот раз рассмотрим одну интересную C-шную функцию (в прошлый раз были Tcl_CreateInterp, Tcl_Eval, Tcl_GetStringResult): ... . Нужно было компилировать с библиотекой, компоновать... Но эта библиотека не ограничена этими тремя функциями. Рассмотрим новые функции:
- Tcl_CreateCommand(Tcl_Interp * interp, char * cmdName, cmdProc * proc, 0, 0 )
- interp — интерпретатор, в котором хотим создать команду
- cmdName — имя команды
- proc — указатель на функцию, которая возвращает инт, получает ClientData, интерпретатор и argc, argv
Два последних параметра не обязательно нули, один из них вроде бы ClientData, но обычно ставят туда именно нули.
typedef int (* cmdProc)(ClientData, Tcl_Interp *, int /* argc */, const char ** /* argv */);
Позволяет встроить в Tcl команду, написанную на Си или Си++. Так обычно и делают: создают интерпретатор, погружают в него проблемно-ориентированные вещи, так как просто интерпретатор неинтересен. Мало того, можно сделать интерпретатор главной программой. То есть программа написана на Tcl и дополнена функциями, которые загнаны в библиотеку, а в интерпретаторе скажем load. Более того, можно расширить уже встроенный интерпретатор.
Пример. Функция, которая делает реверс слова
reverse: abcde → edcba
#include <tcl.h>
int ReverseString(ClientData ignored, Tcl_Interp * interp, int argc, char * argv[]) { int len = 0; char * result = 0; int i = 0; if (argc != 2) { Tcl_SetResult(interp, "sampleReverseString", TCL_STATIC); return TCL_ERROR; /* TCL_STATIC — результат есть статическая константа, * то есть копировать и удалять её не надо, мы гарантируем, * что эта строка никуда не денется и можно просто поставить * на неё указатель */ } len = strlen(argv[1]); res = (char *)malloc(len + 1); for (i = 0; i < len; i++) { res[i] = argv[1][len – 1 – i]; } res[len] = 0; Tcl_SetResult(interp, res, TCL_VOLATILE); /* TCL_VOLATILE — вот тебе строка, сними с неё копию прямо сейчас */ free(res); return TCL_OK; }
Эту функцию можно встроить в любой интерпретатор Tcl с помощью Tcl_CreateCommand. Как это встроить в отдельно стоящий интерпретатор: общая идея в следующем: мы должны создать разделяемую библиотеку (tcl_sample.so), в которой будет несколько функций, которые прилинкованы как static, и одна нестатическая, которая будет называться аналогично имени библиотеки:
Tcl_sample_Init
/* Библиотека может подгружать несколько функций, ибо делать * библиотеку ради одной функции обычно смысла не имеет */ int Tcl_sample_Init(Tcl_Interp * interp) { struct { char * cmdname; cmdProc cmdp; } funcs[] = { { "sampleReverseString". sampleReverseString } ... {0, 0} }; int i = 0; for (i = 0; funcs[i].cmdp; i++) { Tcl_CreateCommand(interp, funcs[i].cmdname, funcs[i].cmdp, 0, 0); } }
Что такое mangling — в С++ есть перегрузка функций. Там может быть много функций с одинаковым именем и разными типами. Загрузчик этого не умеет, поэтому компилятор вынужден информацию о типах сохранять в имени функции. Но Tcl ищет конкретную функцию. Добиться неприменения mangling просто:
#ifdef _cplusplus extern "C" { int Tcl_sample_Init(Tcl_Interp *); } #endif
В самом интерпретаторе:
load "tcl_sample.so"
Где это нужно: например, в Tcl/Tk (Tk — Toolkit, набор виджетов)
Есть wish. Что он собой представляет: Сишное приложение с встроенным интерпретатором тикля, каковой интерпретатор расширен командами тикля для работы с графическими элементами.
Пример на Tcl/Tk: небольшое приложение, которое будет собой представлять 4 элемента диалогового окна — 3 кнопки и лэйбл. Есть главное окно, которое обозначается точкой. Под ним будут дочерние окна:
- .lab
- .buttons.left
- .buttons.right
- .quit
почему это происходит быстро? В wish есть возможность автоматически рапролагать новые элементы. Изначально есть пустое окно. Новые элементы можно паковать (pack) на форме — прижимать их к краю окна. В Windows же нужно использовать файлы ресурсов, и т. д. Здесь таких проблем нет вообще. wish может работать с ncurses, в иксах, и т. д.
#!/usr/bin/wish
Нехорошо, так как в разных системах лежит в разных местах.
Особенности wish: если в конце комментария бэкслеш, то комментарий продолжается. В связи с этим используем следующий хак:
#!/bin/sh #\ export PATH=/opt/bin:$PATH exec wish "$0" -name mydemo "$@"
Команды Tk:
- label
- frame
- button
- pack
- wm
set w "" ;#root window label $w.lab -text {Just a label} pack $w.lab -side top frame $w.buttons button $w.buttons.left -text Left -command { $w.lab configure -text {Left pressed} } button $w.buttons.right -text Right -command { $w.lab configure -text {Right pressed} } pack $w.buttons.left $w.buttons.right -side left pack $w.buttons -side top button $w.quit -text {Quit program} -command exit wm title . {This is a demo }
Здесь есть аналоги виндовых файлов ресурсов, но предназначены они немного для другого. Например, мы хотим сделать приложение, в котором можно менять язык. С символами нацкодировок всё, в принципе, неплохо. Но есть Юникод. Это в принципе плохо, но не так, как если бы пришлось вставлять это в код программы. Как это сделать:
- Выкидываем все -text
- Добавляем файл опций sample.rc
set w "" ;#root window option readfile "sample.rc" ... (sample.rc) mydemo.lab.text: Just a label mydemo.buttons.left.text : Left Button mydemo.buttons.right.text : Right Button mydemo.quit.text : Quit Program
Как компилировать .so
Тайное знание: в расширяемых библиотеках код должен обладать свойством переносимости, то есть код должен быть с относительными адресами. Всё, что нужно сказать компилятору — компилировать сошку с определёнными ключами. Для С:
- Из файла объектный файла
gcc -Wall -g -fpic -c tcl_sample.c
- Как собрать библиотеку
gcc -shared tcl_sample.o ... -o tcl_sample.so
Практика мультипарадигмального программирования
Календарь
пн | пн | пн | пн | пн | |
Февраль
| 12 | 19 | |||
Март
| 12 | 19 | 26 | ||
Апрель
| 02 | 09 | 16 |