Access и ODBC (часть 1)

Для начала

ODBC (Open DataBase Connectivity) - это открытый интерфейс доступа к базам данных, разработанный фирмой Microsoft. Он представляет собой API довольно низкого уровня и предназначен, в основном, для прямого использования в программах, написанных на C, C++ или, в крайнем случае, на VB. Несмотря на своё происхождение, этот интерфейс является кроссплатформным и с успехом работает и в Windows, и в UNIX/Linux, и в MacOS.

Нет ничего невозможного и в том, чтобы воспользоваться функциями этого замечательного интерфейса из VBA Access. Надо только ясно представлять себе, что большая часть работы уже и так проделана разработчиками MS Access. Для того, чтобы понять, что же осталось на нашу долю, рассмотрим более подробно сам механизм ODBC.

Как это работает

За относительно долгую историю ODBC API про него написано уже достаточно статей и даже книг, в которых подробнейшим образом описано из чего состоит, как работает этот интерфейс и как им пользоваться. Нам будет вполне достаточно лишь общего представления о его структуре.

Итак, для нас ODBC - это, прежде всего, Менеджер драйверов ODBC (для платформы Win32 это odbc32.dll). Драйверы непосредственно взаимодействуют с источниками данных. Что это за драйверы и как осуществляется такое взаимодействие для нас не важно. Просто, когда мы хотим получить доступ, например, к MS SQL Server 7.0, то нужно установить драйвер MS SQL Server 7.0, когда к серверу MySQL - драйвер MySQL.

Работа с Менеджером драйверов ODBC (далее - просто Менеджер) заключается в вызове необходимых функций с определёнными параметрами и в определенной последовательности. В функции Менеджера входит:

  • установка и завершение связи с источником данных (сервером БД);
  • подготовка и выполнение SQL-операторов;
  • получение результатов и навигация по полученным наборам записей, если имеется такая возможность;
  • управление транзакциями;
  • идентификация ошибок;
  • получение различной вспомогательной информации и прочие функции.
Для выполнения этих функций Менеджер вначале должен подготовить некоторые системные ресурсы:
  • Идентификатор окружения HENV. Он указывает на область памяти для общей информации (сведения обо всех соединениях с базами данных, информация о том, какое соединение является текущим и т.п.).
  • Идентификатор соединения HDBC. Этот идентификатор указывает на область памяти для информации о конкретном соединении. Идентификатор соединения ассоциируется с единственным идентификатором окружения, в то время как этот идентификатор окружения может иметь несколько связанных с ним идентификаторов соединения.
  • Идентификатор оператора HSTMT. Он указывает на область памяти для информации об SQL-операторе. Идентификатор оператора связан с единственным идентификатором соединения. Идентификатор соединения может иметь более одного связанного с ним идентификатора оператора.
В конце Менеджер должен освободить эти ресурсы и вернуть их системе.

С точки зрения программы на VBA, все эти идентификаторы - просто переменные типа Long, в которых хранятся адреса соответствующих областей памяти. При выделении ресурсов идентификатору, его переменная передаётся в специальную функцию по ссылке. При последующем использовании - по значению.

Dim HENV As Long
Dim HDBC As Long
Dim HSTMT As Long

Declare Function SQLAllocEnv Lib "odbc32.dll" (ByRef HENV As Long) As Integer
Declare Function SQLFreeEnv Lib "odbc32.dll" (ByVal HENV As Long) As Integer

Declare Function SQLAllocConnect Lib "odbc32.dll" (ByVal HENV As Long, _
                                                   ByRef HDBC As Long) As Integer
Declare Function SQLFreeConnect Lib "odbc32.dll" (ByVal HDBC As Long) As Integer

Declare Function SQLAllocStmt Lib "odbc32.dll" (ByVal HDBC As Long, _
                                                ByRef HSTMT As Long) As Integer
Declare Function SQLFreeStmt Lib "odbc32.dll" (ByVal HSTMT As Long, _
                                               ByVal EndOption As Integer) As Integer

Немного практики

Перво-наперво, создадим отдельный модуль, в котором будем держать все объявления констант, функций, типов и переменных, так или иначе связанных с ODBC API. Бесхитростно назовём его "ODBC_API" и, для начала, включим следующие строки:

Option Compare Database
Option Explicit

'Константы ODBC

Global Const MAX_DATA_BUFFER = 2047
Global Const SQL_SUCCESS = 0
Global Const SQL_SUCCESS_WITH_INFO = 1
Global Const SQL_ERROR = -1
Global Const SQL_NO_DATA_FOUND = 100
Global Const SQL_FETCH_FIRST = 2
Global Const SQL_FETCH_NEXT = 1

'Декларации функций ODBC

Declare Function SQLAllocEnv Lib "odbc32.dll" (ByRef HENV As Long) As Integer

Declare Function SQLFreeEnv Lib "odbc32.dll" (ByVal HENV As Long) As Integer

Declare Function SQLDrivers Lib "odbc32.dll" (ByVal HENV As Long, _
                                              ByVal Direction As Long, _
                                              ByVal Description As String, _
                                              ByVal DescriptionMax As Integer, _
                                              ByRef DescriptionLen As Integer, _
                                              ByVal Attributes As String, _
                                              ByVal AttributesMax As Integer, _
                                              ByRef AttributesLen As Integer) As Integer
                                              
