Уроки Iczelion'а

         

Основы


  Это первая "консультация" из целой серии, которая имеет дело с базами данных, программируемых в win32asm. Программирование баз данных становится всё более актуально в современном мире. В настоящее время существует множество различных форматов баз данных. Если мы желаем изучить файловый формат баз данных, чтобы программировать их используя win32asm, то нам необходимо много времени и желания.

  К счастью, Микрософт имеет технологию, которая значительно помогает нам в этом отношении. Она называется ODBC, что означает Open Database Connectivity, т.е. представляет собой спецификацию интерфейса для доступа к базам данных различных форматов. По сути, это некий интерфейс API, такой же как и Windows API, который имеет дело с программированием баз данных. То есть, с возможностями ODBC API, нам открывается сравнительно лёгкий путь для доступа к целому ряду баз данных.

  Как же функционирует ODBC? Какова её структура? Вы должны получить ясное представление об архитектуре ODBC перед ее использованием. ODBC включает в себя четыре компонента:

Приложение (ваша программа) ODBC менеджер ODBC Драйвера Источник Данных (базы данных)

  Центральным компонентом является менеджер. Вы можете понимать под этим термином некого мастера управляющего работой. Вы сообщаете ему, что вы хотите сделать и он передаёт ваше желание своим рабочим (драйверам ODBC), которые и выполняют эту работу. Если рабочие имеют некоторые сообщения для вас, они сообщают об этом мастеру (менеджеру ODBC) и он передает сообщения вам.

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

Ваша программа <----> ODBC менеджер<----> ODBC Драйвера <----> Базы Данных

  Менеджер ODBC поставляется Микрософт. Проверьте вашу Панель Управления. Если ваша машина имеет правильно установленный ODBC, то вы найдете Источник Данных ODBC (ODBC Data Sources) здесь. Что касается драйверов ODBC, то Микрософт поставляет их с продуктами, и вы можете всегда получить новые драйвера ODBC от поставщиков баз данных. Устанавливая новые драйверы ODBC, мы даём возможность нашей машине использовать новые базы данных, о которых она не знала прежде.

  ODBC API - просты в использовании, но в любом случае вам необходимо обладать некоторыми знаниями о SQL и базах данных. Например: значение области, первичный ключ, записи, столбцы, колонки и т.п... Если у вас нет таких знаний, то я советую сначала их приобрести. Как вы могли заметить, менеджер ODBC пытается спрятать детали реализации от вашей программы, взамен он предлагает некоторый интерфейс для работы с базами данных, а конкретно с драйверами. Драйвера ODBC отличаются в своих возможностях, поэтому приложения должны обладать возможностью, чтобы обнаружить поддерживает ли драйвер ODBC конкретную характеристику. ODBC предоставляет три уровня услуг, называемых Уровнями Соответствия Интерфейса (Interface Conformance Levels). Это ядро, Уровень 1 и Уровень 2. Каждый драйвер ODBC должен включать все характеристики определенные на данном уровне ядра. С точки зрения приложений, ODBC API делятся между тремя уровнями. Если специфическая функция помечена как ядерная, то это означает, что вы можете использовать её не проверяя, поддерживается ли она конкретным драйвером. Если это функция уровня 1 или 2, то вам необходимо убедиться, что драйвер ODBC поддерживает её перед её использованием. Вы можете получить подробные сведения о ODBC API из MSDN.

  Вам нужно знать некоторые термины ODBC перед началом программирования.



Соединение с базой данных


  На этой консультации, мы изучим механику использования ODBC API.

  Ваша программа не общается непосредственно с драйверами ODBC, она пользуется услугами менеджера Управление работой менеджера осуществляется с помощью API функций, к которым вы можете обращаться непосредственно из своей программы, вы должны только подключить odbc32.inc и odbc32.lib, а так же windows.inc.

  Шаги которые необходимо предпринять для соединения с базой данных следующие:

Получить идентификатор окружения. Вам нужно делать это только один раз за ODBC-сеанс. Как только вы получите идентификатор, вы сможете модифицировать свойства окружения так, чтобы они удовлетворяли вашим требованиям. Вы можете понимать этот шаг как создание некой рабочей области. Указать какую версию ODBC ваша программа хочет использовать. Вы можете выбрать между версиями ODBC 2.x и 3.x. Они различны во многих аспектах, поэтому этот шаг - необходим. Таким образом менеджер ODBC сможет решить какой синтаксис он должен использовать, чтобы связаться с вашей программой и проинтерпретировать её команды. Выделить память для идентификатора соединения. Этот шаг может рассматриваться как создание пустого соединения. Вы не обязаны определять нужный для соединения с базой данных драйвер. Установить связь. Вы вызываете функцию ODBC, чтобы установить связь.

  Когда вы закончите работу с базой данных, вы должны закрыть связь с ней следующим образом:

Отключится от источника данных Уничтожить идентификатор соединения Уничтожить идентификатор окружения (если вы не желаете использовать это окружение для других соединений)

ВЫДЕЛЕНИЕ ПАМЯТИ ДЛЯ ИДЕНТИФИКАТОРА

  В версиях ODBC до 3.x, вам нужно вызывать отдельные функции, чтобы выделить память для идентификатора окружения, соединения иинструкции (SQLAllocEnv, SQLAllocConnect, SQLAllocStmt). Теперь под ODBC 3.x все функции заменяются SQLAllocHandle, которая имеет следующий синтаксис: SQLRETURN SQLAllocHandle(SQLSMALLINT HandleType, SQLHANDLE InputHandle, SQLHANDLE * OutputHandlePtr );


  Вышеуказанный синтаксис может утомить ваш взор, поэтому мы его немного упростим. SQLAllocHandle proto HandleType:DWORD, InputHandle:DWORD, OutputHandlePtr:DWORD
  SQLRETURN определен как тип SQLSMALLINT, SQLSMALLINT определен как короткое целое, т.e. слово (16 бит). Таким образом функция возвращает выходную величину в ax, а не в eax, что является очень важным. Однако параметр передаётся функции под Win32 в 32-битном стеке. Поэтому, даже если параметр определён как словный (16-бит) вы должны его расширить до 32-бит. Вот почему HandleType - dword вместо word. Вы можете свериться с библиотекой импорта: odbc32.lib. Вход для SQLAllocHandleэто _SQLAllocHandle@12. Эта запись означает, что комбинированный размер параметров составляет 12 байт (3 слова). Тем не менее, это не означает, что функциональный прототип C - неверный. SQLAllocHandle будет только использовать младшее слово HandleTypeи игнорировать старшее слово. Таким образом, функциональный прототип C - корректен пока наш asm-функциональный прототип соответствует сказанному выше.
  С типом SQL разберёмся более обстоятельно. Рассмотрим входные функциональные параметры и обратную величину.
