1

Тема: Использование функции PrintDbgStr в QLua

В терминале QUIK версии 6.15.0 в Lua добавлена новая функция: PrintDbgStr.
Описание в справке про неё крайне скудное, поэтому здесь расскажу подробнее. (пруф)

Теория

Исключительно для справки:

В основе лежит вызов WinAPI функции OutputDebugString. Описание как она устроена и как её использовать есть в MSDN и не только.
Кратко суть её работы в том, что при вызове эта функция ищет есть ли в системе зарегистрированная "программа-ловушка" сообщений, отправленных через OutputDebugString. Если такая программа есть - ей отправляется сообщение - и ваш скрипт продолжает работать. Что далее делать с полученным сообщением - зависит от функционала программы-"ловушки". Если никакой подобной программы нет - вызов OutputDebugString ничего не делает.
Хорошо в этой функции то, что если её вывод никто "не ловит" - то работает она очень быстро, практически не задерживая работу вашей программы. Это позволяет оставлять вывод отладочной информации даже в готовой программе. (Запустив просмотрщик, с удивлением увидел выводимые некоторыми программами явно отладочные сообщения.)
Хорошо то, что наблюдать отладочный вывод можно даже подключившись с другого компьютера.
Условно-плохо то, что эта функция не годится для логирования: если в момент её вызова в системе нет "приёмника сообщений" - то выведенные сообщения нигде не сохранятся.

Практика

На самом деле использовать эту функцию очень просто. Вызывая из Lua-скрипта в QUIK функцию PrintDbgStr("строка"), мы получаем отправку отладочного сообщения, т.е. того текста, который в неё передали.
Надо отметить сразу, что выводимые сообщения не пишутся ни в какой файл. Чтобы их увидеть - необходима какая-нибудь специальная программа. Например, средства просмотра такого отладочного вывода содержатся во многих популярных отладчиках.

Но мы возьмём программу намного проще и удобнее: DebugView от Марка Русиновича. Скачать DebugView можно совершенно бесплатно.

Для примера сделаем простой скрипт:

PrintDbgStr("Start: [" ..  getScriptPath() .. "]")

is_run = true

function main()
  while is_run do
    sleep(1000)
    PrintDbgStr("main() Time:" .. os.date("%H:%M:%S"))
  end
end

function OnStop()
  PrintDbgStr("onStop()")
  is_run = false
end

Запустим программу DebugView и вот что мы в ней увидим:

https://quik2dde.ru/static-img/108/DebugView.png

Чтобы в столбце Time видеть время в нормальном виде - нужно в меню Options включить пункт Clock time.
Причем строки будут появляться прямо "в on-line режиме" во время работы скрипта.

Может оказаться, что в окне DebugView мы увидим не только отправленные нами сообщения, но и отладочные сообщения от других программам. Так что есть смысл отфильтровать строки по PID интересующего нас процесса info.exe.

Немного про настройки вывода

...позже...

2

Re: Использование функции PrintDbgStr в QLua

  < reserved >

3

Re: Использование функции PrintDbgStr в QLua

Кстати, с помощью этой же программы можно посмотреть ошибки Lua на этапе загрузки и анализа скриптов для индикаторов терминалом

4

Re: Использование функции PrintDbgStr в QLua

Михаил, спасибо за информацию!
Это относится к любой версии QUIK, где есть индикаторы, или только к определённой?

5

Re: Использование функции PrintDbgStr в QLua

admin пишет:

Михаил, спасибо за информацию!
Это относится к любой версии QUIK, где есть индикаторы, или только к определённой?

начиная с 6.15

6 (2014-11-18 20:52:03 отредактировано GREEN_X5)

Re: Использование функции PrintDbgStr в QLua

Интересная фича. Попробовал - работает. Нужно будет потестить на скорость в сравнении например с выводом в vcl.Memo. Ибо тормоза нам не нужны, тормозов у нас и так хватает )

