Взаимодействие Lua и библиотеки DLL, написанной на C/C++

Автор записи: Дмитрий (Admin)

Qlua-csharp-connector-dll
Взаимодействие происходит по стандартному алгоритму: в скрипте QLua(Lua) вызываются функции из подключенной при помощи директивы "require" библиотеки DLL, написанной на языке C/C++ (о том как ее создать, можно прочитать в данной статье). В функцию можно передавать данные, в виде параметров. Обрабатывать их внутри функции и получать в скрипт результат(ы) выполнения функции.

Для этого служит Lua-стек, который представляет из себя массив разнотипных данных (таблицу Lua). Первое значение, помещенное в стек, получает индекс 1, второе - 2 и т.д.

Для каждого отдельного вызова функции автоматически используется отдельный стек!

Получить результаты можно обычным способом, и, если их несколько, используя параллельное присваивание.
Пример, если бы функция из приведенного примера DLL возвращала 4 значения:

require("QluaCSharpConnector");
-- Получение значений в QLua
a,b,c,d = QluaCSharpConnector.TestFunc();
 
-- Передать значения в функцию на C/C++ можно обычным способом
QluaCSharpConnector.TestFunc(true, 1, 2.5, "Текст");

Для того, чтобы получить значения внутри функции DLL, переданные из скрипта, в Lua существует несколько функций, приведу основные:

lua_gettop(L); //Вернет индекс последнего элемента в стеке. Т.к. индексы начинаются с 1, то это значение, так же, означает количество элементов в стеке. По-умолчанию, максимально возможный индекс установлен в 8000, но может быть изменен в файле luaconf.h
 
lua_settop(L, i); //Меняет размер стека, устанавливая количество элементов равным i, если i больше, чем на данный момент элементов в стеке, то функция добавляет в стек недостающие элементы, устанавливая их значения как nil. Если i меньше, чем на данный момент элементов в стеке, то функция удаляет из стека лишние элементы. Если передать i равное 0, то функция очистит стек.
 
lua_toboolean(L, i); //Возьмет из стека значение по индексу i (индексы, как и всегда в Lua, начинаются с 1) и преобразует его к типу boolean, вернет 1 для всех значений, кроме false и nil, иначе вернет 0. Так же, вернет 0, если указан несуществующий индекс.
   lua_isboolean(L, i); //Проверяет, является-ли значение в стеке по данному индексу типом boolean.
 
lua_tointeger(L, i); //Возьмет из стека значение по индексу i, и попытается преобразовать его к типу int. Вернет полученное число, либо 0. Результат преобразования не целого числа непредсказуем.
 
lua_tonumber(L, i); //Возьмет из стека значение по индексу i, и попытается преобразовать его к типу double. Вернет полученное число, либо 0.
 
   lua_isnumber(L, i); //Проверяет, является-ли значение в стеке по данному индексу числом.
 
lua_tostring(L, i); //Возьмет из стека значение по индексу i, и попытается преобразовать его к типу string. Вернет полученную строку, либо NULL, если элемент стека не являлся ни строкой, ни числом. Значение в стеке, так же, изменится на тип string!!!
   lua_isstring(L, i); //Проверяет, является-ли значение в стеке по данному индексу типом string.

Пример получения значений внутри функции C/C++ с предварительной проверкой на тип, переданных в примере выше:

static int forLua_TestFunc(lua_State *L)
{
	boolean B;
	int I;
	double D;
	const char *S;
	// Получает из стека переданные скриптом данные
	if (lua_isboolean(L, 1)) B = lua_toboolean(L, 1);
	if (lua_isnumber(L, 2))  I = lua_tointeger(L, 2);
	if (lua_isnumber(L, 3))  D = lua_tonumber(L, 3);
	if (lua_isstring(L, 4))  S = lua_tostring(L, 4);
 
	return(0);
}

Основные функции для добавления элементов в стек:

lua_pushboolean(L, B); //Добавляет в стек элемент типа boolean
 
lua_pushinteger(L, B); //Добавляет в стек элемент типа int
 
lua_pushnumber(L, B); //Добавляет в стек элемент типа double
 
lua_pushstring(L, B); //Добавляет в стек элемент типа const char*

Пример с добавлением отправки полученных значений обратно в скрипт:

static int forLua_TestFunc(lua_State *L)
{
	boolean B;
	int I;
	double D;
	const char *S;
	// Получает из стека переданные скриптом данные
	if (lua_isboolean(L, 1)) B = lua_toboolean(L, 1);
	if (lua_isnumber(L, 2)) I = lua_tointeger(L, 2);
	if (lua_isnumber(L, 3)) D = lua_tonumber(L, 3);
	if (lua_isstring(L, 4)) S = lua_tostring(L, 4);
 
	// Очищает стек Lua
	lua_settop(L, 0);
 
	// Добавляет в стек полученные ранее значения
	lua_pushboolean(L, B);
	lua_pushinteger(L, I);
	lua_pushnumber(L, D);
	lua_pushstring(L, S);
 
	// Возвращает значения в скрипт
	return(1,2,3,4);
}

