Предисловие
Данная заметка не будет слишком уж объемной, а скорее даже наоборот, небольшой.
Достаточно продолжительное время я слежу за серией статей о языке D, публикуемой на Хабре. Ознакомившись с рядом источников, начиная от Википедии и заканчивая официальными руководствами по данному языку, пришел к выводу о целесообразности использования оного в своих научных проектах. Главный проект по докторской диссертации зашел в состояние тупика, требовал переработки (всплыл ряд механических вопросов). Переработку проекта и изучение нового для меня языка было решено совместить.
Сказано сделано — большая часть кода довольно быстро была перенесена с C/C++ на D. Не смотря на различные мнения по поводу D бытующие в среде разработчиков ПО, язык пришелся мне по вкусу.
Одна беда — в старом варианте проекта для задания параметров модели поезда и изменения логики работы без перекомпиляции использовался Lua-скриптинг. Те кто сталкивается с ним по роду деятельности знают, что существует хорошо разработанный и достаточно документированный API для разработки на C/C++.
Что касается Lua-скриптинга в D, то существует ряд проектов, например LuaD, привносящий в программы на D возможность работы с Lua. Однако LuaD рассчитан на предыдущую версию 5.1. Попадался мне проект и для 5.2 — DerelictLua, однако навскидку с «легкого пинка» завести его не удалось. При наличии времени можно разобраться, но времени как всегда нет. Пришлось напрячь мыслительные мощности и придумать более быстрое и простое решение. Если читателю интересно, что из этого вышло, добро пожаловать под кат.
1. Вызов C-функций из программы на D
Реализуется это не просто, а очень просто — в модуле D пишем прототип функции, указывая что она расположена во внешнем модуле и использует C-соглашение о вызове функций
extern(C) double my_C_func(int param1, double param2);
Естественно, внешний модуль надо тем или иным способом скомпоновать с программой на D.
В этой связи возникла первая идея — реализовать работу с Lua на C, а прототипы прописать в модуле на D и скомпилировать всё в одну программу. Был даже переделан скрипт сборки проекта (использую SCons) таким вот образом
SConstruct для компиляции программы из модулей на C/С++ и D
#--------------------------------------------------------------------
# Project globals
#--------------------------------------------------------------------
source_dir = 'src/'
d_include_path = 'src/D/'
release_target_dir = 'bin/release/'
debug_target_dir = 'bin/debug/'
target_name = 'train'
#--------------------------------------------------------------------
# Release build configuration
#--------------------------------------------------------------------
release_env = Environment(
CC='gcc',
CXX='g++',
DMD='dmd',
DPATH=d_include_path,
LINK='gcc',
CPPFLAGS='-O3',
DFLAGS='-O'
)
release_env.VariantDir(release_target_dir,
source_dir,
duplicate=0)
d_sources = Glob(release_target_dir + 'D/*.d')
c_sources = Glob(release_target_dir + 'C/*.c')
c_obj = release_env.Object(c_sources)
d_obj = release_env.Object(d_sources)
release_env.Program(release_target_dir + target_name, d_obj + c_obj, LIBS=['phobos2', 'lua'])
#--------------------------------------------------------------------
# Debug build configuration
#--------------------------------------------------------------------
debug_env = Environment(
CC='gcc',
CXX='g++',
DMD='dmd',
DPATH=d_include_path,
LINK='gcc',
CPPFLAGS='-g3',
DFLAGS='-g'
)
debug_env.VariantDir(debug_target_dir,
source_dir,
duplicate=0)
d_sources = Glob(debug_target_dir + 'D/*.d')
c_sources = Glob(debug_target_dir + 'C/*.c')
c_obj = debug_env.Object(c_sources)
d_obj = debug_env.Object(d_sources)
debug_env.Program(debug_target_dir + target_name, d_obj + c_obj, LIBS=['phobos2', 'lua'])
После успешного чтения тестового Lua-скрипта пришло понимание того, что сущностей слишком много — зачем писать модуль на C, если можно написать его на D, а необходимые функции экспортировать непосредственно из liblua.so?!? К тому же на D можно реализовать уже необходимый мне функционал, оформив его скажем в виде класса (так было сделано в предыдущей C++-версии проекта).
Вооружившись руководством к Lua 5.2 и лежащими в /usr/include/ заголовочными файлами я приступил. Первоначально была мысль экспортировать только нужные мне функции и остановится на этом. Но потом мне стало стыдно — наверняка результаты этой работы могут пригодится кому-нибудь ещё. Поэтому C API к Lua был практически полностью портирован на D.
2. D-библиотека для работы с Lua API
Результат доступен на GitHub
В архиве содержаться следующие файлы
- lua.d — прототипы основных функций
- lualib.d — функции для работы с библиотеками Lua
- lauxlib.d — дополнительные функции
- luaconf.d — описание некоторых типов и констант
Что касается последнего файла то он портирован в той части, что используется внутри остальных модулей. Эту работу ещё предстоит проделать. В остальном данная библиотека позволяет использовать интерфейс к Lua из программы на D, так, как это делается при разработке на C/C++. Перечисленные модули подключаются к проекту на D и он компонуется с библиотекой liblua.so (ключ линкера -llua, если речь идет GNU/Linux).
Реализованы все функции, описанные в Lua C API. При написании модулей все макросы в оригинальных заголовках, реализующие упрощенные вызовы базовых функций API, были заменены функциями.
Для проверки был написан крошечный скрипт на Lua
test.lua
-- Глобальная переменная целого типа
nv = 2
-- Функция, вычисляющая квадрат аргумента
my_number_sqr = function(x)
return x*x
end
Для его чтения пишем такой код на D, подключая нужные библиотеки
main.d
module main;
import std.stdio;
import lua;
import lualib;
import lauxlib;
//-------------------------------------------------------------------
//
//-------------------------------------------------------------------
void main()
{
// Получаем состояние интерпретатора Lua и грузим все библиотеки
lua_State *lua_state = luaL_newstate();
luaL_openlibs(lua_state);
// Выполняем тестовый скрипт
if (luaL_dofile(lua_state, "test.lua"))
{
writeln("Error");
}
// Читаем целочисленное значение
// Запоминаем вершину стека Lua
int top = lua_gettop(lua_state);
// Кладем в стек значение nv
lua_getglobal(lua_state, "nv");
// Снимаем полученное значение со стека
lua_Integer tmp = lua_tointeger(lua_state, -1);
// Восстанавливаем стек
while (top - lua_gettop(lua_state))
lua_pop(lua_state, 1);
// Вызываем функцию my_number_sqr
top = lua_gettop(lua_state);
lua_getglobal(lua_state, "my_number_sqr");
double x = 8;
lua_pushnumber(lua_state, x);
lua_pcall(lua_state, 1, 1, 0);
double ret = lua_tonumber(lua_state, 01);
while (top - lua_gettop(lua_state))
lua_pop(lua_state, 1);
// Выводим результат
writeln("readed integer nv = ", tmp);
writefln("sqr(%f) = %f ", x, ret);
lua_close(lua_state);
}
Собираем его, не забыв прилинковать liblua.so (скрипт сборки может быть и проще, но отдельно писать было лень).
SConstruct для компиляции тестовой программы
#--------------------------------------------------------------------
# Project globals
#--------------------------------------------------------------------
source_dir = 'src/'
d_include_path = 'src/D/'
release_target_dir = 'bin/release/'
target_name = 'test_lua_api'
#--------------------------------------------------------------------
# Release build configuration
#--------------------------------------------------------------------
release_env = Environment(
CC='gcc',
CXX='g++',
DMD='dmd',
DPATH=d_include_path,
LINK='gcc',
CPPFLAGS='-O3',
DFLAGS='-O'
)
release_env.VariantDir(release_target_dir,
source_dir,
duplicate=0)
d_sources = Glob(release_target_dir + 'D/*.d')
d_obj = release_env.Object(d_sources)
release_env.Program(release_target_dir + target_name, d_obj, LIBS=['phobos2', 'lua'])
имея на выходе
readed integer nv = 2
sqr(8.000000) = 64.000000
Вместо заключения
Большинство функций пока что не тестировалось, собственно в моем проекте используется пока чтение полей таблиц и вызов Lua-функций. Для хорошей проверки данный код следует безвозмездно отдать народу, что я и делаю. Надеюсь, кому-нибудь пригодится. Благодарю за внимание к моему труду.
P.S.:
Код придется серьезно поправить. Допустим, для корректной работы со строками пришлось внести некоторые коррективы в lua_tostring(...)
string lua_tostring(lua_State *L, int idx)
{
return to!string(lua_tolstring(L, idx, null));
}
добавив преобразование в string. Что ж, по мере использвания коррективы будут внесены