Потестил, нормуль, можно брать )
время выполнения PrintDbgStr= 0.209 msec, VCL.Memo= 0.868 msec, message= 0.541 msec

VCL = require "qvcl"
mainForm = VCL.Form({Height = 300, Width = 300})
logMemo  = VCL.Memo(mainForm, {Height=200 , Width = 200, ScrollBars="ssAutoVertical"})
mainForm:Show()
n,str = 10000,"ABC"

local t1 = os.clock()
for i=1,n do
  PrintDbgStr(str)
end
local t2 = os.clock()
for i=1,n do
  logMemo.Lines:Add(str)
end
local t3 = os.clock()
for i=1,n do
  message(str)
end
local t4 = os.clock()
message(string.format("время выполнения PrintDbgStr= %0.3f\ msec, VCL.Memo= %0.3f\ msec, message= %0.3f\ msec", (t2-t1)/n*1000,(t3-t2)/n*1000,(t4-t3)/n*1000))

А на слабенькой Intel HD3000 и того интереснее
время выполнения PrintDbgStr= 0.175 msec, VCL.Memo= 1.691 msec, message= 1.398 msec

7

Re: Использование функции PrintDbgStr в QLua

Спасибо за результаты и эксперимент!
Это замеры при условии запущенного DebugView или нет?

8 (2014-11-19 00:00:30 отредактировано GREEN_X5)

Re: Использование функции PrintDbgStr в QLua

swerg пишет:

Спасибо за результаты и эксперимент!
Это замеры при условии запущенного DebugView или нет?

Да, форма VCL, окно DebugView и окно сообщений QUIK были запущены. Визуально в окна DebugView и VCL строки выводились в реал-тайме, окно сообщений QUIK "молчало" и выплюнуло все в конце одним разом. ) Наверное потому что прерывания в основном потоке небыло.

9

Re: Использование функции PrintDbgStr в QLua

Обсуждение записи логов в файл(ы) вынесено в отдельную тему

10 (2016-09-29 11:32:29 отредактировано sam063rus)

Re: Использование функции PrintDbgStr в QLua

-

11

Re: Использование функции PrintDbgStr в QLua

Добрый день,
насколько я понимаю в самой луа тоже есть функция OutputDebugString только я нигде не нашел доки как еe вызвать.
Не подскажите как?

12

Re: Использование функции PrintDbgStr в QLua

Раз уж похоже на мой вопрос нет ответа решил переделать  функцию toLog из QL для своих нужд (может кому-нибудь пригодится):

local function toLog(file_path,value, isLog, isDebug, isTest)
    -- запись в лог параметра value
    -- value может быть числом, строкой или таблицей
    -- file_path  -  путь к файлу
    -- файл открывается на дозапись и закрывается после записи строки
    if value~=nil then
        local lStr
        if type(value)=="string" or type(value)=="number" then lStr = value
        elseif type(value)=='boolean' then lStr = tostring(value)
        elseif type(value)=="table" then lStr = table2string(value)
        end
        if file_path~=nil and lStr ~=nil and isLog then
            local lf=io.open(file_path,"a+")
            if lf~=nil then
                lf.write(tostring(os.date("%H:%M:%S")) .. "\t" .. lStr .."\n")
                if io.type(lf)~="file" then    lf=io.open(file_path,"a+") end
                lf.flush()
                if io.type(lf)=="file" then    lf:close() end
            end
        end
        if isDebug then
            if isTest then
                print(os.date("%H:%M:%S") .. " " .. lStr)
            else
                PrintDbgStr(lStr)
            end
        end
    end
end

13

Re: Использование функции PrintDbgStr в QLua

andrv пишет:

Раз уж похоже на мой вопрос нет ответа решил переделать  функцию toLog из QL для своих нужд (может кому-нибудь пригодится):

а где же функция table2string(value)? )) Есть гораздо более эффективные функции для логирования, чем toLog.