Скрипт, демонстрирующий работу данной функции:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
require("QluaCSharpConnector");
 
IsRun = true;
 
function main()
   while IsRun do
      sleep(1000);
   end;
end;
 
function OnStop()
   -- Отправляет в DLL и получает обратно значения
   b,i,d,s = QluaCSharpConnector.TestFunc(true, 1, 2.5, "Текст");
 
   -- Выводит полученные значения в сообщении
   message(tostring(b).." "..tostring(i).." "..tostring(d).." "..s);
 
   IsRun = false;
end;

Так же, из QLua(Lua) в DLL(C/C++) можно передавать МАССИВЫ (таблицы Lua).
Ниже приведен пример DLL с функцией forLua_SendArray, которая получает из QLua массив чисел, вычисляет сумму значений и отправляет результат в C#:

Код DLL(С/С++), обрабатывающей массив
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include <windows.h>
 
//=== Необходимые для Lua константы ============================================================================//
#define LUA_LIB
#define LUA_BUILD_AS_DLL
 
//=== Заголовочные файлы LUA ===================================================================================//
extern "C" {
#include "Lua\lauxlib.h"
#include "Lua\lua.h"
}
 
//=== Получает указатель на выделенную именованную память =====================================================//
// Имя для выделенной памяти
TCHAR Name[] = TEXT("MyMemory");
// Создаст, или подключится к уже созданной памяти с таким именем
HANDLE hFileMapMyMemory = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 256, Name);
 
//=== Стандартная точка входа для DLL ==========================================================================//
BOOL APIENTRY DllMain(HANDLE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
   return TRUE;
}
 
//=== Реализация функций, вызываемых из LUA ====================================================================//
static int forLua_SendArray(lua_State *L) // Получает массив из QLua, вычисляет сумму значений, отправляет сумму в C#
{
   // Если указатель на память получен
   if (hFileMapMyMemory)
   {
      // Получает доступ (представление) непосредственно к чтению/записи байт
      PBYTE pb = (PBYTE)(MapViewOfFile(hFileMapMyMemory, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 256));
 
      // Если доступ получен
      if (pb != NULL)
      {
         // Если запись пустая (либо первая отправка, либо C# очистил память, подтвердив получение)
         if (pb[0] == 0)
         {
            // Проверяет, является-ли первый элемент стека массивом (таблицей Lua)
            if (lua_istable(L, 1))
            {
               int ArraySum = 0;
 
               lua_pushnil(L); //Первый ключ
               //Функция lua_next перебирает все пары "ключ"-"значение" в таблице,
               //вторым параметром указывается индекс в стеке, по которому расположен массив (таблица Lua)
               while (lua_next(L, 1) != 0)
               {
                  // в паре "ключ" находится по индексу -2, "значение" находится по индексу -1
                  lua_tonumber(L, -2); // Так можно получить числовое значение ключа
                  ArraySum = ArraySum + lua_tonumber(L, -1); // Считает сумму значений массива
                  // освобождает стек для следующей итерации
                  lua_pop(L, 1);
               }
 
               char Str[10] = ""; // массив символов для строкового представления суммы значений
               itoa(ArraySum, Str, 10); // конвертирует число в строку
 
               memcpy(pb, Str, strlen(Str)); // записывает строку в именованную память
            }
         }
 
         // Закрывает представление
         UnmapViewOfFile(pb);
         // Закрывает указатель на память
         CloseHandle(hFileMapMyMemory);
      }
   }
   return(0);
}
 
//=== Регистрация реализованных в dll функций, чтобы они стали "видимы" для Lua ================================//
static struct luaL_reg ls_lib[] = {
   { "SendArray", forLua_SendArray },
   { NULL, NULL }
};
 
//=== Регистрация названия библиотеки, видимого в скрипте Lua ==================================================//
extern "C" LUALIB_API int luaopen_QluaCSharpConnector(lua_State *L) {
   luaL_openlib(L, "QluaCSharpConnector", ls_lib, 0);
   return 0;
}
Если данную DLL назвать QluaCSharpConnector.dll и запустить нижеприведенный скрипт, то в именованной памяти "MyMemory" будет строка "15", которую можно обработать в приложении C#.

1
2
3
4
5
6
require("QluaCSharpConnector");
 
function main()
   Array = {1,2,3,4,5};
   QluaCSharpConnector.SendArray(Array);
end;

Разные функции:

lua_getglobal(L, name);     //Ищет глобальную переменную с заданным именем и помещает на вершину стека

Если у Вас появились какие-то вопросы, задайте их в комментариях под статьей !!!