Написание сервисов windows nt на winapi


Содержание

Создание сервисов для Windows NT / Система / Visual C++

Создание сервиса

Приложение, управляющее сервисом Код, описанный в этой статье, работает только в Windows NT / 2000 / XP, поскольку Windows 98 не поддерживает работу с сервисами.

Создание сервиса

Как правило сервис представляет собой консольное приложение, поэтому программа должна содержать функцию main().

Сначала объявим глобальные переменные:

Функция main()

Здесь мы создаем таблицу точек входа сервиса.

Структура типа SERVICE_TABLE_ENTRY позволяет задать функцию ServiceMain() для сервиса, носящего указанное в структуре имя.

Функция StartServiceCtrlDispatcher() связывает главный поток сервиса с менеджером сервисов (service control manager, SCM).

Когда SCM запускает сервис, он ожидает выполнения сервисом функции StartServiceCtrlDispatcher(). Главный поток сервиса должен вызвать эту функцию как можно быстрее. Если функция выполняется успешно, она связывает вызывающий ее поток с SCM и не завершается, пока не будет остановлен сервис. SCM использует это соединение, чтобы посылать сервису управляющие запросы.

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

Функция ServiceMain()

Функция ServiceMain() определяется процессом в качестве точки входа для данного сервиса. Эта функция может носить любое имя, заданное приложением.

Аргументы функции ServiceMain() (аналогичны аргументам функции main()): dwArgc — количество аргументов, lpszArgv — указатель на массив, который содержит указатели на строковые аргументы функции.

Функция RegisterServiceCtrlHandler() регистрирует функцию, которая будет обрабатывать управляющие запросы к сервису (такие, например, как остановка сервиса). В случае успешного завершения функция возвращает дескриптор статуса сервиса (service status handle). При неудачном завершении функция возвращает нулевое значение.

Поле dwServiceType структуры ss устанавливаем в SERVICE_WIN32_OWN_PROCESS. Это означает, что сервис будет выполняться как отдельный процесс, т.е. будет иметь собственное адресное пространство.

Затем устанавливаем состояние ожидания запуска сервиса c помощью созданной нами вспомогательной функции SetSomeServiceStatus(). Эта функция изменяет содержимое структуры ss, которое использует SCM для получения информации о сервисе.

Далее можно выполнить всю необходимую инициализацию для сервиса (определяемая пользователем функция InitSomeServiceData()).

Когда инициализация выполнена, вызываем функцию SetSomeServiceStatus() с параметром SERVICE_RUNNING, чтобы установить состояние работающего сервиса.

После изменения состояния сервиса выполняется основной код программы.

Функция ServiceControl()

Эта функция является управляющей функцией сервиса и может носить любое имя, определенное приложением.

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

Далее с помощью функции SetSomeServiceStatus() сообщаем SCM, что сервис остановлен.

Когда сервис получает команду SERVICE_CONTROL_INTERROGATE, это означает, что он должен немедленно обновить информацию о статусе сервиса, используемую SCM.

Функция SetSomeServiceStatus()

Эта функция изменяет содержимое структуры ss, которое использует SCM для получения информации о статусе сервиса.

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

Поле dwControlsAccepted определяет управляющий код, который может принимать и обрабатывать данный сервис. По умолчанию все сервисы могут принимать команду SERVICE_CONTROL_INTERROGATE. Если это поле имеет значение SERVICE_ACCEPT_STOP, то сервис может быть остановлен с помощью команды SERVICE_CONTROL_STOP.

Функции InitSomeServiceData() и StopSomeService()

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

Приложение, управляющее сервисом

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

Здесь не будут описываться функции WinMain() и InitApp(), поскольку они не обладают никакими особенностями, заслуживающими внимания. Функция WinMain() создает главное окно с шестью кнопками: «Install Service», «Start Service», «Get Service Configuration», «Stop Service», «Remove Service» и «Exit».

Функция WndProc()

В функции WndProc() используются макросы для обработки сообщений WM_COMMAND и WM_DESTROY:

Функция WndProcOnCommand()

Функция WndProcOnCommand() вызывается функцией WndProc(), если окно получает сообщение WM_COMMAND. Функция WndProcOnCommand() содержит код, выполняющийся при нажатии на кнопки главного окна приложения.

Приведем полностью код этой функции:

Здесь мы отрываем базу данных Service Control Manager (SCM) с помощью функции OpenSCManager() с уровнем доступа SC_MANAGER_CREATE_SERVICE, который позволяет создавать сервисы.

Для создания сервиса используется функция CreateService(), которая возвращает дескриптор созданного сервиса или NULL в случае неудачного выполнения. Одним из параметров функции является путь к сервису, который должен соответствовать расположению сервиса в Вашем компьютере. Проследите, чтобы путь был указан правильно, иначе создать сервис не получится.

В качестве типа сервиса мы указали SERVICE_WIN32_OWN_PROCESS. Это означает, что сервис будет выполняться как отдельный процесс. При успешном завершении функция CreateService() добавляет сервис в базу данных SCM. Вы можете в этом убедиться, открыв системную панель управления сервисами (Панель управления -> Администрирование -> Службы).

Для открытия сервиса мы используем функцию OpenService(). Третьим параметром этой функции является тип доступа к сервису. Указывая тип доступа SERVICE_START, мы получаем возможность запускать сервис.

Запуск сервиса осуществляется с помощью функции StartService(), которая возвращает нулевое значение, если запустить сервис не удалось.

На этот раз, открывая сервис, мы указываем тип доступа SERVICE_QUERY_CONFIG, чтобы получить информацию о конфигурации сервиса. Эту информацию мы помещаем в структуру типа QUERY_SERVICE_CONFIG (через указатель на эту структуру lpBufConfig, используя динамически выделяемую память) с помощью функции QueryServiceConfig(). Функция QueryServiceConfig() помещает в структуру типа QUERY_SERVICE_CONFIG информацию о сервисе, которая находится в реестре. Эта информация была помещена в реестр функцией CreateService().

Далее мы помещаем содержимое некоторых полей структуры в буфер szBufConfig и выводим его содержимое с помощью функции MessageBox().

Здесь мы открываем сервис, используя уровень доступа SERVICE_STOP.

Для остановки сервиса используется функция ControlService(). Эта функция посылает сервису управляющий код, который она получает в качестве второго параметра. В нашем случае управляющий код равен константе SERVICE_CONTROL_STOP. Третьим параметром функции является адрес структуры типа SERVICE_STATUS, в которой содержится информация о текущем статусе сервиса. Содержимое этой структуры использует SCM. Если сервис остановить не удалось функция ControlService() возвращает нулевое значение.

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

Функции WndProcOnDestroy() и GetSomeSvcError()

Приведем код функции WndProcOnDestroy() и функции GetSomeSvcError(), которая используется для вывода сообщений об ошибках:

В функции GetSomeSvcError() анализируется код ошибки, полученный с помощью функции Win API GetLastError(). Полный список кодов ошибок и их значений можно посмотреть в файле WinError.h или в документации по Win API.

Написание сервисов windows nt на winapi

Поместить в файл с расширением dpr, например, srv1.dpr.
Установка службы — srv1.exe /Install
Удаление службы — srv1.exe /Delete
Имя службы — «Sample_Srv»
Выполняемое действие — периодический звуковой сигнал beep()
Источник — кажется, эта статья
http://ishodniki.ru/art/artshow.php?cat=1& >»Написание сервисов Windows NT на WinAPI»

Служба будет периодически выдавать окно Предупреждения.
Чтобы показывалось это окно сообщения, установите
Мой компьютер-Правый клик-Управление-Службы и приложения-Службы (англ. Services)
В списке выберите свою службу (в данном случае Sample_Srv).
Свойства — закладка Вход в систему — установить флажок
«Разрешить взаимодействие с рабочим столом» (англ. «Interact with desktop»).

В чем суть WinApi?

Windows API — это самый низкоуровневый интерфейс Windows, доступный прикладному программисту — в том плане, что он на долгосрочной поддержке и не изменится с Windows 11.

Поверх Windows API работают все BOOST и STL.

Пример: читать файл в 130 мегабайт по одному байту. Добавив асинхронного чтения через OVERLAPPED, я сумел это сделать менее чем за 2 секунды (это был поток общего назначения с виртуальными read(), write() и seek(); специализированный прикладной буфер даст ещё выигрыша, но и это хорошо). То же самое через FILE* — не дождался.

Пример второй, всё те же файлы. Дело в том, что Excel захватывает свои файлы на всё время, пока он открыт. Закрывать? — плохой выбор. Добавив один флажок в CreateFile, документы всё-таки стало возможным открывать при работающем Excel.

Как создать и установить службу Windows при помощи WinApi на C/C++

У меня есть задача создать службу Windows при помощи WinApi и ее установщик на C/C++. Это должен быть Win32-проект. Буду очень признателен, если кто-нибудь прольет свет в этом вопросе.

2 ответа 2

Google лучше всех проливает свет. )

А мне нравится вот эта статья. По ней и писал свои первые службы..

Если собираетесь создавать интерактивные службы, советую обратить внимание, что, начиная с Windows Vista, службам запрещено взаимодействие с рабочим столом — поэтому никаких MessageBox, DialogBox и прочего в собственно службе быть не должно. Так что придется писать отдельное приложение, каким-либо образом подключаться к службе (например, используя TCP/IP) и дальше по задуманному.

LAN, WinAPI и другие

Ad cogitandum et agendum homo natus est
Мыслить и действовать рожден человек

Вот и настал светлый миг в твоей жизни — трясущимися после вчерашнего руками ты возишь мышку по давно немытому коврику, в надежде выцепить из Сети чего-нибудь новенького и полезненького и немедленно воплотить его в жизнь. Как в песне поется «на радость народу, врагам на погибель». Могу сказать тебе — зайдя на сайт ][ ты не ошибся адресом, да ты и сам это знаешь. Что ж, приступим.

Поговорим сегодня о LAN’e, она же Local Area Network, она же ЛВС, а в просторечии локалка или локальная вычислительная сеть. Как ты помнишь, локалки бывают одноранговые и с выделенным сервером. Простейшая одноранговая сеть делается обычно по причине а) лени б) бедности. Яркий пример: есть, например, в офисе два компьютера и один принтер. Кому поставить принтер? Не вопрос — конечно, себе, любимому. Тем более, если он цветной :). Однако если сосед по комнате — твой начальник, то принтер будет стоять у него, можешь мне поверить. Он тоже себя любит больше, чем тебя. Как ни странно. А для того, чтобы не чувствовать себя обделенным таким нужным сервисом (на чем же тёток из Интернета ты будешь печатать?), ты идешь в первый попавшийся компьютерный магазин (хотя можно пойти и во второй или третий), покупаешь две сетевые карточки, кусок кабеля, коннекторы-терминаторы. Карточки вставляешь в компы, соединяешь их кабелем, устанавливаешь протокол TCP/IP, делаешь себе доступ к принтеру, и — дело в шляпе. Хотя с другой стороны, можно всего этого и не делать. Можно бегать к своему сокамернику с пачкой дискет и дрожащим голосом просить распечатать парочку файлов. И если тебя через неделю не пошлют на три хорошо известные буквы, то через две недели тебя просто уволят. Опять же, можно купить еще один цветной лазерный или струйный принтер, но если ты не любимый племянник директора или, на худой конец, дяди Билла, в чем лично я что-то сомневаюсь, то, скорее всего, этого тебе сделать не дадут.

