oldi

Особенности технологий раннего и позднего связывания в Visual Basic

Андрей Колесов

Принципиальные отличия

Как работает механизм связывания в VB

Зачем нужно позднее связывание

Связывание с внешними объектами

Используйте раннее связывание, где это возможно

Экспериментируйте

 

В классических процедурных языках (например, в DOS’овских версиях MS Basic) основополагающим принципом было использование технологии раннего связывания. А позднее связывание впервые было реализовано в системах-интерпретаторах, так что парадокс, возможно, заключается в том, что примитивный язык для начинающих под названием «Бейсик» стал прообразом «крутых» ООП-систем.

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

Принципиальные отличия

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

  1. С точки зрения программирования связывание — это процедура установки связи между идентификатором, используемым в коде программы, и его физическим объектом (в общем случае — любым программным компонентом: переменной, процедурой, модулем, приложением и т.д.).
  2. Раннее связывание — установка таких связей до начала выполнения программы. Обычно под этим понимается связывание в процессе компиляции исходных модулей и компоновки исполняемого модуля из объектных. Однако сюда же относится процедура проверки наличия всех библиотек времени выполнения (Run-Time module) при запуске приложения.
  3. Позднее связывание — установка связей в процессе выполнения программы. Речь обычно идет либо о динамических связях (когда только в ходе работы приложения определяется, какие объекты будут нужны), либо о формировании таких объектов во время работы.

Примечание. Многие VB-программисты вообще не очень хорошо представляют себе, что процедура формирования исполняемого модуля (или запуска программы в среде VB) состоит из компиляции отдельных модулей и последующей их компоновки в загрузочный. Дело в том, что VB не позволяет подключать внешние объектные модули, поэтому компания Microsoft решила не детализировать этот процесс, назвав его компиляцией. Отметим, что это не характеристика языка Basic, а исключительно желание Microsoft. Например, во времена MS Basic/DOS отдельно были компилятор и компоновщик, которые можно было использовать автономно, вне среды разработки.

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

Sub Main()
  Dim MyVal%
  MyVal = 13 
  If MyVal Mod 5 = 0 Then	‘ если нацело делится на 5
    Call MyProc  	        ‘ то обращение к функции
  End If
End Sub

Sub MyProc()
  Call HisProc          	‘ обращение к какой-то процедуре
End Sub

При работе, например с QuickBasic, еще в момент запуска программы в среде интерпретатора сразу будет выдано сообщение об ошибке: «Не определена процедура HisProc». Дело в том, что в QB стал одним из первых массовых инструментов разработки, в котором был реализован принцип компилирующего интерпретатора.

Напомним, что классическая схема интерпретатора предполагает, что проверка синтаксиса оператора — в том числе разрешенность ссылок (фактического наличия указанной ссылки: переменной, процедуры и пр.) — осуществляется только в момент его выполнения. То есть только в момент обращения к данному оператору производятся синтаксический разбор текстовой записи оператора и его исполнение.

Например, если вы напишете на GWBasic (мне с трудом удалось найти на сохранившемся дистрибутиве MS-DOS 4.0) такой код:

100 GOTO 200
110 a$ = MID$(“asdf”,1,1,1,1)
200 PRINT “Привет”

то ошибка в операторе 110 (явно неверная запись оператора) появится, только если мы уберем оператор 100.

Компилирующий интерпретатор QB при отладке в среде сначала проводил полный синтаксический контроль кода всей программы, включая связывание всех идентификаторов и преобразование ее текстового кода во внутренний p-Code. И только потом уже выполнял этот внутренний код в режиме интерпретации. Кроме транслятора, QB имел настоящий компилятор, создавший объектные двоичные модули, которые потом объединялись компоновщиком в программы на машинном коде.

Таким образом, в GWBasic был реализован механизм позднего связывания (на этапе выполнения программы), а в QB — раннего (на этапе компиляции). Преимущества последнего не требуют особого объяснения — масса ошибок программиста автоматически вылавливалась транслятором. Не говоря уже о том, что при использовании позднего связывания многие ошибки очень сложно выловить с помощью тестовых запусков, так как нет гарантий, что тест пройдет через абсолютно все операторы приложения.

