Шаг 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.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).

Hosted by uCoz