76

Re: WSH: обмен данными и объектами между скриптами — 2

Rumata пишет:

Пересечение имен объекта и метода вроде бы не критично.

Угу.

Речь именно про возможную путаницу.

77

Re: WSH: обмен данными и объектами между скриптами — 2

Хорошо. Думаю, что никто не будет против, если я внесу соответствующие правки в свой пост #73:

s/ConnectObject/GlobalConnect/g
( 2 * b ) || ! ( 2 * b )

78

Re: WSH: обмен данными и объектами между скриптами — 2

Rumata пишет:

Мне показалось, что GlobalObject это спорное название для объекта, так как во всех диалектах Javascript существует глобальный объект.

Отнюдь. GlobalObject — это теоретический объект, начало иерархии объектов JavaScript от Microsoft. В общем, это this в глобальном контексте.

79

Re: WSH: обмен данными и объектами между скриптами — 2

GlobalObject — это теоритический объект

Что значит "теоретический"? Есть спецификация языка ECMAScript. Javascript и JScript - соответствующие реализации. Глобальный объект реален, доступ к нему осуществляется посредством this.

Цитата из документа ECMA-262, стр. 2 (Также глобальный объект подробно описан в отдельном разделе спецификации)

ECMAScript defines a collection of built-in objects that round out the definition of ECMAScript entities. These built-in objects include the global object, ... (дальше идет перечисление всех встроенных объектов)

( 2 * b ) || ! ( 2 * b )

80

Re: WSH: обмен данными и объектами между скриптами — 2

А я в GlobalContainer переименовал. smile На мой взгляд как то логичнее звучит. В первом посте уже давно поправил. )

Передумал переделывать мир. Пашет и так, ну и ладно. Сделаю лучше свой !

81

Re: WSH: обмен данными и объектами между скриптами — 2

Хотя GlobalContainer тоже спорно (объект ничего не хранит, он только создает невидимое окно для связи). Наверно, логичнее было назвать GlobalConnector. Но я последовал Вашему примеру - идея, название. И добавил несколько вступительных слов.

( 2 * b ) || ! ( 2 * b )

82

Re: WSH: обмен данными и объектами между скриптами — 2

Как не хранит ? yikes Мы же через методы PutProperty и GetProperty передаём ему данные ? Передаём. Да, в случае объектов хранится только ссылка, но в случае строк и числовых данных, реально хранятся сами данные.

Передумал переделывать мир. Пашет и так, ну и ладно. Сделаю лучше свой !

83

Re: WSH: обмен данными и объектами между скриптами — 2

Rumata пишет:

Глобальный объект реален, доступ к нему осуществляется посредством this.

Опять же повторяю, что это теоретическое понятие. С точки зрения практики — это только this в глобальном контексте. И все, и никакого GlobalObject. Хотите оспорить: приведите код без this (и без window smile ).

84

Re: WSH: обмен данными и объектами между скриптами — 2

Поднимаю старую тему. Как разминка для ума.

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


:: простой вывод списка файлов
cscript FindFiles.vbs "*.vbs" "*.js"

:: перенумерация файлов и папок
cscript FindFiles.vbs "*.vbs" "*.js" /exec:vrenum.vbs <Опции для vrenum>

:: переименование файлов
cscript FindFiles.vbs "*.vbs" "*.js" /exec:vrenn.vbs <Опции для vrenn>
( 2 * b ) || ! ( 2 * b )

85

Re: WSH: обмен данными и объектами между скриптами — 2

Rumata, задача тривиальна. Может стоит направить усилия на создание потоков?

86

Re: WSH: обмен данными и объектами между скриптами — 2

JSman, согласен, что задача тривиальна. К сожалению, не вижу путей оптимальной реализации потоков средствами WSH. Предложенная идея - всего лишь иллюстрация применения данной идеи.

( 2 * b ) || ! ( 2 * b )

87

Re: WSH: обмен данными и объектами между скриптами — 2

Всем доброго времени суток. Внёс изменения в код и исправил ошибки (см. первый пост). Экземпляры объекта ShellBrowserWindow оставались в памяти после выгрузки класса.

Передумал переделывать мир. Пашет и так, ну и ладно. Сделаю лучше свой !

88

Re: WSH: обмен данными и объектами между скриптами — 2

Доброго времени суток. Опробовал скрипты из 1 поста, все замечательно. Запустил чуть позднее еще раз - ошибка! Как выяснилось, "Ошибка: Неопознанная ошибка Код: 80004005 Источник: (null)" возникает в 1 скрипте в строке 16 при вызове метода wnd.StatusText. Ошибка начала появляться после запуска моего скрипта, в котором создается объект

set ie = wscript.createobject("internetexplorer.application")

Создание в моем скрипте окна ie для вывода данных сопровождается появлением (по крайней мере на моей машине) в диспетчере 2 процессов iexplore.exe, например:
CommandLine: "C:\Program Files (x86)\Internet Explorer\iexplore.exe" SCODEF:8432 CREDAT:145409
CommandLine: "C:\Program Files (x86)\Internet Explorer\iexplore.exe" -Embedding
После завершения этих процессов оба скрипта из 1 поста вновь начинают работать без ошибок.
Windows 7 Домашняя Базовая, Service Pack 1, x64;
IE 9.0.8112.16421, Выпуски обновления 9.0.12 (KB2761465).
Результат не меняется в зависимости от запуска скриптов в \SysWOW64\WScript.exe, либо \System32\WScript.exe.

