Этот шаг написан по заявкам зрителей. Вопрос из зала звучал так:
я хочу внедрить 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.lua.org/ за свежей версией Lua. Хотел я там скачать готовые бинарники, однако ничего не вышло. Ну, может оно и к лучшему, есть возможность лишний раз потренироваться в сборке Lua. Забираем исходники. На момент написания этого шага была доступна версия 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:
На следующей странице визарда выбираем Application Settings или нажимаем кнопку Next:
В Application Type выбираем DLL. Ставим галочку напротив Empty Project, поскольку исходники Lua уже содержат все необходимые файлы. Нажимаем Finish:
Отличо, пустой проект для Lua у нас готов. Наполним его содержимым. Распаковываем архив с исходниками в папку LuaTutorial/LuaDll. Поскольку .dll с Lua будет использоваться другими приложениями, то .h файлы переносим в папку include/Lua. В папке LuaTutorial/LuaDll остаются только .c файлы. Добавим их в проект:
Добавляем все файлы, кроме lua.c (интерпретатор) и luac.c (компилятор) .lua скриптов, их можно будет собрать позже.
Из меню Project->Properties вызываем диалог с настройками проекта.
Зададим путь к .h файлам.
В выпадающем списке Configuration выбираем AllConfigurations. Далее идем Configuration Properties->C/C++->Additional Include Directories. Там вводим путь ../include/lua:
Теперь настроим пути, куда нужно складывать готовые .dll и .lib файлы. Для Debug и Release эти пути будут разные. Сначала настроим пути для Debug. В выпадающем списке Configuration выбираем Debug. Далее идем Configuration Properties->Linker->General->Output File. Там вводим путь ../bin/debug/lua5.1.4.dll:
Затем идем в Configuration Properties->Linker->Advanced->Import Library. Там вводим путь ../lib/debug/lua5.1.4.lib:
Для Release в выпадающем списке Configuration выбираем Release. Далее идем Configuration Properties->Linker->General->Output File. Там вводим путь ../bin/release/lua5.1.4.dll:
Затем идем в Configuration Properties->Linker->Advanced->Import Library. Там вводим путь ../lib/debug/lua5.1.4.lib:
Для того, чтобы собрать 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:
и для Release:
Теперь у нас все готово для того, чтобы собрать библиотеку Lua.
Идем в меню Build->Build Solution. После того, как студия выдаст кучу варнингов, в указанных в свойствах проекта папках окажутся файлы lua5.1.4.dll и lua5.1.4.lib. Отлично.
Для проверки работоспособности .dll соберем интерпретатор Lua. В том же солюшене LuaDll добавим новый проект:
Назовем его LuaInterpreter:
На следующей странице визарда выбираем Application Settings или нажимаем кнопку Next. Ставим галочку напротив Empty Project. Нажимаем Finish:
Добавляем в проект файл lua.c из папки LuaDll (файлы добавляются так же, как здесь).
Настраиваем пути к .h и .lib файлам. Я покажу подробно, как это делается для Debug, а для Release читатель должен смочь сделать это самостоятельно.
Путь к .h файлам настраивается точно так же, как в проекте LuaDll:
Поскольку интерпретатор использует Lua как .dll, то нам нужно указать файл библиотеки (если мы, конечно, не собираемся сами вручную импортировать все функции из .dll), при помощи которого будет осуществлена привязка к .dll. .lib файл указывается в Configuration Properties->Linker->Input->Additional Dependencies:
Пути к библиотекам задаются в Properties->Linker->General->Additional Library Directories:
Получившийся .exe нужно положить в одну папку с .dll файлом Lua:
В окне Solution Explorer выделяем проект LuaInterpreter.
В главном меню выбираем Project->Set As Startup Project. Затем Build->Build Solution. Затем Debug->StartDebugging.
Должно появиться консольное окно с приветствием:
Ведем какую-нибудь команду (не будем уклоняться от классики). Результат должен приятно поразить ;)
Выходим цивилизованно:
Вуаля! Lua собран и готов к работе внутри приложения.
Теперь определимся с GUI. Попытка использовать чистый WinAPI для создания простейшего GUI погрузила меня в пучину мрачной депресии, особенно учитывая то, что в бесплатной версии VC++ редактор ресурсов не работает. Но тут я вспомнил, что на работе один товарищ для настройки спецэффектов использовал FOX Toolkit. Я зашел на Google, там разыскал FOX Toolkit и скачал последнюю версию исходников.
Добрые люди из FOX Toolkit сделали проект для VC++. Итак, распаковываем исходники в какую-нибудь папку. Запускаем VC++. Открываем солюшен windows/vcpp/win32.dsw. Солюшен содержит кучу проектов. Можно, конечно, просто запустить Build->Build Solution, однако тогда соберется все что есть и ждать окончания сборки, возможно, придется очень долго. А можно выбрать проект foxdll и из контекстного меню собрать только его:
Так и сделаем. После этого в папке 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:
Добавим в проект .cpp файл:
Назовем его GuiTest.cpp:
В этот файл копируем приведенный выше исходный код из примера для FOX Toolkit.
Настраиваем пути к .h файлам:
Настраиваем путь к библиотеке FOX Toolkit и путь к exe-шнику:
Указываем файл библиотеки FOX Toolkit:
Собираем проект, запускаем его. Должен получиться такой результат:
Замечательно, GUI работает.
Однако, можно заметить, что в этом примере нет никакой интерактивности. Значит, эту интерактивность нужно поискать в примерах FOX Toolkit! Поискав немного, я нашел проект dialog, который кажется вполне подходящим для наших целей. Запущенный из солюшена FOX Toolkit, выглядит он так:
Я скопировал исходники из 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. Заглядываем в его свойства:
Этот проект собирает библиотеку 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
// 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
// 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();
}
Результат стал выглядеть так:
Для того, чтобы прикрутить Lua к проекту GuiTest нужно повторить шаги, описанные при создании проекта LuaInterpreter.
Всю функциональность глобального Lua-stat'а реализуем в классе GlobalLuaState. Добавим в проект GuiTest класс:
Выбираем С++ Class. Нажимаем Add:
Вводим имя класса. Нажимаем Finish:
Небольшое отступление. Вообще, это хорошая идея оформлять все взаимодействие со скриптами в отдельном объекте для каждой предметной области (например звук, ввод, физика и т.д), а не пользоваться голым указателем на Lua-state (типа lua_State* luaSound, lua_State* luaInput, lua_State* luaPhysics и т.д), поскольку во втором случае через некоторое время вы гарантированно столкнетесь с классической проблемой типа "глобальная переменная", с которой непонятно кто, непонятно что и непонятно где вытворяет, и отыскать ошибку "какая сволочь затерла мою таблицу options?" будет весьма затруднительно.
Файл GlobalLuaState.h:
#pragma once
#include <string>
#include <exception>
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++
// <<extern "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 выглядит вот так:
Исходники лежат здесь.
Для написания этой статьи использовался инструмент markdown.
Статья в формате .markdown здесь (кодировка UTF-8).