Но не будем о печальном. Одноранговая сетка — это конечно хорошо. Но для централизации управления доступом к ресурсам БОЛЬШОЙ КОРПОРАТИВНОЙ СЕТИ ставят сервер, часто под управлением Windows NT/2000, организовывают домен, создают глобальные и локальные группы, учетные записи пользователей, содержащие их логины и пароли, а также другую информацию. А вот это уже интересно — ведь ты бы хотел посмотреть, какие группы есть на сервере, кто имеет право входить в сетку твоей конторы, да и вообще, если ты читаешь ][, то я думаю у тебя еще не атрофировалось самое человеческое качество (это я о любопытстве).

Тут, как зачастую бывает в жизни, есть несколько путей-дорог. Можно сходить к админу и попросить его показать структуру домена, учетные записи пользователей, а заодно и ключ от квартиры, где деньги лежат. Итог этой беседы, скорее всего, будет печален для одной из сторон. Не будем конкретизировать, для какой. Можно воспользоваться утилитами, которые входят в состав Windows NT/2000 (ты же не пользуешься Win9x, я надеюсь?), можно взять софтину «стороннего производителя», а можно, как говорил классик, пойти другим путём. Вот мы им и пойдем.

Мы идём другим путём

На просторах СНГ любят Делфи. Это — аксиома. Да и как же его не любить, если многие из нас вышли из ДОСа и росли вместе с Turbo/Borland Паскалем, который фирма Борланд долгое время не защищала от копирования. А ни что так не было любимо советским человеком, как халява (по вполне понятным, должен я сказать, и объективным причинам). Вот и плодились по институтам-университетам Союза незаконно размноженные копии программ известной фирмы, на которых учили программистскому делу юных комсомольцев, многие из которых так и не успели стать коммунистами (что радует). Но — хватит лирических отступлений. Перейдем к делу, запустим Делфи и посмотрим, чем она сможет нам помочь в деле запускания своих ручек к LAN в карман.

Как ты уже наверняка знаешь, M$ Windows написана на С, и многие функции этой ОСи собраны в dynamic-link libraries (DLL), которые представляют из себя файлы-библиотеки, естественно, бесплатные :). Использование этих функций и позволит нам узнать много интересного. Итак, из меню Help запустим справку Windows SDK, в строке поиски наберем что-нибудь типа NetServerGetInfo. Нажимаем Enter и видим описание этой функции — кто дружит с аглицким языком, тот поймет, что с ее помощью мы можем получить некую информацию о сервере, указанного типа, видимых в указанном домене. «Всё это хорошо, — скажешь ты. А как мне использовать эту функцию в Делфи?» И будешь совершенно прав в своём праведном возмущении. Для того чтобы что-то использовать, надо это что-то сначала получить. Нажимаем на кнопочку Quick Info и видим такую картину: Import Library netapi32.lib, Header File Imserver.h. То есть для использования этой функции, ее надо импортировать из библиотеки netapi32. Чем мы сейчас и займёмся.

Итак, для того чтобы осуществить эту нехитрую операцию, кинем на форму компоненты класса TListBox(для отображения результатов работы функции), TButton (в обработчике OnClick которого мы будем выполнять полученную функцию), TComboBox (в котором можно будет указывать имя сервера) , а также (для удобства) кнопку закрытия этого приложения. В целом картина должна выглядеть примерно так как на рис.1 (p1_export.jpg).
Затем нужно добавить код, исполняющий функцию и обрабатывающий её результат (листинг 1). Разберём его подробнее. В начале листинга идёт код, традиционно написанный мисс Делфи, в то время как мы своими руками занимались дизайном формы. Далее объявлены типы, необходимые для работы с функцией. Переменная типа NET_API_STATUS будет содержать код результата выполнения функции, а переменная типа PServerInfo101 — указатель на запись с результатами выполнения этой функции. Далее идёт блок кода, содержащий объявления трех функций, необходимых нам для работы. NetApiBufferAllocate и NetApiBufferFree — функции, необходимые для выделения и освобождения области памяти заданного размера, они пригодятся нам при работе с любой функцией из группы портированных из LAN Manager. NetServerGetInfo — одна из функций этой группы, которая, как можно догадаться из названия, выдаёт нам информацию о данном сервере. Всю инфу о типах передаваемых функции переменных и возвращаемых ею значениях смотрим там же, в многострадальном хэлпе Windows SDK. Да, не забудь добавить после объявления функции волшебное слово stdcall, поскольку наша функция будет вызываться из внешнего модуля, который является частью API сей оси. Затем мы должны указать из какой DLL и под какими именами мы с тобой хотим поиметь эти функции и если испарина уже покрыла твой высокий лоб, то можешь отхлебнуть пива, кофе, водки на худой конец или что там плещется в твоем гранёном стакане? И пойдем дальше.

Листинг 1. Пример использования внешней функции NetServerGetInfo (архив
ExportDllTest.zip)

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

type
TfrmMain = class(TForm)
btnMain: TButton;
lbMain: TListBox;
cbMain: TComboBox;
lblMain: TLabel;
btnClose: TButton;
procedure btnMainClick(Sender: TObject);
procedure btnCloseClick(Sender: TObject);
end;

//Переменная типа NET_API_STATUS будет содержать код результата выполнения функции Win API
NET_API_STATUS = DWORD;

//переменная типа PServerInfo101 — указатель на запись с результатами выполнения функции Win //API
PServerInfo101 = ^_SERVER_INFO_101;
_SERVER_INFO_101 = record
sv101_platform_id: DWORD;
sv101_name: LPWSTR;
sv101_version_major: DWORD;
sv101_version_minor: DWORD;
sv101_type: DWORD;
sv101_comment: LPWSTR;
end;

//собственно используемая функция
function NetServerGetInfo(servername: LPWSTR; level: DWORD;
var bufptr: Pointer): NET_API_STATUS; stdcall;
// функция для выделения области памяти заданного размера
function NetApiBufferAllocate(ByteCount: DWORD;
var Buffer: Pointer): NET_API_STATUS; stdcall;
//функция освобождения области памяти заданного размера
function NetApiBufferFree(Buffer: Pointer): NET_API_STATUS; stdcall;
//указываем из какой DLL и под какими именами мы будем использовать внешние функции
function NetServerGetInfo; external ‘netapi32.dll’ name ‘NetServerGetInfo’;
function NetApiBufferAllocate; external ‘netapi32.dll’ name ‘NetApiBufferAllocate’;
function NetApiBufferFree; external ‘netapi32.dll’ name ‘NetApiBufferFree’;

var
frmMain: TfrmMain;

procedure TfrmMain.btnMainClick(Sender: TObject);
const
// level указывает функции, какой тип данных вернуть
level :DWORD = 101;
// в случае успешного выполнения, функция возвращает 0, т.е. NERR_SUCCESS
NERR_SUCCESS = 0;
var
//х содержит результат выполнения функции
x :NET_API_STATUS;
// переменная для хранения NetBIOS имени ПК в формате Unicode
servername :array [0..14] of WideChar;
//bufptr — нетипизированный указатель на область памяти, заполненную в результате выполнения
bufptr :pointer;
//myInfo — указатель на возвращаемую структуру
myInfo :PServerInfo101;
begin
//очищаем область вывода
lbMain.Items.Clear;
//конвертируем введенное имя ПК в Unicode-строку
StringToWideChar(cbMain.Text, servername,15);
//выделяем память
NetApiBufferAllocate(1000,bufptr);
//выполняем функцию
x:=NetServerGetInfo(servername, level, bufptr);

if x=NERR_SUCCESS then
begin
//если выполнение успешно, то выполняем присвоение указателей.
myInfo:=bufptr;
//. и выводим в область вывода полученную информацию
lbMain.Items.Add(‘1. ID платформы: ‘+IntToStr(myInfo.sv101_platform_id));
lbMain.Items.Add(‘2. Имя сервера : ‘+WideCharToString(myInfo.sv101_name));
lbMain.Items.Add(‘3. Версия :’ +IntToStr(myInfo.sv101_version_major)+’.’+IntToStr(myInfo.sv101_version_minor));
lbMain.Items.Add(‘5. Комментарий:’+WideCharToString(myInfo.sv101_comment));
end;//if ok
//в любом случае освобождаем выделенную память
NetApiBufferFree(bufptr);
end;//prc

procedure TfrmMain.btnCloseClick(Sender: TObject);
begin
Close
end;//prc

А дальше ты видишь блок кода, который вызывается в ответ на нажатие кнопки по имени btnMain. Первым делом объявим константы — тут они объявлены локально, поскольку в этом примере используется только одна функция, а вообще-то ничто не мешает их сделать глобальными. Итак, константа level будет содержать число, указывающее насколько подробный ответ мы хотим получить. Обычно, для любой функции из группы LAN Manager эта константа может принимать несколько значений, мы же возьмём 101, поскольку для выполнения функции на этом уровне нет никаких особых требований к членству в группах домена или рабочей станции(т.е. админом можешь ты не быть, но программистом быть обязан :)). Затем константа NERR_SUCCESS — ничего особенного, просто ноль, однако ноль — не всегда ничто, иногда это признак успеха. Как раз наш случай — если выполнение функции пройдет успешно, то будет возвращен этот самый ноль.

Дальше объявляем переменные: х — в нем будет храниться результат выполнения функции NetServerGetInfo, servername — NetBIOS имя опрашиваемого сервера, длина которого, если мне не изменяет память, не может быть больше 15 символов, bufptr — не типизированный указатель на область памяти с результатом выполнения, myInfo — указатель на запись.

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

От практики к теории или мы, кажется, притопали

«Ну, вот, — скажешь ты. Столько мучений и все ради чего?» А есть ради чего. Посмотри на функции, портированные из LAN Manager, и у тебя зачешутся руки :). Перечислю тебе только некоторые из них:

подгруппа File
функция NetFileEnum
предоставляет информацию об открытых файлах на сервере*

подгруппа Global group (одна из самых преспективных :))
функция NetGroupAdd
создает на сервере глобальную группу
функция NetGroupAddUser
добавляет к существующей глобальной группе указанного пользователя
функция NetGroupDel
удаляет глобальную группу
функция NetGroupDelUser
удаляет указанного пользователя из глобальной группы
(представь себе картину: заходит админ в сеть, а он уж больше не админ :))
функция NetGroupEnum
перечесляет все глобальные группы
функция NetGroupGetUsers
выдаёт список членов глобальной группы

подгруппа Local group (теже функции, что и для глобальных групп, только с заменой в названии NetGroup на NetLocalGroup)

подгруппа Server
функция NetServerEnum
перечисляет все ПК, видимые в указанном домене, можно указать признак для ПК. Т.е. отдать команду типа «а перечислить мне все ПК домена на которых запущен MS SQL Server». Занятная функция.
функция NetServerDiskEnum
возвращает список дисков на сервере

подгруппа User
функция NetUserAdd
добавляет пользователя, устанавливает пароль и права
функция NetUserEnum
возвращает учетные записи всех пользователей на на указанном сервере
функция NetUserGetGroups
возвращает список глобальных групп, членом которых является указанный пользователь
функция NetUserGetLocalGroups
возвращает список локальных групп, членом которых является указанный пользователь

Примечание. *В данном контексте и далее «сервер» — опрашиваемый ПК

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

Листинг 2. Пример использования функции NetLocalGroupEnum (архив
Lanv.zip)

procedure TfrmMain.btnLGShowClick(Sender: TObject);
var
//bufptr — нетипизированный указатель на область памяти, заполненную в результате выполнения
bufptr : pointer;
//prefmaxlen — максимальный объем возвращаемых данных в байтах
prefmaxlen,
// entriesread — количество считанных элементов (записей)
entriesread,
// totalentries — всего элементов (записей)
totalentries : DWORD;
//х содержит результат выполнения функции
x : NET_API_STATUS;
// resume_handle — указатель, использующийся для дальгейшего поиска в локальной группе
resume_handle
:PDWORD;
// указатель на возвращаемую структуру
myLG :PLocalGroupInfo1;
i :integer;
// переменная для хранения NetBIOS имени ПК в формате Unicode
servername :array [0..14] of WideChar;
begin
//конвертируем введенное имя ПК в Unicode-строку
StringToWideChar(cbLG.Text,servername,15);
//выбираем произвольный объем памяти для данных, возвращаемых функцией
prefmaxlen :=15000;
//очистка области вывода
lbLG.Items.Clear;
//обнуляем переменные для повторного использования
entriesread:=0;totalentries:=0;
//выделяем память для возвращаемых данных
NetApiBufferAllocate(15000, bufptr);
//указатель на хэндл существующей локальной группы, при первом вызове должен быть равен 0
new(resume_handle);resume_handle^:=0;
//выполняем функцию
x:=NetLocalGroupEnum(servername, 1, bufptr, prefmaxlen, entriesread, totalentries, resume_handle);
//выполняем присвоение указателей
myLG:=bufptr;
//усли выполнение функции успешно.
if x=NERR_SUCCESS then begin
//. и была хоть одна локальная группа.
if totalentries>0 then
//. то заполняем область вывода.
lbLG.Items.Add(‘Группа:’+WideCharToString(myLG^.lgrpi1_name)+’;’+’Комментарий:’
+WideCharToString(myLG^.lgrpi1_comment));
//. для всех считанных вхождений
for i:=1 to entriesread-1 do begin
Inc(myLG,1);
lbLG.Items.Add(‘Группа:’+WideCharToString(myLG^.lgrpi1_name)+’;’+’Комментарий:’+
WideCharToString(myLG^.lgrpi1_comment));
end;//for
end;//if

