Как быть, если нет COM-модели

27 7

В IS-Builder существует несколько способов расширения возможностей языка

1) Использование COM-объектов

2) Вызов внешних программ через командную строку

3) Вызов функций и процедур, определённых по стандартам PE (к примеру, знакомые, думаю, всем программистам функции WinAPI)

Первые 2 варианта, думаю, всем знакомы и очень часто встречаются в программном коде, и достаточно описаны. Создаются и вызываются через стандартные функции CreateObject и ExecuteProcess. А вот насчет третьего мало где есть упоминания о том, как его использовать. Думаю, было бы полезно узнать как вызвать функции WinAPI к примеру для программного закрытия открытого на текущий момент окна из ISBL. Вызов функций из внешних библиотек производится при помощи функции CallProcedure.

Поискав по DIRECTUM Club, я нашёл как минимум 3 статьи, в которых используется этот способ, но нигде конкретно как его использовать, описано не было. В руководстве разработчика о нём упоминаний не встречается. Единственное более достойное описание приводится в справке к функции.

CallProcedure(DLL: String; Function: String; DataTypes: String; Arguments: Variant)

Я не буду сейчас рассказывать про форматы значений, которые используются в функции. Это вы сможете сделать лучше меня, прочитав справку к функции. Но чтобы прояснить, как все-таки её использовать рассмотрим пример. Один из участников форума задавал вопрос, как обеспечить запуск сценария на одном компьютере единовременно, то есть, чтобы сценарий нельзя было запустить ещё раз, пока он не завершит свою работу. Как более эффективный метод на обозрение было предложено использовать мьютексы. Для сведения мьютексы - это  это один из вариантов семафорных механизмов для организации взаимного исключения, которые хранятся в памяти операционной системы и содержат только одну информацию, заблокирован он или нет. “Великий гугл”, конечно же сразу даст алгоритм использования мьютексов, но скорее всего на каком-нибудь высокоуровневом языке, как c++. Возьмём его как за основу.

Для работы с мьютексами нам понадобятся 4 функции:

  • CreateMutex - для создания мьютекса.
  • WaitForSingleObject - для блокировки мьютекса.
  • ReleaseMutex - для освобождения мьютекса.
  • CloseHandle - для освобождения памяти под мьютекс.

Все эти функции можно успешно найти  на MSDN. Получив необходимое описание можно приступить уже к описанию кода.

Каждая из этих функций располагаются в библиотеке kernel32.dll (смотрите в конце описания каждой из функций) поэтому смело указываем его в качестве первого параметра. Второй параметр это имя функции. Также берём его из справки по конкретной функции.

Далее согласно справке необходимо описать тип результата, а также каждого аргумента функции. Данный этап может вызвать ступор у начинающих разработчиков. Для тех кто более или менее знаком с С/C++ это не составит особых проблем. Рассмотрим функцию CreateMutex. На вход подаются 3 аргумента с типами LPSECURITY_ATTRIBUTES, BOOL, LPCTSTR, а на выходе мы получаем HANDLE. Первый аргумент, если внимательно посмотреть, являются указателем, о чем свидетельствует префикс LP (Long Pointer). Не вдаваясь в подробности архитектуры ОС, в Windows эти указатели имеют размер 4 байта. По справке такой тип соответствует значению "E". Тип второго аргумента, думаю, понятно без слов, соответствует "A". Третий параметр хоть и является указателем, но представляет собой строку. Строковому формату соответствует значение "D". Ну и результат типа HANDLE (Если опять углубиться внутрь описания формата, то это по факту тот же Integer) имеет размер 4 байта, соответствует значению "E". Итого третий аргумент для функции CallProcedure будет "EEAD".

Далее мы указываем просто список аргументов через ";". Для первого аргумента функции CreateMutex мы ничего указать не сможем, поэтому ставим 0. Наверно, это покажется странным, почему нужно писать 0, а не NULL или NIL. NULL или NIL имеет совершенно другой тип, отличный от того, что мы указали. А мы указали тип E, то есть integer. Почему именно 0 - в windows такой стандарт, что NULL имеет значение 0. Вторым параметром по описанию функции мы запишем TRUE (при создании мьютексом будет владеть наш сценарий). 3 параметром указываем имя мьютекса.

