Lua и C/C++/C#. Часть 1

Интеграцию Lua с C/C++/C# было решено вынести в отдельную серию материалов. Сам язык является по существу универсальным, а наиболее частое его использование — в игровых проектах. Всю вводную часть по Lua, его основному синтаксису, вы можете прочесть в цикле материалов «Lua для игр и не только». Сейчас же углубимся в узкоспециализированные ниши.

Сегодня мы начнем с самых азов интеграции, инсталлируем Lua и сделаем несколько простых примеров для C#.

Для урока в этой части вам понадобится Visual C# или же MSVS (Microsoft Visual Studio) 2003 и выше (я их покажу на 2008), а также LuaInterface (вариант для .NET CLR), скачанный с http://luaforge.net/projects/luainterface. Распакуйте его в удобную для вас папку, но при этом смотрите, чтобы вы не смогли ее случайно удалить.

Создаем новое приложение

Итак, первым делом создаем новый проект Visual C# (Console Application), назовем его TestLua01. После того как базовая модификация загружена, нам нужно прикрепить LuaInterface.dll. Делается это через Solution Explorer: нажимаете правую кнопку мыши над References, в появившемся контекстном меню выбираете пункт Add Reference, в результате чего появляется одноименное окно. В нем переходите на закладку Browse (в 2003-й это делается отдельной кнопкой) и указываете путь к библиотеке LuaInterface.dll, которая находится в директории Built той папки, в которую вы распаковали LuaInterface. В результате проведенных действий у вас должна появиться новая запись в пункте Reference. Дело сделано.

Теперь переходим непосредственно к файлу с кодом и прописываем в первые строки:

using System;
using LuaInterface;

namespace LuaTest01
{
class Program
{
static void Main(string[] args)
{
}
}
}

Теперь в функцию Main () пропишем небольшой фрагмент кода, который, кстати, дается в документации по LuaInterface, но в данном случае сделаем и некий вывод результатов в консольном окне.

static void Main(string[] args)
{
Lua lua = new Lua();
lua["num"] = 2;
lua["str"] = "a string";
double num = (double) lua["num"];
string str = (string) lua["str"];
Console.WriteLine("str:" + str + "\tnum:" + num);
Console.ReadLine();
}

Итак, если переводить строка за строкой, сначала мы запускаем интерпретатор Lua, затем создаем две глобальные переменные «num» и «str» и присваиваем им некие значения. После мы считываем значения этих глобальных переменных и выводим на экран консольного окна. Нужно отметить, что сам LuaInterface предназначен, прежде всего, для обеспечения взаимодействия/совместимости между двумя языками, а лучше сказать — между языком Lua и определенной платформой. В частности, в нашем случае мы используем библиотеку для .NET CLR. CLR (Common Language Runtime) — это общеязыковая исполняющая среда платформы .NET, также параллельно с этим понятием часто используются CTS (Common Type System — общая система типов) и CLS (Common Languages Specification — общеязыковая спецификация). В принципе, все это относится больше к .NET, и в нашем случае глубоко копать мы не будем. Главное понимать, что есть исполняющая среда CLR, имеется общая система типов, а LuaInterface обеспечивает полноценную трансляцию и конвертацию. В рамках приведенного выше кода был осуществлен автоматический перевод string в System.String и number в
System.Double.

DoString

Теперь давайте немного исправим наш пример, используя команду DoString(), в рамках которой можно вставлять исполняемые блоки кода (или, как их еще называют, порциями — chunks) на Lua:

static void Main(string[] args)
{
Lua lua = new Lua();
lua.DoString("num=2");
lua.DoString("str='a string'");
double num = (double) lua["num"];
string str = (string) lua["str"];
Console.WriteLine("str:" + str + "\tnum:" + num);
Console.ReadLine();
}

Как видите, ситуация никак не изменилась, с одной лишь разницей, что мы стали использовать порции кода Lua.

RegisterFunction

В исполняемой среде класс Lua имеет метод RegisterFunction, позволяющий регистрировать методы CLR как глобальные функции Lua. В качестве первого ее аргумента указывается имя, которое вы бы хотели присвоить этой функции в рамках Lua. Второй аргумент — это объект или класс, содержащий метод, а третий аргумент — непосредственно сам метод.