if x<>NERR_SUCCESS then ShowMessage(‘Ошибка выполнения’);
//освобождаем память
NetApiBufferFree(bufptr);
dispose(resume_handle);
end;//prc

ЧТО ТАКОЕ WIN32 API?

API (Application Programming Interface) — интерфейс программирования приложений и всегда связан с другим приложением. Например, Microsoft Excel, Lotus Organizer и множество других приложений имеют API. Pазработчики программного обеспечения не покупают программный интерфейс, они строят его при создании приложений.

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

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

Илон Маск рекомендует:  file_exists - Проверить наличие указанного файла или каталога

Рассмотрев, как используется API, требуется описать, что такое API? API — это обычно не более чем просто набор функций, с помощью которых можно обратиться к средствам разрабатываемого приложения. Программа, реализующая API, часто занимает не больше 10 или 20 процентов всего приложения, однако, она должна обеспечивать доступ к 100 процентам функций этого приложения.

Win32 API идеально подходит под это описание: он обеспечивает доступ практически ко всем функциям Windows 95/98 и Windows NT. Win32 API помогает Windows 95/98 и Windows NT управлять памятью, различными устройствами, например принтером, обрабатывать события, рисовать на экране диалоговые окна и т. д.

Кроме того, Win32 API поддерживает связь одного приложения с другим. Например, большая часть Windows 9х является встроенной поддержкой сетей. Конечно, эта часть должна также выводить диалоговые окна, отображать сообщения и управлять памятью. В ней используются функции API, которые можно применять в разрабатываемом приложении VBA.

Во многих программах, например, Microsoft Excel и Lotus cc:Mail, также используется Win32 API. Если приложению или модулю Windows 9х или Wiindows NT требуется некоторое средство, то обычно вызывается функция Win32 API.

Использование библиотек динамической компоновки

Win32 API оформлен в виде библиотек динамической компоновки. В этих библиотеках хранятся все средства, к которым требуется обеспечить доступ в других приложениях. Библиотеки динамической компоновки получили такое имя благодаря тому, что приложения подключаются к ним во время выполнения и используют их функции.

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

Одни файлы библиотек динамической компоновки имеют расширение DLL, другие — расширение ЕХЕ. Следующие файлы составляют большую часть Win32 API:

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

Когда нужно использовать Win32 API?

С помощью Win32 API можно использовать в разрабатываемом приложении не только средства VBA или основного приложения, но и те же фунции, что применяет Windows 9х или Windows NT. Эти средства позволяю пример, управлять памятью или создавать диалоговые окна для установки системного времени. Хотя в проекте VBA обычно используется только процент функций Win32 API, однако доступны практически все 100 процентов.

Win32 АPI включает более 1500 функций, поэтому здесь невозможно описать каждое средство. Вместо этого приводится классификация функцией API:

Управление Windows. Данная группа функций управляет рисованием окон на экране, а также обрабатывает нажатия клавиш и действия мышью при работе с окнами.

Элементы управления Windows. Данная группа функций управляет инструментами управления, используемыми в приложениях Windows, например, полями, кнопками и списками, а также стандартными диалоговыми окнами, такими как диалоговые окна «Открытие файла» и «Печать».

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

Графические средства. Win32 API включает большое количество функций, которые управляют графическими элементами окон приложений и самой операционной системой. Данная группа включает базисные функции, которые управляют рисованием точек на экране, а также цветом и печатью.

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

Языковая поддержка. Данная группа обеспечивает языковую поддержку для Windows 9х, Windows NT и их приложений.

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

Подробную информацию о группах и функциях Win32 API смотрите в руководстве по Win32 SDK, которое поставляется Microsoft.

Не нашли то, что искали? Воспользуйтесь поиском:

Работа с функциями Windows API и DLL

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

Windows API — набор функций операционной системы

Аббревиатура API многим начинающим программистам кажется весьма таинственной и даже пугающей. На самом же деле Application Programming Interface (API) — это просто некоторый готовый набор функций, который могут использовать разработчики приложений. В общем случае данное понятие эквивалентно тому, что раньше чаще называли библиотекой подпрограмм. Однако обычно под API подразумевается особая категория таких библиотек.

В ходе разработки практически любого достаточно сложного приложения (MyAppication) для конечного пользователя формируется набор специфических внутренних функций, используемых для реализации данной конкретной программы, который называется MyApplication API. Однако часто оказывается, что эти функции могут эффективно использоваться и для создания других приложений, в том числе другими программистами. В этом случае авторы, исходя из стратегии продвижения своего продукта, должны решить вопрос: открывают они доступ к этому набору для внешних пользователей или нет? При утвердительном ответе в описании программного пакета в качестве положительной характеристики появляется фраза: «Комплект включает открытый набор API-функций» (но иногда за дополнительные деньги).

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

Соответственно Windows API — это набор функций, являющийся частью самой операционной системы и в то же время — доступный для любого другого приложения, в том числе написанного с помощью VB. В этом плане вполне оправданна аналогия с набором системных прерываний BIOS/DOS, который фактически представляет собой DOS API.

Отличие заключается в том, что состав функций Windows API, с одной стороны, значительно шире по сравнению с DOS, с другой — не включает многие средства прямого управления ресурсами компьютера, которые были доступны программистам в предыдущей ОС. Кроме того, обращение к Windows API выполняется с помощью обыкновенных процедурных обращений, а вызов функций DOS — через специальную машинную команду процессора, которая называется Interrupt («прерывание»).

Win16 API и Win32 API

Как известно, смена Windows 3.x на Windows 95 ознаменовала собой переход от 16-разрядной архитектуры операционной системы к 32-разрядной. Одновременно произошла замена 16-разрядного Windows API (Win16 API) на новый 32-разрядный вариант (Win32 API) — о некоторых аспектах этого перехода будет упомянуто в этой главе. В данном случае нужно просто иметь в виду, что, за небольшим исключением, набор Win32 API является единым для семейств Windows 9x и Windows NT.

Далее в этой статье под термином API будет подразумеваться Win API, более того, по умолчанию — Win32 API.

Зачем нужен Win API для VB-программистов

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

При знакомстве с Win API обнаруживается, что многие встроенные VB-функции — не что иное, как обращение к соответствующим системным процедурам, но только реализованное в виде синтаксиса данного языка. С учетом этого необходимость использования API определяется следующим вариантами:

  1. API-функции, которые полностью реализованы в виде встроенных VB-функций. Тем не менее иногда и в этом случае бывает полезным перейти к применению API, так как это позволяет порой существенно повысить производительность (в частности, за счет отсутствия ненужных преобразований передаваемых параметров).
  2. Встроенные VB-функции реализуют лишь частный случай соответствующей API-функции. Это довольно обычный вариант. Например, API-функция CreateDirectory обладает более широкими возможностями по сравнению со встроенным VB-оператором MkDir.
  3. Огромное число API-функций вообще не имеет аналогов в существующем сегодня варианте языка VB. Например, удалить каталог средствами VB нельзя — для этого нужно использовать функцию DeleteDirectory.

Следует также подчеркнуть, что некоторые API-функции (их доля в Win API весьма незначительна) не могут вызываться из VB-программ из-за ряда ограничений языка, например из-за отсутствия возможности работы с адресами памяти. Но в ряде случаев могут помочь нетривиальные приемы программирования (в частности, в случае с теми же адресами).


Личная точка зрения автора такова — вместо расширения от версии к версии встроенных функций VВ следовало бы давать хорошее описание наиболее ходовых API-функций. В то же время хочется посоветовать разработчикам не ждать появления новой версии средства с расширенными функциями, а внимательнее изучить состав существующего Win API — вполне вероятно, что нужные вам возможности можно было реализовать уже в версии VB 1.0 выпуска 1991 года.

Как изучать Win API

Это не такой простой вопрос, если учесть, что число функций Win32 API оценивается величиной порядка 10 тысяч (точной цифры не знает никто, даже Microsoft).

В состав VB (версий 4-6) входит файл с описанием объявлений Win API — WIN32API.TXT (подробнее о его применении мы расскажем позднее). Но, во-первых, с его помощью можно получить сведения о назначении той или иной функции и ее параметрах только по используемым мнемоническим именам, а во-вторых — перечень функций в этом файле далеко не полный. В свое время (семь лет назад) в VB 3.0 имелись специальные справочные файлы с описанием функций Win16 API. Однако уже в v.4.0 эта полезная информация с удобным интерфейсом исчезла.

Исчерпывающую информацию о Win32 API можно найти в справочной системе Platform Software Development Kit, которая, в частности, находится на компакт-дисках MSDN Library, включенных в состав VB 5.0 и 6.0 Enterprise Edition и Office 2000 Developer Edition. Однако разыскать там нужную информацию и разобраться в ней совсем не просто. Не говоря уж о том, что все описания там приводятся применительно к языку C.

Общепризнанным в мире пособием для изучения API-программирования в среде VB являются книги известного американского эксперта Даниэля Эпплмана (Daniel Appleman). Его серия Dan Appleman’s Visual Basic Programmer’s Guide to the Windows API (для Win16, Win32, применительно к разным версиям VB) с 1993 года неизменно входит в число бестселлеров для VB-программистов. Книгу Dan Appleman’s VB 5.0 Programmer’s Guide to the Win32 API, выпущенную в 1997 году, автору привез из США приятель, который нашел ее в первом же книжном магазине небольшого провинциального городка.

Эта книга объемом свыше 1500 страниц включает описание общей методики API-программирования в среде VB, а также более 900 функций. Прилагаемый компакт-диск содержит полный текст книги и всех программных примеров, а кроме того, несколько дополнительных глав, не вошедших в печатный вариант. В 1999 году Дэн Эпплман выпустил новую книгу Dan Appleman’s Win32 API Puzzle Book and Tutorial for Visual Basic Programmers, которая включает сведения о еще 7600 функциях (хотя и не столь обстоятельные).

Набор Win API реализован в виде динамических DLL-библиотек. Далее речь фактически пойдет о технологии использования DLL в среде VB на примере библиотек, входящих в состав Win API. Однако, говоря о DLL, необходимо сделать несколько важных замечаний.

В данном случае под DLL мы подразумеваем традиционный вариант двоичных динамических библиотек, которые обеспечивают прямое обращение приложений к нужным процедурам — подпрограммам или функциям (примерно так же, как это происходит при вызове процедур внутри VB-проекта). Такие библиотеки могут создаваться с помощью разных инструментов: VC++, Delphi, Fortran, кроме VB (посмотрим, что появится в версии 7.0) — последний может делать только ActiveX DLL, доступ к которым выполняется через интерфейс OLE Automation.

Обычно файлы динамических библиотек имеют расширение .DLL, но это совсем не обязательно (для Win16 часто применялось расширение .EXE); драйверы внешних устройств обозначаются с помощью .DRV.

Как мы уже отмечали, определить точное число API-функций Windows и содержащих их файлов достаточно сложно, однако все они находятся в системном каталоге. В этом плане лучше выделить состав библиотек, входящих в ядро операционной системы, и основных библиотек с ключевыми дополнительными функциями.