HandleType - константа, которая определяет тип идентификатора, для которого вы хотите распределить память. Возможные величины:



SQL_HANDLE_ENV идентификатор окружения
SQL_HANDLE_DBC идентификатор соединения
SQL_HANDLE_STMT идентификатор запроса
SQL_HANDLE_DESC идентификатор дескриптора

Дескриптор - это коллекция метаданных, которые описывают параметры инструкций SQL или столбцов с набором результатов, которые обрабатываются приложением или драйвером.
InputHandle - идентификатор родительского "контекста". Если вы хотите выделить память для идентификатора подключения, вы должны получить идентификатор окружения, потому что подключение будет сделано в контексте этого окружения. Если вы хотите выделить память для идентификатора окружения, этот параметр должен быть SQL_HANDLE_NULL (остерегайтесь значения SQL_HANDLE_NULL в windows.inc версии 1.18 и ниже, т.к. там допущена ошибка и определено ненадлежащим образом как 0L. Вы должны стереть "L",иначе ваша программа не будет транслироваться. Что касается операторных и дескрипторных идентификаторов, то вы должны передать идентификатор подключения как этот параметр. OutputHandlePtr указывает на dword переменную, которая получит распределенный идентификатор, если запрос успешен.


  Возможные возвращаемые значения SQLAllocHandle могут быть:
 
SQL_SUCCESS Функция завершена успешно
SQL_SUCCESS_WITH_INFO Функция завершена успешно, но с предупреждением
SQL_ERROR Функция потерпела неудачу.
SQL_INVALID_HANDLE Идентификатор переданный функции, недействителен

  Выполнилась ли функция успешно или потерпела неудачу, вы можете получить подробную информацию относительно этого, вызывая SQLGetDiagRecили SQLGetDiagField. Они играют ту же самую роль, что и GetLastError в Win32 API.
  Пример: .data? hEnv dd ?
.code invoke SQLAllocHandle, SQL_HANDLE_ENV, SQL_HANDLE_NULL, addr hEnv .if ax==SQL_SUCCESS || ax==SQL_SUCCESS_WITH_INFO
ВЫБОР ВЕРСИИ ODBC
  После выделения памяти для идентификатора окружения вы должны установить атрибут окружения, SQL_ATTR_ODBC_VERSION, в соответствующее значение. Установка значения атрибута окружения делается, вызовом SQLSetEnvAttr. К настоящему времени вы должны знать, что имеются также функции SQLSetConnectAttr и SQLSetStmtAttr. SQLSetEnvAttr определена как: SQLSetEnvAttr proto EnvironmentHandle:DWORD, Attribute:DWORD, ValuePtr:DWORD, StringLength:DWORD
EnvironmentHandle. Содержит идентификатор окружения, атрибут которого вы хотите установить. Attribute. Константа, которая представляет атрибут, который вы хотите установить. Для нашей цели, это - SQL_ATTR_ODBC_VERSION. Вы можете искать полный список в MSDN. ValuePtr. Значение этого параметра зависит от атрибута, который вы хотите установить. Если атрибут - 32-разрядное значение, этот параметр обрабатывается как значение, которое вы хотите установить. Если атрибут - текстовая строка или двоичный буфер, то это интерпретируется как указатель на строку или буфер. Если вы определяете SQL_ATTR_ODBC_VERSION, то имеются два возможных значения, которые вы можете использовать: SQL_OV_ODBC3 и SQL_OV_ODBC2, для ODBC версий 3.x и 2.x соответственно. StringLength. Размер значения, указанного ValuePtr. Если значение - строка или двоичный буфер, этот параметр должен быть действителен. Если атрибут, который вы хотите установить - dword, этот параметр игнорируется. С тех пор как SQL_ATTR_ODBC_VERSION атрибут содержит значение dword, вы можете передавать NULL как этот параметр.


  Список возможных возвращаемых значений идентичен SQLAllocHandle.
  Пример: .data? hEnv dd ?
.code invoke SQLAllocHandle, SQL_HANDLE_ENV, SQL_HANDLE_NULL, addr hEnv .if ax==SQL_SUCCESS || ax==SQL_SUCCESS_WITH_INFO invoke SQLSetEnvAttr, hEnv, SQL_ATTR_ODBC_VERSION, SQL_OV_ODBC3, NULL .if ax==SQL_SUCCESS || ax==SQL_SUCCESS_WITH_INFO
ВЫДЕЛЕНИЕ ПАМЯТИ ДЛЯ ИДЕНТИФИКАТОРА ПОДКЛЮЧЕНИЯ
  Этот шаг подобен выделению памяти для ид. окружения, вы также вызываете SQLAllocHandle, но передаёте другое значение параметра.
  Пример: .data? hEnv dd ? hConn dd ?
.code invoke SQLAllocHandle, SQL_HANDLE_ENV, SQL_HANDLE_NULL, addr hEnv .if ax==SQL_SUCCESS || ax==SQL_SUCCESS_WITH_INFO invoke SQLSetEnvAttr, hEnv, SQL_ATTR_ODBC_VERSION, SQL_OV_ODBC3, NULL .if ax==SQL_SUCCESS || ax==SQL_SUCCESS_WITH_INFO invoke SQLAllocHandle, SQL_HANDLE_DBC, hEnv, addr hConn .if ax==SQL_SUCCESS || ax==SQL_SUCCESS_WITH_INFO
УСТАНОВКА СВЯЗИ
  Теперь мы готовы сделать попытку фактического подключения к источнику данных через выбранный ODBC драйвер. Имеются фактически три функции ODBC, которые мы можем использовать, чтобы достичь этой цели. Они предлагают различную степень "выбора", который вы можете сделать.
 
SQLConnect Ядро Это - самая простая функция. Требуется только DSN (название источника Данных) и необязательное название пользователя и пароль. Она не предлагает интерфейса GUI типа подсказки пользователю в виде диалогового окна для получения дополнительной информации. Вы должны использовать эту функцию, если вы уже имеете DSN для заданной базы данных.
SQLDriverConnect Ядро Эта функция предлагает более широкий спектр услуг чем SQLConnect. Вы можете соединяться с источником данных, который не определен в системной информации, то есть без DNS. Кроме того, вы можете определить, отобразит ли эта функция диалоговое окно, запрашивающее пользователя для получения дополнительной информации. Например, если вы опустили имя файла базы данных, она будет инструктировать ODBC драйвер об отображении диалогового окна, запрашивающего пользователя выбрать базу данных, для соединения с ней.
SQLBrowseConnect Уровень 1 Эта функция предлагает перечисление источников данных во время выполнения. Она обеспечивает более гибкий интерфейс в сравнении с SQLDriverConnect, потому что вы можете вызывать SQLBrowseConnect неско раз последовательно, каждый раз запрашивая пользователя для получения более конкретной информации, пока наконец вы не получите рабочую строку подключения.
<


  Я буду исследовать сначала SQLConnect. Чтобы использовать SQLConnect, вы должны знать кое-что относительно DSN. DSN расшифровывается как Название Источника Данных, т.е. это строка, которая уникально идентифицирует источник данных. DSN идентифицирует строение данных, которое содержит информацию о том, как соединиться с удельным источником данных. Информация включает и то, какой ODBC-драйвер использовать и с какой базой данных соединиться. Вы создаете, изменяете и удаляете DSN, используя 32-разрядного ODBC Администратора в панели управления.
  SQLConnect имеет следующий синтаксис: SQLConnect proto ConnectionHandle:DWORD pDSN:DWORD, DSNLength:DWORD, pUserName:DWORD, NameLength:DWORD, pPassword:DWORD, PasswordLength:DWORD
ConnectionHandle. Идентификатор подключения который вы хотите использовать. pDSN. Указатель на DSN-строку. DSNLength. Длина DSN-строки. pUserName. Указатель на строку содержащую имя пользователя. NameLength. Длинна строки содержащей имя пользователя. pPassword. Указатель на строку содержащую пароль ассоциированный с данным именем пользователя. PasswordLength. Длина пароля
  По минимуму, SQLConnect требует идентификатор соединения, DSN и их длину: имя пользователя и пароль необязательны, если источник данных не требует их. Список возможных возвращаемых значений идентичен таковому SQLAllocHandle. Предположим мы имеем DSN, называемый "Продажи" в нашей системе, и мы хотим соединиться с ним. Мы можем сделать это следующим образом: .data DSN db "Sales",0
.code ...... invoke SQLConnect, hConn, addr DSN, sizeof DSN,0,0,0,0
  Один из недостатков SQLConnect - то, что, вы должны создать DSN прежде, чем сможете соединяться с источником данных. SQLDriverConnect предлагает более гибкий вариант. Она имеет следующий синтаксис: SQLDriverConnect proto ConnectionHandle:DWORD, hWnd:DWORD, pInConnectString:DWORD, InStringLength:DWORD, pOutConnectString:DWORD, OutBufferSize:DWORD, pOutConnectStringLength:DWORD, DriverCompletion:DWORD


ConnectionHandle. Идентификатор соединения. hWnd. Дескриптор вашего окна. Если вы передадите NULL как параметр, драйвер не будет запрашивать пользователя для получения дополнительной информации (если необходимо). pInConnectString. Указатель на строку подключения. Это - ASCIIZ строка, которая отформатирована, согласно специфике ODBC драйвера, с которым вы хотите соединиться. Она описывает название драйвера и источника данных а так же некоторые дополнительные параметры. Полное описание строки подключения можно найти в MSDN. Здесь я не буду углубляться в подробности. InStringLength. Длина строки соединения. pOutConnectString. Указатель на буфер, который будет заполнен законченной строкой подключения. Размер этого буфера должен быть, по крайней мере, 1,024 байта. Это может звучать запутывающе. Если строка подключения, которую вы передаёте функции, не закончена, то в этом случае, ODBC драйвер может запрашивать пользователя для получения дополнительной информации. ODBC драйвер в этом случае создает законченную строку подключения из всей располагаемой информации и помещает её в буфер. Даже если строка подключения, которую вы составили, была функциональна, этот буфер будет заполнен большим количеством атрибутов. Цель этого параметра - сохранить законченную строку подключения для будущего подключения. OutBufferSize. Размер буфера, указанного pOutConnectString. pOutConnectStringLength. Указатель на dword переменную, которая получит фактическую длину законченной строки подключения, возвращенной ODBC драйвером. DriverCompletion. Флаг, который определяет запросит ли ODBC менеджер/драйвер пользователя для получения дополнительной информации. Однако, флаг зависит от того, передаёте ли вы дескриптор окна hWnd параметру SQLDriverConnect. Если вы не делали этого, ODBC менеджер/драйвер не будет запрашивать пользователя, даже если этот флаг инструктирует об этом.

SQL_DRIVER_PROMPT ODBC драйвер запрашивает пользователя относительно информации. Эта информация используется для создания строки подключения.
SQL_DRIVER_COMPLETE
SQL_DRIVER_COMPLETE_REQUIRED
ODBC драйвер запросит пользователя только, если строка подключения, составленная в вашей программе не закончена.
SQL_DRIVER_NOPROMPT ODBC драйвер не будет запрашивать пользователя для получения дополнительной информации.
<


/li>
  Пример: .data strConnect db "DBQ=c:\data\test.mdb;DRIVER={Microsoft Access Driver (*.mdb)};",0
.data? buffer db 1024 dup(?) OutStringLength dd ?
.code ..... invoke SQLDriverConnect, hConn, hWnd, addr strConnect, sizeof strConnect, addr buffer, sizeof buffer, addr OutBufferLength, SQL_DRIVER_COMPLETE
РАЗЪЕДИНЕНИЕ С ИСТОЧНИКОМ ДАННЫХ
  После того, как подключение сделано успешно, вы можете создать одну или большее количество инструкций и сделать запрос источнику данных. Я буду исследовать эту часть на следующей консультации. Пока, давайте предположим, что вы уже отработали с источником данных, и должны разъединится с ним, вызывая SQLDisconnect. Эта функция проста (Это отражение грубой и грустной действительности о том, что разрушение - намного проще чем конструкция или созидание). Требуется только один параметр, маркер подключения. invoke SQLDisconnect, hConn
УДАЛЕНИЕ ИДЕНТИФИКАТОРОВ ПОДКЛЮЧЕНИЯ И СРЕДЫ
  После успешного разъединения вы можете уничтожить идентификаторы подключения и среды, вызывая SQLFreeHandle. Это - новая функция, вводимая в ODBC 3.x., она заменяет SQLFreeConnect, SQLFreeEnvи SQLFreeStmt. SQLFreeHandle имеет следующий синтаксис:
SQLFreeHandle proto HandleType:DWORD, Handle:DWORD
HandleType. Константа, которая идентифицирует тип идентификатора, который вы передаёте этой функции как второй параметр. Возможные значения - те же самые, как и в SQLAllocHandle Handle. Идентификатор, который вы хотите удалить.
  Например:
invoke SQLFreeHandle, SQL_HANDLE_DBC, hConn invoke SQLFreeHandle, SQL_HANDLE_ENV, hEnv
[C] Iczelion, пер. SheSan

Подготовка и Использование Инструкций


  На этой консультации, мы продолжим изучение приёмов программирования. Мы изучим, как взаимодействовать с источником данных. Из предыдущей консультации мы рассмотрели как осуществить подключение к источнику данных. Это - первый шаг. Подключение определяет путь данных между вами и источником данных. Это пассивно. Чтобы взаимодействовать с источником данных, вы должны использовать инструкции. Вы можете понимать эти инструкции как команды, которые вы посылаете источнику данных. "Команда" должна быть написана на SQL. Используя инструкции, вы можете изменить строение источника данных, сделать запрос для получения некоторых данных, модифицировать и удалить данные.

  Шаги для подготовки и использования инструкций следующие:

Выделить память для операторного идентификатора Создать инструкцию SQL Выполнить инструкцию Уничтожить инструкцию

ВЫДЕЛЕНИЕ ПАМЯТИ ДЛЯ ОПЕРАТОРНОГО ИДЕНТИФИКАТОРА

  Вы выделяете память для операторного идентификатора вызывая SQLAllocHandle, передав ей соответствующие параметры.

  Например: .data? hStmt dd ? .code ...... invoke SQLAllocHandle, SQL_HANDLE_STMT, hConn, addr hStmt

СОЗДАНИЕ SQL-ИНСТРУКЦИИ

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

ВЫПОЛНЕНИЕ ИНСТРУКЦИИ

  Имеются четыре пути выполнения инструкции, в зависимости от того, когда они откомпилированы и кто определяет их.

 

Немедленное выполнение Ваша программа определяет инструкцию SQL. Инструкция компилируется и исполняется во время выполнения за один шаг.
Подготовленное Выполнение Ваша программа также определяет инструкцию SQL. Однако, подготовка и выполнение разделены на два шага: сначала инструкция SQL компилируется, а затем она выполняется. Используя этот метод, вы можете откомпилировать инструкцию SQL лишь однажды а затем выполнять ту же самую инструкцию SQL определённое число раз. Это экономит время.
Процедуры Инструкции SQL компилируются и сохраняются в источнике данных. Ваша программа вызывает их во время выполнения.
Каталог Инструкции SQL жестко закодированы в ODBC драйвер. Цель функций каталога - возвратить предопределенные наборы исхода, типа имен таблиц в базе данных. В целом, функции каталога используются, чтобы получить информацию относительно источника данных. Ваша программа вызывает их во время выполнения.
<

Возвращаемые величины


  На этой консультации, вы узнаете как извлекать записи возвращенные инструкциями SQL.

  Мы назовём группу записей возвращаемых инструкцией - набором результатов. Общие шаги для получения установленного результата следующие:

Определить доступен ли набор результатов. Связать множество столбцов результатов с переменными Выборка столбца

  Когда вы проанализируете набор результатов, вам нужно уничтожить его вызвав SQLCloseCursor.

ОПРЕДЕЛЕНИЕ ДОСТУПНОСТИ НАБОРА ТЕЗУЛЬТАТОВ

  Иногда, как вы уже знаете, в результате выполнения инструкции SQL создаётся набор результатов. Если инструкция SQL - не возвращает набора результатов, то, естественно, что никакого доступна к ним быть не может. Тем не менее, иногда вы даже и не знаете, что возвращает инструкция SQL, Это возникает тогда, когда вы позволяете пользователю вводить заказные инструкции SQL. В этом случае вы должны проверить был ли создан набор результатом вызовом SQLNumResultCols. Эта функция возвращает количество столбцов (полей) в наборе результатов (если они существуют) и имеет следующий синтаксис: SQLNumResultCols proto StatementHandle:DWORD, pNumCols:DWORD

StatementHandle. Идентификатор инструкции. pNumCols. Указатель на переменную типа dword которая возвращает число столбцов в наборе результатов.

  Если величина в переменной указанной pNumCols это 0, значит результат не устанавливался.

СВЯЗЫВАНИЕ СТОЛБЦОВ

  В некотором отношении, понятие - идентичное той же связи переменной с параметром инструкции SQL. Вы ассоциируете(связываете) переменную со специфическим столбцом в наборе результатов. В этом случае используется функция SQLBindCol, которая имеет следующий синтаксис:

SQLBindCol proto StatementHandle:DWORD, ColumnNumber:DWORD, TargetValuePtr:DWORD, BufferLength:DWORD, pStrLenOrIndPtr:DWORD

StatementHandle. Идентификатор инструкции ColumnNumber. Номер столбца в наборе результатов, с которым будет произведено связывание. Номер столбца начинается с 1. Столбец 0 - столбец закладки. TargetType. Константа, которая указывает тип переменной (буфера) указанного TargetValuePtr. TargetValuePtr. Указатель на переменную или буфер, который будет связан со столбцом. Когда вы вызываете SQLFetch, чтобы извлечь столбец из набора результатов, переменная или буфер будут заполняться величиной из связанного столбца. BufferLength. Размер буфера указанного TargetValuePtr.



ODBC пример


  На этой консультации, мы обобщим всё, что мы знаем на данный момент. Мы напишем программу, которая использует ODBC API. Для простоты я выбирал базу данных Microsoft Access (Microsoft Access 97) для этой программы.

  Загрузите пример.

  Примечание: Если вы используете windows.inc версии 1.18 или ниже, вы должны прежде устранить небольшой баг. Запустив поиск в файле windows.inc для SQL_NULL_HANDLE, вы найдете строку: SQL_NULL_HANDLE equ 0L

  Удалите символ "L" после нуля, вот так: SQL_NULL_HANDLE equ 0

  Эта программа построена на базе диалогового окна с простым меню. Когда пользователь выбирает пункт меню "connect", он пытается подключиться к test.mdb, т.е. своей базе данных. После того, как связь будет установлена, программа отобразит конечную полную строку связи возвращенную драйвером После этого, пользователь может выбрать пункт меню "View All Records", чтобы заполнить контрол listview данными из базы. Кроме того, пользователь может выбрать "Query", чтобы найти специфическую запись. Программа представит небольшой диалоговый блок, предлагающий пользователю набрать имя человека, которого он хочет искать. Когда пользователь нажимает кнопку OK или клавишу Enter, программа выполняет запрос выбрать запись(записи), которые сопоставлены имени. Когда пользователь сделал всё, что ему было необходимо, он может выбрать пункт меню "disconnect", чтобы отключиться от базы данных.

  Теперь давайте посмотрим на исходный код: .386 .model flat,stdcall include \masm32\include\windows.inc include \masm32\include\kernel32.inc include \masm32\include\odbc32.inc include \masm32\include\comctl32.inc include \masm32\include\user32.inc includelib \masm32\lib\odbc32.lib includelib \masm32\lib\comctl32.lib includelib \masm32\lib\kernel32.lib includelib \masm32\lib\user32.lib

IDD_MAINDLG equ 101 IDR_MAINMENU equ 102 IDC_DATALIST equ 1000 IDM_CONNECT equ 40001 IDM_DISCONNECT equ 40002 IDM_QUERY equ 40003 IDC_NAME equ 1000 IDC_OK equ 1001 IDC_CANCEL equ 1002 IDM_CUSTOMQUERY equ 40004 IDD_QUERYDLG equ 102


DlgProc proto hDlg:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD QueryProc proto hDlg:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD SwitchMenuState proto :DWORD ODBCConnect proto :DWORD ODBCDisconnect proto :DWORD RunQuery proto :DWORD
.data? hInstance dd ? hEnv dd ? hConn dd ? hStmt dd ? Conn db 256 dup(?) StrLen dd ? hMenu dd ? ; handle to the main menu hList dd ? ; handle to the listview control TheName db 26 dup(?) TheSurname db 26 dup(?) TelNo db 21 dup(?) NameLength dd ? SurnameLength dd ? TelNoLength dd ? SearchName db 26 dup(?) ProgPath db 256 dup(?) ConnectString db 1024 dup(?)
.data SQLStatement db "select * from main",0 WhereStatement db " where name=?",0 strConnect db "DRIVER={Microsoft Access Driver (*.mdb)};DBQ=",0 DBName db "test.mdb",0 ConnectCaption db "Complete Connection String",0 Disconnect db "Disconnect successful",0 AppName db "ODBC Test",0 AllocEnvFail db "Environment handle allocation failed",0 AllocConnFail db "Connection handle allocation failed",0 SetAttrFail db "Cannot set desired ODBC version",0 NoData db "You must type the name in the edit box",0 ExecuteFail db "Execution of SQL statement failed",0 ConnFail db "Connection attempt failed",0 AllocStmtFail db "Statement handle allocation failed",0 Heading1 db "Name",0 Heading2 db "Surname",0 Heading3 db "Telephone No.",0
.code start: invoke GetModuleHandle, NULL mov hInstance,eax call GetProgramPath invoke DialogBoxParam, hInstance, IDD_MAINDLG,0,addr DlgProc,0 invoke ExitProcess,eax invoke InitCommonControls DlgProc proc hDlg:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD .if uMsg==WM_INITDIALOG invoke GetMenu, hDlg mov hMenu,eax invoke GetDlgItem, hDlg, IDC_DATALIST mov hList,eax call InsertColumn .elseif uMsg==WM_CLOSE invoke GetMenuState, hMenu, IDM_CONNECT,MF_BYCOMMAND .if eax==MF_GRAYED invoke ODBCDisconnect, hDlg .endif invoke EndDialog,hDlg, 0 .elseif uMsg==WM_COMMAND .if lParam==0 mov eax,wParam .if ax==IDM_CONNECT invoke ODBCConnect,hDlg .elseif ax==IDM_DISCONNECT invoke ODBCDisconnect,hDlg .elseif ax==IDM_QUERY invoke RunQuery,hDlg .elseif ax==IDM_CUSTOMQUERY invoke DialogBoxParam, hInstance, IDD_QUERYDLG,hDlg, addr QueryProc, 0 .endif .endif .else mov eax,FALSE ret .endif mov eax,TRUE ret DlgProc endp


GetProgramPath proc invoke GetModuleFileName, NULL,addr ProgPath,sizeof ProgPath std mov edi,offset ProgPath add edi,sizeof ProgPath-1 mov al,"\" mov ecx, sizeof ProgPath repne scasb cld mov byte ptr [edi+2],0 ret GetProgramPath endp
SwitchMenuState proc Flag:DWORD .if Flag==TRUE invoke EnableMenuItem, hMenu, IDM_CONNECT, MF_GRAYED invoke EnableMenuItem, hMenu, IDM_DISCONNECT, MF_ENABLED invoke EnableMenuItem, hMenu, IDM_QUERY, MF_ENABLED invoke EnableMenuItem, hMenu, IDM_CUSTOMQUERY, MF_ENABLED .else invoke EnableMenuItem, hMenu, IDM_CONNECT, MF_ENABLED invoke EnableMenuItem, hMenu, IDM_DISCONNECT, MF_GRAYED invoke EnableMenuItem, hMenu, IDM_QUERY, MF_GRAYED invoke EnableMenuItem, hMenu, IDM_CUSTOMQUERY, MF_GRAYED .endif ret SwitchMenuState endp
ODBCConnect proc hDlg:DWORD invoke SQLAllocHandle, SQL_HANDLE_ENV, SQL_NULL_HANDLE, addr hEnv .if ax==SQL_SUCCESS || ax==SQL_SUCCESS_WITH_INFO invoke SQLSetEnvAttr, hEnv,SQL_ATTR_ODBC_VERSION, SQL_OV_ODBC3,0 .if ax==SQL_SUCCESS || ax==SQL_SUCCESS_WITH_INFO invoke SQLAllocHandle, SQL_HANDLE_DBC, hEnv, addr hConn .if ax==SQL_SUCCESS || ax==SQL_SUCCESS_WITH_INFO invoke lstrcpy,addr ConnectString,addr strConnect invoke lstrcat,addr ConnectString, addr ProgPath invoke lstrcat, addr ConnectString,addr DBName invoke SQLDriverConnect, hConn, hDlg, addr ConnectString, sizeof ConnectString, addr Conn, sizeof Conn,addr StrLen, SQL_DRIVER_COMPLETE .if ax==SQL_SUCCESS || ax==SQL_SUCCESS_WITH_INFO invoke SwitchMenuState,TRUE invoke MessageBox,hDlg, addr Conn,addr ConnectCaption, MB_OK+MB_ICONINFORMATION .else invoke SQLFreeHandle, SQL_HANDLE_DBC, hConn invoke SQLFreeHandle, SQL_HANDLE_ENV, hEnv invoke MessageBox, hDlg, addr ConnFail, addr AppName, MB_OK+MB_ICONERROR .endif .else invoke SQLFreeHandle, SQL_HANDLE_ENV, hEnv invoke MessageBox, hDlg, addr AllocConnFail, addr AppName, MB_OK+MB_ICONERROR .endif .else invoke SQLFreeHandle, SQL_HANDLE_ENV, hEnv invoke MessageBox, hDlg, addr SetAttrFail, addr AppName, MB_OK+MB_ICONERROR .endif .else invoke MessageBox, hDlg, addr AllocEnvFail, addr AppName, MB_OK+MB_ICONERROR .endif ret ODBCConnect endp


ODBCDisconnect proc hDlg: DWORD invoke SQLDisconnect, hConn invoke SQLFreeHandle, SQL_HANDLE_DBC, hConn invoke SQLFreeHandle, SQL_HANDLE_ENV, hEnv invoke SwitchMenuState, FALSE invoke ShowWindow,hList, SW_HIDE invoke MessageBox,hDlg,addr Disconnect, addr AppName, MB_OK+MB_ICONINFORMATION ret ODBCDisconnect endp
InsertColumn proc LOCAL lvc:LV_COLUMN mov lvc.imask,LVCF_TEXT+LVCF_WIDTH mov lvc.pszText,offset Heading1 mov lvc.lx,150 invoke SendMessage,hList, LVM_INSERTCOLUMN,0,addr lvc mov lvc.pszText,offset Heading2 invoke SendMessage,hList, LVM_INSERTCOLUMN, 1 ,addr lvc mov lvc.pszText,offset Heading3 invoke SendMessage,hList, LVM_INSERTCOLUMN, 3 ,addr lvc ret InsertColumn endp
FillData proc LOCAL lvi:LV_ITEM LOCAL row:DWORD
invoke SQLBindCol, hStmt,1,SQL_C_CHAR, addr TheName, sizeof TheName,addr NameLength invoke SQLBindCol, hStmt,2,SQL_C_CHAR, addr TheSurname, sizeof TheSurname,addr SurnameLength invoke SQLBindCol, hStmt,3,SQL_C_CHAR, addr TelNo, sizeof TelNo,addr TelNoLength mov row,0 .while TRUE mov byte ptr ds:[TheName],0 mov byte ptr ds:[TheSurname],0 mov byte ptr ds:[TelNo],0 invoke SQLFetch, hStmt .if ax==SQL_SUCCESS || ax==SQL_SUCCESS_WITH_INFO mov lvi.imask,LVIF_TEXT+LVIF_PARAM push row pop lvi.iItem mov lvi.iSubItem,0 mov lvi.pszText, offset TheName push row pop lvi.lParam invoke SendMessage,hList, LVM_INSERTITEM,0, addr lvi mov lvi.imask,LVIF_TEXT inc lvi.iSubItem mov lvi.pszText,offset TheSurname invoke SendMessage,hList,LVM_SETITEM, 0,addr lvi inc lvi.iSubItem mov lvi.pszText,offset TelNo invoke SendMessage,hList,LVM_SETITEM, 0,addr lvi inc row .else .break .endif .endw ret FillData endp
RunQuery proc hDlg:DWORD invoke ShowWindow, hList, SW_SHOW invoke SendMessage, hList, LVM_DELETEALLITEMS,0,0 invoke SQLAllocHandle, SQL_HANDLE_STMT, hConn, addr hStmt .if ax==SQL_SUCCESS || ax==SQL_SUCCESS_WITH_INFO invoke SQLExecDirect, hStmt, addr SQLStatement, sizeof SQLStatement .if ax==SQL_SUCCESS || ax==SQL_SUCCESS_WITH_INFO invoke FillData .else invoke ShowWindow, hList, SW_HIDE invoke MessageBox,hDlg,addr ExecuteFail, addr AppName, MB_OK+MB_ICONERROR .endif invoke SQLCloseCursor, hStmt invoke SQLFreeHandle, SQL_HANDLE_STMT, hStmt .else invoke ShowWindow, hList, SW_HIDE invoke MessageBox,hDlg,addr AllocStmtFail, addr AppName, MB_OK+MB_ICONERROR .endif ret RunQuery endp QueryProc proc hDlg:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD .if uMsg==WM_CLOSE invoke SQLFreeHandle, SQL_HANDLE_STMT, hStmt invoke EndDialog, hDlg,0 .elseif uMsg==WM_INITDIALOG invoke ShowWindow, hList, SW_SHOW invoke SQLAllocHandle, SQL_HANDLE_STMT, hConn, addr hStmt .if ax==SQL_SUCCESS || ax==SQL_SUCCESS_WITH_INFO invoke lstrcpy, addr Conn, addr SQLStatement invoke lstrcat, addr Conn, addr WhereStatement invoke SQLBindParameter,hStmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR,25,0, addr SearchName,25, addr StrLen invoke SQLPrepare, hStmt, addr Conn, sizeof Conn .else invoke ShowWindow, hList, SW_HIDE invoke MessageBox,hDlg,addr AllocStmtFail, addr AppName, MB_OK+MB_ICONERROR invoke EndDialog, hDlg,0 .endif .elseif uMsg==WM_COMMAND mov eax, wParam shr eax,16 .if ax==BN_CLICKED mov eax,wParam .if ax==IDC_OK invoke GetDlgItemText, hDlg, IDC_NAME, addr SearchName, 25 .if ax==0 invoke MessageBox, hDlg,addr NoData, addr AppName, MB_OK+MB_ICONERROR invoke GetDlgItem, hDlg, IDC_NAME invoke SetFocus, eax .else invoke lstrlen,addr SearchName mov StrLen,eax invoke SendMessage, hList, LVM_DELETEALLITEMS,0,0 invoke SQLExecute, hStmt invoke FillData invoke SQLCloseCursor, hStmt .endif .else invoke SQLFreeHandle, SQL_HANDLE_STMT, hStmt invoke EndDialog, hDlg,0 .endif .endif .else mov eax,FALSE ret .endif mov eax,TRUE ret QueryProc endp end start


АНАЛИЗ start: invoke GetModuleHandle, NULL mov hInstance,eax call GetProgramPath
  Когда программа стартует, она получает описатель экземпляра, затем обнаруживает свой собственный путь. Это делается с расчётом на то, что база данных, test.mdb, находится в той же папке, что и программа. GetProgramPath proc invoke GetModuleFileName, NULL,addr ProgPath,sizeof ProgPath std mov edi,offset ProgPath add edi,sizeof ProgPath-1 mov al,"\" mov ecx,sizeof ProgPath repne scasb cld mov byte ptr [edi+2],0 ret GetProgramPath endp
  Функция GetProgramPath вызывает GetModuleFileName, чтобы получить полный путь и имя программы. После этого мы ищем последний символ "\" в этом пути, чтобы отделить имя файла от пути заменяя имя файла нулём. Таким образом мы получили путь к программе в ProgPath.
  Программа затем отображает основное диалоговое окно, используя DialogBoxParam. Сначала, как только основное диалоговое окно загружается, мы получаем дескрипторы меню и контрола listview. Затем создаём три столбца в элементе управления listview (поскольку мы знаем заблаговременно, что множество результатов состоит из трех столбцов. Далее мы переходим к заполнению таблицы созданной на первом шаге.)
  После этого мы ждем действия пользователя. Если пользователь выбирает "connect" из меню, он вызывает функцию ODBCConnect ODBCConnect proc hDlg:DWORD invoke SQLAllocHandle, SQL_HANDLE_ENV, SQL_NULL_HANDLE, addr hEnv .if ax==SQL_SUCCESS || ax==SQL_SUCCESS_WITH_INFO
  Первое, что мы делаем это выделяем память для идентификатора окружения, используя SQLAllocHandle: invoke SQLSetEnvAttr, hEnv,SQL_ATTR_ODBC_VERSION, SQL_OV_ODBC3,0 .if ax==SQL_SUCCESS || ax==SQL_SUCCESS_WITH_INFO
  После того как мы получили ид. окружения, мы устанавливаем его параметры сообщая о том, что мы будем использовать синтаксис ODBC 3.x вызывая SQLSetEnvAttr: invoke SQLAllocHandle, SQL_HANDLE_DBC, hEnv, addr hConn .if ax==SQL_SUCCESS || ax==SQL_SUCCESS_WITH_INFO
  Если все идет хорошо, то мы можем начать соединение выделив память для идентификатора соединения, используя SQLAllocHandle: invoke lstrcpy,addr ConnectString,addr strConnect invoke lstrcat,addr ConnectString, addr ProgPath invoke lstrcat, addr ConnectString,addr DBName


  Конструируем строку соединения, которую мы будем использовать при подключении: invoke SQLDriverConnect, hConn, hDlg, addr ConnectString, sizeof ConnectString, addr Conn, sizeof Conn, addr StrLen, SQL_DRIVER_COMPLETE .if ax==SQL_SUCCESS || ax==SQL_SUCCESS_WITH_INFO invoke SwitchMenuState,TRUE invoke MessageBox,hDlg, addr Conn,addr ConnectCaption, MB_OK+MB_ICONINFORMATION
  Когда строка соединения - готова, мы вызываем SQLDriverConnect, чтобы попытаться подключаться к test.mdb, используя MS Access драйвер Если файл test.mdb не обнаружен, то драйвер ODBC известит пользователя об этом поскольку мы определили флаг SQL_DRIVER_COMPLETE. Когда ф-я SQLDriverConnect успешно выполнится, переменная Conn заполнится полной строкой связи созданной драйвером Мы отображаем её используя окно сообщений. SwitchMenuState - простая функция, она прорисовывает серым цветом пункты соответствующего меню.
  Сейчас связь с базой данных будет установлена до тех пор пока пользователь не разорвёт её.
  Когда пользователь выбирает пункт меню "View All Records", процедура диалогового окна вызывает RunQuery: RunQuery proc hDlg:DWORD invoke ShowWindow, hList, SW_SHOW invoke SendMessage, hList, LVM_DELETEALLITEMS,0,0
  С Тех пор как элемент управления listview был создан, он оставался невидимым, но теперь самое время, чтобы показать его. Также нам нужно удалить все пункты (если имеются) с контрола listview. invoke SQLAllocHandle, SQL_HANDLE_STMT, hConn, addr hStmt .if ax==SQL_SUCCESS || ax==SQL_SUCCESS_WITH_INFO
  Затем, программа выделяет память для идентификатора инструкции. invoke SQLExecDirect, hStmt, addr SQLStatement, sizeof SQLStatement .if ax==SQL_SUCCESS || ax==SQL_SUCCESS_WITH_INFO
  Выполняем инструкцию SQL используя SQLExecDirect. Я решаю здесь использовать SQLExecDirect, поскольку эта инструкция SQL исполняется лишь однажды: invoke FillData
  После выполнения инструкции SQL, набор результатов должен быть возвращен. Мы извлекаем данные результатов в элемент управления listview используя функцию FillData: FillData proc LOCAL lvi:LV_ITEM LOCAL row:DWORD


invoke SQLBindCol, hStmt,1,SQL_C_CHAR, addr TheName, sizeof TheName,addr NameLength invoke SQLBindCol, hStmt,2,SQL_C_CHAR, addr TheSurname, sizeof TheSurname,addr SurnameLength invoke SQLBindCol, hStmt,3,SQL_C_CHAR, addr TelNo, sizeof TelNo,addr TelNoLength
  Здесь, набор результатов уже возвращен. Нам нужно связать все три колонки набора результатов с буфером. Мы вызываем ф-ю SQLBindCol чтобы сделать это. Имейте в виду, что нам нужен отдельный вызов для каждой колонки, и мы не должны связывать все столбцы: только столбцы, из которых нам нужно получить данные. mov row,0 .while TRUE mov byte ptr ds:[TheName],0 mov byte ptr ds:[TheSurname],0 mov byte ptr ds:[TelNo],0
  Мы инициализируем буфер значением NULL в случае, если в столбце(столбцах) нет данных. Лучше использовать длину данных возвращенных в переменных которые мы определили в SQLBindCol. В нашем примере, мы могли бы проверить величины в NameLength, SurnameLength, TelNoLength, чтобы получить фактическую длину возвращенных строк. invoke SQLFetch, hStmt .if ax==SQL_SUCCESS || ax==SQL_SUCCESS_WITH_INFO mov lvi.imask,LVIF_TEXT+LVIF_PARAM push row pop lvi.iItem mov lvi.iSubItem,0 mov lvi.pszText, offset TheName push row pop lvi.lParam invoke SendMessage,hList, LVM_INSERTITEM,0, addr lvi
  Остальное - просто. Вызываем SQLFetch, чтобы извлечь колонку из набора результата а затем сохранить величины в буферах на контроле listview. Когда колонка становится недоступна (мы достигли конца файла), SQLFetch возвращает SQL_NO_DATA и мы выходим из бесконечного цикла. invoke SQLCloseCursor, hStmt invoke SQLFreeHandle, SQL_HANDLE_STMT, hStmt
  Когда мы получили набор результатов, мы должны закрыть курсор, используя SQLCloseCursor, а затем освободить идентификатор инструкции используя SQLFreeHandle.
  Когда пользователь выбирает пункт меню "Query", программа отображает другое диалоговое окно, приглашающее пользователя ввести имя по которому будет осуществлён поиск. .elseif uMsg==WM_INITDIALOG invoke ShowWindow, hList, SW_SHOW invoke SQLAllocHandle, SQL_HANDLE_STMT, hConn, addr hStmt .if ax==SQL_SUCCESS || ax==SQL_SUCCESS_WITH_INFO invoke lstrcpy, addr Conn, addr SQLStatement invoke lstrcat, addr Conn, addr WhereStatement invoke SQLBindParameter,hStmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR,25,0, addr SearchName, 25,addr StrLen invoke SQLPrepare, hStmt, addr Conn, sizeof Conn


  Первая вещь, которую делает диалоговое окно - показывает элемент listview управления. Затем распределяет память для идентификатора инструкции и наконец, создает инструкцию SQL. Эта инструкция SQL означает вернуть все значения содержащиеся в таблице main, где имя имеет значение ?. select * from main where name=?
  Затем, оно вызывает SQLBindParameter, чтобы соединить ид. инструкции с буфером SearchName. Так что когда инструкция SQL выполняется, драйвер ODBC может получить строку, которая ему нужна из SearchName. Потом оно вызывает SQLPrepare, чтобы скомпилировать инструкцию SQL. Здесь отложенное выполнение разумно т.к. мы подготавливаем/компилируем утверждение SQL только раз и затем, мы будем использовать его много раз. Когда инструкция SQL скомпилирована, последующее выполнение - намного быстрее. .if ax==IDC_OK invoke GetDlgItemText, hDlg, IDC_NAME, addr SearchName, 25 .if ax==0 invoke MessageBox, hDlg,addr NoData, addr AppName, MB_OK+MB_ICONERROR invoke GetDlgItem, hDlg, IDC_NAME invoke SetFocus, eax .else
  Когда пользователь занес некоторое имя в окно редактирования и нажал ввод, программа извлекает текст из этого окна редактирования и проверяет действительно ли он был введён, если нет - возвращается значение NULL. В этом случае отображается сообщение и устанавливается клавиатурный фокус на окно редактирования, чтобы подсказать пользователю о необходимости ввода имени. invoke lstrlen,addr SearchName mov StrLen,eax invoke SendMessage, hList, LVM_DELETEALLITEMS,0,0 invoke SQLExecute, hStmt invoke FillData invoke SQLCloseCursor, hStmt
  Если имя набирается в окне редактирования, мы находим его длину и сохраняем в StrLen для использования драйвером ODBC (вспомните, что фактически мы получили адрес StrLen для ф-ции
SQLBindParameter). Затем мы вызываем ф-ю SQLExecute, передавая ей в качестве входного параметра ид. инструкции, чтобы выполнить подготовленную инструкцию SQL. Когда происходит возврат из ф-ции SQLExecute, мы вызываем FillData, чтобы отобразить результат в элементе управления listview. Поскольку мы не имеем более полезной информации, мы закрываем курсор вызывая SQLCloseCursor.
[C] Iczelion, пер. SheSan