Модули расширения Lua

From FxCodeBaseWiki

(Redirected from Lua Extension Modules/ru)
Jump to: navigation, search

Contents

Что такое расширения Lua?

Модуль расширения Lua — это динамически подключаемая библиотека, содержащая набор функций на C или C++, которые могут использоваться из кода Lua.

В этой статье предполагается, что вы уже знакомы с Lua VM и разработкой на базе этой виртуальной машины. Прочитать о Lua VM можно в документации по Lua, Section 3. The Application Program Interface.

Обратите внимание на то, что модуль расширения Lua может быть разработан только на C или C++. В этой статье предполагается, что используется среда Microsoft Visual Studio, однако можно использовать и компилятор Win32 C++.

С чего начать

Подготовка системы

Достаточно загрузить и установить последнюю версию Indicore Integration SDK. Ее всегда можно найти по следующему адресу: IndicoreDev.exe

Создание проекта

  • Создайте новый проект C++/Win32.
  • Выберите тип приложения DLL и создайте пустой проект.
  • В разделе «Ввод» параметров компоновщика добавьте C:\Gehtsoft\IndicoreDev\lib\lua5.1.lib и C:\Gehtsoft\IndicoreDev\lib\indicore2.lib в свойство дополнительных зависимостей.
  • Название выходного файла должно полностью совпадать с названием таблицы расширений, которую вы собираетесь использовать. В данном случае мы реализуем функцию вычисления среднего геометрического значения для Lua, поэтому предположим, что выходной модуль называется geomean.dll.

Файлы заголовков

Добавьте в исходный файл следующие заголовки:

#include <windows.h>
#include <stdio.h>
#include <math.h>
#include <tchar.h>
#include <string>
#include <vector>
#include <map>
#include <list>
#define INDICORE2_EXTKIT
#include "C:/Gehtsoft/IndicoreDev/include/indicore2.h"

Запись шаблона для функции расширения

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

/** Функция geomean.calc(stream, from, to). 
 
    stream поток тиков
    from   первый период диапазона для расчета
    to     последний период диапазона для расчета
 
    Функция возвращает среднее геометрическое значение (1 число)
  */
int geomean_calc(lua_State *L)
{
    double ret = 0;
    //Необходимо определить: расчет среднего значения в возвращаемой переменной
 
    lua_pushnumber(L, ret);
    return 1;
}

Создание описания модуля

Теперь создадим описание для таблицы модуля расширения. Таблица модуля расширения — это массив структур luaL_reg. Каждый его элемент соответствует одной функции и определяет ее название, отображаемое в Lua. Последний элемент должен быть структурой, заполненной нолями.

static const struct luaL_reg geomean_lib [] =
{
     {"calc", geomean_calc},
     {NULL, NULL}
};

Создание функции регистрации модуля

Теперь необходимо создать экспортируемую функцию, которая будет вызываться, когда Lua получает запрос на загрузку нашего модуля расширения. Название этой функции должно иметь вид luaopen_name, где name — это название модуля расширения (в нашем случае geomean).

extern "C" int __declspec(dllexport) luaopen_extension (lua_State *L)
{
    luaL_register(L, "geomean", geomean_lib);
 
    lua_getglobal(L, "geomean");
    if (lua_isnil(L, -1) || !lua_istable(L, -1)) {
        luaL_error(L, "the geomean library is not registered");
    }
    return 1;
}

Подготовка тестового индикатора

Этот индикатор очень похож на простое скользящее среднее (MVA.lua). Он принимает всего один параметр N (число периодов), может применяться к данным тиков и рассчитываться начиная с N-го бара. Создадим заготовку индикатора. Сведения о создании индикаторов можно найти в Indicore SDK (в частности, в пошаговых инструкциях по созданию простого индикатора).

function Init()
    indicator:name("Test indicator");
    indicator:description("");
    indicator:requiredSource(core.Tick);
    indicator:type(core.Indicator);
 
    indicator.parameters:addGroup("Calculation");
    indicator.parameters:addInteger("N", "Number of periods", "", 10, 2, 1000);
    indicator.parameters:addGroup("Style");
    indicator.parameters:addColor("C", "Line Color", "", core.rgb(0, 0, 255));
    indicator.parameters:addInteger("W", "Line Width", "", 1, 1, 5);
    indicator.parameters:addInteger("S", "Line Style", "", core.LINE_SOLID);
    indicator.parameters:setFlag("S", core.FLAG_LEVEL_STYLE);
end
 
local n;
local first = nil;
local source = nil;
local out = nil;
 
require("geomean"); 
function Prepare(onlyName)
    local name;
 
    name = profile:id() .. "(" .. instance.source:name() .. ")";
    instance:name(name);
    if onlyName then
        return ;
    end
 
    source = instance.source;
 
    n = instance.parameters.N;
    first = source:first() + n - 1;
    out = instance:addStream("GEOMEAN", core.Line, name, "GEOMEAN", instance.parameters.C, first);
    out:setWidth(instance.parameters.W);
    out:setStyle(instance.parameters.S);
end
 
function Update(period, mode)
    if period >= first then
        out[period] = geomean.calc(source, period - n + 1, period);    end
end