А теперь несколько советов.

Совет 1. Следите за правильным оформлением объявления DLL-процедур

Само обращение к DLL-процедурам в программе выглядит точно так же, как к «обычным» процедурам Visual Basic, например:

Однако для использования внешних DLL-функций (в том числе и Win API) их нужно обязательно объявить в программе с помощью оператора Declare, который имеет следующий вид:

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

Объявления внешних функций должны размещаться в секции General Declarations модуля. Если вы размещаете его в модуле формы, то обязательно нужно указать ключевое слово Private (это объявление будет доступно только внутри данного модуля) — таково ограничение для всех процедур модуля формы.

Набор Win32 API реализован только в виде функций (в Win16 API было много подпрограмм Sub). В большинстве своем — это функции типа Long, которые чаще всего возвращают код завершения операции.

Оператор Declare появился в MS Basic еще во времена DOS, причем он использовался и для объявления внутренних процедур проекта. В Visual Basic этого не требуется, так как объявлением внутренних процедур автоматически является их описание Sub или Function. По сравнению с Basic/DOS в новом описании обязательно указывать имя файла-библиотеки, где находится искомая процедура. Библиотеки Wip API размещаются в системном каталоге Windows, поэтому достаточно привести только название файла. Если же вы обращаетесь к DLL, которая находится в произвольном месте, нужно записать полный путь к данному файлу.

Описание оператора Declare обычно занимает довольно много места и не помещается в одну строку в окне кода. Поэтому мы рекомендуем придерживаться при написании приложений какой-либо определенной схемы переноса строк, например:

В этом случае все основные элементы описания разнесены на разные строчки и поэтому хорошо читаются.

Совет 2. Будьте особенно внимательны при работе с DLL-функциями

Использование Win API и разнообразных DLL-функций существенно расширяет функциональные возможности VB и зачастую позволяет повысить производительность программ. Однако расплата за это — риск снижения надежности работы приложения, особенно в процессе его отладки.

Одним из самых важных достоинств среды VB является надежность процесса разработки программ: функционируя под управлением интерпретатора, программный код теоретически не может нарушить работу Windows и самого VB. Программист может не очень внимательно следить за правильностью передачи параметров в вызываемые функции — подобные ошибки будут легко обнаружены самим интерпретатором либо в процессе трансляции кода, либо во время его выполнения. В самом неприятном случае просто произойдет прерывание режима обработки, причем с указанием, где и почему произошла ошибка.

Использование напрямую функций Windows API или других DLL-библиотек снимает такой контроль за передачей данных и процессом выполнения кода вне среды VB. Поэтому ошибка в обращении к внешним функциям может привести к неработоспособности и VB и операционной системы. Это особенно актуально на этапе разработки программы, когда наличие ошибок — дело вполне естественное. Таким образом, применяя более широкие возможности функций базового слоя системы, программист берет на себя ответственность за правильность их применения.

Проблема усугубляется еще и тем, что разные языки программирования используют различные способы передачи параметров между процедурами. (Точнее, разные способы передачи используются по умолчанию, так как многие языки могут поддерживать несколько способов.) Win API реализованы на C/C++ и применяют соглашения о передаче параметров, принятые в этой системе, которые отличаются от привычного для VB варианта.

В связи с этим следует отметить, что появление встроенных в VB аналогов API-функций оправданно именно адаптацией последних к синтаксису VB и реализацией соответствующего механизма контроля обмена данными. Обратим также внимание, что на этапе опытной отладки приложения при создании исполняемого модуля лучше использовать вариант компиляции P-code вместо Native Code (машинный код). В первом случае программа будет работать под управлением интерпретатора — медленнее по сравнению с машинным кодом, но более надежно с точки зрения возможного ошибочного воздействия на операционную систему и обеспечивая более удобный режим выявления возможных ошибок.

Совет 3. Десять рекомендаций Дэна Эпплмана по надежному API-программированию в среде VB

Использование функции API требует более внимательного программирования с использованием некоторых не очень привычных методов обращения к процедурам (по сравнению с VB). Далее мы будем постоянно обращаться к этим вопросам. А сейчас приведем изложение сформулированных Дэном Эпплманом советов на эту тему (их первый вариант появился еще в 1993 году) с некоторыми нашими дополнениями и комментариями.

1. Помните о ByVal. Наиболее частая ошибка, совершаемая при обращении к функциям API и DLL, заключается в некорректном использовании ключевого слова ByVal: его или забывают ставить, или, наоборот, ставят, когда в нем нет необходимости.

На этих примерах показано влияние оператора ByVal на передачу параметров

Тип параметра С ByVal Без ByVal
Integer В стек помещается 16-разрядное целое В стек помещается 32-разрядный адрес 16-разрядного целого
Long В стек помещается 32-разрядное целое В стек помещается 32-разрядный адрес 32-разрядного целого
String Строка преобразуется в формат, используемый в С (данные и завершающий нулевой байт). 32-разрядный адрес новой строки помещается в стек В стек помещается VB-дескриптор строки. (Такие дескрипторы никогда не используются самим Windows API и распознаются только в DLL, реализованных специально для VB.)

Здесь следует напомнить, что передача параметров в любой системе программирования, в том числе и VB, выполняется двумя основными путями: по ссылке (ByRef) или по значению (ByVal). В первом случае передается адрес переменной (этот вариант используется в VB по умолчанию), во втором — ее величина. Принципиальное отличие заключается в том, что с помощью ссылки обеспечивается возврат в вызывающую программу измененного значения передаваемого параметра.

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

Запустив на выполнение этот пример, вы получите сообщение со значением переменной, равным 3. Дело в том, что в данном случае в подпрограмму MyProc передается адрес переменной v, физически созданной в вызывающей программе. Теперь измените описание процедуры на

В результате при выполнении теста вы получите v = 2, потому что в процедуру передается лишь исходное значение переменной — результат выполненных с ним операций не возвращается в вызывающую программу. Режим передачи по значению можно поменять также с помощью оператора Call следующим образом:

Однако при обращении к внутренним VB-процедурам использование в операторе Call ключевого слова ByVal запрещено — вместо него применяются круглые скобки. Этому есть свое объяснение.

В классическом случае (С, Fortran, Pascal) различие режимов ByRef и ByVal зависит от того, что именно помещается в стек обмена данными — адрес переменной или ее значение. В Basic исторически используется вариант программной эмуляции ByVal — в стеке всегда находится адрес, но только при передаче по значению для этого создается временная переменная. Чтобы отличить два этих варианта (классический и Basic), используются разные способы описания режима ByVal. Отметим, что эмуляция режима ByVal в VB обеспечивает более высокую надежность программы: перепутав форму обращения, программист рискует лишь тем, что в вызывающую программу вернется (или не вернется) исправленное значение переменной. В «классическом» же варианте такая путаница может привести к фатальной ошибке при выполнении процедуры (например, когда вместо адреса памяти будет использоваться значение переменной, равное, скажем, нулю).

DLL-функции реализованы по «классическим» принципам и поэтому требуют обязательного описания того, каким образом происходит обмен данными с каждым из аргументов. Именно этой цели служат объявления функций через описание Declare (точнее, списка передаваемых аргументов). Чаще всего передача параметров в функцию Windows API или DLL выполняется с помощью ключевого слова ByVal. Причем оно может быть задано как в операторе Declare, так и непосредственно при вызове функции.

Последствия неправильной передачи параметров легко предугадать. В случае получения явно недопустимого адреса вам будет выдано сообщение GPF (General Protection Fault — ошибка защиты памяти). Если же функция получит значение, совпадающее с допустимым адресом, то функция API залезет в чужую область (например, в ядро Windows) со всеми вытекающими отсюда катастрофическими последствиями.

2. Проверяйте тип передаваемых параметров. Не менее важны верное число и тип передаваемых параметров. Необходимо, чтобы объявленные в Declare аргументы соответствовали ожидаемым параметрам в функции API. Наиболее часто встречающийся случай ошибки в передаче параметров связан с различием между NULL и строкой нулевой длины — следует помнить, что это не одно и то же.

3. Проверяйте тип возвращаемого значения.

VB довольно терпимо относится к несовпадению типов возвращаемых функцией значений, поскольку числовые значения обычно возвращаются через регистры, а не через стек. Следующие правила помогут определить корректное значение, возвращаемое функцией API:

  • DLL-функция, не возвращающая значения (аналог void в ‘C’), должна быть объявлена как VB Sub.
  • функция API, возвращающая целое значение (Integer или Long), может быть определена или как Sub, или как Function, возвращающая значение соответствующего типа.
  • ни одна из функций API не возвращает числа с плавающей точкой, но некоторые DLL вполне могут возвращать такой тип данных.

4. С большой осторожностью используйте конструкцию «As Any». Множество функций Windows API имеют возможность принимать параметры различных типов и используют при этом обращение с применением конструкции As Any (интерпретация типа выполняется в зависимости от значения других передаваемых параметров).

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

5. Не забывайте инициализировать строки. В Win API существует множество функций, возвращающих информацию путем загрузки данных в передаваемые как параметр строковые буферы. В своей программе вы можете вроде бы все сделать правильно: не забыть о ByVal, верно передать параметры в функцию. Но Windows не может проверить, насколько велик размер выделенного под строку участка памяти. Размер строки должен быть достаточным для размещения всех данных, которые могут быть в него помещены. Ответственность за резервирование буфера нужного размера лежит на VB-программисте.

Следует отметить, что в 32-разрядных Windows при использовании строк производится преобразование из Unicode (двухбайтовая кодировка) в ANSI (однобайтовая) и обратно, причем с учетом национальных установок системы. Поэтому для резервирования буферов порой удобнее использовать байтовые массивы вместо строковых переменных. (Подробнее об этом будет рассказано ниже.)

Чаще всего функции Win API позволяют вам самим определить максимальный размер блока. В частности, иногда для этого нужно вызвать другую функцию API, которая «подскажет» размер блока. Например, GetWindowTextLength позволяет определить размер строки, необходимый для размещения заголовка окна, получаемого функцией GetWindowText. В этом случае Windows гарантирует, что вы не выйдете за границу.

6. Обязательно используйте Option Explicit.

7. Внимательно проверяйте значения параметров и возвращаемых величин. VB обладает хорошими возможностями по проверке типов. Это означает, что, когда вы пытаетесь передать неверный параметр в функцию VB, самое плохое, что может случиться, — вы получите сообщение об ошибке от VB. Но данный механизм, к сожалению, не работает при обращении к функциям Windows API.

Windows 9x обладает усовершенствованной системой проверки параметров для большинства функций API. Поэтому наличие ошибки в данных обычно не вызывает фатальной ошибки, однако определить, что же явилось ее причиной — не так-то просто.

Здесь можно посоветовать использовать несколько способов отладки ошибки данного типа:

  • используйте пошаговый режим отладки или команду Debug.Print для проверки каждого подозрительного вызова функции API. Проверьте результаты этих вызовов, чтобы удостовериться, что все в пределах нормы и функция корректно завершилась;
  • используйте Windows-отладчик типа CodeView и отладочную версию Windows (имеется в Windows SDK). Эти средства могут обнаружить ошибку параметров и по меньшей мере определить, какая функция API приводит к ошибке;
  • используйте дополнительные средства третьих фирм для проверки типов параметров и допустимости их значений. Такие средства могут не только находить ошибки параметров, но даже указать на строку кода VB, где произошла ошибка.

Кроме того, нужно обязательно проверять результат выполнения API-функции.

8. Помните, что целые числа в VB и в Windows — не одно и то же. В первую очередь следует иметь в виду, что под термином «Integer» в VB понимается 16-разрядное число, в документации Win 32 — 32-разрядное. Во-вторых, целые числа (Integer и Long) в VB — это величины со знаком (то есть один разряд используется как знак, остальные — как мантисса числа), в Windows — используются только неотрицательные числа. Это обстоятельство нужно иметь в виду, когда вы формируете передаваемый параметр с помощью арифметических операций (например, вычисляете адрес с помощью суммирования некоторой базы и смещения). Для этого стандартные арифметические функции VB не годятся. Как быть в этом случае, мы поговорим отдельно.

