## Шаг 10. Этот шаг написан по заявкам зрителей. Вопрос из зала звучал так: > я хочу внедрить Lua в качестве настраемого интерефейса к программе. Примерно так: есть набор кнопок, с каждой кнопкой ассоциирован скрипт Lua. Когда пользователь нажмет на кнопку скрипт должен выполнится. Какой порядок вызовов произойдет? > EngineButton()==>lua\_dobuffer()==>скрипт() ==>modulLua.dll==>функция\_внутри\_modulLua\_dll() так? >а если так, то как можно связать функцию\_\_внутри\_modulLua\_dll() и основную программу? Ведь фактически луашный модуль собирается в отдельном проекте, реализуется отдельной DLL-ой, и никак практически не свзан с основной программой. Чтобы передать какой-нибудь указатель, в том же пространстве адресов, то единственной возможностью станет загрузка как и потоком EngineButton() так и функции\_внутри\_modulLua\_dll() еще одной общей для них DLL, обеспечивающей передачу данных. Или я чего-то недопонимаю? Этот вопрос мы трансформируем в следующую задачу: Есть GUI с кнопками Button1, Button2 и Button3. Программма при запуске создает Lua-state, который живет от начала до конца программы. В этот Lua-state загружается скрипт из файла ./scripts/button1.lua, который в процессе загрузки вызовет несколько функций из основной программы. По нажатию на Button1 будут вызваны несколько функций из скрипта. По нажатию на Button2 создастся новый Lua-state и в нем выполнится скрипт из файла с именем ./scripts/button2.lua, который вернет результат своей работы в основную программу. Нажатие на кнопку Button3 ничего не делает, просто демонстрируя серьезность наших намерений :) Как обычно, начнем плясать от печки. Во первых, в качестве С++ IDE выбираем VC++ от Microsoft. После упражнений с несколькими другими IDE я понял, что это лучшее, благо для некоммерческих разработок MS отдает его даром. Брать можно здесь [http://www.microsoft.com/express/vc/](http://www.microsoft.com/express/vc/ "http://www.microsoft.com/express/vc/"). Скачали. Установили. Идем на [http://www.lua.org/](http://www.lua.org/ "http://www.lua.org/") за свежей версией Lua. Хотел я там скачать готовые бинарники, однако ничего не вышло. Ну, может оно и к лучшему, есть возможность лишний раз потренироваться в сборке Lua. Забираем исходники. На момент написания этого шага была доступна версия [http://www.lua.org/ftp/lua-5.1.4.tar.gz](http://www.lua.org/ftp/lua-5.1.4.tar.gz "http://www.lua.org/ftp/lua-5.1.4.tar.gz"). Готовим рабочее место. Создаем папку LuaTutorial. В ней создаем папки bin, include, lib. В папках bin и lib создаем папки debug и release. Сначала соберем Lua. Запускаем VC++. Из меню File->New...->Project создаем новый проект. Выбираем Win32 Console Application. Проект называем LuaDll: ![](./images/step10/new_lua_project.png) На следующей странице визарда выбираем Application Settings или нажимаем кнопку Next: ![](./images/step10/lua_project_app_settings.png) В Application Type выбираем DLL. Ставим галочку напротив Empty Project, поскольку исходники Lua уже содержат все необходимые файлы. Нажимаем Finish: ![](./images/step10/lua_project_app_settings_end.png) Отличо, пустой проект для Lua у нас готов. Наполним его содержимым. Распаковываем архив с исходниками в папку LuaTutorial/LuaDll. Поскольку .dll с Lua будет использоваться другими приложениями, то .h файлы переносим в папку include/Lua. В папке LuaTutorial/LuaDll остаются только .c файлы. Добавим их в проект: ![](./images/step10/lua_project_add_files.png) Добавляем все файлы, кроме lua.c (интерпретатор) и luac.c (компилятор) .lua скриптов, их можно будет собрать позже. Из меню Project->Properties вызываем диалог с настройками проекта. Зададим путь к .h файлам. В выпадающем списке Configuration выбираем AllConfigurations. Далее идем Configuration Properties->C/C++->Additional Include Directories. Там вводим путь ../include/lua: ![](./images/step10/lua_project_include_path.png) Теперь настроим пути, куда нужно складывать готовые .dll и .lib файлы. Для Debug и Release эти пути будут разные. Сначала настроим пути для Debug. В выпадающем списке Configuration выбираем Debug. Далее идем Configuration Properties->Linker->General->Output File. Там вводим путь ../bin/debug/lua5.1.4.dll: ![](./images/step10/lua_project_debug_output_dll.png) Затем идем в Configuration Properties->Linker->Advanced->Import Library. Там вводим путь ../lib/debug/lua5.1.4.lib: ![](./images/step10/lua_project_debug_output_lib.png) Для Release в выпадающем списке Configuration выбираем Release. Далее идем Configuration Properties->Linker->General->Output File. Там вводим путь ../bin/release/lua5.1.4.dll: ![](./images/step10/lua_project_release_output_dll.png) Затем идем в Configuration Properties->Linker->Advanced->Import Library. Там вводим путь ../lib/debug/lua5.1.4.lib: ![](./images/step10/lua_project_release_output_lib.png) Для того, чтобы собрать Lua как .dll нужно включить дерективу препроцессора: #define LUA_BUILD_AS_DLL Это знание я почерпнул из файла luaconf.h строчка 154: #if defined(LUA_BUILD_AS_DLL) #if defined(LUA_CORE) || defined(LUA_LIB) #define LUA_API __declspec(dllexport) #else #define LUA_API __declspec(dllimport) #endif #else #define LUA_API extern #endif Для определения этой дерективы препроцессора окне настроек проекта Configuration Properties->C/C++->Preprocessor->ProcessorDefinitions добавляем `LUA_BUILD_AS_DLL`. Это нужно сделать для Debug: ![](./images/step10/lua_project_debug_preprocessor.png) и для Release: ![](./images/step10/lua_project_release_preprocessor.png) Теперь у нас все готово для того, чтобы собрать библиотеку Lua. Идем в меню Build->Build Solution. После того, как студия выдаст кучу варнингов, в указанных в свойствах проекта папках окажутся файлы lua5.1.4.dll и lua5.1.4.lib. Отлично. Для проверки работоспособности .dll соберем интерпретатор Lua. В том же солюшене LuaDll добавим новый проект: ![](./images/step10/add_new_lua_interpreter_project.png) Назовем его LuaInterpreter: ![](./images/step10/new_lua_interpreter_project.png) На следующей странице визарда выбираем Application Settings или нажимаем кнопку Next. Ставим галочку напротив Empty Project. Нажимаем Finish: ![](./images/step10/lua_interpreter_app_settings.png) Добавляем в проект файл lua.c из папки LuaDll (файлы добавляются так же, как здесь). Настраиваем пути к .h и .lib файлам. Я покажу подробно, как это делается для Debug, а для Release читатель должен смочь сделать это самостоятельно. Путь к .h файлам настраивается точно так же, как в проекте LuaDll: ![](./images/step10/lua_interpreter_include_path.png) Поскольку интерпретатор использует Lua как .dll, то нам нужно указать файл библиотеки (если мы, конечно, не собираемся сами вручную импортировать все функции из .dll), при помощи которого будет осуществлена привязка к .dll. .lib файл указывается в Configuration Properties->Linker->Input->Additional Dependencies: ![](./images/step10/lua_interpreter_lib.png) Пути к библиотекам задаются в Properties->Linker->General->Additional Library Directories: ![](./images/step10/lua_interpreter_lib_path.png) Получившийся .exe нужно положить в одну папку с .dll файлом Lua: ![](./images/step10/lua_interpreter_debug_output_exe.png) В окне Solution Explorer выделяем проект LuaInterpreter. В главном меню выбираем Project->Set As Startup Project. Затем Build->Build Solution. Затем Debug->StartDebugging. Должно появиться консольное окно с приветствием: ![](./images/step10/lua_interpreter_start.png) Ведем какую-нибудь команду (не будем уклоняться от классики). Результат должен приятно поразить ;) ![](./images/step10/lua_interpreter_result.png) Выходим цивилизованно: ![](./images/step10/lua_interpreter_exit.png) Вуаля! Lua собран и готов к работе внутри приложения. Теперь определимся с GUI. Попытка использовать чистый WinAPI для создания простейшего GUI погрузила меня в пучину мрачной депресии, особенно учитывая то, что в бесплатной версии VC++ редактор ресурсов не работает. Но тут я вспомнил, что на работе один товарищ для настройки спецэффектов использовал FOX Toolkit. Я зашел на Google, там разыскал [FOX Toolkit](http://www.fox-toolkit.org/ "http://www.fox-toolkit.org/") и скачал последнюю версию исходников. Добрые люди из FOX Toolkit сделали проект для VC++. Итак, распаковываем исходники в какую-нибудь папку. Запускаем VC++. Открываем солюшен windows/vcpp/win32.dsw. Солюшен содержит кучу проектов. Можно, конечно, просто запустить Build->Build Solution, однако тогда соберется все что есть и ждать окончания сборки, возможно, придется очень долго. А можно выбрать проект foxdll и из контекстного меню собрать только его: ![](./images/step10/fox_build_foxdll.png) Так и сделаем. После этого в папке FoxSources/lib окажутся готовая .dll и .lib файлы библиотеки FOX Toolkit. По умолчанию солюшен для FOX Toolkit собирается для Debug. Поэтому полученные файли кладем в соответствующие папки уже нашего рабочего места LuaTutorial/bin/debug и LuaTutorial/lib/debug. Для сборки релизной версии библиотеки FOX Toolkit нужно выбрать на тулбаре Release и можно также из контекстного меню выбрать сборку только проекта foxdll, а полученные .dll и .lib файлы библиотеки FOX Toolkit разложить в соответствующие папки нашего рабочего места LuaTutorial/bin/release и LuaTutorial/lib/release. Сделали. Теперь неплохо было бы проверить, как это все работает. В солюшене для FOX Toolkit можно обнаружить проект hello. Похоже, для проверки работоспособности нашего проекта это как раз то, что нам нужно. Заглянем в него. Там единственный файл hello.c. Откроем его. Если удалить обильные (очень, очень хорошо!) комментарии, то в сухом остатке будет следующее: #include "fx.h" int main(int argc,char **argv) { FXApp application("Hello","FoxTest"); application.init(argc,argv); FXMainWindow *main=new FXMainWindow(&application,"Hello",NULL,NULL,DECOR_ALL); new FXButton(main,"&Hello, World!",NULL,&application,FXApp::ID_QUIT); application.create(); main->show(PLACEMENT_SCREEN); return application.run(); } Кажется, все очень понятно. Наш проект есть смысл сделать на основе этого примера. В нашем солюшене LuaDll создаем новый проект. Назовем его GuiTest: ![](./images/step10/new_guitest_project.png) ![](./images/step10/guitest_project_app_settings.png) Добавим в проект .cpp файл: ![](./images/step10/guitest_project_add_cpp.png) Назовем его GuiTest.cpp: ![](./images/step10/guitest_project_add_cpp_end.png) В этот файл копируем приведенный выше исходный код из примера для FOX Toolkit. Настраиваем пути к .h файлам: ![](./images/step10/guitest_include_path.png) Настраиваем путь к библиотеке FOX Toolkit и путь к exe-шнику: ![](./images/step10/guitest_lib_bin_path.png) Указываем файл библиотеки FOX Toolkit: ![](./images/step10/guitest_fox_lib.png) Собираем проект, запускаем его. Должен получиться такой результат: ![](./images/step10/guitest_project_test_result.png) Замечательно, GUI работает. Однако, можно заметить, что в этом примере нет никакой интерактивности. Значит, эту интерактивность нужно поискать в примерах FOX Toolkit! Поискав немного, я нашел проект dialog, который кажется вполне подходящим для наших целей. Запущенный из солюшена FOX Toolkit, выглядит он так: ![](./images/step10/fox_dialog_project_result.png) Я скопировал исходники из dialog.cpp в GuiTest.cpp. Собираем проект. Упс! Проект не собирается :( GuiTest.obj : error LNK2001: unresolved external symbol "public: static class FX::FXMetaClass const FX::FXDialogBox::metaClass" (?metaClass@FXDialogBox@FX@@2VFXMetaClass@2@B) GuiTest.obj : error LNK2001: unresolved external symbol "public: static class FX::FXMetaClass const FX::FXMainWindow::metaClass" (?metaClass@FXMainWindow@FX@@2VFXMetaClass@2@B) ../bin/debug/guitest.exe : fatal error LNK1120: 2 unresolved externals Идем обратно в солюшен FOX Toolkit. Находим проект fox. Заглядываем в его свойства: ![](./images/step10/fox_fox_project_properties.png) Этот проект собирает библиотеку FOX Toolkit в статическую библиотеку. Собираем этот проект. В папке FoxSources/lib споявился файл FOXD-1.6.lib. Копируем его в нашу папку LuaTutorials/lib/debug. В настройках проекта GuiTest исправляем FOXDLLD-1.6.lib на FOXD-1.6.lib. Заново собираем проект GuiTest. Собрался. Запускаем. Результат идентичен оригинальному. Замечательно. Выкидываем из исходников все лишнее. Для двух кнопок у меня осталось это: #include "fx.h" #include #include #include // Mini application object class DialogTester : public FXMainWindow { FXDECLARE(DialogTester) protected: // Member data FXHorizontalFrame *contents; protected: DialogTester(){} public: // Message handlers long onCmdShowDialog(FXObject*,FXSelector,void*); long onCmdShowDialogModal(FXObject*,FXSelector,void*); public: // Messages enum { ID_SHOWDIALOG=FXMainWindow::ID_LAST, ID_SHOWDIALOGMODAL }; public: DialogTester(FXApp *app); virtual void create(); virtual ~DialogTester(); }; // Map FXDEFMAP(DialogTester) DialogTesterMap[]={ FXMAPFUNC(SEL_COMMAND, DialogTester::ID_SHOWDIALOG, DialogTester::onCmdShowDialog), FXMAPFUNC(SEL_COMMAND, DialogTester::ID_SHOWDIALOGMODAL, DialogTester::onCmdShowDialogModal), }; // FXDialogBoxApp implementation FXIMPLEMENT(DialogTester,FXMainWindow,DialogTesterMap,ARRAYNUMBER(DialogTesterMap)) // Make some windows DialogTester::DialogTester(FXApp* a):FXMainWindow(a,"Group Box Test",NULL,NULL,DECOR_ALL,0,0,400,200){ // Contents contents=new FXHorizontalFrame(this,LAYOUT_SIDE_BOTTOM|FRAME_NONE|LAYOUT_FILL_X|PACK_UNIFORM_WIDTH); // Button to pop normal dialog new FXButton(contents,"&Non-Modal Dialog...\tDisplay normal dialog",NULL,this,ID_SHOWDIALOG,FRAME_RAISED|FRAME_THICK|LAYOUT_CENTER_X|LAYOUT_CENTER_Y); // Button to pop modal dialog new FXButton(contents,"&Modal Dialog...\tDisplay modal dialog",NULL,this,ID_SHOWDIALOGMODAL,FRAME_RAISED|FRAME_THICK|LAYOUT_CENTER_X|LAYOUT_CENTER_Y); } // Clean up DialogTester::~DialogTester(){ } // Open long DialogTester::onCmdShowDialog(FXObject*,FXSelector,void*){ return 1; } // Option long DialogTester::onCmdShowDialogModal(FXObject*,FXSelector,void*){ return 1; } // Start void DialogTester::create(){ FXMainWindow::create(); show(PLACEMENT_SCREEN); } // Start the whole thing int main(int argc,char *argv[]){ // Make application FXApp application("Dialog","FoxTest"); // Open display application.init(argc,argv); new DialogTester(&application); // Create app application.create(); // Run return application.run(); } Добавим третью кнопку и немного поменяем внешний вид: #include "fx.h" #include #include #include // Mini application object class DialogTester : public FXMainWindow { FXDECLARE(DialogTester) protected: // Member data FXHorizontalFrame *contents; protected: DialogTester(){} public: // Message handlers long onButton1(FXObject*,FXSelector,void*); long onButton2(FXObject*,FXSelector,void*); long onButton3(FXObject*,FXSelector,void*); public: // Messages enum { ID_BUTTON1 = FXMainWindow::ID_LAST, ID_BUTTON2, ID_BUTTON3, }; public: DialogTester(FXApp *app); virtual void create(); virtual ~DialogTester(); }; // Map FXDEFMAP(DialogTester) DialogTesterMap[]={ FXMAPFUNC(SEL_COMMAND, DialogTester::ID_BUTTON1, DialogTester::onButton1), FXMAPFUNC(SEL_COMMAND, DialogTester::ID_BUTTON2, DialogTester::onButton2), FXMAPFUNC(SEL_COMMAND, DialogTester::ID_BUTTON3, DialogTester::onButton3), }; // FXDialogBoxApp implementation FXIMPLEMENT(DialogTester,FXMainWindow,DialogTesterMap,ARRAYNUMBER(DialogTesterMap)) // Make some windows DialogTester::DialogTester(FXApp* a):FXMainWindow(a,"Lua Gui Test",NULL,NULL,DECOR_ALL,0,0,200,50){ // Contents contents=new FXHorizontalFrame(this,LAYOUT_SIDE_BOTTOM|FRAME_NONE|LAYOUT_FILL_X|PACK_UNIFORM_WIDTH); // Button1 new FXButton(contents,"Button1",NULL,this,ID_BUTTON1,FRAME_RAISED|FRAME_THICK|LAYOUT_CENTER_X|LAYOUT_CENTER_Y); // Button2 new FXButton(contents,"Button2",NULL,this,ID_BUTTON2,FRAME_RAISED|FRAME_THICK|LAYOUT_CENTER_X|LAYOUT_CENTER_Y); // Button3 new FXButton(contents,"Button3",NULL,this,ID_BUTTON3,FRAME_RAISED|FRAME_THICK|LAYOUT_CENTER_X|LAYOUT_CENTER_Y); } // Clean up DialogTester::~DialogTester(){ } // Open long DialogTester::onButton1(FXObject*,FXSelector,void*){ return 1; } // Option long DialogTester::onButton2(FXObject*,FXSelector,void*){ return 1; } long DialogTester::onButton3(FXObject*,FXSelector,void*){ return 1; } // Start void DialogTester::create(){ FXMainWindow::create(); show(PLACEMENT_SCREEN); } // Start the whole thing int main(int argc,char *argv[]){ // Make application FXApp application("Dialog","FoxTest"); // Open display application.init(argc,argv); new DialogTester(&application); // Create app application.create(); // Run return application.run(); } Результат стал выглядеть так: ![](./images/step10/guitest_3_button_result.png) Для того, чтобы прикрутить Lua к проекту GuiTest нужно повторить шаги, описанные при создании проекта LuaInterpreter. Всю функциональность глобального Lua-stat'а реализуем в классе GlobalLuaState. Добавим в проект GuiTest класс: ![](./images/step10/guitest_add_class_menu.png) Выбираем С++ Class. Нажимаем Add: ![](./images/step10/guitest_add_class.png) Вводим имя класса. Нажимаем Finish: ![](./images/step10/guitest_add_class_name.png) > Небольшое отступление. Вообще, это хорошая идея оформлять все взаимодействие со скриптами в отдельном объекте для каждой предметной области (например звук, ввод, физика и т.д), а не пользоваться голым указателем на Lua-state (типа lua\_State* luaSound, lua\_State* luaInput, lua\_State* luaPhysics и т.д), поскольку во втором случае через некоторое время вы гарантированно столкнетесь с классической проблемой типа "глобальная переменная", с которой непонятно кто, непонятно что и непонятно где вытворяет, и отыскать ошибку "какая сволочь затерла мою таблицу options?" будет весьма затруднительно. Файл GlobalLuaState.h: #pragma once #include #include struct lua_State; class GlobalLuaState { public: struct Exception : public std::exception { Exception(const std::string& what) : std::exception(what.c_str()) { } }; GlobalLuaState(const std::string& filename); ~GlobalLuaState(); // функции, определенные внутри скрипта filename void outerFunction1(); int outerFunction2(); std::string outerFunction3(); // функции, которые могут быть вызваны из скрипта filename void innerFunction1(); int innerFunction2(); std::string innerFunction3(); protected: lua_State* L_; }; // кто-то где-то должен создать этот указатель extern GlobalLuaState* globalLuaState; Для компиляции Lua внутри С++ (Lua написан на чистом С) сделан специальный файлик lua.hpp: // lua.hpp // Lua header files for C++ // <> not supplied automatically because Lua also compiles as C++ extern "C" { #include "lua.h" #include "lualib.h" #include "lauxlib.h" } Файл GlobalLuaState.сpp: #include "GlobalLuaState.h" #include "lua.hpp" using namespace std; namespace { // маленький помощник, чтобы самим не считать количество lua_push...() и lua_pop() class LuaStackGuard { public: LuaStackGuard(lua_State* L) : luaState_(L) { top_ = lua_gettop(L); } ~LuaStackGuard() { lua_settop(luaState_, top_); } private: lua_State* luaState_; int top_; }; int fun1(lua_State* L) { globalLuaState->innerFunction1(); return 0; } int fun2(lua_State* L) { // LuaStackGuard здесь не нужен! // мы ИЗМЕНЯЕМ Lua-state! int retVal = globalLuaState->innerFunction2(); lua_pushnumber(L, retVal); return 1; } int fun3(lua_State* L) { // LuaStackGuard здесь не нужен! // мы ИЗМЕНЯЕМ Lua-state! string retVal = globalLuaState->innerFunction3(); lua_pushstring(L, retVal.c_str()); return 1; } } GlobalLuaState::GlobalLuaState(const string& filename) { L_ = luaL_newstate(); luaL_openlibs(L_); // поскольку внутренние функции могут быть вызваны в процессе загрузки скрипта, // то сначала регистрируем их lua_register(L_, "innerFunction1", fun1); lua_register(L_, "innerFunction2", fun2); lua_register(L_, "innerFunction3", fun3); // загружаем скрипт if(luaL_dofile(L_, filename.c_str())) { string err(lua_tostring(L_, -1)); lua_close(L_); throw Exception(err); } } GlobalLuaState::~GlobalLuaState() { lua_close(L_); } void GlobalLuaState::outerFunction1() { // stack: LuaStackGuard stackGuard(L_); lua_getglobal(L_, "outerFunction1"); // stack: outerFunction1 if(lua_pcall(L_, 0, 0, 0)) // stack: { string err(lua_tostring(L_, -1)); lua_close(L_); throw Exception(err); } printf("GlobalLuaState::outerFunction1() called!\n"); } int GlobalLuaState::outerFunction2() { // stack: LuaStackGuard stackGuard(L_); lua_getglobal(L_, "outerFunction2"); // stack: outerFunction1 if(lua_pcall(L_, 0, 1, 0)) // stack: число { string err(lua_tostring(L_, -1)); lua_close(L_); throw Exception(err); } const int result = (int)lua_tonumber(L_, 1); printf("GlobalLuaState::outerFunction2() called! Result %d\n", result); return result; // после выхода из функции стек Lua должен быть в том же состоянии, // что и до входа в функцию. // stackGuard сам все подчистит за нами } std::string GlobalLuaState::outerFunction3() { // stack: LuaStackGuard stackGuard(L_); lua_getglobal(L_, "outerFunction3"); // stack: outerFunction1 if(lua_pcall(L_, 0, 1, 0)) // stack: строка { string err(lua_tostring(L_, -1)); lua_close(L_); throw Exception(err); } string result; // функция может вернуть nil if(const char* s = lua_tostring(L_, 1)) { result = s; } printf("GlobalLuaState::outerFunction3() called! Result %s\n", result.c_str()); return result; // после выхода из функции стек Lua должен быть в том же состоянии, // что и до входа в функцию. // stackGuard сам все подчистит за нами } void GlobalLuaState::innerFunction1() { printf("GlobalLuaState::innerFunction1() called!\n"); } int GlobalLuaState::innerFunction2() { printf("GlobalLuaState::innerFunction2() called!\n"); return 12345; } string GlobalLuaState::innerFunction3() { printf("GlobalLuaState::innerFunction3() called!\n"); return "Hello from GlobalLuaState!"; } Ну и на закуску GuiTest.cpp: #include "fx.h" #include "GlobalLuaState.h" #include "lua.hpp" GlobalLuaState* globalLuaState = 0; // Mini application object class DialogTester : public FXMainWindow { FXDECLARE(DialogTester) protected: // Member data FXHorizontalFrame *contents; protected: DialogTester(){} public: // Message handlers long onButton1(FXObject*,FXSelector,void*); long onButton2(FXObject*,FXSelector,void*); long onButton3(FXObject*,FXSelector,void*); public: // Messages enum { ID_BUTTON1 = FXMainWindow::ID_LAST, ID_BUTTON2, ID_BUTTON3, }; public: DialogTester(FXApp *app); virtual void create(); virtual ~DialogTester(); }; // Map FXDEFMAP(DialogTester) DialogTesterMap[]={ FXMAPFUNC(SEL_COMMAND, DialogTester::ID_BUTTON1, DialogTester::onButton1), FXMAPFUNC(SEL_COMMAND, DialogTester::ID_BUTTON2, DialogTester::onButton2), FXMAPFUNC(SEL_COMMAND, DialogTester::ID_BUTTON3, DialogTester::onButton3), }; // FXDialogBoxApp implementation FXIMPLEMENT(DialogTester,FXMainWindow,DialogTesterMap,ARRAYNUMBER(DialogTesterMap)) // Make some windows DialogTester::DialogTester(FXApp* a):FXMainWindow(a,"Lua Gui Test",NULL,NULL,DECOR_ALL,0,0,200,50){ // Contents contents=new FXHorizontalFrame(this,LAYOUT_SIDE_BOTTOM|FRAME_NONE|LAYOUT_FILL_X|PACK_UNIFORM_WIDTH); // Button1 new FXButton(contents,"Button1",NULL,this,ID_BUTTON1,FRAME_RAISED|FRAME_THICK|LAYOUT_CENTER_X|LAYOUT_CENTER_Y); // Button2 new FXButton(contents,"Button2",NULL,this,ID_BUTTON2,FRAME_RAISED|FRAME_THICK|LAYOUT_CENTER_X|LAYOUT_CENTER_Y); // Button3 new FXButton(contents,"Button3",NULL,this,ID_BUTTON3,FRAME_RAISED|FRAME_THICK|LAYOUT_CENTER_X|LAYOUT_CENTER_Y); } // Clean up DialogTester::~DialogTester(){ } // Open long DialogTester::onButton1(FXObject*,FXSelector,void*){ printf("Button1 clicked!\n"); globalLuaState->outerFunction1(); globalLuaState->outerFunction2(); globalLuaState->outerFunction3(); return 1; } // Option long DialogTester::onButton2(FXObject*,FXSelector,void*){ printf("Button2 clicked!\n"); lua_State* L = luaL_newstate(); luaL_openlibs(L); if(luaL_dofile(L, "./scripts/button2.lua")) { // что-то не так... printf("%s\n", lua_tostring(L, -1)); } lua_close(L); return 1; } long DialogTester::onButton3(FXObject*,FXSelector,void*){ printf("Button3 clicked!\n"); return 1; } // Start void DialogTester::create(){ FXMainWindow::create(); show(PLACEMENT_SCREEN); } // Start the whole thing int main(int argc,char *argv[]){ // Make application FXApp application("Dialog","FoxTest"); // Open display application.init(argc,argv); // создаем глобальный Lua-state globalLuaState = new GlobalLuaState("./scripts/button1.lua"); new DialogTester(&application); // Create app application.create(); // Run int result = application.run(); // удаляем глобальный Lua-state delete globalLuaState; return result; } Скрипты просты и незатейливы. button1.lua: -- эти функции будут вызваны из программы function outerFunction1() print('outerFunction1 called!') end function outerFunction2() print('outerFunction2 called!') return 54321 end function outerFunction3() print('outerFunction3 called!') return 'Hello from button1.lua!' end -- вызываем внутренние функции программы print('innerFunction1() returns ' .. (innerFunction1() or 'nil')) print('innerFunction2() returns ' .. (innerFunction2() or 'nil')) print('innerFunction3() returns ' .. (innerFunction3() or 'nil')) button2.lua: -- я тупой, я тупой, я тупой... print('Hi! I am а dumb lua script. I know nothing about ptrogram that called me. Sorry...') Результат запуска програмы и последовательного нажатия на кнопки Button1, Button2 и Button3 выглядит вот так: ![](./images/step10/guitest_result.png) Исходники лежат [здесь](./files/step10/step10.zip). Для написания этой статьи использовался инструмент [markdown](http://daringfireball.net/projects/markdown/). Статья в формате .markdown [здесь](./step10.txt) (кодировка UTF-8).