Если вы написали все это в таком виде, то, скорее всего, вы получите ошибку о том, что функции CreateMutex нет в данной библиотеке. И это чистая правда. На самом деле в windows с самых ранних версий заведено содержать в библиотеке 2 вида функций Ansi и Unicode(WideChar). Справка MSDN описывает конкретно функции из C++, где разработчики заботливо позаботились о других разработчиках и написали один алиас для этих двух функций. А сами функции на самом деле имеют имена CreateMutexA и CreateMutexW. В IS-Builder используется кодировка Ansi поэтому нам необходима функция CreateMutexA. Чтобы не допускать такие ошибки, нужно обращать внимание на аргументы функции. Явным признаком, что нужно дописывать постфикс A, является наличие строкового параметра в аргументах. То есть, к примеру, функция ReleaseMutex в неизменённом виде, так как не имеет строковых параметров. Такое правило распространяется в основном на функции WinAPI, но может встречаться и в других библиотеках.

Итого: чтобы вызвать функцию CreateMutex в коде мы должны написать:

MutexHandle = CallProcedure("Kernel32.dll"; "CreateMutexA"; "EEAD"; 0; TRUE; "TestScriptMutex")

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

  MutexHandle = CallProcedure("Kernel32.dll"; "CreateMutexA"; "EEAD"; 0; TRUE; "TestScriptMutex")
  Result = CallProcedure("Kernel32.dll"; "WaitForSingleObject"; "EEE"; MutexHandle; 0)
  if Result == 0
    try
        Sleep(30) // Уж очень сложный алгоритм, который выполняется много много лет.
    finally
        CallProcedure("Kernel32.dll"; "ReleaseMutex"; "EE"; MutexHandle)
    endfinally
  else
    ShowMessage("Дождитесь окончания работы сценария.")
  endif
  CallProcedure("Kernel32.dll"; "CloseHandle"; "EE"; MutexHandle)

В ответ на повторный запуск, сценарий выдаст нам нужное окошко и завершит работу.

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

Во-первых, если есть возможность указать значение аргумента-указателя, это не значит, что вы сможете поставить туда конкретное значение. Чаще всего указатель указывает на структуру, которую вы вряд ли сможете создать без использования внешних средств.

Во-вторых, часто встречаются такие функции, которые имеют out-аргументы (это те аргументы, значение которых меняется, в контексте функции так и места вызова данной функции). Стоит понимать, что ISBL не может работать с out параметрами.

В-третьих, некоторые функции на вход требуют указания CALLBACK функции (наша функция обработчик для некоторых ситуаций, вызываемая из другой функции, в которую мы её передали). Их вы также не сможете указать.

Все же применяя этот функционал можно значительно расширить функционал ISBL, хотя бы, например, для организации собственных блокировок, без использования каких-то дополнительных ресурсов.

27
Авторизуйтесь, чтобы оценить материал.
Денис Баранов

Никита, я правильно понимаю, что если сценарий в своей работе вызовет исключение, или sbrte вообще вылетит по каким-то причинам, то mutex не освободится и сценарий запустить будет нельзя до перезагрузки компа?

Никита Данилов
Никита, я правильно понимаю, что если сценарий в своей работе вызовет исключение, или sbrte вообще вылетит по каким-то причинам, то mutex не освободится и сценарий запустить будет нельзя до перезагрузки компа?

Мьютекс не освободится, но тут есть одно но. Мьютекс привязан к дескриптору процесса. Т.е по завершению работы sbrte в контексте которого был запущен сценарий, ОС может сама уничтожить мьютекс. Чаще всего сценарии запускаются через вариант запуска, и как правило в отдельном процессе, поэтому проблем возникнуть не должно.

Степан Мурашов
Для сведения мьютексы - это такие семафоры

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

2Денис: Думаю ты верно указал проблему приведенного примера кода, и очень желательно закрытие мьютекса заключить в блок защиты ресурсов (try-finally).

А за статью однозначно плюс.

Николай Шестаков

Отличная статья. Так держать. Все, кратко и доходчиво.

Александр Солонтой

Кто сталкивался, можно ли как то использовать функции из DLL написаной на C#?

Алексей Пестов
Стоит понимать, что ISBL не может работать с out параметрами.

Процитирую справку:

Значение выходного параметра доступно после выполнения метода. Для его получения в параметре ErrorMessage следует передавать указатель на строковую переменную. В вычислениях ISBL для этого нужно:

- инициализировать переменную строковым значением;

- в вызове метода после имени переменной указать символ «^».

Это не оно?

Михаил Извеков

Оно, но работает только со строками.

Авторизуйтесь, чтобы написать комментарий