При работе в VB и VBA реализованы два режима компиляции кода при работе в среде, которые определяются состоянием флажка Compile On Demand на вкладке General диалогового окна Tools|Options.

  1. Если флажок установлен (этот режим определяется по умолчанию при инсталляции VB или Office 2000), то компиляция исходного кода будет выполняться только в момент его выполнения. В этом случае при запуске приведенного выше примера в среде VB сообщение об ошибке появится только в случае, если d будет нацело делиться на 5 (например, MyVal = 15). То есть проверка разрешенности ссылки на HisProc будет выполняться только в момент выполнения процедуры MyProc, что является явным признаком механизма позднего связывания.
  2. Если флажок сброшен, то сначала будет выполняться полная компиляция всей программы. Сообщение об ошибке появится сразу же при запуске программы на выполнение.

Преимущество первого варианта — более высокая скорость запуска программы, что особенно важно при отладке отдельных фрагментов большого проекта. Кроме того, вы можете сосредоточиться на конкретном коде и пока не отвлекаться на ошибки в других частях приложения. Не говоря уже о том, что в случае динамического подключения VBA-проектов такой режим поможет обращаться к внешним процедурам (см. статью «Программное взаимодействие проектов Office 2000» в этом номере журнала).

Преимущества второго варианта также очевидны — более высокое качество отладки и надежная работа программы. В связи с этим можно сформулировать два совета.

Совет для VBA-разработчиков. При отладке проекта работайте только со вторым вариантом режима компиляции кода. Это поможет вам избежать множества проблем, которые могут быть автоматически выявлены транслятором. Если вас волнует проблема скорости запуска программы (большой проект), то можно установить Compile On Demand, но на завершающем этапе отладки все же сбросьте его. После завершения отладки (в том числе в режиме опытной эксплуатации) можно установить первый режим — запуск программы будет выполняться быстрее.

Совет для VB-разработчиков. Он формулируется не столь категорично. Установка режима Compile On Demand происходит без особых проблем, так как при создании EXE-модуля все равно будет выполнена полная компиляция проекта. Тем не менее здесь можно дать два «подсовета».

  1. При работе с небольшими проектами сбросьте Compile On Demand.
  2. Если время запуска программы в среде является критичным, то можно установить Compile On Demand. Однако время от времени для тестового запуска проекта в среде VB используйте вместо команды Run|Start (F5 или соответствующей кнопки на панели инструментов) команду Run|Start With Full Compile (Ctrl+F5), которая производит обязательную компиляцию всего проекта, независимо от установки Compile On Demand.
В начало

В начало

Как работает механизм связывания в VB

Но все же механизм позднего связывания VB в режиме Compile On Demand несколько отличается от «классического» варианта, реализованного в GWBasic. Дело в том, что VB в этом случае выполняет синтаксический контроль не пооператорно, а попроцедурно, то есть производится трансляция всего кода процедуры в момент обращения к ней.

Чтобы убедиться в этом, напишите для процедуры MyProc такой код:

Sub MyProc()
  Dim a$
  Exit Sub                  ‘  выход из процедуры
  	                            ‘ эти операторы содержат ошибки:
  D = 1                      	‘ переменная не определена
  A$ = Mid$(“asdr”,1,1,1,1)  	‘ неверный синтаксис функции Mid
End Sub

При обращении к этой процедуре тут же будут выданы ошибки в последних двух строках, хотя реально эти операторы никогда не будут выполняться.

Здесь мне хотелось бы сделать замечание относительно того, что, несмотря на многие достоинства интеллектуального редактора VB/VBE, по некоторым функциям (очень важным для разработчика) он заметно уступает тому, что было реализовано Microsoft почти полтора десятка лет назад в QB.

Например, редактор VB совершенно спокойно реагирует на ввод такой явно ошибочной строки

A$= Mid$(“asdr”,1,1,1,1) ‘ неверный синтаксис функции     Mid

QB выдал бы сообщение об ошибке синтаксиса непосредственно при вводе кода.

Еще один пример подобного спокойствия VB:

A$ = TextBox.test

Здесь очевидно, что программист ошибся, введя «test» вместо «text». VB отлично знает, что у текстового поля нет свойства test (он привел список допустимых свойств в своей подсказке), но при этом ничего не сообщает разработчику, ограничившись тем, что не «поднял» первую букву идентификатора.

 

Совет: используйте в идентификаторах переменных и процедур прописные буквы и вводите код только с использованием строчных букв нижнего регистра клавиатуры. Тогда вы по реакции редактора (он должен «поднять» нужные символы) сразу увидите — определен идентификатор или нет (поворчим немного: хороший редактор мог бы сообщить об этом в явном виде).

В начало

В начало

Зачем нужно позднее связывание

Исходя из приведенных выше примеров может создаться впечатление, что использование позднего связывания выливается исключительно в головную боль для программиста. Зачем же тогда оно нужно?

Простой пример использования этого механизма — использование внешних DLL и, в частности, функций Win API.