14 (2015-07-19 02:15:35 отредактировано andrv)

Re: Использование функции PrintDbgStr в QLua

вполне возможно я использую это если знаете подскажите посмотрю
а table2string(value) там же в либе QL да и у Ееразалимского она описана в общем виде подобные функции выглядят вот так:

local function table2string(table)
    local str
    for k,v in pairs(table) do
        if type(v)=="string" or type(v)=="number" then
            str=str..k.."="..v..';'
        elseif type(v)=="table"then
            str=str..k.."={"..table2string(v).."};"
        elseif type(v)=="function" or type(v)=='boolean' then
            str=str..k..'='..tostring(v)..';'
        end
    end
    return str
end

либо так (пример из книги Программирование на lua):

function serialize (o)
    if type(o) == "number" then io.write (o)
    elseif type(o) == "string" then io.write(string.format("%q", o))
    elseif type(o) == "table" then
        io.write("{\n")
        for k,v in pairs(o) do
            io.write(" ", k, " = ") serialize(v) io.write(",\n")
        end
        io.write("}\n")
    else
        error("cannot serialize a " .. type(o))
    end
end

15 (2015-07-19 03:22:04 отредактировано kalikazandr)

Re: Использование функции PrintDbgStr в QLua

вот этот участок очень странно выглядит, в духе QL:
if lf~=nil then
  lf.write(tostring(os.date("%H:%M:%S")) .. "\t" .. lStr .."\n")
  if io.type(lf)~="file" then    lf=io.open(file_path,"a+") end
    lf.flush()
  if io.type(lf)=="file" then    lf:close() end
end
вот эта строчка у вас что проверяет?
if io.type(lf)~="file" then    lf=io.open(file_path,"a+") end - вдруг выше не получилось открыть файл?

и следом... if io.type(lf)=="file" then    lf:close() end - вдруг после записи он закрылся самостоятельно?

я не использую логирование, дорогое удовольствие.
и уж если предполагаемых записей много, то открыть файл надо при запуске скрипта и закрыть его перед остановкой.

16

Re: Использование функции PrintDbgStr в QLua

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

Меня на самом деле смущает другое не будет ли правильней записать функцию table2string следующим образом:

local table2string
table2string = function (table)
    local str
    for k,v in pairs(table) do
        if type(v)=="string" or type(v)=="number" then
            str=str..k.."="..v..';'
        elseif type(v)=="table"then
            str=str..k.."={"..table2string(v).."};"
        elseif type(v)=="function" or type(v)=='boolean' then
            str=str..k..'='..tostring(v)..';'
        end
    end
    return str
end

17

Re: Использование функции PrintDbgStr в QLua

local table2string = function (table) ... end
local  function table2string  (table) ... end
идентичны, объявить выше function toLog()
из разных скриптов дебаговые записи в один файл? зачем этот геморрой? необходимо дополнительный текст вставлять типа - "робот Вася", "робот Петя", что делать если одновременно захотят роботы запись сделать?
Если файл общий для нескольких роботов для передачи данных, то лучше БД использовать, на форуме есть примеры БД от уважаемого swerg

18 (2015-07-19 12:00:23 отредактировано kalikazandr)

Re: Использование функции PrintDbgStr в QLua

kalikazandr пишет:

local table2string = function (table) ... end
local  function table2string  (table) ... end
идентичны, объявить выше function toLog()

if type(v)=="string" or type(v)=="number" then - не нужна такая проверка, проверьте на функцию, таблицу, bolean
остальное через else - не надо лишний раз type() вызывать.

из разных скриптов дебаговые записи в один файл? зачем этот геморрой? необходимо дополнительный текст вставлять типа - "робот Вася", "робот Петя", что делать если одновременно захотят роботы запись сделать?
Если файл общий для нескольких роботов для передачи данных, то лучше БД использовать, на форуме есть примеры БД от уважаемого swerg