Можно ли как-то обойти данную ошибку?

Щт Уккщк Куыгьу Туче

89

Re: WSH: обмен данными и объектами между скриптами — 2

omegastripes
Попробуйте выгружать объект и процесс в Вашем скрипте:
ie.ExecWB 45, 2 : ie.Quit
Я на Win7 этим всегда пользуюсь, когда отправляю что-то в буфер.

90

Re: WSH: обмен данными и объектами между скриптами — 2

Всем спасибо за ответы.

Flasher, я не уточнил в предыдущем посте, что цели выгрузить ie не преследуется, напротив, действующий ie не должен вызывать ошибку в GlobalContainer.

Xameleon, для конкретики приведу такой скрипт:

0.vbs

set logger = new logwindow
do
    logger.write now
    wscript.sleep 1000
loop

class logwindow
    
    private ie
    
    private sub class_initialize()
        set ie = wscript.createobject("internetexplorer.application", "ie_")
        with ie
            .menubar = false
            .toolbar = false
            .resizable = true
            .statusbar = false
            .addressbar = false
            .visible = true
            .navigate "about:blank"
        end with
    end sub
    
    public sub write(text)
        ie.document.write(text & "<br>")
    end sub
    
    private sub class_terminate()
        on error resume next
        ie.quit
    end sub
    
end class

sub ie_onquit
    wscript.quit
end sub

После запуска появится окно ie, в который скрипт будет что-то писать. Если после этого запустить 1.vbs из первого поста - вылетает ошибка, которую я описывал выше.

Все заработало после замены метода Open класса GlobalContainer в скриптах из первого поста на одноименный из GlobalContainer.wsc Вашего примера! По всей видимости ошибку выдавал .StatusText окна ie. Предлагаю обновить GlobalContainer в первом посте.

Щт Уккщк Куыгьу Туче

91

Re: WSH: обмен данными и объектами между скриптами — 2

2 omegastripes: Да. Видимо проблема была именно в StatusText. Ок. Попробую заменить в первом посте. ) Забавно. Я думал, что тема не просуществует больше недели.

Передумал переделывать мир. Пашет и так, ну и ладно. Сделаю лучше свой !

92

Re: WSH: обмен данными и объектами между скриптами — 2

Xameleon пишет:

Надеюсь, остальные участники тоже покреативят. )

Доброго всем времени суток. Ранее уже поднимался вопрос о многопоточности JS/VBS, готовых решений я не нашел, поэтому предлагаю костыль, реализующий механизм многопоточности (а точнее «многопроцессности») для WSH VBScript. Не рекомендуется лицам с аллергией на индокод. Подробнее:

+ открыть спойлер

Особенности:
• Запущенный скрипт выполняет служебную функцию. Основной код выполняется в последующих процессах.
• В «полной версии» основной код размещается только в классах, экземпляр любого из которых можно создать в отдельном процессе. В каждом новом процессе скрипт создает экземпляр служебного класса, который создает экземпляр указанного целевого класса, вызывает целевой метод, контролирует его ход, создает в глобальном контексте скриптов переменные, ссылающиеся на созданные экземпляры классов. В предшествующий скрипт возвращается объект инициализированного целевого класса, и «обрабатываются» события инициализации и завершения целевого класса запущенного скрипта.
• В «легкой версии» логика аналогична, только основной код размещается внутри sub’ов, предшествующему скрипту возвращается не объект, а индекс запущенного, и нет обработки событий.
• Для создания нового процесса скрипт рекурсивно запускает самого себя, передав данные о вызываемом методе и идентифицирующую информацию в именованном аргументе.
• Единого пространства исполнения нет, каждый скрипт хранит свои данные и объекты в своем контексте. Для обмена данными объект Me первого скрипта передается в последующие с применением GlobalContainer, описанным в этой теме. Каждый скрипт после завершения целевого метода ожидает разрешения на завершение, такая задержка позволяет забрать из него полученную информацию.
• Функционирует одинаково в WSH WScript и CScript, и в виде скомпилированного в ScriptCryptor exe-файла (правда, в exe не работают методы class_terminate).

Минусы:
• Сложность в отладке из-за использования во многих методах on error resume next и execute. Все ж таки это костыль.
• Громоздкость.
• Порядок завершения скриптов полностью в ответственности разработчика. Естественно, при обращении к объектам завершенного скрипта возникнет ошибка.

Актуально:
• Не реализованы мьютексы и механизмы залочивания метода от использования более чем одним процессом.
• Возможны сбои при одновременном доступе к переменным скрипта из контекстов других.
• Окно проводника остается в памяти при закрытии окна консоли CScript.exe.

Первая мысль о потенциальных удобствах многопоточности пришла однажды во время решения на добровольных началах относительно несложной задачи по обработке и визуализации информации. И, поскольку установка IDE навроде Visual Studio и наш Helpdesk - понятия несовместимые, из «легальных» оставались только VBA и WSH. В то время был выбран последний. А действующие административные ограничения и желание получить дружественные методы продиктовали требования к данной реализации многопроцессности:
• Код расположен в одном файле, как обычный сценарий.
• Использует минимум ActiveX, и только встроенных, работоспособен без WMI, под WSH x86/x64.
• Достаточная функциональность и удобство важнее быстродействия и паттернов.