Здесь проверка существования нужного файла и заданной процедуры выполняется только в момент обращения к этой процедуре (проверка соответствия описания и обращения к процедуре выполняется на этапе компиляции). Но данный пример работы VB как раз не в его пользу: конечно же, было бы лучше предусмотреть вариант, чтобы такая проверка выполнялась на этапе запуска (именно запуска, а не компиляции) приложения.

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

Dim MyObject As Object
…
‘ тут объект должен быть создан, например
If SomeVar = 0 Then 	‘ объект = ссылка
  Set MyObject = ActiveDocument.VBProject.References.Item(1) 
Else
  Set MyObject =  ActiveDocument.VBProject.  ‘ объект = проект активного документа
End If
…
a$ = MyObject.FullPath 	‘ это будет работать, только если SomeVar = 0

С точки зрения традиционного компилятора с механизмом раннего связывания в последнем операторе имеется явная ошибка, так как непонятно, что за объект будет реально создан и будет ли он обладать свойством FullPath (это станет известно только в момент выполнения программы). «Хороший» компилятор вполне может отследить тип присваиваемого объекта при линейном алгоритме, но в случае нашего ветвления он будет так же бессилен.

Вот еще один пример на ту же тему. Очевидно, что такая процедура

Sub MyProc(cntControl As Control)
    MsgBox cntControl.Text
End Sub

в зависимости от передаваемого в нее конкретного элемента управления будет либо работать (Text), либо не работать (Label).

Рассмотрим еще одну любопытную ситуацию на примере связи двух VBA-проектов. Если мы хотим из нашего активного проекта обратиться к процедуре OtherProcedure документа OtherDoc.doc, то сначала можно сделать ссылку на этот документ в окне Tools|References и написать такой простой код:

Call OtherProcedure

(Мы считаем, что имя процедуры является уникальным, поэтому не указываем полный путь к ней — Call OtherDoc.OtherModule.OtherProcedure.)

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

Sub MyProcedure
  ‘ установка ссылки программным образом:
  ActiveDocument.VBProject.References.AddFromFile “OtherDoc.doc”
  ‘ обращение к процедуре:
  Call OtherProcedure
End If

Но такой код не будет работать! Дело в том, что, как только процедура MyProcedure получит управление, будет выполнена компиляция кода, в ходе которой обнаружится, что идентификатор OtherProcedure не определен. Решение этой проблемы выглядит следующим образом:

Sub MyProcedure
  ‘ установка ссылки программным образом:
  ActiveDocument.VBProject.References.AddFromFile “OtherDoc.doc”
  ‘ обращение к промежуточной процедуре:
  Call MyOtherProcedure
End If

Sub MyOtherProcedure
  ‘ к этому моменту имя OtherProcedure будет уже определено
  Call OtherProcedure
End If

Но такая конструкция будет работать только в режиме Compile On Demand. Радикальное же решение проблемы заключается в использовании в данном случае свойства Run для обращения к процедурам подключаемых проектов (подробнее см. статью «Программное взаимодействие проектов Office 2000»).

В начало

В начало

Связывание с внешними объектами

Достаточно типичной задачей является использование в приложении неких внешних ActiveX-объектов (приложений или объектов). Например, вы хотите обратиться к приложению Word. В этом случае можно выбрать один из вариантов — с ранним или поздним связыванием:

Sub EarlyBinding()
  ‘ Пример раннего связывания
  ‘ с внешним объектом Word
  Dim oWord As New Word.Application  ‘создаем конкретный объект
  ‘ Можно создать объект таким образом:
  ‘ Dim oWord As Word.Application  ‘ описываем конкретный
  ‘ Set oWord = New Word.Application  ‘ создаем новый экземпляр
  
  oWord.Visible = True
     MsgBox “Раннее связывание”
  oWord.Quit
  Set oWord = Nothing
  
End Sub

Sub LateBinding()
  ‘ Пример позднего связывания
  ‘ с внешним объектом Word
  Dim oWord As Object  ‘ неопределенный объект
  
  Set oWord = CreateObject(“Word.Application”)
  oWord.Visible = True
    MsgBox “Позднее связывание”
  oWord.Quit
  Set oWord = Nothing
  
End Sub

Достоинство раннего связывания: работают подсказки и синтаксический контроль при создании исполняемого модуля. Но этот вариант (точнее, вся программа, независимо от того, было или не было обращение к EarlyBinding) будет работать только в случае наличия ссылки (Project|References) на реально существующее приложение. При этом имеется два варианта создания объекта:

Dim oWord As New Word.Application ‘создаем конкретный     объект