накосячил с изменениями))

19

Re: Использование функции PrintDbgStr в QLua

нет функция table2string родная пруф: http://sourceforge.net/projects/qllib/f … rce=navbar
в данном случае скрипт будет один но по куче инструментов, сейчас просто занимаю рефакторингом всего кода.
Спасибо за подсказку внесу изменения.

20

Re: Использование функции PrintDbgStr в QLua

kalikazandr пишет:

if io.type(lf)~="file" then    lf=io.open(file_path,"a+") end - вдруг выше не получилось открыть файл?

как показала практика эта проверка просто необходима smile

21 (2015-07-20 09:37:20 отредактировано kalikazandr)

Re: Использование функции PrintDbgStr в QLua

andrv пишет:
kalikazandr пишет:

if io.type(lf)~="file" then    lf=io.open(file_path,"a+") end - вдруг выше не получилось открыть файл?

как показала практика эта проверка просто необходима smile

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

local base = _G

module('Serializer')
-- модуль для сохранения таблицы в файл
-- могут быть сохранены переменные типа число, строка или булевский тип
-- таблица сохраняется в текущий поток вывода
-- если вы хотите, чтобы сохранение производилось в файл,
-- то поток вывода Lua должен быть предварительно настроен, например так:
-- открываем файл для записи данных
-- local f = io.open("data.lua", "w")
-- сохраняем предыдущий поток вывода
-- local prevOutput = io.output()
-- устанавливаем вывод стандартного потока в наш файл
-- io.output(f)
-- сериализация...
-- закрываем файл
-- f:close()
-- восстанавливаем предыдущий поток вывода
-- io.output(prevOutput)
-- ...

-- проверка, что переменная может быть сохранена как строка 
-- т.е это число, строка или булевский тип)
local function isValidType(valueType)
  return "number" == valueType or 
         "boolean" == valueType or 
         "string" == valueType
end

-- конвертация переменной в строку
local function valueToString (value)
    local valueType = base.type(value)
  
    if "number" == valueType or "boolean" == valueType then
        result = base.tostring(value)
    else  -- assume it is a string
    --result = base.string.format("%q", value)
        value = base.string.gsub( value, "\n", "\\n" )
        if base.string.match( base.string.gsub(value,"[^'\"]",""), '^"+$' ) then
            return "'" .. value .. "'"
        else
            return '"' .. base.string.gsub(value,'"', '\\"' ) .. '"'
        end
    end
    return result
end

function save (name, value, saved)
  saved = saved or {}       -- initial value
  base.io.write(name, " = ")
  local valueType = base.type(value)
  if isValidType(valueType) then
    base.io.write(valueToString(value), "\n")
  elseif "table" == valueType then
    if saved[value] then    -- value already saved?
      base.io.write(saved[value], "\n")  -- use its previous name
    else
      saved[value] = name   -- save name for next time
      base.io.write("{}\n")     -- create a new table
      for k,v in base.pairs(value) do      -- save its fields
        -- добавляем проверку ключа таблицы
        local keyType = base.type(k)
        if isValidType(keyType) then
          local fieldname = base.string.format("%s[%s]", name, valueToString(k))
          save(fieldname, v, saved)
        else
          base.error("cannot save a " .. keyType)
        end
      end
    end
  else
    base.error("cannot save a " .. valueType)
  end
end

обратите внимание на описание модуля

22

Re: Использование функции PrintDbgStr в QLua

Хороший пример но подобный код я буду использовать для конфига. Да и логирование это на сегодняшний день не самая актуальная тема, оно сейчас мне нужно в основном для отладки скрипта, да и то с появлением  PrintDbgStr не сильно актуально.
В будущем я обязательно присмотрюсь к СУБД но на сегодня вывод в текст самое то что нужно.
И кстати я практически везде где возможно избавился от циклов. Чтение первоисточников помогает.