Легкая версия mproclite.vbs:

option explicit
launch "base"

' main programm section

sub base()
    startproc "msg"
    startproc "msg"
    startproc "msg"
    msgbox "base, id = " & id, 64
    free id
end sub

sub msg()
    msgbox "msg, id = " & id, 64
    free id
end sub

' do not modify service section

sub launch(byval destination)
    dim job
    executeglobal "dim scene, container, signature, subname, jobs, id, state, release"
    release = false
    if not wscript.arguments.named.exists("task") then
        dim elt
        executeglobal "dim found, lost"
        id = 0
        found = 0
        lost = 0
        signature = ""
        randomize
        do
            signature = signature & hex(rnd * 16)
        loop while len(signature) < 16
        set scene = me
        set jobs = createobject("Scripting.Dictionary")
        set jobs(0) = scene
        set container = getobject("new:{C08AFD90-F2A1-11D1-8455-00A0C91F3880}")
        container.putproperty signature, scene
        startproc destination
        on error resume next
        do until (lost >= found) or release
            for elt = found to 1 step -1
                if typename(jobs(elt)) = "Object" then
                    lost = lost + 1
                    jobs(elt) = empty
                end if
                err.clear
                wscript.sleep 1
            next
        loop
        release = true
        executeglobal "scene_beforeterminate"
        for elt = found to 1 step -1
            if typename(jobs(elt)) = "VBScriptTypeInfo" then
                jobs(elt).wscript.timeout = 1
                jobs(elt).wscript.quit
                err.clear
                nojobs = false
            end if
            wscript.sleep 1
        next
        container.quit
    else
        job = split(wscript.arguments.named("task"), ";")
        signature = cstr(job(0))
        id = clng(job(1))
        subname = cstr(job(2))
        do
            for each container in createobject("Shell.Application").windows
                if isobject(container.getproperty(signature)) then
                    exit do
                end if
            next
            wscript.sleep 1
        loop
        set scene = container.getproperty(signature)
        set jobs = scene.jobs
        state = 4
        set jobs(id) = me
        executeglobal subname
        state = 24
        do until release
            wscript.sleep 10
        loop
        state = 28
    end if
end sub

function startproc(subname)
    startproc = createproc(subname)
    joint startproc, 4, 0
    REM do while getstate(startproc) < 4
        REM wscript.sleep 10
    REM loop
end function