и

Dim oWord As Word.Application ‘ описываем конкретный
Set oWord = New Word.Application ‘ создаем новый экземпляр

Второй вариант обычно используется, когда нужно создавать глобальный объект для всего модуля или приложения.

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

В случае позднего связывания можно было бы динамически менять имя открываемого объекта, например, таким образом:

Sub LateBinding(AppName$)
  ‘ Пример позднего связывания
  ‘ с внешним объектом AppName$
  Dim oApp As Object  ‘ неопределенный объект
  
  Set oApp = CreateObject(AppName$)
  oApp.Visible = True
  oApp.Quit
  Set oApp = Nothing
  
End Sub

Разумеется, тут нужно быть уверенным, что написанные методы действительно могут работать с объектом. Но в данном простом примере можно применять практически любое приложение, использующее ActiveX-интерфейс.

В начало

В начало

Используйте раннее связывание, где это возможно

Одна из проблем использования технологии позднего связывания заключается в том, что в этом случае отключается предварительный синтаксический контроль кода, в том числе отсутствуют интеллектуальные подсказки при вводе сложных выражений. То есть программисту приходится отказываться от очень полезной функции интеллектуального редактора. (Несмотря на отмеченные выше «потери» качества, современная среда разработки, конечно же, получила очень много полезных для разработчика функций по сравнению с инструментами десятилетней давности.)

Например, вам нужно распечатать некоторые свойства проектов и библиотек, на которые имеет ссылки ваш активный документ (об этом говорится в статье «Программное взаимодействие проектов Office 2000»). Для этого можно написать следующий код:

Dim ActiveRef As Object
For Each ActiveRef In ActiveDocument.VBProject.References
    ‘ имя проекта или библиотеки
  Debug.Print “Имя проекта = “ & ActiveRef.name   
    ‘ полное имя файла 
  Debug.Print “Полное имя файла = “ & ActiveRef.fullpath
Next

Недостаток этой конструкции, с точки зрения разработчика, очевиден: в операторе For Each выполняет динамическое определение объекта ActiveRef в качестве ссылки. Поэтому редактор ничего не знает о том, как будет произведена эта установка, и в последующих операторах Print не может показать список допустимых свойств для объекта ActiveRef (именно это мы и хотели подчеркнуть, написав свойства Name и FullPath строчными буквами — редактор также «не поднимет» буквы в этих именах).

Оригинальное решение этой проблемы приводит Владимир Биллиг в своей статье «Документы Office 2000 и их проекты» (www.microsoft.ru/offext/officedev/articles/articles.htm). Он предлагает в процессе ввода кода описать ActiveRef в виде конкретного объекта, в данном случае как Dim ActiveRef As Reference. При этом, как утверждается, будет работать синтаксис-подсказка. Однако при запуске кода на выполнение такое определение объекта окажется недействительным, поэтому нужно будет написать определение Dim ActiveRef As Object. (То есть «As Reference» используется только для ввода кода, а затем для отладки и выполнения эта строка меняется на «As Object».)

К сожалению, мои попытки воспользоваться этим советом не увенчались успехом. Возможно, причина заключается в использовании разных релизов продукта или в каких-то тонких настройках. Но здесь можно констатировать только одно: механизм нетривиального определения объектов в Office 97/2000 выглядит пока довольно сырым. Например, Владимир Биллиг отмечает, что идентичные программные конструкции в одних приложениях работают, а в других нет. В нашем случае видны явные противоречия, когда редактор «поднимает» название типа в строке

Dim MyRef As Reference

показывая, что ключевое слово «Reference» знакомо ему, но при запуске программы сообщает, что этот тип объекта ему неизвестен.

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

With ActiveDocument.VBProject.References
  For i = 1 To .Count
    Debug.Print “Имя проекта = “ & .Item(i).Name
    Debug.Print “Полное имя файла = “ & .Item(i).FullPath
  Next
End With

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

For i = 0 To .Count –1
В начало

В начало

Экспериментируйте

Из всего сказанного выше можно сделать следующие выводы:

  1. VB предоставляет достаточно гибкие возможности по управлению процессом «связывания» кода;
  2. Механизм связывания объектов в VB находится в затянувшейся стадии становления, здесь имеется много подводных камней;
  3. Возможно, установка сервисных пакетов обновления поможет устранить некоторые из отмеченных проблем и противоречий.

Так или иначе — будьте внимательны при написании программ и выборе тех или иных конструкций. И еще — экспериментируйте для поиска наиболее удобных для вас вариантов.

КомпьютерПресс 9'2000