Sunday 1 May 2011

Обмен данными и объектами между скриптами

Однажды на Сером форуме шло обсуждение как можно осуществить обмен данными между скриптами. Идея основана на создании нового экземпляра, наследуемого от интерфейса IWebBrowser2 (ссылка: IWebBrowser2 Interface). Объект хранит в себе данные даже после завершения всех скриптов, практически как буфер обмена, и может служить как средство обмена данными и объектами между скриптами.

Примечательно, что объект не удаляется из системы по завершении скрипта, создавшего его. Фактически, этот объект является новым окном Проводника с параметром Visible = false. В русской локализованной версии Windows объект определяется как Проводник Windows (проверялось на Windows 7 Ultimate x64), в нелокализованых версиях Windows - Microsoft Internet Explorer (Windows XP Service Pack 3) или Windows Explorer (Windows Vista Enterprise Service Pack 2):
var appWindow = GetObject("new:{C08AFD90-F2A1-11D1-8455-00A0C91F3880}");
WScript.Echo(appWindow);


Переменные примитивных типов хранятся достаточно долго и доступны все время, пока объект существует в системе. Сложные объекты (например, Object или Array) доступны между скриптами только пока скрипты не выгружены из памяти. То есть окно Проводника хранит ссылки на объекты. По завершении скрипта, сохранившего ссылку, объект становится недоступен, а обращение к ссылке вызывает исключение в скрипте, обратившемся к данным по этой ссылке. В целом это можно рассматривать как некий инструмент для передачи данных между скриптами, хотя и не обладающий достаточной надежностью.
Исходный код GlobalContainer.js с комментариями
/**
 * Конструктор объекта-обертки
 * Создает объект типа WebBrowser2 (невидимое окно Проводника)
 *
 * @param  String  Сигнатура для идентификации уникального окна
 * @return Object  GlobalContainer
 */
function GlobalContainer(signature)
{
    signature = signature || GlobalContainer.signature;

    this.appWindow = GlobalContainer.getInstance(signature);

    if ( ! this.appWindow ) {
        this.appWindow = GetObject('new:{C08AFD90-F2A1-11D1-8455-00A0C91F3880}');
        this.appWindow.StatusText = signature;
    }
};

/**
 * Константа, определяет сигнатуру по умолчанию
 */
GlobalContainer.signature = 'GlobalContainer';

/**
 * Ищет окно с заданной сигнатурой
 *
 * @param  String  Сигнатура для идентификации уникального окна
 * @return Object  WebBrowser2
 */
GlobalContainer.getInstance = function(signature)
{
    signature = signature || GlobalContainer.signature;
    var appList = (new ActiveXObject('Shell.Application')).Windows();
    var i = appList.Count;
    while ( i-- ) {
        var e;
        try {
            var app = appList.Item(i);
            if ( app.StatusText && app.StatusText.indexOf(signature) == 0 ) {
                return app;
            }
        } catch (e) {
        }
    }
};

/**
 * Возвращает данные по ключу, сохраненные в окне. 
 * Данные могут быть сохранены с помощью. 
 * GlobalContainer::putProperty()
 *
 * @param  String  Ключ
 * @return Mixed
 */
GlobalContainer.prototype.getProperty = function(name)
{
//    return this.appWindow.GetProperty(name);
    var e;
    try {
        return this.appWindow.GetProperty(name);
    } catch (e) {
    }
};

/**
 * Сохраняет данные с заданным ключом. 
 * Данные могут быть получены с помощью. 
 * GlobalContainer::putProperty()
 *
 * @param  String  Ключ
 * @param  Mixed   Данные
 * @return void
 */
GlobalContainer.prototype.setProperty = function(name, value)
{
//    return this.appWindow.PutProperty(name, value);
    var e;
    try {
        return this.appWindow.PutProperty(name, value);
    } catch (e) {
    }
};

/**
 * Закрывает существующее окно Проводника
 *
 * @param  void
 * @return void
 */
GlobalContainer.prototype.close = function()
{
//    return this.appWindow.Quit();
    var e;
    try {
        return this.appWindow.Quit();
    } catch (e) {
    }
};
Тестовые файлы
test.js:
// Этот фрагмент кода выполняется при запуске с ключом /init
if ( WScript.Arguments.Named.Exists('init') ) {

    var co = new GlobalContainer();
    co.setProperty('that', this);

    var flag = false;
    while ( ! flag ) {
        WScript.Sleep(10);
    }

    WScript.Echo("Эта строка выполняется не всегда!!!");
    co.close();

}
 
if ( WScript.Arguments.Named.Exists('soft') ) {

    var co = new GlobalContainer();
    var that = co.getProperty('that');

    if ( ! that ) {
        WScript.Echo("Связь не обнаружена.");
    } else {
        WScript.Echo("Обнаружена связь с внешним процессом.");
        var e;
        try {
            WScript.Echo("Попытаемся мягко завершить внешний процесс.");
            that.flag = true;
        } catch (e) {
            WScript.Echo("Попытка мягко завершить неудачна. Возможно процесс уже удален.");
        }
    }

    WScript.Echo("Закроем связного.");
    co.close();

}
 
if ( WScript.Arguments.Named.Exists('hard') ) {

    var co = new GlobalContainer();
    var that = co.getProperty('that');

    if ( ! that ) {
        WScript.Echo("Связь не обнаружена.");
    } else {
        WScript.Echo("Обнаружена связь с внешним процессом.");
        var e;
        try {
            WScript.Echo("Попытаемся жестко завершить внешний процесс.");
            that.WScript.Quit();
        } catch (e) {
            WScript.Echo("Попытка жестко завершить неудачна. Возможно процесс уже удален.");
        }
    }

    WScript.Echo("Закроем связного.");
    co.close();
 
}
test.wsf
<?xml version="1.0" encoding="utf-8" ?>
 
<package>
<job id="wscmd">
<?job error="true" debug="false" ?>
<script language="javascript" src="./GlobalContainer.js"></script>
<script language="javascript" src="./test.js"></script>
</job>
</package>
Тест 1. "Мягкое" закрытие первичного процесса
1. в отдельном окне выполнить:
cscript //nologo test.wsf /init
2. в другом окне выполнить
cscript //nologo test.wsf /soft
Тест 2. "Жесткое" закрытие первичного процесса
3. в отдельном окне выполнить
cscript //nologo test.wsf /init
4. в другом окне выполнить
cscript //nologo test.wsf /hard
Тесты 3 и 4. Эмуляция сбоя в работе первичного скрипта
Повторить пункты 1-4, но после пунктов 1 и 3 эмулировать аварийное завершение процесса по CTRL+C.

No comments:

Post a Comment