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

Автор записи: Дмитрий (Admin)
1 звезда2 звезды3 звезды4 звезды5 звезд (Голосов 6, среднее: 5,00 из 5)
Загрузка...

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(С/С++), обрабатывающей массив
Если данную 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);     //Ищет глобальную переменную с заданным именем и помещает на вершину стека

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

Добавить комментарий

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

  1. Здравствуйте, Дмитрий!
    Вопрос не заставил себя долго ждать ))) :
    В свое время пытался использовать DDE и Win API для обращения к меню Quik. Бросил это направление, осознав его бесперспективность. Сейчас изучаю Ваш сайт - огромное спасибо за него, и пытаюсь, тестируя кусочки кода, реализовать готовые наработки на Qlua плюс C++ dll.
    Собственно вопрос в следующем: - Не получается получить список классов. Хочу из dll вызвать getclasseslist, получить строку вида name1,name2.., И затем вызвать message с этой строкой. Quik пишет "attempt to call a nil value". Что делаю не так?

    static int forLua_GetClassesNames(lua_State *L)
    {
    const char* ClassesStr;
    lua_getglobal(L, "getclasseslist");
    lua_pcall(L, 1, 1, 0);
    ClassesStr = lua_tostring(L, 1);
    lua_settop(L, 0); // Очищает стек Lua

    lua_getglobal(L, "message");
    lua_pushstring(L, ClassesStr);
    lua_pcall(L, 1, 0, 0);

    lua_settop(L, 0);// Очищает стек Lua
    return (0);
    }

  2. Так, вот таким образом я пришел к
    1) lua_State *luaL_newstate (void);
    2) lua_State *lua_newstate (lua_Alloc f, void *ud)
    Это было бы прекрасным решением, без костылей. но lua_State *luaL_newstate (void); сразу убивает Квик даже ошибку не говорит. 2 вариант реализовать не получилось.
    3) если регистрировать несколько библиотек(это возможно?), для них lua_State будет разный? а потом раздавать отдельно для каждой функции обратного вызова.

    1. Мне кажется Вы немного усложняете без надобности. Сделайте в длл так же 2 потока, один из main, другой из OnInit, например, или вообще только 1 из main, или Вам нужно в длл обязательно много потоков и из каждого обращаться к функциям QLua ?

    2. Вот еще добрый человек ссылку дал, там, чтобы свернутые комментарии читать нажмите в браузере F12, чтобы добраться до исходного кода страницы 🙂
      http://hghltd.yandex.net/yandbtm?fmode=inject&url=http%3A%2F%2Fforum-archive.quik.ru%2Fforum%2Flua%2F107862%2F&tld=ru&lang=ru&la=1437533568&tm=1459954331&text=%D0%BC%D0%B8%D1%85%D0%B0%D0%B8%D0%BB%20%D0%B1%D1%83%D0%BB%D1%8B%D1%87%D0%B5%D0%B2%20lua_newthread&l10n=ru&mime=html&sign=b6ba1bc405b14a4e0fae855bcd217ef6&keyno=0

  3. Здравствуйте!
    Кто нибудь пробовал получить lua_State?
    есть 2 способа
    1 lua_State *luaL_newstate (void);
    Выдает lua_State не если по нему вызывать функцию то сразу ложит QUIK
    2 lua_State *lua_newstate (lua_Alloc f, void *ud)
    Не разобрался что за lua_Alloc f и void *ud?

      1. Да задача самая простая.
        Хочу вызвать getClassSecurities. Накидал метод, если его вызывать и main. Возвращает классы инструментов.
        Теперь хочу вызвать туже функцию, из произвольного места. Из метода в который нельзя передать lua_State.
        под lua_State я имею ввиду L в вашем коде lua_toboolean(L, 1);

          1. Как я понял это вызов из Луа, я же хочу на луа писать как можно меньше, вообще не писать по возможности. И вызываю функцию из Dll в Dll.
            Может можно как то набрать этих указателей штук 20, закинуть в массив и потом использовать каждый для своего места.

            1. Вы, при подключении библиотеки, вот здесь:

              1
              2
              3
              4
              5
              6
              
              ...
              //=== Регистрация названия библиотеки, видимого в скрипте Lua ==================================================//
              extern "C" LUALIB_API int luaopen_LuaCallback(lua_State *L) {
                 luaL_openlib(L, "LuaCallback", ls_lib, 0);
                 return 0;
              }

              уже получаете lua_State, сохраняйте его глобально в dll и делайте, что хотите.

                  1. Сейчас проверил, сделал коллекцию. И в нее закидываю все lua_State, которые встретились.
                    1) при регистрации библиотеки 87206344
                    2) OnInit 87206344
                    3) Main 87152336
                    Получается для Main был создан другой стек.
                    Да и странно это как то, а если у меня одновременно две функции к одному стеку обратятся, ошибка будет. Надо разносить.

                    1. Честно говоря, давно с DLL не баловался 🙂 Наверное дело в том, что main работает в отдельном потоке. По этому, наверное, есть смысл получать lua_State из нее, но, в любом случае, обеспечение потокобезопасности на Ваши плечи ложится. Если Вы обратитесь одновременно к одному стеку, думаю проблем не будет, так как обращение к стеку это просто чтение, или запись памяти. А вот что будет если, например, одновременно из разных потоков вызвать функцию message, даже не знаю, если честно. Хотя, скорее всего, ничего страшного не случится, ведь мы можем вызвать одновременно любую функцию внутри main и OnAllTrade, например, а это ведь уже разные потоки.

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

                    3. Да, максимум 10 вложенных комментариев. Можете новую ветку начать, я увижу. По поводу Вашего вопроса думаю.

        1. Дмитрий, Иван - Добрый день!
          Наткнулся на ваши комментарии. Данная тема также очень интересует - стоит аналогичная задача "хочу вызвать туже функцию, из произвольного места. Из метода в который нельзя передать lua_State.", и тоже не получить lua_State. В связи с чем интересуюсь - удалось ли вам решить данную проблему???

          1. Добрый день! Думаю проблемы никакой нет, т.к. разработчики QLua позаботились о потокобезопасности, по этому, просто сохраняйте где-то lua_State и обращайтесь к его функциям из любого места, а вот если, все таки, произойдет ошибка, тогда сообщите, будем думать!

            1. Спасибо. Вечером начну экспериментировать, о результатах сообщу. Вот только пока не очень понимаю как, только ради эксперимента, в dll организовать одновременный вызов из разных потоков той же функции message, т.е. как синхронизировать вызовы чтобы они были одновременными.

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

  4. Попробовал передать вот такую таблицу, без функций

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    function main()
      IsRun = true
      while IsRun do    
     
        Table = {}
        for i=1, 2, 1 do
            Table[i] = {["one"]=i*10+1,["two"]=i*10+2,["three"]=i*10+3, ["four"]=i*10+4}
        end 
        --Table={11,22,33,111,222,333}
        r=wind.Test(Table)      --функция С++
     
        sleep(3000)
     
       if r then
            IsRun = false
        end
      end
    end

    функция С++ в 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
    
    wchar_t  str[100];
     
        if(lua_istable(L,1))            // если в стеке указатель на массив
        {       
            lua_pushnil(L); //Добавляем в стек первый ключ равный нулю, ссылка на таблицу на втором месте по индексу -2
            lua_next(L, 1); //ссылка на таблицу в стеке на месте -3
                            //ключ в стеке на месте -2
                            //ссылка на строку в таблице t на месте -1. Или что ????
            lua_pushnil(L); //Добавляем в стек ключ равный нулю для второй ссылки
     
     
     
            swprintf(BUF,sizeof(BUF),L"");  // очищаем BUF
            while (lua_next(L, 1) != 0) //берет ключ из стека и помещает туда пару "значение-ключ" из таблицы (следующую после данного ключа). 
                   {                    //Если не имеется больше элементов, то функция возвращает 0 (и не помещает в стек ничего)
                                        // в паре "ключ" находится по индексу -2, "значение" находится по индексу -1    
     
                          // формирование строки
                          swprintf(str,sizeof(str),L"-1=%d  -2=%d  -3=%d  -4=%d  -5=%d\n",lua_tointeger(L, -1), lua_tointeger(L, -2),lua_tointeger(L, -3),lua_tointeger(L, -4),lua_tointeger(L, -5));
     
                          // добавить строку в BUF
                          wcscat(BUF,str);                       
     
                      lua_pop(L, 1);        // освобождает стек для следующей итерации
                                            //снимает со стека 1 элемент, это "значение", ключ становится на первой позиции по индексу -1                                       
                   }                        // конец while (lua_next(L, 1) != 0)    
        }

    Выводит:

    -1=0 -2=1 -3=0 -4=1 -5=0
    -1=0 -2=2 -3=0 -4=1 -5=0

    тоже не могу добраться до данных.

      1. Это для одномерного массива, а у меня двумерный. Если вызывать так r=wind.Test(Table[1]), то с одним lua_pushnil(L) данные выводятся, только в странном порядке (1-4-3-2).

    1. А еще строка 19 у Вас не будет работать так, как Вы ожидаете, алгоритм следующий:
      1.Проверяете на таблицу if(lua_istable(L,1))
      2.Добавляете первый ключ lua_pushnil(L);
      3.Получаете первую пару "ключ"="значение" lua_next(L, 1);
      lua_tointeger(L, -1) - здесь должно быть значение первой пары (i*10+1)
      lua_tostring(L, -2) - здесь должен быть ключ первой пары ("one")
      4.Очищаете стек от нового элемента lua_pop(L, 1);
      3.Получаете вторую пару "ключ"="значение" lua_next(L, 1);
      lua_tointeger(L, -1) - здесь должно быть значение первой пары (i*10+2)
      lua_tostring(L, -2) - здесь должен быть ключ первой пары ("two")
      4.Очищаете стек от нового элемента lua_pop(L, 1);
      ...
      И так повторяете пункты 3,4 для каждой пары.

      1. "lua_tointeger(L, -1) - здесь должно быть значение первой пары (i*10+1)" - что там, посмотреть не удается, какая то ссылка, т.к. функция lua_istable(L,-1) дает true. Но то что там нет значения первой пары это точно.

    2. Скорее всего можно достать результат из изначальной таблицы в C++, но я сейчас не готов Вам ответить как это сделать, нужно пробовать.

  5. То есть, если в стеке ссылка на функцию, то из С++ никак не достать результат.
    Сейчас так и делаю, но хотелось, что бы в скрипте Lua было минимум кода, а вся обработка ушла в DLL. Передаю 4 таблицы, в каждой по 7-8 параметров и получается вместо 4-х строк целых 30.

    Спасибо за оперативный ответ.

  6. Как передать в С++ таблицу?
    Делаю так:
    В Lua

    1
    2
    3
    4
    5
    6
    
    quantity = 5
    	local n = getNumCandles(ind)--кол-во свечек, где ind = идентификатор графика
    	local t, res, _ = getCandlesByIndex (ind, 0, n - quantity, quantity)--получить последние n свечей (для справки)
    	if res > 0 then
    		r=wind.Test(t)   --вызов функции С++
          end

    В С++

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    wchar_t  str[100];
     
    	if(lua_istable(L,-1))			// если в стеке указатель на массив
    	{		
    		lua_pushnil(L);	//Добавляем в стек первый ключ равный нулю, ссылка на таблицу t на втором месте по индексу -2
    		lua_next(L, 1);	//ссылка на таблицу в стеке на месте -3
    						//ключ в стеке на месте -2
    						//ссылка на строку в таблице t на месте -1. Или что ????
    		lua_pushnil(L);	//Добавляем в стек ключ равный нулю для второй ссылки
    		lua_next(L, 1);	//выводим в стек значение
     
    		swprintf(str,sizeof(str),L"%d",lua_tointeger(L, -1); // выводим в str первое значение
    	}

    Выводит одни нули.

      1. Здравствуйте! Вы передаете в DLL из Lua единственную таблицу t, следовательно в стеке один индекс, по этому индексу лежит таблица t, индекс равен 1, и т.к. в стеке один элемент, то можно проверить его и по индексу -1 (индекс 1 - это первый положенный в стек элемент, индекс -1 - это последний положенный в стек элемент).
        Если Вы хотите перебирать элементы не в цикле, а поочередно, вот рабочий пример:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        
        require("MyDLL");
        Run = true;
         
        function main()
           local t = {"a","b","c"}
           local t1,t2,t3 = MyDLL.Test(t);
           message(t1..t2..t3); -- Выведет "abc"
         
           while Run do sleep(10); end;
        end;
         
        function OnStop()
           Run = false;
        end;
        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
        
        static int forLua_Test(lua_State *L)
        {
           // Проверяет, является-ли первый элемент стека массивом (таблицей Lua)
           if (lua_istable(L, 1))
           {
              lua_pushnil(L);
         
              //Получает последовательно 3 элемента массива
              lua_next(L, 1);
              const char *t1 = lua_tostring(L,-1);//Сохраняет значение в переменной
              lua_pop(L, 1);
         
              lua_next(L, 1);
              const char *t2 = lua_tostring(L, -1);//Сохраняет значение в переменной
              lua_pop(L, 1);
         
              lua_next(L, 1);
              const char *t3 = lua_tostring(L, -1);//Сохраняет значение в переменной
              lua_pop(L, 1);
         
              //Кладет полученные значения обратно в стек
              lua_pushstring(L, t1);
              lua_pushstring(L, t2);
              lua_pushstring(L, t3);
              return (3);//Возвращает 3 значения
           }
         
           lua_pushstring(L, "Ошибка");
           return (1);
        }

        Но вся проблема в том, что функция getCandlesByIndex возвращает не просто таблицу - массив значений, а таблицу, элементы которой являются функциями QLua, такие как O(), C(), H(), L(), о чем написано в статье https://quikluacsharp.ru/quik-qlua/poluchenie-v-qlua-lua-dannyh-iz-grafikov-i-indikatorov/
        По этому, проще будет еще на стороне QLua представить эти данные в виде обычного массива и уже его обрабатывать в DLL.

        Т.е. в QLua сделать что-то вроде:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        
        local Bars = {};
        local quantity = 5
        local n = getNumCandles(ind)--кол-во свечек, где ind = идентификатор графика
        local t, res, _ = getCandlesByIndex (ind, 0, n - quantity, quantity)--получить последние n свечей (для справки)
         
        for i=1,res do
           table.insert(Bars, t:O(i));
           table.insert(Bars, t:C(i)); 
           table.insert(Bars, t:H(i));
           table.insert(Bars, t:L(i));
        end;
         
        r=wind.Test(Bars)   --вызов функции С++