Lua 扩展模块

From FxCodeBaseWiki
(Redirected from Lua Extension Modules/zh)
Jump to: navigation, search

什么是 Lua 扩展模块?

Lua 扩展模块是一个动态链接库,它包含使用 C 或 C++ 编写、可通过 Lua 代码使用的函数集合。

本文假设您熟悉 Lua VM 并使用 Lua VM 进行开发。您可以参阅 Lua 文档Section 3. The Application Program Interface,了解 Lua VM。

请注意,Lua 扩展模块能用 C 或 C++ 开发。本文假设您使用 Microsoft Visual Studio,但您可替代使用任何 Win32 C++ 编译器。

如何开始?

准备环境

您只需下载并安装最新版 Indicore Integration SDK。最新版本始终可以在这里获取:IndicoreDev.exe

创建项目

  • 创建新 C++/Win32 项目。
  • 选择 DLL 应用程序类型,并创建一个空项目
  • 在链接器选项“输入”部分,添加 C:\Gehtsoft\IndicoreDev\lib\lua5.1.libC:\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"

编写扩展函数模板

然后,您必须创建一个扩展函数模板。所有扩展函数的原型完全相同。您可以使用任何名称,但好的做法是使用与我们要用的名称相似的名称。

/** The 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, "geomean 库未注册");
    }
    return 1;
}

准备测试指标

该指标与简单移动平均线 (MVA.lua) 指标十分相似。它只采用一个整数参数 N(周期数),可应用于分笔成交点数据,还可从第 N 条棒线开始计算。让我们创建指标框架。有关编写指标的信息,请参阅 Indicore SDK,特别是关于创建简单指标的分步说明

function Init()
    indicator:name("测试指标");
    indicator:description("");
    indicator:requiredSource(core.Tick);
    indicator:type(core.Indicator);

    indicator.parameters:addGroup("计算");
    indicator.parameters:addInteger("N", "周期数", "", 10, 2, 1000);
    indicator.parameters:addGroup("样式");
    indicator.parameters:addColor("C", "线条颜色", "", core.rgb(0, 0, 255));
    indicator.parameters:addInteger("W", "线条宽度", "", 1, 1, 5);
    indicator.parameters:addInteger("S", "线条样式", "", 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 类专为实现该目的而设计。

因此,从堆栈获取参数值,并检查类型。

/** The 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”参数。这两个参数都必须在价格源数据范围之内,并且“to”必须晚于“from”。

/** The 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 的几何平均值的公式为:

<math>\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}}</math>

/** The 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 放入 lua5.1.dll 所在的同一个应用程序(例如汇图宝指标调试器)文件夹(通常为应用程序的安装文件夹);
  • 安装指标;
  • 运行指标。

已知问题

C++ 异常

使用的 LuaJIT 版本不支持 Microsoft Visual C++/Win32 代码的 C++ 异常。因此,您不得在从 Lua 调用的函数中使用 try/catch 权限,也不得从 try/catch 块内部回调 Lua。

另请参阅该问题的相关内容:LuaJIT 文档讨论贴

本文的其他语言版本

Language: English  • español • français • русский • 中文 • 中文(繁體)‎