Declare Function SQLDataSources Lib "odbc32.dll" (ByVal HENV As Long, _
                                                  ByVal Direction As Long, _
                                                  ByVal DSN As String, _
                                                  ByVal DSNMax As Integer, _
                                                  ByRef DSNLen As Integer, _
                                                  ByVal Description As String, _
                                                  ByVal DescriptionMax As Integer, _
                                                  ByRef DescriptionLen As Integer) As Integer

Здесь SQLDrivers и SQLDataSources уже не просто выполняют сервисные функции, а позволяют получить конкретные данные об установленных в операционной системе драйверах ODBC и именах источников данных (DSN - DataSource Name). Выглядят объявления этих функций довольно громоздко, но далее я подробно их опишу.

Так как эти функции получают информацию непосредственно от Менеджера, потребность в выделении ресурсов на соединение и операторы отсутствует. Достаточно лишь инициализировать идентификатор окружения HENV. Сделать это лучше всего, создав специальный модуль класса. Почему? Потому, что выделение и освобождение ресурсов можно осуществить, соответственно, в конструкторе (Class_Initialize) и деструкторе (Class_Terminate) класса и, тем самым, исключить ситуацию, когда при сбросе программы функция освобождения ресурсов SQLFreeEnv не будет вызвана. Итак, назовём его ODBC_HENV:

Option Compare Database
Option Explicit

Dim lngHENV As Long       'Переменная окружения (Environment Handle).
Dim booValid As Boolean   'Индикатор возможности использования переменной окружения.

Public Property Get HENV() As Long
'Это свойство содержит уже готовый идентификатор окружения.
On Error Resume Next
  HENV = lngHENV
End Property

Public Property Get Valid() As Boolean 
'Это свойство представляет собой флаг, который показывает возможность (True) или
'невозможность (False) использования идентификатора окружения. Оно необходимо,
'так как нет гарантии, что любое ненулевое значение идентификатора окружения является
'пригодным для использования. Данный флаг устанавливается в True, только если функция
'инициализации окружения отрапортует об отсутствии ошибки.
On Error Resume Next
  Valid = booValid
End Property

Private Sub Class_Initialize()
On Error Resume Next
Dim intStatus As Integer
  intStatus = SQLAllocEnv(lngHENV) 'Инициализируем идентификатор окружения.
  If intStatus = SQL_SUCCESS Then
    booValid = True
  Else
    booValid = False
    MsgBox "Unable to Allocate ODBC Environment Handle!", vbCritical, "Error"
  End If
End Sub

Private Sub Class_Terminate()
On Error Resume Next
Dim intStatus As Integer
  intStatus = SQLFreeEnv(lngHENV) 'Освободим ресурсы окружения.
  If intStatus = SQL_ERROR Then
    MsgBox "Error Freeing ODBC Environment!", vbCritical, "Error"
  End If
End Sub

Теперь, при создании экземпляра класса, будет происходить выделение ресурсов окружения, а при уничтожении - их освобождение. Это очень похоже на работу с сеансом (Workspace) MS Assess. На этом подготовку можно считать законченной. Перейдём к следующему этапу - получению какой-нибудь полезной информации с помощью ODBC API.

Например, попробуем получить списки всех зарегистрированных драйверов и источников данных. Именно зарегистрированных, потому что эта информация просто читается из реестра Windows или из определённых конфигурационных файлов. Если физически удалить файл драйвера, то он всё равно останется в списке, но при попытке обращения к нему Менеджер будет выдавать ошибку. Аналогично, если новый драйвер просто скопировать в системную папку Windows, он не появится в списке установленных драйверов и работать с ним будет невозможно. Именно поэтому, драйверы ODBC обычно комплектуются специальной программой установки.

Чтобы получить интересующие нас списки, есть пара объявленных ранее функций: SQLDrivers и SQLDataSources. Они настолько похожи, что рассматривать я буду только первую из них, хотя использоваться будут обе. Первым параметром в ней идёт идентификатор окружения. Будем считать, что он у нас уже есть:

Dim ODBC_ENV As New ODBC_HENV

Дальше идёт параметр Direction. Он определяет порядок просмотра результирующего множества. И надо сказать, что и другие функции ODBC, работающие с наборами записей, устроены аналогично. Когда мы хотим получить первый элемент набора, надо передать в функцию значение SQL_FETCH_FIRST, следующее - SQL_FETCH_NEXT, предыдущее - SQL_FETCH_PRIOR и т.д.

Dim Direction As Long

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

Dim Description As String * MAX_DATA_BUFFER
Dim Attributes As String * MAX_DATA_BUFFER

Второй параметр - длина этой строки. Он не позволит функции сформировать слишком длинную строку, испортив при этом соседние ячейки памяти. В нашем случае - это просто MAX_DATA_BUFFER. Третьим идёт указатель на целое число, в которое функция запишет, сколько она на самом деле использовала символов в отведённой строке:

Dim DescriptionLen As Integer
Dim AttributesLen As Integer

Вот и вся премудрость. Дальше - дело техники: в цикле заполняем пару таблиц данными о драйверах (название и атрибуты) и источниках данных (имя и название драйвера), а потом выводим их содержимое с помощью простенькой формы. Полностью пример для Access'97 можно взять здесь.

Смирнов Сергей Юрьевич (SSY) ssmirnov@fromru.com



Опубликовал admin
15 Сен, Среда 2004г.



Программирование для чайников.