Обратите внимание на выделенные строки. Первая заставляет Lua загрузить наше расширение. Вторая вызывает нашу функцию.

Реализация функции расширения

Теперь реализуем функцию расширения. Во-первых, необходимо получить параметры из стека. Как видно из кода индикатора, первый параметр является потоком. Нам необходимо знать, как таблица Lua преобразуется в интерфейс IIndicatorTickSource, поэтому для данного проекта нам нужен Indicore Integration SDK. В него входит класс IndicoreAccessors, предназначенный специально для этой цели.

Получим значения параметров из стека и проверим типы.

/** Функция geomean.calc(stream, from, to). 
 
    stream поток тиков
    from   первый период диапазона для расчета
    to     последний период диапазона для расчета
 
    Функция возвращает среднее геометрическое значение (1 число)
  */
int geomean_calc(lua_State *L)
{
    double ret = 0;
 
    IIndicatorTickSource *src = IndicoreAccessors::getIIndicatorTickSource(L, 1);    if (src == 0)        return luaL_error(L, "первый параметр должен быть источником.");     if (lua_isnumber(L, 2) == 0)        return luaL_error(L, "второй параметр должен быть числом");    int from = (int)lua_tonumber(L, 2);     if (lua_isnumber(L, 3) == 0)        return luaL_error(L, "третий параметр должен быть числом");    int to = (int)lua_tonumber(L, 3); 
    lua_pushnumber(L, ret);
    return 1;
}

Теперь проверим параметры from и to. Они оба должны соответствовать диапазону исходных цен, причем параметр from должен предшествовать параметру to.

/** Функция geomean.calc(stream, from, to). 
 
    stream поток тиков
    from   первый период диапазона для расчета
    to     последний период диапазона для расчета
 
    Функция возвращает среднее геометрическое значение (1 число)
  */
int geomean_calc(lua_State *L)
{
    double ret = 0;
 
    IIndicatorTickSource *src = IndicoreAccessors::getIIndicatorTickSource(L, 1);
    if (src == 0)
        return luaL_error(L, "первый параметр должен быть источником.");
 
    if (lua_isnumber(L, 2) == 0)
        return luaL_error(L, "второй параметр должен быть числом");
    int from = (int)lua_tonumber(L, 2);
 
    if (lua_isnumber(L, 3) == 0)
        return luaL_error(L, "третий параметр должен быть числом");
    int to = (int)lua_tonumber(L, 3);
 
    if (from < src->getFirstPeriod() || from >= src->size() ||        to < src->getFirstPeriod() || to >= src->size())        return luaL_error(L, "индекс периода не соответствует диапазону");     if (to <= from)        return luaL_error(L, "третий параметр должен быть больше второго"); 
 
    lua_pushnumber(L, ret);
    return 1;
}

Реализуем расчет геометрического среднего. Формула среднего геометрического значения n выглядит следующим образом:

\operatorname{geomean}(price, n)_i = \sqrt[n]{\prod_{j=i-n+1}^i price_j} = ({\prod_{j=i-n+1}^i price_j})^{\frac{1}{n}}

/** Функция geomean.calc(stream, from, to). 
 
    stream поток тиков
    from   первый период диапазона для расчета
    to     последний период диапазона для расчета
 
    Функция возвращает среднее геометрическое значение (1 число)
  */
int geomean_calc(lua_State *L)
{
    double ret = 0;
 
    IIndicatorTickSource *src = IndicoreAccessors::getIIndicatorTickSource(L, 1);
    if (src == 0)
        return luaL_error(L, "первый параметр должен быть источником.");
 
    if (lua_isnumber(L, 2) == 0)
        return luaL_error(L, "второй параметр должен быть числом");
    int from = (int)lua_tonumber(L, 2);
 
    if (lua_isnumber(L, 3) == 0)
        return luaL_error(L, "третий параметр должен быть числом");
    int to = (int)lua_tonumber(L, 3);
 
    if (from < src->getFirstPeriod() || from >= src->size() ||
        to < src->getFirstPeriod() || to >= src->size())
        return luaL_error(L, "индекс периода не соответствует диапазону");
 
    if (to <= from)
        return luaL_error(L, "третий параметр должен быть больше второго");
 
    double n = (double)(to - from + 1);    double prod = 1;     for (int i = from; i <= to; i++)        prod *= src->getTick(i);     ret = pow(prod, 1.0 / n); 
    lua_pushnumber(L, ret);
    return 1;
}

Расширение готово. Скомпилируем его.

Развертывание и тестирование

  • Поместите скомпилированное расширение geomean.dll в ту же папку приложения (например, Marketscope или Отладчик Индикатора), где находится lua5.1.dll (обычно это папка, в которую установлено приложение).
  • Установите индикатор.
  • Запустите индикатор.

Известные проблемы

Исключения С++

Используемая версия LuaJIT не поддерживает исключения C++ для кода Microsoft Visual C++/Win32. В связи с этим не используйте конструкцию try/catch в функции, которая вызывается из Lua, а также не выполняйте обратные вызовы кода Lua из блока try/catch.

Об этой проблеме также рассказывается здесь: документация LuaJIT, Тема обсуждения

Эта же статья на других языках

Language: English  • Español • Français • Русский • 中文 • ‪中文(繁體)‬
Personal tools