function createproc(subname)
    if me is scene then
        if not release then
            found = found + 1
            createproc = found
            set jobs(createproc) = nothing
            createobject("WScript.Shell").exec("""" & wscript.fullname & """ """ & wscript.scriptfullname & """ ""/task:" & join(array(signature, createproc, subname), ";") & """")
        end if
    else
        createproc = scene.createproc(subname)
    end if
end function

function getjob(target)
    on error resume next
    if jobs.exists(target) then
        set getjob = jobs(target)
        if err.number = 0 then exit function
        err.clear
    end if
    set getjob = nothing
end function

sub share(varname, value)
    scene.newvar varname
    if isobject(value) then
        execute "set scene." & varname & " = value"
    else
        execute "scene." & varname & " = value"
    end if
end sub

sub newvar(varname)
    executecommand "dim " & varname
end sub

sub executecommand(command)
    executeglobal command
end sub

function getstate(target)
    dim elt
    if jobs.exists(target) then
        on error resume next
        set elt = jobs(target)
        getstate = elt.state
        if err.number <> 0 then
            if not(elt is nothing) then
                getstate = 64
            else
                getstate = 1
            end if
        end if
        set elt = nothing
    else
        getstate = 64
    end if
end function

function isresponsive(target)
    isresponsive = cbool(getstate(target) and 28)
end function

sub free(target)
    if jobs.exists(target) then
        on error resume next
        jobs(target).release = true
    else
        dim elt, subname
        for elt = scene.found to 1 step -1
            on error resume next
            subname = jobs(elt).subname
            if subname = target then
                free jobs(elt).id
            end if
            err.clear
        next
    end if
end sub

function joint(target, state, timeout)
    dim reftime
    reftime = timer
    on error resume next
    if jobs.exists(target) then
        if isnumeric(target) then
            do while getstate(target) < state
                if timeisout(timeout, reftime) then
                    joint = false
                    exit function
                end if
                wscript.sleep 10
            loop
        else
            dim elt, subname
            for elt = scene.found to 1 step -1
                subname = jobs(elt).subname
                err.clear
                if subname = target then
                    do while getstate(target) < state
                        if timeisout(timeout, reftime) then
                            joint = false
                            exit function
                        end if
                        wscript.sleep 10
                    loop
                end if
                err.clear
            next
        end if
    end if
    joint = true
end function

function timeisout(timeout, reftime)
    if timeout > 0 then
        dim delta
        delta = timer - reftime
        if delta < 0 then delta = delta + 86400
        if delta > timeout then
            timeisout = true
        end if
    else
        timeisout = false
    end if
end function

sub interrupt(target, timeout)
    if jobs.exists(target) then
        on error resume next
        jobs(target).wscript.timeout = timeout
        jobs(target).wscript.quit
    else
        dim elt, subname
        for elt = scene.found to 1 step -1
            on error resume next
            subname = jobs(elt).subname
            if subname = target then
                interrupt jobs(elt).id
            end if
            err.clear
        next
    end if
end sub

sub push(name, value)
    container.putproperty name, value
end sub

function pop(name)
    on error resume next
    if isobject(container.getproperty(name)) then
        set pop = container.getproperty(name)
    else
        pop = container.getproperty(name)
    end if
end function

Описание:

+ открыть спойлер

Функции механизма многопроцессности легкой версии:

launch subname
Запускает служебный алгоритм, выполнение скрипта всегда начинается с его вызова.
subname – строка, указывает целевой sub, который будет выполнен в новом процессе.
Аргументы этого метода используются только в первично запущенном скрипте для начала выполнения основного кода.
В каждом новом скрипте:

+ открыть спойлер

Объявляются переменные в глобальном пространстве запущенного скрипта:
scene – ссылка на объект Me первого скрипта,
container – ссылка на окно проводника GlobalContainer,
signature – строка, сгенерирована для идентификации GlobalContainer,
found, lost – только для scene – счетчики созданных и завершенных процессов скриптов,
subname – строка, содержит цель скрипта,
jobs – ссылка на словарь с объектами Me запущенных скриптов, созданный в scene,
id – порядковый номер данного скрипта - ключ в словаре,
release = Ложь - после завершения целевого sub’а скрипт будет завершен после присвоения Истина,
state = 4 – состояние, в котором находится данный скрипт.

Добавляется элемент в словарь jobs.

startproc(subname)
subname – см. launch().
Создает новый процесс скрипта, ожидает его готовности до состояния 4 (см. getstate()), когда можно обращаться к объекту Me скрипта. Возвращает число - id запущенного скрипта.

createproc(subname)
subname – см. launch().
Создает новый процесс скрипта, не ожидая, возвращает число - id запущенного скрипта. Используется для асинхронного создания нескольких процессов в цикле, без ожидания готовности каждого. Заметно быстрее по сравнению с использованием startproc() для такого применения.

getjob(target)
target – число, id скрипта или строка, имя subname группы созданных скриптов.
Обеспечивает доступ к объекту Me скрипта. Возвращает ссылку на объект Me скрипта, если id не найден или скрипт завершен – Nothing.

getstate(target)
target – число, id скрипта.
Определяет состояние скрипта. Возвращает число, этап выполнения:
1 процесс создан (new process exec),
4 скрипт запущен (initialized),
24 целевой sub выполнен (sub completed),
28 скрипт освобожден (released),
64 не найден (host not found), скрипт завершен (terminated).

isresponsive(target)
target - см. getstate().
Определяет доступность объекта Me скрипта (состояния с 4 по 28). Возвращает булево значение.

executecommand command
command - строка, содержащая инструкции.
Вызов интерпретатора для выполнения операторов в глобальном пространстве скрипта.

share varname, value
varname - строка, содержащая имя переменной, value – любое значение.
Объявляет в глобальном пространстве первого скрипта переменную с именем varname, которая становится доступна всем скриптам в виде свойства scene, присваивает переменной содержимое value.

newvar varname
varname - строка, содержащая имя переменной.
Объявляет новую переменную в глобальном пространстве скрипта.

free target
target – число, id скрипта или строка, имя subname группы созданных скриптов. Разрешает завершение скрипта после выполнения целевого sub’а. Работает с одним скриптом или с группой.

joint(target, state, timeout)
target - см. free(), state - см. getstate(), timeout – число, в секундах, с миллисекундами.
Ожидает наступления состояния скрипта state, для группы скриптов ожидание длится, пока каждый  не достигнет state. Ожидание ограничено таймаутом, timeout = 0 означает неограниченное ожидание. Возвращает булево значение, Истина – ожидание закончено, Ложь – таймаут. Предназначен для синхронизации работы скриптов. Например, если необходимо дождаться запуска скрипта - 4, полного завершения скрипта - 64;

interrupt target, timeout
target - см. free(), timeout – значение для wscript.timeout, в секундах.
Переводит скрипт к штатному завершению, с выполнением методов class_terminate. Если в скрипте были открыты диалоговые окна, он перейдет к завершению только после паузы timeout. Повторное появление диалогового окна в методах class_terminate остановит завершение.

push name, value
name – строка, имя свойства, value – любое значение.
Помещает в свойство GlobalContainer с именем name содержимое value.

pop(name)
name – строка, имя свойства.
Возвращает из GlobalContainer содержимое свойства с именем name.

Полная версия mproc.vbs:

option explicit
dim mproc
set mproc = new multiprocess
mproc.launch "base", "run", ""

' main programm section

class base
    
    public sub run()
        host.startproc "msg", "run", "first"
        host.startproc "msg", "run", "second"
        host.startproc "msg", "run", "third"
        msgbox "base, id = " & host.id, 64
        host.free host.id
    end sub
    
end class

class msg
    
    public sub run()
        msgbox host.aliasname & ", id = " & host.id, 64
        host.free host.id
    end sub
    
end class

' do not modify service class section

class multiprocess
    
    public primary, ancestor, parent, process, err
    public names, execs, hosts
    public id, aid, isprimary
    public classname, methodname, aliasname
    public found, lost, active
    public state, permit, release
    private container, signature, wshshell
    
    public sub launch(startclassname, startmethodname, startaliasname)
        permit = false
        release = false
        executeglobal "dim scene, host, ancestor, process"
        if not isempty(host) then exit sub
        set host = me
        executeglobal "set host.err = err"
        executeglobal "function getroot: set getroot = me: end function"
        set parent = getroot
        isprimary = not wscript.arguments.named.exists("task")
        if isprimary then
            dim sample
            state = 24
            randomize
            signature = ""
            do
                signature = signature & hex(rnd * 16)
            loop while len(signature) < 16
            aid = empty
            id = 0
            found = 0
            lost = 0
            set wshshell = createobject("WScript.Shell")
            set primary = host
            set ancestor = nothing
            set process = nothing
            set scene = parent
            set parent.ancestor = nothing
            set parent.process = nothing
            set hosts = createobject("Scripting.Dictionary")
            set execs = createobject("Scripting.Dictionary")
            set names = createobject("Scripting.Dictionary")
            classname = empty
            methodname = empty
            aliasname = empty
            set hosts(0) = host
            set container = getobject("new:{C08AFD90-F2A1-11D1-8455-00A0C91F3880}")
            container.putproperty signature, parent
            startproc startclassname, startmethodname, startaliasname
            on error resume next
            do
                for each sample in execs.keys
                    if release or active = 0 then exit do
                    if not (execs(sample) is nothing) then
                        if execs(sample).status > 0 then
                            abolish sample
                        end if
                    end if
                    wscript.sleep 1
                next
            loop
            release = true
            state = 28
            scenequit
        else
            dim job
            job = split(wscript.arguments.named("task"), ";")
            signature = cstr(job(0))
            do
                for each container in createobject("Shell.Application").windows
                    if isobject(container.getproperty(signature)) then
                        exit do
                    end if
                next
                wscript.sleep 1
            loop
            aid = clng(job(1))
            id = clng(job(2))
            found = null
            lost = null
            set scene = container.getproperty(signature)
            set primary = scene.host
            set hosts = primary.hosts
            set ancestor = hosts(aid)
            if isresponsive(aid) then
                set parent.ancestor = ancestor.parent.process
            else
                set parent.ancestor = nothing
            end if
            classname = cstr(job(3))
            methodname = cstr(job(4))
            aliasname = cstr(job(5))
            state = 4
            primary.implicate id, aliasname, host
            executeglobal "set process = new " & classname
            executeglobal "set host.process = process"
            executeglobal "set scene." & aliasname & " = process"
            if isresponsive(aid) then
                executeglobal "set host.ancestor.parent." & aliasname & " = process"
            end if
            state = 8
            primary.staff host
            ancestorevent "oninitialized"
            state = 12
            if methodname <> "" then
                do until permit
                    wscript.sleep 10
                loop
                state = 16
                executeglobal "process." & methodname
            end if
            state = 20
            ancestorevent "oncompleted"
            state = 24
            do until release
                wscript.sleep 10
            loop
            state = 28
        end if
    end sub
    
    public default function startproc(classname, methodname, aliasname)
        set startproc = start(createproc(classname, methodname, aliasname))
    end function
    
    public function createproc(classname, methodname, aliasname)
        if aliasname = "" then aliasname = classname
        newvar aliasname
        scene.host.newvar aliasname
        createproc = primary.spawn(id, classname, methodname, aliasname)
    end function
    
    public function spawn(issuer, classname, methodname, aliasname)
        if not release then
            found = found + 1
            spawn = found
            active = found - lost
            names(spawn) = aliasname
            set hosts(spawn) = nothing
            if not hosts.exists(aliasname) then
                hosts.add aliasname, createobject("Scripting.Dictionary")
            end if
            set hosts(aliasname)(spawn) = nothing
            execs.add spawn, wshshell.exec("""" & wscript.fullname & """ """ & wscript.scriptfullname & """ ""/task:" & join(array(signature, issuer, spawn, classname, methodname, aliasname), ";") & """")
        end if
    end function
    
    public function start(target)
        select case outline(target)
        case "Nothing", "multiprocess"
            do while getstate(target) < 12
                wscript.sleep 10
            loop
            if isresponsive(target) then
                set start = hosts(target).process
                hosts(target).permit = true
            else
                set start = nothing
            end if
        case "Dictionary"
            dim elt
            set start = hosts(target)
            for each elt in start.keys
                do while getstate(elt) < 12
                    wscript.sleep 10
                loop
            next
            for each elt in start.keys
                if isresponsive(elt) then
                    hosts(elt).permit = true
                end if
            next
        case else
            set start = nothing
        end select
    end function
    
    public sub implicate(id, aliasname, host)
        set hosts(aliasname)(id) = host
        set hosts(id) = host
    end sub
    
    public sub staff(host)
        set hosts(host.process) = host
    end sub
    
    public sub abolish(id)
        if hosts.exists(names(id)) then
            hosts(names(id))(id) = empty
        end if
        names(id) = empty
        if isresponsive(id) then
            hosts(hosts(id).process) = empty
        end if
        hosts(id) = empty
        set execs(id) = nothing
        lost = lost + 1
        active = found - lost
    end sub
    
    private sub ancestorevent(eventname)
        if aid > 0 then
            on error resume next
            executeglobal "ancestor." & aliasname & "_" & eventname & " host.hosts(" & id & ")"
            if err.number = 424 or err.number = 438 then err.clear
        end if
    end sub
    
    public sub assignhandler(handlername, byval varsqty)
        dim vars
        vars = ""
        if varsqty > 0 then
            do
                vars = vars & "param" & varsqty
                varsqty = varsqty - 1
                if varsqty = 0 then exit do
                vars = vars & ", "
            loop
        end if
        executeglobal "sub " & handlername & "(" & vars & "): process." & handlername & " " & vars & ": end sub"
    end sub
    
    public sub newvar(varname)
        executecommand "dim " & varname
    end sub
    
    public sub executecommand(command)
        executeglobal command
    end sub
    
    public function getstate(target)
        select case outline(target)
        case "multiprocess"
            on error resume next
            getstate = hosts(target).state
            if err.number <> 0 then
                err.clear
                getstate = 64
            end if
        case "Nothing"
            getstate = 1
        case "Dictionary"
            getstate = null
        case empty
            getstate = 0
        case else
            getstate = 64
        end select
    end function
    
    private function outline(target)
        on error resume next
        if hosts.exists(target) then
            outline = typename(hosts(target))
            if err.number <> 0 then
                err.clear
                outline = "Object"
            end if
        else
            outline = empty
        end if
    end function
    
    public function isresponsive(target)
        isresponsive = cbool(getstate(target) and 28)
    end function
    
    public function getid(target)
        on error resume next
        if isobject(target) then
            if isresponsive(target) then
                getid = hosts(target).id
                if err.number = 0 then exit function
                err.clear
            end if
        elseif primary.execs.exists(target) then
            getid = target
            exit function
        end if
        getid = null
    end function
    
    public function gethost(target)
        on error resume next
        if hosts.exists(target) then
            set gethost = hosts(target)
            if err.number = 0 then exit function
            err.clear
        end if
        set gethost = nothing
    end function
    
    public sub free(target)
        select case outline(target)
        case "multiprocess"
            on error resume next
            gethost(target).release = true
            err.clear
        case "Dictionary"
            dim elt
            for each elt in gethost(target)
                free(elt)
            next
        end select
    end sub
    
    public function joint(target, state, timeout)
        dim reftime
        reftime = timer
        select case outline(target)
        case "multiprocess", "Nothing"
            do while getstate(target) < state
                if timeisout(timeout, reftime) then
                    joint = false
                    exit function
                end if
                wscript.sleep 10
            loop
        case "Dictionary"
            dim elt
            for each elt in gethost(target)
                do while getstate(elt) < state
                    if timeisout(timeout, reftime) then
                        joint = false
                        exit function
                    end if
                    wscript.sleep 10
                loop
            next
        end select
        joint = true
    end function
    
    private function timeisout(timeout, reftime)
        if timeout > 0 then
            dim delta
            delta = timer - reftime
            if delta < 0 then delta = delta + 86400
            if delta > timeout then
                timeisout = true
            end if
        else
            timeisout = false
        end if
    end function
    
    public sub interrupt(target, timeout)
        select case outline(target)
        case "multiprocess"
            on error resume next
            with gethost(target).parent
                .wscript.timeout = timeout
                .wscript.quit
            end with
            err.clear
        case "Dictionary"
            dim elt
            for each elt in gethost(target)
                interrupt elt, timeout
            next
        end select
    end sub
    
    public sub kickout(target)
        if primary.execs.exists(target) then
            if getstate(target) < 64 then
                on error resume next
                primary.execs(target).terminate
                err.clear
            end if
        else
            select case outline(target)
            case "multiprocess"
                kickout getid(target)
            case "Dictionary"
                dim elt
                for each elt in gethost(target)
                    kickout(elt)
                next
            end select
        end if
    end sub
    
    public sub terminate(target)
        interrupt target, 1
        if not joint(target, 64, 2) then kickout target
    end sub
    
    public sub push(name, value)
        container.putproperty name, value
    end sub
    
    public function pop(name)
        on error resume next
        if isobject(container.getproperty(name)) then
            set pop = container.getproperty(name)
        else
            pop = container.getproperty(name)
        end if
    end function
    
    private sub scenequit
        if isprimary then
            dim col, i, status
            col = execs.keys
            for i = ubound(col) to 0 step -1
                interrupt col(i), 1
            next
            wscript.sleep 2000
            on error resume next
            for i = ubound(col) to 0 step -1
                status = execs(col(i)).status
                if err.number = 0 and status = 0 then execs(col(i)).terminate
                err.clear
            next
            container.quit
        end if
    end sub
    
    private sub class_terminate()
        if state < 28 and isprimary then scenequit
    end sub
    
end class

Описание:

+ открыть спойлер

Методы класса multiprocess:

launch classname, methodname, aliasname
Запускает служебный алгоритм, выполнение скрипта всегда начинается с его вызова.
classname, methodname, aliasname – строки. Указывают целевой класс classname и метод этого класса methodname, который будет выполнен в новом процессе, экземпляр класса classname будет помещен в переменную с именем aliasname (или classname, если aliasname – пустая строка).
Аргументы этого метода используются только в первично запущенном скрипте для начала выполнения основного кода.
В каждом новом скрипте:

+ открыть спойлер

Объявляются переменные в глобальном пространстве запущенного скрипта:
scene – ссылка на объект Me первого скрипта,
host – экземпляр класса multiprocess данного скрипта,
process – экземпляр целевого класса с именем classname, которое было указано предшествующим скриптом при создании процесса данного скрипта,
ancestor – экземпляр целевого класса предшествующего скрипта.

Объявляются переменные в глобальном пространстве scene и предшествующего скрипта, с именем aliasname – ссылки на экземпляр целевого класса данного скрипта.

Задаются свойства host:
primary – ссылка на экземпляр класса multiprocess первого скрипта,
ancestor – ссылка на экземпляр класса multiprocess предшествующего скрипта,
parent – объект Me данного скрипта,
process – ссылка на process в глобальном пространстве,
err – ссылка на объект err данного скрипта,
names – ссылка на словарь с aliasname, созданный в primary,
execs – ссылка на словарь с объектами wshexec запущенных процессов, созданный в primary,
hosts – ссылка на словарь с объектами host запущенных процессов, созданный primary,
id – порядковый номер данного скрипта - ключ в словарях,
aid – то же, предшествующего скрипта,
isprimary – булево, является ли данный скрипт первично запущенным,
found, lost, active – только для primary – счетчики созданных, завершенных и активных процессов скриптов,
classname, methodname, aliasname – содержат цель скрипта,
permit = Ложь - целевой метод будет запущен после присвоения Истина,
release = Ложь - после завершения целевого метода скрипт будет завершен после присвоения Истина,
state = 4 – состояние, в котором находится данный скрипт.

Добавляются элементы в словари names, execs, hosts.

startproc(classname, methodname, aliasname)
classname, methodname, aliasname – см. launch().
Создает новый процесс скрипта, ожидает его готовности до состояния 12 (см. getstate()), запускает целевой метод. Возвращает ссылку на инициализированный в новом процессе экземпляр целевого класса.

createproc(classname, methodname, aliasname)
classname, methodname, aliasname – см. launch().
Создает новый процесс скрипта, не ожидая, возвращает его id. Используется для асинхронного создания нескольких процессов в цикле, без ожидания готовности каждого. Заметно быстрее по сравнению с использованием startproc() для такого применения.

start(target)
target – число, id скрипта, или строка, имя aliasname группы созданных скриптов.
Ожидает готовности скрипта, созданного с использованием createproc(), до состояния 12, разрешает выполнение целевого метода. Возможно использование для группы скриптов, имеющих одинаковый aliasname. Для одного скрипта возвращает ссылку на его инициализированный в новом процессе экземпляр целевого класса, для группы скриптов возвращает ссылку на субсловарь, содержащий все host с данными aliasname.

gethost(target)
target – число, id скрипта или строка, имя aliasname группы созданных скриптов, или объект process скрипта.
Обеспечивает доступ к экземпляру host класса multiprocess требуемого скрипта. Для одного скрипта возвращает ссылку на его host, для группы скриптов возвращает ссылку на субсловарь, содержащий все host с данными aliasname, если id не найден или скрипт завершен – Nothing.

getid(target)
target – число, id скрипта, или объект process скрипта.
Возвращает id срипта, определенное по объекту process. Только для действующих скриптов.

getstate(target)
target - см. getid().
Определяет состояние скрипта. Возвращает число, этап выполнения:
0 не найден (host not found),
1 процесс создан (new process exec),
4 host инициализирован (host initialized),
8 целевой класс инициализирован (process initialized),
12 целевой класс инициализирован, событие обработано (process initialized handled),
16 целевой метод запущен (process method launched),
20 целевой метод выполнен (process completed),
24 целевой метод выполнен, событие обработано (process completed handled),
28 скрипт освобожден (released),
64 скрипт завершен (terminated).

isresponsive(target)
target - см. getid().
Определяет доступность объекта host скрипта (состояния с 4 по 28). Возвращает булево значение.

assignhandler handlername, varsqty
handlername - строка, имя события, varsqty - число, количество передаваемых аргументов.
Создает в глобальном пространстве хэндлер события sub с именем события handlername, связывет его с одноименным методом в созданном объекте process. При наступлении события, хэндлер перенаправит вызов в process. handlername().

executecommand command
command - строка, содержащая инструкции.
Вызов интерпретатора для выполнения операторов в глобальном пространстве скрипта.

newvar varname
varname - строка, содержащая имя переменной.
Объявляет новую переменную в глобальном пространстве скрипта.

free target
target - см. gethost().
Разрешает завершение скрипта после выполнения целевого метода. Работает с одним скриптом или с группой.

joint(target, state, timeout)
target - см. gethost(), state - см. getstate(), timeout – число, в секундах, с миллисекундами.
Ожидает наступления состояния скрипта state, для группы скриптов ожидание длится, пока каждый  не достигнет state. Ожидание ограничено таймаутом, timeout = 0 означает неограниченное ожидание. Возвращает булево значение, Истина – ожидание закончено, Ложь – таймаут. Предназначен для синхронизации работы скриптов. Например, если необходимо дождаться создания объекта process - 8, полного завершения скрипта - 64;

interrupt target, timeout
target - см. gethost(), timeout – значение для wscript.timeout, в секундах.
Переводит скрипт к штатному завершению, с выполнением методов class_terminate. Если в скрипте были открыты диалоговые окна, он перейдет к завершению только после паузы timeout. Повторное появление диалогового окна в методах class_terminate остановит завершение.

kickout target
target - см. gethost().
Завершает процесс скрипта на уровне ОС, используя wshexec.terminate. Возможно длительное выполнение, до 2 сек для каждого скрипта. Работает с одним скриптом или с группой.

terminate target
target - см. gethost().
Завершает скрипт, использует сначала interrupt, затем при необходимости kickout.

push name, value
name – строка, имя свойства, value – любое значение.
Помещает в свойство GlobalContainer с именем name содержимое value.

pop(name)
name – строка, имя свойства.
Возвращает из GlobalContainer содержимое свойства с именем name.

Методы, которые можно разместить в целевом классе данного скрипта в качестве хэндлеров событий инициализации целевого класса и выполнения целевого метода запущенного скрипта:

<aliasname>_ oninitialized(source)
source – передаваемый в метод объект host скрипта, вызвавшего метод, его aliasname содержится в имени метода. Метод вызывается после инициализации целевого класса запущенного скрипта (state = 8).

<aliasname>_ oncompleted(source)
source - передаваемый в метод объект host скрипта, вызвавшего метод, его aliasname содержится в имени метода. Метод вызывается после выполнения целевого метода запущенного скрипта (state = 20).

В прикрепленных к посту скриптах демонстрируется работа на примере абстрактной задачи: для каждого символа из строки letters создаются отдельные процессы, каждый в цикле помещает свой символ в буфер, по мере заполнения которого другой процесс выводит по 3 "слова" в консоль. Для WScript консоль сымитирована окном IE. Попутно выводятся запущенные и остановленные процессы ОС (в примере используется WMI, но для функционирования механизма он не обязателен).
В ходе дебага у меня накопились некоторые наблюдения и комменты, кои я выложу, если будет спрос - дело в том, что потребуется время, чтобы привести их в читабельный вид. Конструктивная критика приветствуется.

Post's attachments

mproclite_letters.zip 6.78 kb, 7 downloads since 2013-08-10 

You don't have the permssions to download the attachments of this post.
Щт Уккщк Куыгьу Туче

93

Re: WSH: обмен данными и объектами между скриптами — 2

omegastripes
Объясните значение термина "индокод"?

( 2 * b ) || ! ( 2 * b )

94

Re: WSH: обмен данными и объектами между скриптами — 2

Видимо, это ирония на тему Индусский код smile

WBR. Roman

95

Re: WSH: обмен данными и объектами между скриптами — 2

2 Rumata
Да, Rom5 абсолютно прав. После дебага, когда практически все было готово, я полистал скрипт - местами отсвечивал индусский код. Например, вот строчка:

executeglobal "sub " & handlername & "(" & vars & "): process." & handlername & " " & vars & ": end sub"

Но, поскольку скрипт выполняет свою функцию достаточно стабильно, решил опубликовать.

Щт Уккщк Куыгьу Туче

96 (изменено: teadrinker, 2017-03-03 19:25:06)

Re: WSH: обмен данными и объектами между скриптами — 2

Xameleon, а как второй скрипт узнает, что какое-то user-defined свойство объекта изменилось и нужно считывать значение? Нужно подключиться к DWebBrowserEvents2. На AHK реализуется через ComObjConnect():

#Persistent
#SingleInstance, Off
DetectHiddenWindows, On
param = %1%

myContainer := new GlobalContainer("storage")

if param  {  ; это будет происходить во втором скрипте
   myContainer.Connect(Func("WatchProperties"))
}
else  {   ; это будет происходить в главном скрипте
   Run, "%A_AhkPath%" "%A_ScriptFullPath%" "1",,, PID
   OnExit(Func("Exit").Bind(myContainer, PID))
   
   WinWait, ahk_pid %PID%
   myContainer.arr1 := {key1: "Первое значение"}
   myContainer.arr2 := {key2: "Второе значение"}
   ExitApp
}

WatchProperties(obj, prop)  {
   key := prop = "arr1" ? "key1" : "key2"
   MsgBox,, Сообщение от второго скрипта:, % obj[prop][key]
}

Exit(obj, PID)  {
   obj.Quit()
   WinClose, ahk_pid %PID%
}

class GlobalContainer  {
   __New(name)  {
      for wnd in ComObjCreate("Shell.Application").Windows  {
         if (wnd.GetProperty("container_name") = name)
            this._obj := wnd
      }
      if !this._obj  {
         this._obj := ComObjGet("new:{C08AFD90-F2A1-11D1-8455-00A0C91F3880}")
         this._obj.PutProperty("container_name", name)
      }
   }
   
   __Set(prop, value)  {
      if prop not in _obj,_userFunc
         Return this._obj.PutProperty(prop, value)
   }
   
   __Get(prop)  {
      if (prop != "_obj")
         Return this._obj.GetProperty(prop)
   }
   
   Connect(userFunc)  {
      this._userFunc := userFunc.Bind(this)
      ComObjConnect(this._obj, this)
   }
   
   PropertyChange(prop, obj)  {
      this._userFunc.Call(prop)
   }
   
   Quit()  {
      this._obj.Quit()
   }
}

Интересно, что пока обработка события во втором скрипте не завершена, поток первого не продолжается. smile

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Skype dmitry_fiveg

97 (изменено: Xameleon, 2017-03-03 19:59:01)

Re: WSH: обмен данными и объектами между скриптами — 2

teadrinker, неужели эта тема ещё актуальна ? @_@. Не ожидал. ) Ну что ж, хорошо, что развитие не заброшено.

Передумал переделывать мир. Пашет и так, ну и ладно. Сделаю лучше свой !

98

Re: WSH: обмен данными и объектами между скриптами — 2

А её я только заметил. smile

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Skype dmitry_fiveg