9. Внимательно следите за именами функций. В отличие от Win16 имена всех функций Win32 API являются чувствительными к точному использованию строчных и прописных букв (в Win16 такого не было). Если вы где-то применяете строчную букву вместо прописной или наоборот, то нужная функция не будет найдена. Следите также за правильным использованием суффикса A или W в функциях, применяющих строковые параметры. (Подробнее об этом – см. ниже.)

Илон Маск рекомендует:  Что такое код asp getlistcount

10. Чаще сохраняйте результаты работы. Ошибки, связанные с неверным использованием DLL и Win API, могут приводить к аварийному завершению работы VB-среды, а возможно — и всей операционной системы. Вы должны позаботиться о том, чтобы написанный вами код перед тестовым запуском был сохранен. Самое простое — это установить режим автоматической записи модулей проекта перед запуском проекта в среде VB.

Совет 4. Не нужно бояться применять Win API

После прочтения предыдущего совета может возникнуть мысль, что использование функций Win API — дело рискованное. В какой-то степени это так, но только в сравнении с безопасным программированием, предоставляемым самим VB. Но при умелом их применении и знании возможных подводных камней этот риск минимален. К тому же полностью отказаться от применения Win API зачастую просто невозможно — они все равно потребуются при сколь-нибудь серьезной разработке.

К тому же ранее мы упоминали о «подводных» камнях для широкого класса DLL. В случае с Win API все обстоит гораздо проще, так как здесь четко унифицирована форма обращения к этим функциям. При этом следует иметь в виду следующие основные моменты:

    Функции Win32 API являются именно функциями, то есть процедурами типа Function (в Win16 API было много подпрограмм Sub). Все это функции типа Long, поэтому их описания записываются в следующем виде:

Обращение к API-функции выглядит так:

  1. Чаще всего возвращаемое значение функции является кодом завершения операции. Причем ненулевое значение означает в данном случае нормальное завершение, нулевое — ошибку. Обычно (но не всегда) уточнить характер ошибки можно с помощью обращения к функции GetLastError. Описание этой функции имеет такой вид:

ВНИМАНИЕ! При работе в среде VB для получения значения уточненного кода ошибки лучше использовать свойство LastDLLError объекта Err, так как иногда VB обнуляет функцию GetLastError в промежутке между обращением к API и продолжением выполнения программы.

Интерпретировать код, возвращаемый GelLastError, можно с помощью констант, записанных в файле API32.TXT, с именами, начинающимися с суффикса ERROR_.

Наиболее типичные ошибки имеют следующие коды:

    ERROR_INVAL >Однако многие функции возвращают значение некоторого запрашиваемого параметра (например, OpenFile возвращает значение описателя файла). В таких случаях ошибка определяется каким-либо другим специальным значением Return&, чаще всего 0 или –1.

Win32 API используют строго фиксированные способы передачи самых простых типов данных.

С помощью переменных типа Long выполняется не менее 80% передачи аргументов. Обратите внимание, что аргумент всегда сопровождается ключевым словом ByVal, а это, кроме всего прочего, означает, что выполняется односторонняя передача данных — от VB-программы к API-функции.

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

Первая — резервирование памяти под строку производится в вызывающей программе, поэтому если API-функция будет заполнять строки, то нужно перед ее вызовом создать строку необходимого размера. Например, функция GetWindowsDirectory возвращает путь к каталогу Windows, который по определению не должен занимать более 144 символов. Соответственно обращение к этой функции должно выглядеть примерно так:

Вторая проблема заключается в том, что при обращении к API-функции производится преобразование исходной строки в ее некоторое внутреннее представление, а при выходе из функции — наоборот. Если во времена Win16 эта операция заключалась лишь в добавлении нулевого байта в конце строки, то с появлением Win32 к этому добавилась трансформация двухбайтной кодировки Unicode в ANSI и наоборот. (Об этом подробно говорилось в статье «Особенности работы со строковыми переменными в VB», КомпьютерПресс 10’99 и 01’2000). Сейчас же только отметим, что с помощью конструкции ByVal . As String можно обмениваться строками только с символьными данными.

Это означает, что в стек будет помещен некоторый адрес буфера памяти, интерпретация содержимого которого будет выполняться API-функцией, например, в зависимости от значения других аргументов. Однако As Any может использоваться только в операторе Declare — при конкретном обращении к функции в качестве аргумента должна быть определена конкретная переменная.

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

Форма структуры данных определяется конкретной API-функцией, и на программисте лежит ответственность правильным образом описать и зарезервировать ее в вызывающей программе. Такая конструкция всегда используется без слова ByVal, то есть в данном случае выполняется передача по ссылке — в стек записывается адрес переменной.

Пример обращения к API-функции

Проиллюстрируем сказанное выше на примере использования двух полезных функций работы с файлами — lopen и lread, которые описываются следующим образом:

В VB их аналогами — в данном случае точными — являются операторы Open и Get (для режима Binary). Обратим сразу внимание на использование ключевого слова Alias в объявлении функции — это как раз тот случай, когда без него не обойтись. Настоящие названия функции в библиотеке начинаются с символа подчеркивания (типичный стиль для языка C), что не разрешается в VB.

Операция открытия файла может выглядеть следующим образом:

Здесь нужно обратить внимание на два момента:

  • в качестве значения функции мы получаем значение описателя файла. Ошибке соответствует значение –1;
  • как раз в данном случае не срабатывает обращение к функции GetLastError — для получения уточненного значения ошибки мы обратились к объекту Err (о возможности такой ситуации мы говорили выше).

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

Еще раз обратите внимание: второй аргумент функции передается по ссылке, остальные — по значению.

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

Здесь видно важное отличие от приведенного ранее примера — строковая переменная обязательно сопровождается ключевым словом ByVal.

Чтение содержимого файла в массиве (для простоты будем использовать одномерный байтовый массив) выполняется следующим образом:

Указывая первый элемент массива в качестве аргумента, мы передаем адрес начала области памяти, зарезервированной под массив. Очевидно, что таким образом можно заполнить любой фрагмент массива:

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

Совет 5. Используйте Alias для передачи параметров As Any

Здесь на основе предыдущего примера мы раскроем суть четвертого совета Дэна Эпплмана.

Работая с функцией lread, следует помнить, что при обращении к ней с использованием строковой переменной необходимо использовать ключевое слово ByVal (иначе сообщения о нелегальной операции не избежать). Чтобы обезопасить себя, можно сделать дополнительное специальное описание этой же функции для работы только со строковыми переменными:

При работе с этим описанием указывать ByVal при обращении уже не нужно:

Казалось бы, синтаксис оператора Declare позволяет сделать подобное специальное описание для массива:

неизбежно приводит к фатальной ошибке программы.

Совет 6. Внимание при работе со строковыми переменными

Это продолжение разговора об особенностях обработки строковых переменных в Visual Basic: VB использует двухбайтную кодировку Unicode, Win API — однобайтную ANSI (причем с форматом, принятым в С, — с нулевым байтом в конце). Соответственно при использовании строковых переменных в качестве аргумента всегда автоматически производится преобразование из Unicode в ANSI при вызове API-функции (точнее, DLL-функции) и обратное преобразование при возврате.

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

Как известно, тип String можно использовать для описания пользовательской структуры. В связи с этим нужно помнить следующее:

    Категорически нельзя использовать для обращения к Win API конструкцию следующего вида:

В случае строки переменной длины в составе структуры передается дескриптор строки со всеми вытекающими отсюда последствиями в виде ошибки выполнения программы.

  • Можно использовать в качестве элемента структуры строку фиксированной длины:
  • При этом производится соответствующее преобразование кодировок.

    И последнее замечание: применять массив строковых переменных (как фиксированной, так и переменной длины) при обращении к API-функции нельзя ни в коем случае. Иначе появление «нелегальной операции» будет гарантировано.

    Совет 7. Как обращаться к DLL-функциям

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

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

    Мнение автора таково: сколь-нибудь серьезное занятие программированием требует от разработчика владения по крайней мере двумя инструментами. Разумеется, в современных условиях четкого разделения труда очень сложно быть отличным экспертом даже по двум системам, поэтому более логичной является схема «основной и вспомогательный языки». Идея здесь заключается в том, что даже поверхностное знание «вспомогательного» языка (написание довольно простых процедур) может очень заметно повысить эффективность применения «основного». Отметим, что знание VB хотя бы в качестве вспомогательного является сегодня практически обязательным требованием для профессионального программиста. Кстати, во времена DOS для любого программиста, в том числе Basic, было крайне желательным знание основ Ассемблера.

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

    При изучении межпроцедурного интерфейса следует обратить внимание на следующие возможные «подводные камни»:

    • Разные языки могут использовать различные соглашения о правилах написания идентификаторов. Например, часто используется знак подчеркивания в начале имени процедуры, что запрещено в VB. Эта проблема легко решается с помощью ключевого слова Alias в операторе Declare (см. пример совета 2.3).
    • Может быть использована разная последовательность записи передаваемых аргументов в стек. Например, во времена DOS (честно признаюсь — не знаю, как это выглядит сейчас в среде Windows), C записывал аргументы с конца списка, другие языки (Fortran, Pascal, Basic) — с начала.
    • По умолчанию используются разные принципы передачи параметров — по ссылке или по значению.
    • Различные принципы хранения строковых переменных. Например, в C (так же как в Fortran и Pascal) длина строки определяется нулевым байтом в ее конце, а в Basic длина записывается в явном виде в дескрипторе строки. Разумеется, нужно иметь в виду возможность использования разных кодировок символов.
    • При передаче многомерных массивов следует помнить, что возможны различные варианты преобразования многомерных структур в одномерные (начиная с первого индекса или с последнего, применительно к двухмерным массивам — «по строчкам» или «по столбцам»).

    С учетом всего этого можно сформулировать следующие рекомендации:

    • Используйте самые простые, проверенные способы передачи аргументов в DLL-функции. Стандарты, принятые для Win API, вполне годятся в качестве образца.
    • Ни в коем случае не передавайте массивы строковых переменных.
    • Очень внимательно используйте передачу простых строковых переменных и многомерных массивов.
    • Обязательно специальным образом проверяйте работоспособность механизма передачи аргументов в вызываемую процедуру и обратно. Напишите специальный тест для проверки передачи данных. Отдельно проверьте правильность передачи каждого аргумента. Например, если у вас есть процедура с несколькими аргументами, проверьте сначала корректность передачи каждого параметра для варианта с одним аргументом, а уж потом — для всего списка.

    А что делать, если DLL-функция уже написана, например, на Фортране, но ее входной интерфейс не очень хорошо вписывается в приведенные выше стандарты VB? Здесь можно дать два совета. Первый: напишите тестовую DLL-функцию и с ее помощью постарайтесь методом проб и ошибок подобрать нужное обращение из VB-программы. Второй: напишите процедуру-переходник на том же Фортране, который бы обеспечивал простой интерфейс между VB и DLL-функцией с преобразованием простых структур данных в сложные (например, преобразовывал многомерный байтовый массив в строковый массив).

    Итак: используйте DLL-функции. Но сохраняйте бдительность.

    Написание сервисов windows nt на winapi

    В этом посте мне хочется рассказать, как создать простейшее приложение с использованием WinAPI и языка программирования C++. Обычное, пустое окошко Windows. Причины, побудившие меня к этому, очень просты: источники, которые я читал до определенного момента не давали мне полного представления о том, что и как работает в приложении Win32. Понимать я это стал, как ни удивительно, только после того, как тот же материал был освещен на лекциях в универе. Почему-то в том виде, в каком преподносилась информация на лекциях, она лучше откладывалась в памяти, нежели “книжные” записи, пусть даже совсем неплохие. Еще одно обстоятельство, способствовавшее идее освящения данной темы – желание лучше закрепить материал, излагая его в письменной форме и, возможно, даже расширить свои знания, заглянув лишний раз в MSDN, чтобы дополнить что-то.

    Как, думаю, стало понятно из вступления, материал этого поста и, вероятно, последующих постов на тему WinAPI и программирования под ОС Windows будут основываться на универских лекциях + MSDN с добавлением чего-то от себя (по делу :)).

    Наверное, в начале стоит сказать, что Windows API (Application Programming Interface) – это набор готовых классов, функций, структур и констант, при помощи которых любое приложение может взаимодействовать с операционной системой (ОС) Windows.

    Также отмечу, что основные функции Windows API появились еще в Win16 и с каждой версией Windows их набор расширяется, утрачивая, однако, при этом поддержку некоторых других функций. Схематично, это можно представить так:

    Итак, для любого Windows приложения требуется написать как минимум 2 функции:
    Точка входа в приложение, в которой необходимо:

    1. зарегистрировать класс окна
    2. создать окно
    3. запустить цикл обработки сообщений

    Работа с функциями Windows API и DLL

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

    Windows API — набор функций операционной системы

    Аббревиатура API многим начинающим программистам кажется весьма таинственной и даже пугающей. На самом же деле Application Programming Interface (API) — это просто некоторый готовый набор функций, который могут использовать разработчики приложений. В общем случае данное понятие эквивалентно тому, что раньше чаще называли библиотекой подпрограмм. Однако обычно под API подразумевается особая категория таких библиотек.

    В ходе разработки практически любого достаточно сложного приложения (MyAppication) для конечного пользователя формируется набор специфических внутренних функций, используемых для реализации данной конкретной программы, который называется MyApplication API. Однако часто оказывается, что эти функции могут эффективно использоваться и для создания других приложений, в том числе другими программистами. В этом случае авторы, исходя из стратегии продвижения своего продукта, должны решить вопрос: открывают они доступ к этому набору для внешних пользователей или нет? При утвердительном ответе в описании программного пакета в качестве положительной характеристики появляется фраза: «Комплект включает открытый набор API-функций» (но иногда за дополнительные деньги).

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

    Соответственно Windows API — это набор функций, являющийся частью самой операционной системы и в то же время — доступный для любого другого приложения, в том числе написанного с помощью VB. В этом плане вполне оправданна аналогия с набором системных прерываний BIOS/DOS, который фактически представляет собой DOS API.

    Отличие заключается в том, что состав функций Windows API, с одной стороны, значительно шире по сравнению с DOS, с другой — не включает многие средства прямого управления ресурсами компьютера, которые были доступны программистам в предыдущей ОС. Кроме того, обращение к Windows API выполняется с помощью обыкновенных процедурных обращений, а вызов функций DOS — через специальную машинную команду процессора, которая называется Interrupt («прерывание»).


    Win16 API и Win32 API

    Как известно, смена Windows 3.x на Windows 95 ознаменовала собой переход от 16-разрядной архитектуры операционной системы к 32-разрядной. Одновременно произошла замена 16-разрядного Windows API (Win16 API) на новый 32-разрядный вариант (Win32 API) — о некоторых аспектах этого перехода будет упомянуто в этой главе. В данном случае нужно просто иметь в виду, что, за небольшим исключением, набор Win32 API является единым для семейств Windows 9x и Windows NT.

    Далее в этой статье под термином API будет подразумеваться Win API, более того, по умолчанию — Win32 API.

    Зачем нужен Win API для VB-программистов

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

    При знакомстве с Win API обнаруживается, что многие встроенные VB-функции — не что иное, как обращение к соответствующим системным процедурам, но только реализованное в виде синтаксиса данного языка. С учетом этого необходимость использования API определяется следующим вариантами:

    1. API-функции, которые полностью реализованы в виде встроенных VB-функций. Тем не менее иногда и в этом случае бывает полезным перейти к применению API, так как это позволяет порой существенно повысить производительность (в частности, за счет отсутствия ненужных преобразований передаваемых параметров).
    2. Встроенные VB-функции реализуют лишь частный случай соответствующей API-функции. Это довольно обычный вариант. Например, API-функция CreateDirectory обладает более широкими возможностями по сравнению со встроенным VB-оператором MkDir.
    3. Огромное число API-функций вообще не имеет аналогов в существующем сегодня варианте языка VB. Например, удалить каталог средствами VB нельзя — для этого нужно использовать функцию DeleteDirectory.

    Следует также подчеркнуть, что некоторые API-функции (их доля в Win API весьма незначительна) не могут вызываться из VB-программ из-за ряда ограничений языка, например из-за отсутствия возможности работы с адресами памяти. Но в ряде случаев могут помочь нетривиальные приемы программирования (в частности, в случае с теми же адресами).

    Личная точка зрения автора такова — вместо расширения от версии к версии встроенных функций VВ следовало бы давать хорошее описание наиболее ходовых API-функций. В то же время хочется посоветовать разработчикам не ждать появления новой версии средства с расширенными функциями, а внимательнее изучить состав существующего Win API — вполне вероятно, что нужные вам возможности можно было реализовать уже в версии VB 1.0 выпуска 1991 года.

    Как изучать Win API

    Это не такой простой вопрос, если учесть, что число функций Win32 API оценивается величиной порядка 10 тысяч (точной цифры не знает никто, даже Microsoft).

    В состав VB (версий 4-6) входит файл с описанием объявлений Win API — WIN32API.TXT (подробнее о его применении мы расскажем позднее). Но, во-первых, с его помощью можно получить сведения о назначении той или иной функции и ее параметрах только по используемым мнемоническим именам, а во-вторых — перечень функций в этом файле далеко не полный. В свое время (семь лет назад) в VB 3.0 имелись специальные справочные файлы с описанием функций Win16 API. Однако уже в v.4.0 эта полезная информация с удобным интерфейсом исчезла.

    Исчерпывающую информацию о Win32 API можно найти в справочной системе Platform Software Development Kit, которая, в частности, находится на компакт-дисках MSDN Library, включенных в состав VB 5.0 и 6.0 Enterprise Edition и Office 2000 Developer Edition. Однако разыскать там нужную информацию и разобраться в ней совсем не просто. Не говоря уж о том, что все описания там приводятся применительно к языку C.

    Общепризнанным в мире пособием для изучения API-программирования в среде VB являются книги известного американского эксперта Даниэля Эпплмана (Daniel Appleman). Его серия Dan Appleman’s Visual Basic Programmer’s Guide to the Windows API (для Win16, Win32, применительно к разным версиям VB) с 1993 года неизменно входит в число бестселлеров для VB-программистов. Книгу Dan Appleman’s VB 5.0 Programmer’s Guide to the Win32 API, выпущенную в 1997 году, автору привез из США приятель, который нашел ее в первом же книжном магазине небольшого провинциального городка.

    Эта книга объемом свыше 1500 страниц включает описание общей методики API-программирования в среде VB, а также более 900 функций. Прилагаемый компакт-диск содержит полный текст книги и всех программных примеров, а кроме того, несколько дополнительных глав, не вошедших в печатный вариант. В 1999 году Дэн Эпплман выпустил новую книгу Dan Appleman’s Win32 API Puzzle Book and Tutorial for Visual Basic Programmers, которая включает сведения о еще 7600 функциях (хотя и не столь обстоятельные).

    Набор Win API реализован в виде динамических DLL-библиотек. Далее речь фактически пойдет о технологии использования DLL в среде VB на примере библиотек, входящих в состав Win API. Однако, говоря о DLL, необходимо сделать несколько важных замечаний.

    В данном случае под DLL мы подразумеваем традиционный вариант двоичных динамических библиотек, которые обеспечивают прямое обращение приложений к нужным процедурам — подпрограммам или функциям (примерно так же, как это происходит при вызове процедур внутри VB-проекта). Такие библиотеки могут создаваться с помощью разных инструментов: VC++, Delphi, Fortran, кроме VB (посмотрим, что появится в версии 7.0) — последний может делать только ActiveX DLL, доступ к которым выполняется через интерфейс OLE Automation.

    Обычно файлы динамических библиотек имеют расширение .DLL, но это совсем не обязательно (для Win16 часто применялось расширение .EXE); драйверы внешних устройств обозначаются с помощью .DRV.

    Как мы уже отмечали, определить точное число API-функций Windows и содержащих их файлов достаточно сложно, однако все они находятся в системном каталоге. В этом плане лучше выделить состав библиотек, входящих в ядро операционной системы, и основных библиотек с ключевыми дополнительными функциями.

    А теперь несколько советов.

    Совет 1. Следите за правильным оформлением объявления DLL-процедур

    Само обращение к DLL-процедурам в программе выглядит точно так же, как к «обычным» процедурам Visual Basic, например:

    Однако для использования внешних DLL-функций (в том числе и Win API) их нужно обязательно объявить в программе с помощью оператора Declare, который имеет следующий вид:

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

    Объявления внешних функций должны размещаться в секции General Declarations модуля. Если вы размещаете его в модуле формы, то обязательно нужно указать ключевое слово Private (это объявление будет доступно только внутри данного модуля) — таково ограничение для всех процедур модуля формы.

    Набор Win32 API реализован только в виде функций (в Win16 API было много подпрограмм Sub). В большинстве своем — это функции типа Long, которые чаще всего возвращают код завершения операции.

    Оператор Declare появился в MS Basic еще во времена DOS, причем он использовался и для объявления внутренних процедур проекта. В Visual Basic этого не требуется, так как объявлением внутренних процедур автоматически является их описание Sub или Function. По сравнению с Basic/DOS в новом описании обязательно указывать имя файла-библиотеки, где находится искомая процедура. Библиотеки Wip API размещаются в системном каталоге Windows, поэтому достаточно привести только название файла. Если же вы обращаетесь к DLL, которая находится в произвольном месте, нужно записать полный путь к данному файлу.

    Описание оператора Declare обычно занимает довольно много места и не помещается в одну строку в окне кода. Поэтому мы рекомендуем придерживаться при написании приложений какой-либо определенной схемы переноса строк, например:

    В этом случае все основные элементы описания разнесены на разные строчки и поэтому хорошо читаются.

    Совет 2. Будьте особенно внимательны при работе с DLL-функциями

    Использование Win API и разнообразных DLL-функций существенно расширяет функциональные возможности VB и зачастую позволяет повысить производительность программ. Однако расплата за это — риск снижения надежности работы приложения, особенно в процессе его отладки.

    Одним из самых важных достоинств среды VB является надежность процесса разработки программ: функционируя под управлением интерпретатора, программный код теоретически не может нарушить работу Windows и самого VB. Программист может не очень внимательно следить за правильностью передачи параметров в вызываемые функции — подобные ошибки будут легко обнаружены самим интерпретатором либо в процессе трансляции кода, либо во время его выполнения. В самом неприятном случае просто произойдет прерывание режима обработки, причем с указанием, где и почему произошла ошибка.

    Использование напрямую функций Windows API или других DLL-библиотек снимает такой контроль за передачей данных и процессом выполнения кода вне среды VB. Поэтому ошибка в обращении к внешним функциям может привести к неработоспособности и VB и операционной системы. Это особенно актуально на этапе разработки программы, когда наличие ошибок — дело вполне естественное. Таким образом, применяя более широкие возможности функций базового слоя системы, программист берет на себя ответственность за правильность их применения.

    Илон Маск рекомендует:  Изображение на всю ширину макета

    Проблема усугубляется еще и тем, что разные языки программирования используют различные способы передачи параметров между процедурами. (Точнее, разные способы передачи используются по умолчанию, так как многие языки могут поддерживать несколько способов.) Win API реализованы на C/C++ и применяют соглашения о передаче параметров, принятые в этой системе, которые отличаются от привычного для VB варианта.

    В связи с этим следует отметить, что появление встроенных в VB аналогов API-функций оправданно именно адаптацией последних к синтаксису VB и реализацией соответствующего механизма контроля обмена данными. Обратим также внимание, что на этапе опытной отладки приложения при создании исполняемого модуля лучше использовать вариант компиляции P-code вместо Native Code (машинный код). В первом случае программа будет работать под управлением интерпретатора — медленнее по сравнению с машинным кодом, но более надежно с точки зрения возможного ошибочного воздействия на операционную систему и обеспечивая более удобный режим выявления возможных ошибок.

    Совет 3. Десять рекомендаций Дэна Эпплмана по надежному API-программированию в среде VB

    Использование функции API требует более внимательного программирования с использованием некоторых не очень привычных методов обращения к процедурам (по сравнению с VB). Далее мы будем постоянно обращаться к этим вопросам. А сейчас приведем изложение сформулированных Дэном Эпплманом советов на эту тему (их первый вариант появился еще в 1993 году) с некоторыми нашими дополнениями и комментариями.

    1. Помните о ByVal. Наиболее частая ошибка, совершаемая при обращении к функциям API и DLL, заключается в некорректном использовании ключевого слова ByVal: его или забывают ставить, или, наоборот, ставят, когда в нем нет необходимости.

    На этих примерах показано влияние оператора ByVal на передачу параметров

    Тип параметра С ByVal Без ByVal
    Integer В стек помещается 16-разрядное целое В стек помещается 32-разрядный адрес 16-разрядного целого
    Long В стек помещается 32-разрядное целое В стек помещается 32-разрядный адрес 32-разрядного целого
    String Строка преобразуется в формат, используемый в С (данные и завершающий нулевой байт). 32-разрядный адрес новой строки помещается в стек В стек помещается VB-дескриптор строки. (Такие дескрипторы никогда не используются самим Windows API и распознаются только в DLL, реализованных специально для VB.)

    Здесь следует напомнить, что передача параметров в любой системе программирования, в том числе и VB, выполняется двумя основными путями: по ссылке (ByRef) или по значению (ByVal). В первом случае передается адрес переменной (этот вариант используется в VB по умолчанию), во втором — ее величина. Принципиальное отличие заключается в том, что с помощью ссылки обеспечивается возврат в вызывающую программу измененного значения передаваемого параметра.

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

    Запустив на выполнение этот пример, вы получите сообщение со значением переменной, равным 3. Дело в том, что в данном случае в подпрограмму MyProc передается адрес переменной v, физически созданной в вызывающей программе. Теперь измените описание процедуры на

    В результате при выполнении теста вы получите v = 2, потому что в процедуру передается лишь исходное значение переменной — результат выполненных с ним операций не возвращается в вызывающую программу. Режим передачи по значению можно поменять также с помощью оператора Call следующим образом:

    Однако при обращении к внутренним VB-процедурам использование в операторе Call ключевого слова ByVal запрещено — вместо него применяются круглые скобки. Этому есть свое объяснение.

    В классическом случае (С, Fortran, Pascal) различие режимов ByRef и ByVal зависит от того, что именно помещается в стек обмена данными — адрес переменной или ее значение. В Basic исторически используется вариант программной эмуляции ByVal — в стеке всегда находится адрес, но только при передаче по значению для этого создается временная переменная. Чтобы отличить два этих варианта (классический и Basic), используются разные способы описания режима ByVal. Отметим, что эмуляция режима ByVal в VB обеспечивает более высокую надежность программы: перепутав форму обращения, программист рискует лишь тем, что в вызывающую программу вернется (или не вернется) исправленное значение переменной. В «классическом» же варианте такая путаница может привести к фатальной ошибке при выполнении процедуры (например, когда вместо адреса памяти будет использоваться значение переменной, равное, скажем, нулю).

    DLL-функции реализованы по «классическим» принципам и поэтому требуют обязательного описания того, каким образом происходит обмен данными с каждым из аргументов. Именно этой цели служат объявления функций через описание Declare (точнее, списка передаваемых аргументов). Чаще всего передача параметров в функцию Windows API или DLL выполняется с помощью ключевого слова ByVal. Причем оно может быть задано как в операторе Declare, так и непосредственно при вызове функции.

    Последствия неправильной передачи параметров легко предугадать. В случае получения явно недопустимого адреса вам будет выдано сообщение GPF (General Protection Fault — ошибка защиты памяти). Если же функция получит значение, совпадающее с допустимым адресом, то функция API залезет в чужую область (например, в ядро Windows) со всеми вытекающими отсюда катастрофическими последствиями.

    2. Проверяйте тип передаваемых параметров. Не менее важны верное число и тип передаваемых параметров. Необходимо, чтобы объявленные в Declare аргументы соответствовали ожидаемым параметрам в функции API. Наиболее часто встречающийся случай ошибки в передаче параметров связан с различием между NULL и строкой нулевой длины — следует помнить, что это не одно и то же.

    3. Проверяйте тип возвращаемого значения.

    VB довольно терпимо относится к несовпадению типов возвращаемых функцией значений, поскольку числовые значения обычно возвращаются через регистры, а не через стек. Следующие правила помогут определить корректное значение, возвращаемое функцией API:

    • DLL-функция, не возвращающая значения (аналог void в ‘C’), должна быть объявлена как VB Sub.
    • функция API, возвращающая целое значение (Integer или Long), может быть определена или как Sub, или как Function, возвращающая значение соответствующего типа.
    • ни одна из функций API не возвращает числа с плавающей точкой, но некоторые DLL вполне могут возвращать такой тип данных.

    4. С большой осторожностью используйте конструкцию «As Any». Множество функций Windows API имеют возможность принимать параметры различных типов и используют при этом обращение с применением конструкции As Any (интерпретация типа выполняется в зависимости от значения других передаваемых параметров).

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

    5. Не забывайте инициализировать строки. В Win API существует множество функций, возвращающих информацию путем загрузки данных в передаваемые как параметр строковые буферы. В своей программе вы можете вроде бы все сделать правильно: не забыть о ByVal, верно передать параметры в функцию. Но Windows не может проверить, насколько велик размер выделенного под строку участка памяти. Размер строки должен быть достаточным для размещения всех данных, которые могут быть в него помещены. Ответственность за резервирование буфера нужного размера лежит на VB-программисте.

    Следует отметить, что в 32-разрядных Windows при использовании строк производится преобразование из Unicode (двухбайтовая кодировка) в ANSI (однобайтовая) и обратно, причем с учетом национальных установок системы. Поэтому для резервирования буферов порой удобнее использовать байтовые массивы вместо строковых переменных. (Подробнее об этом будет рассказано ниже.)

    Чаще всего функции Win API позволяют вам самим определить максимальный размер блока. В частности, иногда для этого нужно вызвать другую функцию API, которая «подскажет» размер блока. Например, GetWindowTextLength позволяет определить размер строки, необходимый для размещения заголовка окна, получаемого функцией GetWindowText. В этом случае Windows гарантирует, что вы не выйдете за границу.

    6. Обязательно используйте Option Explicit.

    7. Внимательно проверяйте значения параметров и возвращаемых величин. VB обладает хорошими возможностями по проверке типов. Это означает, что, когда вы пытаетесь передать неверный параметр в функцию VB, самое плохое, что может случиться, — вы получите сообщение об ошибке от VB. Но данный механизм, к сожалению, не работает при обращении к функциям Windows API.

    Windows 9x обладает усовершенствованной системой проверки параметров для большинства функций API. Поэтому наличие ошибки в данных обычно не вызывает фатальной ошибки, однако определить, что же явилось ее причиной — не так-то просто.

    Здесь можно посоветовать использовать несколько способов отладки ошибки данного типа:

    • используйте пошаговый режим отладки или команду Debug.Print для проверки каждого подозрительного вызова функции API. Проверьте результаты этих вызовов, чтобы удостовериться, что все в пределах нормы и функция корректно завершилась;
    • используйте Windows-отладчик типа CodeView и отладочную версию Windows (имеется в Windows SDK). Эти средства могут обнаружить ошибку параметров и по меньшей мере определить, какая функция API приводит к ошибке;
    • используйте дополнительные средства третьих фирм для проверки типов параметров и допустимости их значений. Такие средства могут не только находить ошибки параметров, но даже указать на строку кода VB, где произошла ошибка.

    Кроме того, нужно обязательно проверять результат выполнения API-функции.

    8. Помните, что целые числа в VB и в Windows — не одно и то же. В первую очередь следует иметь в виду, что под термином «Integer» в VB понимается 16-разрядное число, в документации Win 32 — 32-разрядное. Во-вторых, целые числа (Integer и Long) в VB — это величины со знаком (то есть один разряд используется как знак, остальные — как мантисса числа), в Windows — используются только неотрицательные числа. Это обстоятельство нужно иметь в виду, когда вы формируете передаваемый параметр с помощью арифметических операций (например, вычисляете адрес с помощью суммирования некоторой базы и смещения). Для этого стандартные арифметические функции VB не годятся. Как быть в этом случае, мы поговорим отдельно.

    9. Внимательно следите за именами функций. В отличие от Win16 имена всех функций Win32 API являются чувствительными к точному использованию строчных и прописных букв (в Win16 такого не было). Если вы где-то применяете строчную букву вместо прописной или наоборот, то нужная функция не будет найдена. Следите также за правильным использованием суффикса A или W в функциях, применяющих строковые параметры. (Подробнее об этом – см. ниже.)

    10. Чаще сохраняйте результаты работы. Ошибки, связанные с неверным использованием DLL и Win API, могут приводить к аварийному завершению работы VB-среды, а возможно — и всей операционной системы. Вы должны позаботиться о том, чтобы написанный вами код перед тестовым запуском был сохранен. Самое простое — это установить режим автоматической записи модулей проекта перед запуском проекта в среде VB.

    Совет 4. Не нужно бояться применять Win API

    После прочтения предыдущего совета может возникнуть мысль, что использование функций Win API — дело рискованное. В какой-то степени это так, но только в сравнении с безопасным программированием, предоставляемым самим VB. Но при умелом их применении и знании возможных подводных камней этот риск минимален. К тому же полностью отказаться от применения Win API зачастую просто невозможно — они все равно потребуются при сколь-нибудь серьезной разработке.

    К тому же ранее мы упоминали о «подводных» камнях для широкого класса DLL. В случае с Win API все обстоит гораздо проще, так как здесь четко унифицирована форма обращения к этим функциям. При этом следует иметь в виду следующие основные моменты:

      Функции Win32 API являются именно функциями, то есть процедурами типа Function (в Win16 API было много подпрограмм Sub). Все это функции типа Long, поэтому их описания записываются в следующем виде:

    Обращение к API-функции выглядит так:

    1. Чаще всего возвращаемое значение функции является кодом завершения операции. Причем ненулевое значение означает в данном случае нормальное завершение, нулевое — ошибку. Обычно (но не всегда) уточнить характер ошибки можно с помощью обращения к функции GetLastError. Описание этой функции имеет такой вид:

    ВНИМАНИЕ! При работе в среде VB для получения значения уточненного кода ошибки лучше использовать свойство LastDLLError объекта Err, так как иногда VB обнуляет функцию GetLastError в промежутке между обращением к API и продолжением выполнения программы.

    Интерпретировать код, возвращаемый GelLastError, можно с помощью констант, записанных в файле API32.TXT, с именами, начинающимися с суффикса ERROR_.

    Наиболее типичные ошибки имеют следующие коды:

      ERROR_INVAL >Однако многие функции возвращают значение некоторого запрашиваемого параметра (например, OpenFile возвращает значение описателя файла). В таких случаях ошибка определяется каким-либо другим специальным значением Return&, чаще всего 0 или –1.

    Win32 API используют строго фиксированные способы передачи самых простых типов данных.

    С помощью переменных типа Long выполняется не менее 80% передачи аргументов. Обратите внимание, что аргумент всегда сопровождается ключевым словом ByVal, а это, кроме всего прочего, означает, что выполняется односторонняя передача данных — от VB-программы к API-функции.

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

    Первая — резервирование памяти под строку производится в вызывающей программе, поэтому если API-функция будет заполнять строки, то нужно перед ее вызовом создать строку необходимого размера. Например, функция GetWindowsDirectory возвращает путь к каталогу Windows, который по определению не должен занимать более 144 символов. Соответственно обращение к этой функции должно выглядеть примерно так:

    Вторая проблема заключается в том, что при обращении к API-функции производится преобразование исходной строки в ее некоторое внутреннее представление, а при выходе из функции — наоборот. Если во времена Win16 эта операция заключалась лишь в добавлении нулевого байта в конце строки, то с появлением Win32 к этому добавилась трансформация двухбайтной кодировки Unicode в ANSI и наоборот. (Об этом подробно говорилось в статье «Особенности работы со строковыми переменными в VB», КомпьютерПресс 10’99 и 01’2000). Сейчас же только отметим, что с помощью конструкции ByVal . As String можно обмениваться строками только с символьными данными.

    Это означает, что в стек будет помещен некоторый адрес буфера памяти, интерпретация содержимого которого будет выполняться API-функцией, например, в зависимости от значения других аргументов. Однако As Any может использоваться только в операторе Declare — при конкретном обращении к функции в качестве аргумента должна быть определена конкретная переменная.

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

    Форма структуры данных определяется конкретной API-функцией, и на программисте лежит ответственность правильным образом описать и зарезервировать ее в вызывающей программе. Такая конструкция всегда используется без слова ByVal, то есть в данном случае выполняется передача по ссылке — в стек записывается адрес переменной.

    Пример обращения к API-функции

    Проиллюстрируем сказанное выше на примере использования двух полезных функций работы с файлами — lopen и lread, которые описываются следующим образом:

    В VB их аналогами — в данном случае точными — являются операторы Open и Get (для режима Binary). Обратим сразу внимание на использование ключевого слова Alias в объявлении функции — это как раз тот случай, когда без него не обойтись. Настоящие названия функции в библиотеке начинаются с символа подчеркивания (типичный стиль для языка C), что не разрешается в VB.

    Операция открытия файла может выглядеть следующим образом:

    Здесь нужно обратить внимание на два момента:

    • в качестве значения функции мы получаем значение описателя файла. Ошибке соответствует значение –1;
    • как раз в данном случае не срабатывает обращение к функции GetLastError — для получения уточненного значения ошибки мы обратились к объекту Err (о возможности такой ситуации мы говорили выше).

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

    Еще раз обратите внимание: второй аргумент функции передается по ссылке, остальные — по значению.

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

    Здесь видно важное отличие от приведенного ранее примера — строковая переменная обязательно сопровождается ключевым словом ByVal.

    Чтение содержимого файла в массиве (для простоты будем использовать одномерный байтовый массив) выполняется следующим образом:

    Указывая первый элемент массива в качестве аргумента, мы передаем адрес начала области памяти, зарезервированной под массив. Очевидно, что таким образом можно заполнить любой фрагмент массива:

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

    Совет 5. Используйте Alias для передачи параметров As Any

    Здесь на основе предыдущего примера мы раскроем суть четвертого совета Дэна Эпплмана.

    Работая с функцией lread, следует помнить, что при обращении к ней с использованием строковой переменной необходимо использовать ключевое слово ByVal (иначе сообщения о нелегальной операции не избежать). Чтобы обезопасить себя, можно сделать дополнительное специальное описание этой же функции для работы только со строковыми переменными:

    При работе с этим описанием указывать ByVal при обращении уже не нужно:

    Казалось бы, синтаксис оператора Declare позволяет сделать подобное специальное описание для массива:

    неизбежно приводит к фатальной ошибке программы.

    Совет 6. Внимание при работе со строковыми переменными

    Это продолжение разговора об особенностях обработки строковых переменных в Visual Basic: VB использует двухбайтную кодировку Unicode, Win API — однобайтную ANSI (причем с форматом, принятым в С, — с нулевым байтом в конце). Соответственно при использовании строковых переменных в качестве аргумента всегда автоматически производится преобразование из Unicode в ANSI при вызове API-функции (точнее, DLL-функции) и обратное преобразование при возврате.

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

    Как известно, тип String можно использовать для описания пользовательской структуры. В связи с этим нужно помнить следующее:

      Категорически нельзя использовать для обращения к Win API конструкцию следующего вида:

    В случае строки переменной длины в составе структуры передается дескриптор строки со всеми вытекающими отсюда последствиями в виде ошибки выполнения программы.

  • Можно использовать в качестве элемента структуры строку фиксированной длины:
  • При этом производится соответствующее преобразование кодировок.

    И последнее замечание: применять массив строковых переменных (как фиксированной, так и переменной длины) при обращении к API-функции нельзя ни в коем случае. Иначе появление «нелегальной операции» будет гарантировано.

    Совет 7. Как обращаться к DLL-функциям

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

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

    Мнение автора таково: сколь-нибудь серьезное занятие программированием требует от разработчика владения по крайней мере двумя инструментами. Разумеется, в современных условиях четкого разделения труда очень сложно быть отличным экспертом даже по двум системам, поэтому более логичной является схема «основной и вспомогательный языки». Идея здесь заключается в том, что даже поверхностное знание «вспомогательного» языка (написание довольно простых процедур) может очень заметно повысить эффективность применения «основного». Отметим, что знание VB хотя бы в качестве вспомогательного является сегодня практически обязательным требованием для профессионального программиста. Кстати, во времена DOS для любого программиста, в том числе Basic, было крайне желательным знание основ Ассемблера.

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

    При изучении межпроцедурного интерфейса следует обратить внимание на следующие возможные «подводные камни»:

    • Разные языки могут использовать различные соглашения о правилах написания идентификаторов. Например, часто используется знак подчеркивания в начале имени процедуры, что запрещено в VB. Эта проблема легко решается с помощью ключевого слова Alias в операторе Declare (см. пример совета 2.3).
    • Может быть использована разная последовательность записи передаваемых аргументов в стек. Например, во времена DOS (честно признаюсь — не знаю, как это выглядит сейчас в среде Windows), C записывал аргументы с конца списка, другие языки (Fortran, Pascal, Basic) — с начала.
    • По умолчанию используются разные принципы передачи параметров — по ссылке или по значению.
    • Различные принципы хранения строковых переменных. Например, в C (так же как в Fortran и Pascal) длина строки определяется нулевым байтом в ее конце, а в Basic длина записывается в явном виде в дескрипторе строки. Разумеется, нужно иметь в виду возможность использования разных кодировок символов.
    • При передаче многомерных массивов следует помнить, что возможны различные варианты преобразования многомерных структур в одномерные (начиная с первого индекса или с последнего, применительно к двухмерным массивам — «по строчкам» или «по столбцам»).

    С учетом всего этого можно сформулировать следующие рекомендации:

    • Используйте самые простые, проверенные способы передачи аргументов в DLL-функции. Стандарты, принятые для Win API, вполне годятся в качестве образца.
    • Ни в коем случае не передавайте массивы строковых переменных.
    • Очень внимательно используйте передачу простых строковых переменных и многомерных массивов.
    • Обязательно специальным образом проверяйте работоспособность механизма передачи аргументов в вызываемую процедуру и обратно. Напишите специальный тест для проверки передачи данных. Отдельно проверьте правильность передачи каждого аргумента. Например, если у вас есть процедура с несколькими аргументами, проверьте сначала корректность передачи каждого параметра для варианта с одним аргументом, а уж потом — для всего списка.

    А что делать, если DLL-функция уже написана, например, на Фортране, но ее входной интерфейс не очень хорошо вписывается в приведенные выше стандарты VB? Здесь можно дать два совета. Первый: напишите тестовую DLL-функцию и с ее помощью постарайтесь методом проб и ошибок подобрать нужное обращение из VB-программы. Второй: напишите процедуру-переходник на том же Фортране, который бы обеспечивал простой интерфейс между VB и DLL-функцией с преобразованием простых структур данных в сложные (например, преобразовывал многомерный байтовый массив в строковый массив).

    Итак: используйте DLL-функции. Но сохраняйте бдительность.

    Написание сервисов windows nt на winapi

    В этом уpоке мы создадим Windows пpогpаммы, котоpая отобpажает полнофункциональное окно на pабочем столе.

    Скачайте файл пpимеpа здесь.

    Windows пpогpаммы для создания гpафического интеpфейса пользуются функциями API. Этот подход выгоден как пользователям, так и пpогpаммистам. Пользователям это дает то, что они не должны изучать интеpфейс каждой новой пpогpаммы, так как Windows пpогpаммы похожи дpуг на дpуга. Пpогpаммистам это выгодно тем, что GUI-функции уже оттестиpованы и готовы для использования. Обpатная стоpона — это возpосшая сложность пpогpаммиpования. Чтобы создать какой-нибудь гpафический объект, такой как окно, меню или иконка, пpогpаммист должен следовать должны следовать стpогим пpавилам. Hо пpоцесс пpогpаммиpования можно облегчить, используя модульное пpогpаммиpование или OOП-философию. Я вкpатце изложу шаги, тpебуемые для создания окна:

    1. Взять хэндл вашей пpогpаммы (обязательно)
    2. Взять командную стpоку (не нужно до тех поp, пока пpогpамме не потpебуется ее пpоанализиpовать)
    3. Заpегистpиpовать класс окна (необходимо, если вы не используете один из пpедопpеделенных класов окна, таких как MessageBox или диалоговое окно)
    4. Создайте окно (необходимо)
    5. Отобpазите его на экpане
    6. Обновить содеpжимое экpана на окне
    7. Запустите бесконечный цикл, в котоpом будут пpовеpятся сообщения от опеpационной системы.
    8. Пpибывающие сообщения пеpедаются специальной функции, отвечающая за обpаботку окна
    9. Выйти из пpогpаммы, если пользователь закpывает окно.

    Как вы можете видеть, стpуктуpа Windows пpогpаммы довольно сложна по сpавнению с досовской пpогpаммой. Hо миp Windows pазительно отличается от миpа DOS’а. Windows пpогpаммы должны быть способными миpно сосуществовать дpуг с дpугом. Они должны следовать более стpогим пpавилам. Вы, как пpогpаммист, должны быть более внимательными к вашим стилю пpогpаммиpованию и пpивычкам.

    Hиже пpиведен исходник нашей пpогpаммы пpостого окна. Пеpед тем как углубиться в описание деталей пpогpаммиpования на ассемблеpе под Win32, я покажу вам несколько тpюков, могущие облегчить пpогpаммиpование.

    Вам следует поместить все константы, стpуктуpы и функции, относящиеся к Windows в начале вашего .asm файла. Это съэкономит вам много сил и вpемени. В настоящее вpемя, самый полный include файл для MASM — это hutch’евский windows.inc, котоpый вы можете скачать с его или моей стpаницы. Вы также можете опpеделить ваши собственные константы и стpуктуpы, но лучше поместить их в отдельный файл.

    Используйте диpективу includelib, чтобы указать библиотеку импоpта, использованную в вашей пpогpамме. Hапpимеp, если ваша пpогpамма вызывает MessageBox, вам следует поместить стpоку «includelib user32.lib» в начале кода. Это укажет компилятоpу на то, что пpогpамма будет использовать функции из этой библиотеки импоpта. Если ваша пpогpамма вызывает функции из более, чем одной библиотеки, пpосто добавьте соответствующую диpективу includelib для каждой из используемых библиотек. Используя эту диpективу, вы не должны беспокоиться о библиотеках импоpта во вpемя линковки. Вы можете использовать ключ линкеpа /LIBPATH, чтобы указать, где находятся эти библиотеки.

    Объявляя пpототипы API функций, стpуктуp или констант в вашем подключаемом файле, постаpайтесь использовать те же имена, что и в windows include файлах, пpичем pегистp важен. Это избавит вас от головной боли в будущем.

    Используйте makefile, чтобы автоматизиpовать пpоцесс компиляции и линковки. Это избавит вас лишних усилий. (Лично я использую wmake из пакета Watcom C/C++ — пеpеводчик.)

    Понравилась статья? Поделиться с друзьями:
    Кодинг, CSS и SQL