Если немного непонятно, давайте расширим наш пример.

Итак, давайте добавим к коду две C#-функции, IvanSays и MikeSays:

public void IvanSays(string s)
{
Console.WriteLine("Ivan>" + s);
}
public void MikeSays(string s)
{
Console.WriteLine("Mike>" + s);
}

Как видите, они не представляют собой ничего сложного, и просто выводят строку на экран консоли.

Теперь переделаем нашу функцию Main, точнее, перепишем ее наново. Итак:

static void Main(string[] args)
{
Program program = new Program();
Lua lua = new Lua();
lua.RegisterFunction("Ivan", program, program.GetType().GetMethod("IvanSays"));
lua.RegisterFunction("Mike", program, program.GetType().GetMethod("MikeSays"));
lua.DoString("Ivan('Привет'); Mike('Привет, Иван!')");
Console.ReadLine();
}

Тут главное не запутаться, поэтому разъясним все подробно. Program — это базовый класс по умолчанию, в котором мы все делаем, теперь создаем его экземпляр, стартуем интерпретатор Lua, далее регистрируем Lua-функции с помощью RegisterFunction. Первую строку с перенаправлением следует читать так: метод IvanSays объекта program зарегистрирован как Lua функция Ivan. Дальше в рамках DoString мы выполнили порцию кода Lua, в рамках которого вызвали функции уже со своими именами и передали им параметры.

Также обращаю внимание на то, что по синтаксису Lua оператор «;» как таковой является условным, то есть его можно и не ставить в конце каждой исполняемой строки. С другой стороны, с его наличием код является более привычным для понимания для программистов С/С++/С#. В принципе, на Lua не слишком часто пишут очень большие файлы — тут главное, чтобы не получилось как с сокращением объемов «Войны и мира» Л. Н. Толстого после удаления из алфавита буквы «ять».

DoFile

Строки, их чтение, трансляция исполняемого кода из них — это очень хорошо, но скриптовые языки любят использовать по той причине, что они позволяют вынести часть кода за пределы компилируемой части. Иными словами, все, что мы сейчас прописывали внутри, можно перенести во внешний файл.
Итак, давайте создадим свой каталог для скриптов по адресу… …\LuaTest01\bin\Debug\, назовем его scripts. Такое место размещения выбрано для удобства, чтобы не вписывать потом в код абсолютные ссылки. И теперь создадим в папке scripts файл с диалогом, назвав его dialog.txt. Что в него поместим? Те же вызовы функций:

Ivan('Привет')
Mike('Привет, Иван!')
Ivan('Как дела?')
Mike('Тип-топ')

Теперь вместо DoString мы используем метод DoFile, который дает команду на исполнение указанного файла. И функция main у нас уже будет выглядеть следующим образом:

static void Main(string[] args)
{
Program program = new Program();
Lua lua = new Lua();
lua.RegisterFunction("Ivan", program, program.GetType().GetMethod("IvanSays"));
lua.RegisterFunction("Mike", program, program.GetType().GetMethod("MikeSays"));
lua.DoFile("scripts/dialog.txt");
Console.ReadLine();
}

Как вы можете увидеть, мы заменили всего одну строку, а сами команды по вызову функций исполняемого кода вынесли в отдельный файл. В данном случае продемонстрирован один из основных механизмов Lua, благодаря которому можно отделять блок основного кода от вспомогательного.

Подытожим

Двигаться мы будем тематическими блоками и в следующей части начнем работать с классами. Нужно сказать, что многие разработчики часто избегают вставки скриптовых составляющих в свои проекты, на самом же деле, это очень удобно. С другой стороны, часть программистов/проектировщиков в целях улучшения переносимости (на другие языки и/или платформы) все что только можно переводят на скрипты. К последним относится и автор этих строк. Минусы у этого подхода также есть, потому как при неправильной расстановке приоритетов можно проиграть в производительности приложений. То есть всегда необходимо искать золотую середину.

Кристофер http://itcs.3dn.ru


Компьютерная газета. Статья была опубликована в номере 33 за 2011 год в рубрике программирование

©1997-2024 Компьютерная газета