Тема: Вызов функций Win 32 API и пример «асинхронного» программирования в TDMS

$matches[1]

Аннотация
   Для использования функций Win 32 API и функций других библиотек в скриптах для TDMS предлагается решение с помощью ActiveX компонента (СОМ-сервера) dynwrapx.dll. Данное решение позволило создать интерактивный html-виджет управляемый TDMS.

TDMS и dynwrapx.dll. Win 32 API в действии
   Библиотека dynwrapx.dll является результатом труда программиста Юрия Попова. Она написана на языке ассемблера GoAsm и распространяется бесплатно. Основное предназначение - вызов функций Win 32 API в WHS скриптах.
   Перед началом использования данный компонент необходимо скопировать на жесткий диск и зарегистрировать в системе.
   Зарегистрировать компонент можно двумя способами:

regsvr32.exe <путь-к-компоненту>\dynwrapx.dll - для всех пользователей.
regsvr32.exe /i <путь-к-компоненту>\dynwrapx.dll - для текущего пользователя (этот вид регистрации позволяет зарегистрировать компонент без наличия прав администратора).

   Скачать библиотеку dynwrapx.dll сфайлом справки можно  по адресу http://www.script-coding.com/dynwrapx1_00.zip  (версия 1.0.0.0 от 05.09.2008 г., архив 14 191 байт). В архиве находится сама библиотека и html-файл справки с подробным описанием библиотеки и примерами её использования.
   Объект библиотеки создаётся следующим образом:

Set Wrap = CreateObject("DynamicWrapper")

   Объявление функции производится так:

Wrap.Register "USER32.DLL", "FindWindowExA", "i=llsl", "f=s", "r=l"

   Параметры при объявлении содержат имя библиотеки, имя функции, а также:
   

  • i - описывает количество и тип данных параметров функции. Если функция не принимает параметры, этот параметр можно опустить. Параметр i является строкой, количество букв в которой равно количеству параметров объявляемой функции. Первая буква задаёт тип первого параметра, вторая - втрого и т.д.

  • f - тип вызова: _stdcall, _cdecl и т.д. Значение по умолчанию - _stdcall.

  • r - тип возвращаемых данных.

   В примере используется тип данных Long, обозначается “l” (“эль”).
   Передавать и принимать можно не только параметры, но и указатели на строки. В примере используется указатель “p” - в этом случае строка является просто буфером в памяти для хранения любых данных.
   Компонент dynwrapx.dll содержит несколько дополнительных методов для работы с числами, строками и памятью. В примере будет использоваться:
   

  • Space( Count [, Char] ) – метод для создания строки (BSTR) заданной длины. Возвращает строковую переменную. Count - число символов (двухбайтных). Char - необязательный параметр: символ, которым будет заполнена строка. По умолчанию строка заполняется пробелами.

Пример использования библиотеки dynwrapx.dll для создания функции аналогичной DoEvents() в VB.
   Создаваемая нами функция позволит в момент выполнения скрипта передать управление операционной системе, для обработки других событий. И после обработки системой всех событий из очереди возвращает управление обратно скрипту.
   Данную функцию на языке VB можно описать с помощью стандартных функций Win 32 API:

Private Type POINTAPI
        x As Long
        y As Long
End Type

Private Type MSG
    hwnd As Long
    message As Long
    wParam As Long
    lParam As Long
    time As Long
    pt As POINTAPI
End Type

Private Declare Function PeekMessage Lib "user32" Alias "PeekMessageA" _
(lpMsg As MSG, ByVal hwnd As Long, ByVal wMsgFilterMin As Long, _
ByVal wMsgFilterMax As Long, ByVal wRemoveMsg As Long) As Long
Private Declare Function TranslateMessage Lib "user32" _
(lpMsg As MSG) As Long
Private Declare Function DispatchMessage Lib "user32" Alias "DispatchMessageA" _
(lpMsg As MSG) As Long

Private Const PM_REMOVE = &H1

Private Sub DoEvents()
    Dim CurrMsg As MSG

    Do While PeekMessage(CurrMsg, 0, 0, 0, PM_REMOVE) <> 0
        TranslateMessage CurrMsg
        DispatchMessage CurrMsg
    Loop
End Sub

   С помощью компонента dynwrapx.dll преобразуем данный код для использования функции в TDMS.
   Для начала зарегистрируем компонент и объявим функции:

Set Wrap = CreateObject("DynamicWrapperX")
    Wrap.Register "user32.dll", "PeekMessageA", "i=pllll", "r=l"
    Wrap.Register "user32.dll", "TranslateMessage", "i=p", "r=l"
    Wrap.Register "user32.dll", "DispatchMessage", "i=p", "r=l"

   Как вызывать функции с параметрами мы уже знаем, но в данном случае используется такой составной тип, как структура, например MSG. Заменим эту структуру на указатель «p». Он будет ссылаться на строку, содержащую 7 DWord (количество типов в MSG включая два типа в POINTAPI). Зарезервируем под эту строку 28 2-х байтных символа:
   
   

Msg = Wrap.Space(28)

   Окончательный код функции примет вид:

Function DoEvents()
  PM_REMOVE = &H1
  
  Set Wrap = CreateObject("DynamicWrapperX")
    Wrap.Register "user32.dll", "PeekMessageA", "i=pllll", "r=l"
    Wrap.Register "user32.dll", "TranslateMessage", "i=p", "r=l"
    Wrap.Register "user32.dll", "DispatchMessage", "i=p", "r=l"
    Msg = Wrap.Space(28)
      
    Do While Wrap.PeekMessageA(Msg, 0, 0, 0, PM_REMOVE) <> 0
          Wrap.TranslateMessage(Msg)
          Wrap.DispatchMessage(Msg)
          Loop
End Function

Пример использования функции DoEvents для обработки нажатия кнопок на web-форме в TDMS.
   Рассмотрим пример создания информационного виджета написанного на html+css+js и взаимодействующего с TDMS.
   Для начала необходимо создать форму в TDMS и разместить на ней кнопку и объект ActiveX Microsoft Web Browser:

$matches[1]

   Заранее созданную html-страницу назовём index.html и поместим на диск С.
   Теперь, мы хотим, чтобы наша форма отображала необходимую html страничку при открытии TDMS. Для этого напишем обработку события Form_BeforeShow():

Sub Form_BeforeShow(Form, Obj)
  Set WB = Form.Controls("ACTIVEX1").ActiveX
  WB.Navigate "C:\index.html"
End Sub

   Добавим эту форму в профиль, как панель главного окна. Перезагрузим TDMS и получим на главном окне наш «виджет». Кнопки html-страницы будут реагировать на указатель мыши, но они всё ещё не взаимодействуют с TDMS.
   Иногда, панель на главном окне может не отображаться, даже если она помечена для отображения. В этом случае необходимо щелкнуть правой кнопкой мыши на стандартной панели TDMS, снять флажок напротив названия нашей формы и установить его обратно. После этих действий форма должна отобразиться.

$matches[1]

   
   Хотелось бы уточнить, что в коде html-страницы уже прописана обработка нажатия кнопок. При нажатии одной из кнопок происходит передача индекса кнопки в скрытое текстовое поле на странице. Таким образом, следующей нашей задачей будет постоянное наблюдение за текстовым полем html-страницы и при появлении индекса кнопки выполнять соответствующие команды в TDMS.

   Для реализации постоянного наблюдения за полем воспользуемся ранее написанной функцией “DoEvents()” в паре с функцией “WaitMessage()” библиотеки “user32.dll”. Поместим функцию “DoEvents()” в отдельную команду “CMD_DoEvents” для удобства вызова из разных скриптов. Теперь напишем обработку события нажатия кнопки формы “WEB”:

Sub BUTTON1_OnClick()
  Set Form = ThisForm
  'получим доступ к окну браузера и html-документу
  Set WB = Form.Controls("ACTIVEX1").ActiveX
  Set doc = WB.document
  'объявим функцию WaitMessage()
  Set DX = CreateObject("DynamicWrapperX")
  DX.Register "user32.dll", "WaitMessage", "r=l"
      'Очищаем поле html-страницы
       a = doc.all("pole").setAttribute("value", "")
          'Создаем постоянный цикл
          Do While Form.Controls("ACTIVEX1").Visible = True
              DX.WaitMessage
              DoEvents
                  'Цикл с условием на значение поля
                  Do While doc.all("pole").getAttribute("value") = ""
                      DX.WaitMessage' управление у системы
                      DoEvents'и скрипт не мешает работе с TDMS
                  Loop
                  'если появилось значение
                  b = doc.all("pole").getAttribute("value")
                      
                  Select Case b
                     Case 1
                        Set FiltMsgs = ThisApplication.Messages
                        With FiltMsgs
                           .Filter.TimeFrom = "12.02.2013"
                           .Filter.On = True 'включить фильтр сообщений
                        End With
                        MsgBox "Всего " & FiltMsgs.Count & " писем."
                     Case 2
                          MsgBox "На сегодня контактов нет."
                  End Select
        'очищаем поле html-страницы и продолжаем
               'за ним наблюдать
               a = doc.all("pole").setAttribute("value", "")
          Loop
End Sub

   Сохраним скрипт. Нажмем на панели инструментов TDMS “Вид-Обновить все” и получим полноценно рабочий виджет взаимодействующий с TDMS.
   Готовый пример находится в архиве. Импортируйте в любую надстройку TDMS файл “html-виджет.tds”. Разместите файл “index.html” в корень диска С, зарегистрируйте библиотеку dynwrapx.dll. В TDMS добавьте форму в профиль, как панель главного окна. Перезагрузите TDMS.

Выводы.
   Библиотека dynwrapx.dll - небольшой ActiveX компонент, который может быть использован в TDMS. Библиотека позволяет вызывать произвольные функции из DLL-библиотек (например, win32 функции API) расширяя функциональные возможности системы.
   С помощью данной библиотеки удалось портировать стандартную для VB функцию DoEvents на VBScript. Эта функция частично позволила применить в разработке надстроек TDMS  принципы асинхронного программирования. В частности создать интерактивный html-виджет. Использование таких виджетов вносит разнообразие в строгий интерфейс системы TDMS, а также расширяет его функционал.

Ссылки на статьи.
  Для более подробной информации по библиотеке dynwrapx.dll рекомендую прочитать справку в архиве, а также следующие статьи:
   

Ссылка на пример