1 (2022-10-18 14:33:20 отредактировано infinitywaytrader)

Тема: Ошибка ACCESS VIOLATION при подключении внешней dll в lua

Приветствую форумчане.

Имеется сторонняя dll написанная на c++ для получения из quik9.5 свечек по команде, собрана под lua5.3, а также имеется lua скрипт, который должен эти самые свечки отправлять в dll, а затем в приложение написанное на c#.

Для сборки dll используется VS 2019 в release x64.

Dll помещается в корень quik`а.

Суть проблемы - при запуске в quik lua-скрипта, терминал выплёвывает ошибку ACCESS VIOLATION at address SOME_ADDRESS.

Код dll

#include <windows.h>
#include <process.h>

// необходимые для Lua константы
#define LUA_LIB
#define LUA_BUILD_AS_DLL

// заголовочные файлы LUA
extern "C" {
#include "Lua\lauxlib.h"
#include "Lua\lua.h"
}

TCHAR CommandMemory[] = TEXT("Memory4CommandGetCandles");
TCHAR CandlesMemory[] = TEXT("Memory4GetCandles");

HANDLE CommandFileMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 64, CommandMemory);
HANDLE CandlesFileMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 128, CandlesMemory);

// точка входа DLL
BOOL APIENTRY DllMain(HANDLE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
    return TRUE;
}

// функция отправки команд из программы в Lua скрипт
static int forLua_GetCommand(lua_State* L)
{
    if (CommandFileMap) // если память существует
    {
        // считываем данные из памяти и переводим в набор байтов
        PBYTE pb = (PBYTE)(MapViewOfFile(CommandFileMap, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 64));
        if (pb != NULL) // если есть данные
        {
            if (pb[0] == 0) // если первый байт нулевой
            {
                lua_pushstring(L, ""); // отправляем пустую строку в Lua скрипт
            }
            else
            {
                lua_pushstring(L, (char*)(pb));//иначе отправляем набор байтов в Lua скрипт
            }


            UnmapViewOfFile(pb); // закрываем доступ к данным
        }
        else
        {
            lua_pushstring(L, ""); // иначе отправляем пустую строку в Lua скрипт
        }
    }
    else
    {
        lua_pushstring(L, ""); // иначе отправляем пустую строку в Lua скрипт
    }

    return(1);
}

// функция очистки памяти для отправки команд
static int forLua_ClearCommand(lua_State* L)
{
    if (CommandFileMap)//если память существует
    {
        // считываем данные из памяти и переводим в набор байтов
        PBYTE pb = (PBYTE)(MapViewOfFile(CommandFileMap, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 64));

        if (pb != NULL) // если есть данные
        {
            // заполняем нулевыми байтами
            for (int i = 0; i < 64; i++)
            {
                pb[i] = '\0';
            }

            UnmapViewOfFile(pb); // закрываем доступ
        }
    }

    return(0);
}

// функция отправки свечек из lua в программу
static int forLua_SendCandle(lua_State* L)
{
    if (CandlesFileMap) // если память существует
    {
        // считываем данные из памяти и переводим в набор байтов
        PBYTE pb = (PBYTE)(MapViewOfFile(CandlesFileMap, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 128));
        if (pb != NULL) // если есть данные
        {
            const char* Candle = lua_tostring(L, 1); // переменная для хранения данных
            int Size = 0;

            for (int i = 0; i < 128; i++)
            {
                if (Candle[i] == 0)
                {
                    break;
                }
                else
                {
                    Size++;
                }
            }

            memcpy(pb + 1, Candle, Size); // копируем 
            memcpy(pb, "1", 1);
            UnmapViewOfFile(pb); // закрываем доступ
        }
    }

    return(0);
}

// регистрация реализованных в dll функций, чтобы они стали "видимы" для Lua
static const struct luaL_Reg ls_lib[] = {
    { "GetCommand", forLua_GetCommand },
    { "ClearCommand", forLua_ClearCommand },
    { "SendCandle", forLua_SendCandle },
    { NULL, NULL }
};

// регистрация названия библиотеки, видимого в скрипте Lua
extern "C" LUALIB_API int luaopen_GetCandlesDll(lua_State * L) {
    lua_newtable(L);
    luaL_setfuncs(L, ls_lib, 0);
    lua_setglobal(L, "GetCandlesDll");
    return 1;
}

Код lua-скрипта

require("GetCandlesDll");

local Candle = "";
local LastQty = 1;
local CurrentQty = 1;
local DataSet;
local IsOnline = false;
local IsRun = true;

local Minute = "";
local Hour = "";

local Day = "";
local Month = "";
local Year = "";

local High = "";
local Low = "";
local Open = "";
local Close = "";

local ClassName = "TQBR";
local ToolName = "YNDX";

function OnInit()
    DataSet = CreateDataSource(ClassName, ToolName, INTERVAL_M1);
end;

function OnStop()
    IsRun = false;
end;

function main()
    while IsRun do
        if tostring(GetCandlesDll.GetCommand()) == "run" then
            IsOnline = true;
            DataSet = CreateDataSource(ClassName, ToolName, INTERVAL_M1);
            GetCandlesDll.ClearCommand();
        end;
        
        if tostring(GetCandlesDll.GetCommand()) == "stop" then
            StopScript();
        end;

        if IsOnline == true then
            CurrentQty = DataSet:Size() - 1;

            if CurrentQty ~= nil and CurrentQty > LastQty then
                for i = LastQty, CurrentQty do
                    if (i == CurrentQty) then
                        LastQty = CurrentQty;
                    end;

                    if tostring(GetCandlesDll.GetCommand()) == "stop" then
                        StopScript();

                        break;
                    end;

                    Minute = tonumber(DataSet:T(i).min) < 10 and "0"..tostring(DataSet:T(i).min) : tostring(DataSet:T(i).min);
                    Hour = tonumber(DataSet:T(i).hour) < 10 and "0"..tostring(DataSetDataSetT(i).hour) : tostring(DataSet:T(i).hour);

                    Day = tonumber(DataSet:T(i).day) < 10 and "0"..tostring(DataSet:T(i).day) : tostring(DataSet:T(i).day);
                    Month = tonumber(DataSet:T(i).month) < 10 and "0"..tostring(DataSet:T(i).month) : tostring(DataSet:T(i).month);
                    Year = tostring(DataSet:T(i).year);
                    
                    High = tostring(DataSet:H(i));
                    Low = tostring(DataSet:L(i));
                    Open = tostring(DataSet:O(i));
                    Close = tostring(DataSet:C(i));
                    
                    Candle = Day.."."..Month.."."..Year.." "..Hour..":"..Minute..":".."00"..";"..High..";"..Low..";"..Open..";"..Close..";";

                    sleep(1);
                end;
            end;
        end;

        sleep(1000);
    end;
end;

function StopScript()
    GetCandlesDll.ClearCommand()
    IsOnline = false;
    LastQty = 1;
    Candle = "";

    if DataSet:Size() ~= nil then 
        DataSet:Close(); 
    end;
end;

Re: Ошибка ACCESS VIOLATION при подключении внешней dll в lua

Ссылка на GoogleDrive проекта

[url]https://drive.google.com/file/d/1z0BY-pwdQ-ZRkjG7k06GB6odp6gdc2lB/view?usp=sharing[/url]

Re: Ошибка ACCESS VIOLATION при подключении внешней dll в lua

Нашёл решение своей проблемы, для корректного подключения dll в lua-скрипте надо было использовать lua54.lib, это раз.
Два - немного подредактировать регистрацию в коде самой  dll, то есть на выходе получается такой код

// регистрация названия библиотеки, видимого в скрипте Lua
extern "C" LUALIB_API int luaopen_GetCandlesDll(lua_State * L) {
    lua_newtable(L);
    luaL_setfuncs(L, ls_lib, 0);
    lua_pushvalue(L, -1);
    lua_setglobal(L, "GetCandlesDll");
    return 1;
}

4 (2022-10-19 22:43:31 отредактировано swerg)

Re: Ошибка ACCESS VIOLATION при подключении внешней dll в lua

infinitywaytrader пишет:

надо было использовать lua54.lib

Либо переключиться в терминале QUIK на выполнение скрипта на Lua 5.3.

Спасибо что поделились на форуме результатами и решением.

5 (2022-10-20 03:36:55 отредактировано infinitywaytrader)

Re: Ошибка ACCESS VIOLATION при подключении внешней dll в lua

Для удобства выложу исправленный код dll и исправленный lua-скрипт, надеюсь кому нибудь пригодится

Библиотека написана под lua версии 5.4

UDP 20.10.2022: обновлён lua-скрипт

#include <windows.h>
#include <process.h>

// необходимые для Lua константы
#define LUA_LIB
#define LUA_BUILD_AS_DLL

// заголовочные файлы LUA
extern "C" {
#include "Lua\lua.h"
#include "Lua\lualib.h"
#include "Lua\lauxlib.h"
}

TCHAR CommandMemory[] = TEXT("Memory4CommandGetCandles");
TCHAR CandlesMemory[] = TEXT("Memory4GetCandles");

HANDLE CommandFileMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 64, CommandMemory);
HANDLE CandlesFileMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 128, CandlesMemory);

// точка входа DLL
BOOL APIENTRY DllMain(HANDLE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
    return TRUE;
}

// функция отправки команд из программы в Lua скрипт
static int forLua_GetCommand(lua_State* L)
{
    if (CommandFileMap) // если память существует
    {
        // считываем данные из памяти и переводим в набор байтов
        PBYTE pb = (PBYTE)(MapViewOfFile(CommandFileMap, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 64));
        if (pb != NULL) // если есть данные
        {
            if (pb[0] == 0) // если первый байт нулевой
            {
                lua_pushstring(L, ""); // отправляем пустую строку в Lua скрипт
            }
            else
            {
                lua_pushstring(L, (char*)(pb));//иначе отправляем набор байтов в Lua скрипт
            }


            UnmapViewOfFile(pb); // закрываем доступ к данным
        }
        else
        {
            lua_pushstring(L, ""); // иначе отправляем пустую строку в Lua скрипт
        }
    }
    else
    {
        lua_pushstring(L, ""); // иначе отправляем пустую строку в Lua скрипт
    }

    return(1);
}

// функция очистки памяти для отправки команд
static int forLua_ClearCommand(lua_State* L)
{
    if (CommandFileMap)//если память существует
    {
        // считываем данные из памяти и переводим в набор байтов
        PBYTE pb = (PBYTE)(MapViewOfFile(CommandFileMap, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 64));

        if (pb != NULL) // если есть данные
        {
            // заполняем нулевыми байтами
            for (int i = 0; i < 64; i++)
            {
                pb[i] = '\0';
            }

            UnmapViewOfFile(pb); // закрываем доступ
        }
    }

    return(0);
}

// функция отправки свечек из lua в программу
static int forLua_SendCandle(lua_State* L)
{
    if (CandlesFileMap) // если память существует
    {
        // считываем данные из памяти и переводим в набор байтов
        PBYTE pb = (PBYTE)(MapViewOfFile(CandlesFileMap, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 128));
        if (pb != NULL) // если есть данные
        {
            const char* Candle = lua_tostring(L, 1); // переменная для хранения данных
            int Size = 0;

            for (int i = 0; i < 128; i++)
            {
                if (Candle[i] == 0)
                {
                    break;
                }
                else
                {
                    Size++;
                }
            }

            memcpy(pb + 1, Candle, Size); // копируем 
            UnmapViewOfFile(pb); // закрываем доступ
        }
    }

    return(0);
}

// регистрация реализованных в dll функций, чтобы они стали "видимы" для Lua
static const struct luaL_Reg ls_lib[] = {
    { "GetCommand", forLua_GetCommand },
    { "ClearCommand", forLua_ClearCommand },
    { "SendCandle", forLua_SendCandle },
    { nullptr, nullptr }
};

// регистрация названия библиотеки, видимого в скрипте Lua
extern "C" LUALIB_API int luaopen_GetCandlesDll(lua_State * L) {
    lua_newtable(L);
    luaL_setfuncs(L, ls_lib, 0);
    lua_pushvalue(L, -1);
    lua_setglobal(L, "GetCandlesDll");
    return 1;
}
require("GetCandlesDll");

Candle = "";
LastQty = 1;
CurrentQty = 1;
DataSet = nil;
IsOnline = false;
IsRun = true;

Minute = "";
Hour = "";

Day = "";
Month = "";
Year = "";

High = "";
Low = "";
Open = "";
Close = "";

ClassName = "TQBR";
ToolName = "YNDX";

function main()
    while IsRun do    
        if tostring(GetCandlesDll.GetCommand()) == "run" then
            message("bot is started");
            IsOnline = true;
            DataSet = CreateDataSource(ClassName, ToolName, INTERVAL_M1);
            GetCandlesDll.ClearCommand();
        end;
        
        if BotStopCheck() then 
            StopScript();
        end;
        
        if IsOnline == true then
            CurrentQty = DataSet:Size() - 1;

            if CurrentQty ~= nil and CurrentQty > LastQty then
                for i = LastQty, CurrentQty do
                    while tostring(GetCandlesDll.GetCommand()) ~= "send" do
                        if i == 1 or BotStopCheck() then 
                            break;
                        end;

                        sleep(1);
                    end;

                    if BotStopCheck() then 
                        StopScript();
                        break;
                    end;
                    
                    GetCandlesDll.ClearCommand();

                    LastQty = i == CurrentQty and CurrentQty | LastQty;        

                    Minute = CheckNumber(DataSet:T(i).min);
                    Hour = CheckNumber(DataSet:T(i).hour);

                    Day = CheckNumber(DataSet:T(i).day);
                    Month = CheckNumber(DataSet:T(i).month);
                    Year = tostring(DataSet:T(i).year);
                    
                    High = tostring(DataSet:H(i));
                    Low = tostring(DataSet:L(i));
                    Open = tostring(DataSet:O(i));
                    Close = tostring(DataSet:C(i));
                    
                    Candle = Day.."."..Month.."."..Year.." "..Hour..":"..Minute..":".."00"..";"..High..";"..Low..";"..Open..";"..Close;

                    GetCandlesDll.SendCandle(Candle);

                    sleep(1);
                end;
            end;
        end;

        sleep(1);
    end;
end;

function OnInit()
    GetCandlesDll.ClearCommand();
end

function OnStop()
    IsRun = false;
end;

function CheckNumber(Number)
    if (tonumber(Number) < 10) then    
        return "0" .. tostring(Number);
    else
        return tostring(Number);
    end;
end;

function StopScript()
    message("bot is stopped");
    GetCandlesDll.ClearCommand()
    IsOnline = false;
    LastQty = 1;
    Candle = "";

    if DataSet ~= nil then 
        DataSet:Close(); 
    end;
end;


function BotStopCheck()
    return tostring(GetCandlesDll.GetCommand()) == "stop";
end;