Использование windows api. Windows api— набор функций операционной системы

FindWindow
GetWindow
GetWindowText
SetWindowText
IsWindow
MoveWindow
IsWindowVisible
EnableWindow
IsWindowEnabled
WindowFromPoint
ShowWindow
CloseWindow
SetWindowPos
GetClassLong
SetClassLong
GetWindowLong
SetWindowLong
GetDesktopWindow
GetParent

Функция FindWindow

function FindWindow(className,WindowName: PChar) : HWND;
Функция возвращает описатель окна, удовлетворяющий запросу (0 -если такого окна не найдено).

ClassName Имя класса, по которому призводится поиск среди ВСЕХ окон системы. WindowName Заголовок окна

Один из параметров может быть равен nil, тогда поиск ведется по другому параметру.
Пример:

Функция GetWindow

function GetWindow(Wnd: HWND; Param) : HWND
Функция возвращает описатель окна удовлетворяющий запросу.

Wnd Описатель какого-либо начального окна Param Принимает одно из следующих значений-констант: gw_Owner Возвращается описатель окна-предка (0 - если нет предка). gwHWNDFirst Возвращает описатель первого окна (относительно Wnd). gw_HWNDNext Возвращает описатель следующего окна (окна перебираются без повторений, т.е. если вы не меняли параметр Wnd функции, повторно описатели не возвращаются) gw_Child Возвращает описатель первого дочернего окна.

Пример:

Функция GetWindowText

function GetWindowText(hWnd: HWND; lpString: PChar; nMaxCount: Integer): Integer;
Функция возвращает текст окна. Для формы это будет заголовок, для кнопки - надпись на кнопке.

hWnd Описатель того окна, текст которого нужно получить. lpString Переменная, в которую будет помещен результат nMaxCount

Максимальная длина текста, если текст длиннее, то он обрезается.


Пример:

Функция IsWindow

function IsWindow(hWnd: HWND): BOOL; Возвращает True, если окно с заданным описателем существует и False в противном случае.

Hwnd Описатель нужного окна

Функция MoveWindow

MoveWindow(hWnd: HWND; X, Y, nWidth, nHeight: Integer; bRepaint: BOOL): BOOL; Перемещает окно в новую позицию.

hWnd Описатель перемещаемого окна. X, Y, nWidth, nHeight Соответственно: новые координаты X,Y; новая ширина, высота. bRepaint Булево значение, показывающее будет ли окно перерисовано заново.

Функция IsWindowVisible

function IsWindowVisible(hWnd: HWND): BOOL;
Возвращает True если данное окно видимо.

hWnd Описатель окна.

Функция EnableWindow

function EnableWindow(hWnd: HWND; bEnable: BOOL): BOOL;
Устанавливает доступность окна(окно недоступно, если оно не отвечает на события мыши, клавиатуры и т.д.). Аналог в Delphi свойство Enabled компонентов. EnableWindow возвращает True, если всё прошло успешно и False в противром случае.

hWnd Описатель окна. bEnable Булево значение, определяющее доступность окна.


Пример:

Функция IsWindowEnabled

function IsWindowEnabled(hWnd: HWND): BOOL;
Возвращает для заданного окна: True, если окно доступно и False в противном случае.

hWnd Описатель окна.

Функция WindowFromPoint

WindowFromPoint(Point: TPoint): HWND;
Возвращает описатель окна, находящегося в данной точке экрана.

Point Координата точки экрана типа TPoint (определение типа смотри ниже)

Функция

type TPoint = Record x: Longint; y: Longint; end ;

Функция ShowWindow

function ShowWindow(hWnd: HWND; nCmdShow: Integer): BOOL; Показывает или прячет окно.

hWnd Описатель нужного окна nCmdShow Константа, определяющая, что будет сделано с окном: SW_HIDE SW_SHOWNORMAL SW_NORMAL SW_SHOWMINIMIZED SW_SHOWMAXIMIZED SW_MAXIMIZE SW_SHOWNOACTIVATE SW_SHOW SW_MINIMIZE SW_SHOWMINNOACTIVE SW_SHOWNA SW_RESTORE SW_SHOWDEFAULT SW_MAX


Пример:

Функция CloseWindow

function CloseWindow(hWnd: HWND): BOOL; stdcall;
Закрывает окно.

hWnd Описатель закрываемого окна.

SetWindowPos

function SetWindowPos(hWnd: HWND; hWndInsertAfter: HWND; X, Y, cx, cy: Integer; uFlags: UINT): BOOL; stdcall;
Устанавливает окно в новую позицию

hWnd Оптсатель окна hWndInsertAfter Описатель окна, перед которым в списке Z-Order будет вставлено окно hWnd , или одна из следующих констант: HWND_BOTTOM Поместить окно на дно списка Z-Order HWND_TOP Поместить окно на верх списка Z-Order X, Y, cx, cy

Соответственно - новые горизонт. , верт. позиции окна (X, Y ), а также новая ширина
и высота (cx, cy )

uFlags Одна или несколько (разделенных OR ) следующих констант: SWP_NOSIZE Не изменять размер окна после перемещения (cx, cy игнорируются) SWP_NOZORDER Не изменять положение окна в списке Z-Order SWP_SHOWWINDOW Сделать окно видимым после перемещения SWP_HIDEWINDOW Спрятать окно после перемещения SWP_NOACTIVATE Не передавать фокус окну после перемещения SWP_NOMOVE Не перемещать окно (игнорируется X, Y )

Функция GetClassLong

function GetClassLong(hWnd: HWND; nIndex: Integer): Integer;
Эта функция возвращает 32-разрядное целое, взятое из определенного поля записи TWndClassEx указанного окна.

hWnd Описатель окна nIndex Константа, определяющая что будет возвращено. Должна быть одна из следующих: GCL_MENUNAME Возвращает указатель на строку, содержащую имя меню класса, определенного в файле ресурсов связанного с некоторой программой. GCL_HBRBACKGROUND Возвращает описатель (HBRUSH) кисти фона, ассоциированной с классом GCL_HCURSOR Возвращает описатель (HCURSOR) курсора, фссоциированного с классом GCL_HICON Возвращает описатель (HICON) пиктограммы, ассоциированной с классом GCL_HMODULE Возвращает описатель процесса (HMODULE), зарегистртровавшего класс. GCL_CBWNDEXTRA Возвращает размер памяти (в байтах), выделенной под хранение дополнительных данных ДАННОГО ОКНА. Описание как использвать эту память, смотри в описании функции GCL_CBCLSEXTRA Возвращает размер памяти (в байтах), выделенной под хранение дополнительных ДАННОГ КЛАССА GCL_WNDPROC Возвращает адрес оконной процедуры, связанной с классом. GCL_STYLE Возвращает стиль класса (наличие того или иного стиля проверяется побитовой операцией And с помощью констант типа cs_XXX ) GCL_HICONSM

Обратите внимание : в случае, когда функция возвращает указатель, необходимо приведение типов (Integer ->

Функция SetClassLong

function SetClassLong(hWnd: HWND; nIndex: Integer; dwNewLong: Longint): Integer; Парная функция функции . Устанавливает в нужное поле соответствующее значение.
Функция возвращает старое значение поля, чтобы его потом можно было исправить или же возврашается ноль, если что-то пошло не так как надо.

hWnd Описатель окна nIndex Одна из констант GCL_XXX из функции . В зависимости от значения этого поля будет изменнено нужное поле.

Обратите внимание Pointer к типу Integer .

Функция GetWindowLong

function GetWindowLong(hWnd: HWND; nIndex: Integer): Longint; Возвращает информацию о некотором окне в виде 32-битного целого.

hWnd Описатель окна nIndex Константа,определяющая, что будет возвращено. Должна быть одна их следующих:
GWL_WNDPROC Возвращает адрес оконной процедуры, связанной с данным окном. Полученный адрес (после соответсвующих приведений типов) может использоваться в функции CallWindowProc . Данное значение обычно используют, если хотят заменить существующую оконную процедуру на свою собственную, при этом, чтобы не потерять работоспособности окна, обычно и используют CallWindowProc . GWL_HINSTANCE Возвращает описатель приложения, заданный при создании окна функцией CreateWindowEx . GWL_HWNDPARENT Возвращает описатель (HWND) родительского окна GWL_STYLE Возвращает стиль окна. Конкркетные значения стилей узнаются при помощи побитовой операции And и констант WS_XXX GWL_EXSTYLE Возвращает расширенный стиль окна. Конкркетные значения стилей узнаются при помощи побитовой операции And и констант WS_EX_XXX GWL_USERDATA Возвращает 32-битное целое, ассоциированное с окном (это последний параметр в вызове CreateWindow или CreateWindowEx) GWL_ID Возвращает идентификатор окна (он не имеет ничего общего с описателем окна!), задаваемый параметром hMenu для дочерних окон при вызове CreateWindow или CreateWindowEx

Обратите внимание : в случае, когда функция возвращает указатель, необходимо приведение типов (Integer -> Pointer). Делать это можно так:

Функция SetWindowLong

function SetWindowLong(hWnd: HWND; nIndex: Integer; dwNewLong: Longint): Longint;
Парная к функции . Изменяет аттрибуты определенного окна.
Функция возвращает старое значение свойства, если вызов прошел удачно или нуль в противном случае.

hWnd Описатель окна nIndex Константа, определяющая, какое свойство будет изменено. Должна быть одной из констант GWL_XXX из описания функции dwNewLong Новое значение свойства, определяемого константой nIndex

Обратите внимание :при установке полей-указателей необходимо приведение типа Pointer к типу Integer .

Функция GetDesktopWindow

function GetDesktopWindow: HWND
Функция возвращает описатель окна Рабочего Стола (Desktop). Без параметров.

GetParent

function GetParent(hWnd: HWND): HWND;
Возвращает описатель родительского окна для окна hWnd .

hWnd Описатель окна

Disclaimer

Казалось бы, что WinAPI уходит в прошлое. Давно уже существует огромное количество кросс-платформенных фреймфорков, Windows не только на десктопах, да и сами Microsoft в свой магазин не жалуют приложения, которые используют этого монстра. Помимо этого статей о том, как создать окошки на WinAPI, не только здесь, но и по всему интернету, исчисляется тысячами по уровню от дошколят и выше. Весь этот процесс разобран уже даже не по атомам, а по субатомным частицам. Что может быть проще и понятнее? А тут я еще…

Но не все так просто, как кажется.

Почему о WinAPI сейчас?

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

О чем это я? А вот кусочке кода:

Case WM_KEYDOWN: MessageBox(hwndDlg,"Die!","I"m dead!",MB_YESNO|MB_ICONINFORMATION); break;
Таким образом, авторы хотели добавить поддержку клавиатуры, но суровая реальность недр архитектуры диалоговых окон в Windows жестко пресекла такую самодеятельность. Те, кто пользовался эмулятором и отладчиком в нем, хоть раз видели это сообщение?
В чем же проблема?

Ответ такой: так делать нельзя!

И, возвращаясь, к изначальному вопросу о WinAPI: очень много популярных, и не очень, проектов продолжают его использовать и в настоящее время, т.к. лучше, чем на чистом API многие вещи не сделать (тут можно бесконечно приводить аналогии вроде сравнения высокоуровневых языков и ассемблера, но сейчас не об этом). Да и мало ли почему? Просто используют и все тут.

О проблеме

Диалоговые окна упрощают работу с GUI, одновременно лишая нас возможности сделать что-то самостоятельно. Например, сообщения WM_KEYDOWN/WM_KEYUP, приходящие в оконную процедуру, «съедаются» в недрах DefDlgProc, беря на себя такие вещи, как: Навигация по Tab, обработка клавиш Esc, Enter, и т.д. Кроме того, диалоги не нужно создавать вручную: проще, ведь, набросать кнопок, списков, в редакторе ресурсов, вызвать в WinMain CreateDialog/DialogBox и все готово.

Обойти такие мелкие неприятности просто. Есть, как минимум, два вполне легальных способа:

  1. Создать свой собственный класс через RegisterClassEx и в процедуре обработки класса схватывать WM_KEYDOWN, перенаправлять в процедуру обработки самого диалога. Да-да! Можно создавать диалоги со своим собственным классом, и встроенный в VS редактор даже позволяет задавать имя класса для диалога. Вот только кто об этом знает и этим пользуется?
    Минус очевиден: Нужно регистрировать еще один класс, иметь на 1 CALLBACK процедуру больше, суть которой будет всего-навсего в трансляции пары сообщений. Кроме того, мы не будем знать куда их транслировать, и придется городить костыли.
  2. Использовать встроенный механизм акселераторов. И нам даже не придется менять код диалоговой процедуры! Ну, разве что, добавить одну строчку в switch/case, но об этом ниже.

Tutorials?

Не побоюсь сказать, что все туториалы по созданию окон через WinAPI начинаются с такого незамысловатого кода, обозначая его, как «цикл обработки сообщений» (опущу детали по подготовке класса окна и прочую обвязку):

While (GetMessage(&msg, nullptr, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); }
Здесь действительно все просто:

  1. GetMessage() выхватывает очередное сообщение из очереди, и ключевой момент : блокирует поток, если в очереди пусто.
  2. TranslateMessage() из WM_KEYDOWN/WM_KEYUP формирует сообщения WM_CHAR/WM_SYSCHAR (они нужны, если кто-то хочет сделать свой редактор текста).
  3. DispatchMessage() отправляет сообщение в оконную процедуру (если таковая существует).
Начнем с того, что этот код использовать опасно, и вот почему . Обратите внимание на сноску:
Because the return value can be nonzero, zero, or -1, avoid code like this:
while (GetMessage(lpMsg, hWnd, 0, 0)) ...
И ниже приводится пример правильного цикла.

Стоит сказать, что в шаблонах VS для Win32 приложений, написан именно такой неправильный цикл. И это очень печально. Ведь мало кто будет вникать в то, что сделали сами авторы, ведь это априори правильно. И неправильный код множится вместе с багами, которые очень сложно отловить.

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

HACCEL hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR)); BOOL bRet = 0; while (bRet = GetMessage(&msg, nullptr, 0, 0)) { if (-1 == bRet) break; if (!TranslateAccelerator(msg.hwnd, hAccel, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } }
Этот вариант я видел чаще всего. И он (та-дам ) снова неправильный!

Сперва о том, что изменилось (потом о проблемах этого кода):

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

Собственно TranslateAccelerator этим и занимается: если видит WM_KEYDOWN и код клавиши, которые есть в этом списке, то (опять же ключевой момент) будет формировать сообщение WM_COMMAND (MAKEWPARAM(id, 1)) и отправлять в соответствующую для дескриптора окна, указанного в первом аргументе, процедуру обработки.

Из последней фразы, думаю, стало понятно, в чем проблема предыдущего кода.
Поясню: GetMessage выхватывает сообщения для ВСЕХ объектов типа «окно» (в число которых входят и дочерние: кнопки, списки и прочее), а TranslateAccelerator будет отправлять сформированную WM_COMMAND куда? Правильно: обратно в кнопку/список и т.д. Но мы обрабатываем WM_COMMAND в своей процедуре, а значит нам интересно ее получать в ней же.

Ясно, что TranslateAccelerator надо вызывать для нашего созданного окна:

HWND hMainWnd = CreateWindow(...); HACCEL hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR)); BOOL bRet = 0; while (bRet = GetMessage(&msg, nullptr, 0, 0)) { if (-1 == bRet) break; if (!TranslateAccelerator(hMainWnd, hAccel, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } }
И вроде все хорошо и замечательно теперь: мы разобрали все детально и все должно работать идеально.

И снова нет. :-) Это будет работать правильно, пока у нас ровно одно окно - наше. Как только появится немодальное новое окно (диалог), все клавиши, которые будут в нем нажаты оттранслируются в WM_COMMAND и отправляться куда? И опять же правильно: в наше главное окно.

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

IsDialogMessage

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

На самом деле, делает она чуть больше, чем следует из названия. А именно:

  • Осуществляет навигацию по дочерним контролам кнопками Tab/Shift+Tab/вверх/вниз/вправо/влево. Плюс еще кое-что, но этого нам достаточно
  • По нажатии на ESC формирует WM_COMMAND(IDCANCEL)
  • По нажатии на Enter формирует WM_COMMAND(IDOK) или нажатие на текущую кнопку по умолчанию
  • Переключает кнопки по умолчанию (рамочка у таких кнопок чуть ярче остальных)
  • Ну и еще разные штуки, которые облегчают пользователю работу с диалогом
Что она нам дает? Во-первых, нам не надо думать о навигации внутри окна. Нам и так все сделают. Кстати, навигацию по Tab можно сделать, добавив стиль WS_EX_CONTROLPARENT нашему основному окну, но это топорно и не так функционально .

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

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

Although the IsDialogMessage function is intended for modeless dialog boxes, you can use it with any window that contains controls, enabling the windows to provide the same keyboard selection as is used in a dialog box.
Т.е. теперь, если мы оформим цикл так:

HWND hMainWnd = CreateWindow(...); HACCEL hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR)); BOOL bRet = 0; while (bRet = GetMessage(&msg, nullptr, 0, 0)) { if (-1 == bRet) break; if (!TranslateAccelerator(hMainWnd, hAccel, &msg)) { if (!IsDialogMessage(hMainWnd, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } }
То наше окошко будет иметь навигацию, как в родном диалоге Windows. Но теперь мы получили два недостатка:

  1. Этот код также будет хорошо работать только с одним (немодальным) окном ;
  2. Получив все достоинства диалоговой навигации, мы лишаемся прелестей в виде сообщений WM_KEYDOWN/WM_KEYUP (только для самого окна, а не для дочерних контролов);
И вот на этом этапе вообще все туториалы заканчиваются и начинаются вопросы: How to handle keyboard events in a winapi standard dialog?
Это первая ссылка в гугле, но поверьте: тысячи их. Про предлагаемые решений (лучшее из которых - это создать свой класс диалогов, о чем я писал выше, до subclassing и RegisterHotKey. Где-то я даже видел «лучший» из способов: использовать Windows Hooks).

Пора поговорить о том, чего нет в туториалах и ответах.

Как правило (как правило! Если кому-то захочется большего, то можно регистрировать свой класс для диалогов и работать так. И, если же, кому-то это интересно, я могу дополнить этим статью) WM_KEYDOWN хотят тогда, когда хотят обработать нажатие на клавишу, которая выполнит функцию в независимости от выбранного контрола в окне - т.е. некая общая функция для всего данного конкретного диалога. А раз так, то почему бы не воспользоваться богатыми возможностями, которые нам сама WinAPI и предлагает: TranslateAccelerator .

Везде используют ровно одну таблицу акселераторов, и только для главного окна. Ну действительно: цикл GetMessage-loop один, значит и таблица одна. Куда еще их девать?

На самом деле, циклы GetMessage-loop могут быть вложенными . Давайте еще раз посмотрим описание PostQuitMessage :

The PostQuitMessage function posts a WM_QUIT message to the thread"s message queue and returns immediately; the function simply indicates to the system that the thread is requesting to quit at some time in the future.
И GetMessage:
If the function retrieves the WM_QUIT message, the return value is zero.
Таким образом, выход из GetMessage-loop осуществится, если мы вызовем PostQuitMessage в процедуре окна. Что это значит?

Мы можем для каждого немодального окна в нашей программе создавать свой собственный подобный цикл. В данном случае DialogBoxParam нам не подходит, т.к. оно крутит свой собственный цикл и повлиять мы на него не можем. Однако если создадим диалог через CreateDialogBoxParam или окно через CreateWindow, то можно закрутить еще один цикл. При этом в каждом таком окне и диалоге мы должны вызывать PostQuitMessage:

HWND hMainWnd = CreateWindow(...); HACCEL hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR)); BOOL bRet = 0; while (bRet = GetMessage(&msg, nullptr, 0, 0)) { if (-1 == bRet) break; if (!TranslateAccelerator(hMainWnd, hAccel, &msg)) { if (!IsDialogMessage(hMainWnd, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } } // .... LRESULT CALLBACK WndProc(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam) { switch(umsg) { case WM_MYMESSAGE: { HWND hDlg = CreateDialog(hInstance, MAKEINTRESOURCE(IDD_MYDIALOG), hwnd, MyDialogBoxProc); HACCEL hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR_FOR_MY_DIALOG)); BOOL bRet = 0, fSavedEnabledState = IsWindowEnabled(hwnd); EnableWindow(hwnd, FALSE); // disable parent window, as dialog window is modal while (bRet = GetMessage(&msg, nullptr, 0, 0)) { if (-1 == bRet) break; if (!TranslateAccelerator(hDlg, hAccel, &msg)) { if (!IsDialogMessage(hDlg, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } } EnableWindow(hwnd, fSavedEnabledState); // enable parent window. Dialog was closed break; } } } INT_PTR CALLBACK MyDlgProc(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam) { switch(umsg) { case WM_CLOSE: { // EndDialog(hwnd, 0); -- DONT DO THAT! // EndDialog is valid ONLY for Modal Dialogs, created with DialogBox(Param) DestroyWindow(hwnd); break; } case WM_DESTROY: { PostQuitMessage(0); break; } // .... } return 0; }
Обратите внимание: теперь для каждого нового окна в нашей программе мы можем добавить в обработку собственную таблицу акселераторов. WM_QUIT будет выхватывать GetMessage из цикла для диалога, а внешний цикл его даже не увидит. Почему так происходит?

Дело в том, что внешний цикл «встал» на вызове DispatchMessage, который вызвал нашу процедуру, которая крутит свой собственный внутренний цикл GetMessage с таким же DispatchMessage. Классический вложенный вызов (в данном случае DispatchMessage). Посему внешний цикл не получит WM_QUIT и не завершится на этом этапе. Все будет работать стройно.

Но и тут есть свои недостатки:
Каждый такой цикл будет обрабатывать сообщения только для «своего» окна . Про другие-то мы здесь не знаем. А значит, если где-то объявится еще один цикл, то все остальные окна не будут получать нужной обработки своих сообщений парой TranslateAccelerator/IsDialogMessage.

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

Делаем красиво

Т.к. правильная постановка задачи является половиной решения, то сперва надо эту самую задачу правильно же и поставить.

Во-первых, было бы логично, что только активное окно принимает сообщения. Т.е. для неактивного окна мы не будем транслировать акселераторы и передавать сообщения в IsDialogMessage.

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

Создадим простой std::map, который будет мапить дескриптор окна в дескриптор таблицы акселераторов. Вот так:

Std::map l_mAccelTable;
И по мере создания окон будем в него добавлять новые окна с дескриптором на свою любимую таблицу (или нуль, если такая обработка не требуется).

BOOL AddAccelerators(HWND hWnd, HACCEL hAccel) { if (IsWindow(hWnd)) { l_mAccelTable[ hWnd ] = hAccel; return TRUE; } return FALSE; } BOOL AddAccelerators(HWND hWnd, LPCTSTR accel) { return AddAccelerators(hWnd, LoadAccelerators(hInstance, accel)); } BOOL AddAccelerators(HWND hWnd, int accel) { return AddAccelerators(hWnd, MAKEINTRESOURCE(accel)); } BOOL AddAccelerators(HWND hWnd) { return AddAccelerators(hWnd, HACCEL(NULL)); }
Ну и после закрытия окна удалять. Вот так:

Void DelAccel(HWND hWnd) { std::map::iterator me = l_mAccelTable.find(hWnd); if (me != l_mAccelTable.end()) { if (me->second) { DestroyAcceleratorTable(me->second); } l_mAccelTable.erase(me); } }
Теперь, как создаем новый диалог/окно, вызываем AddAccelerators(hNewDialog, IDR_MY_ACCEL_TABLE). Как закрываем: DelAccel(hNewDialog).

Список окон с нужными дескрипторами у нас есть. Немного модифицируем наш основной цикл обработки сообщений:

// ... HWND hMainWnd = CreateWindow(...); AddAccelerators(hMainWnd, IDR_ACCELERATOR); BOOL bRet = 0; while (bRet = GetMessage(&msg, nullptr, 0, 0)) { if (-1 == bRet) break; if (!HandleAccelArray(GetActiveWindow(), msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } // ...
Значительно лучше! Что же там в HandleAccelArray и зачем там GetActiveWindow()?

Немного теории:

Есть две функции, возвращающих дескриптор активного окна GetForegroundWindow и GetActiveWindow . Отличие первой от второй вполне доходчиво описано в описании второй:

The return value is the handle to the active window attached to the calling thread"s message queue. Otherwise, the return value is NULL.
Если первая будет возвращать дескриптор любого окна в системе, то последняя только того, которое использует очередь сообщений нашего потока . Т.к. нас интересуют окна только нашего потока (а значит те, которые будут попадать в нашу очередь сообщений), то и возьмем последнюю.

Так вот HandleAccelArray, руководствуясь переданным ей дескриптором на активное окно, ищет это самое окно в нашей мапе, и если оно там есть, отдает это сообщение на трансляцию в TranslateAccelerator, а затем (если первый не увидел нужного) в IsDialogMessage. Если и последняя не обработала сообщение, то возвращаем FALSE, чтобы пройти по стандартной процедуре TranslateMessage/DispatchMessage.

Выглядит так:

BOOL HandleAccelWindow(std::map::const_iterator mh, MSG & msg) { const HWND & hWnd = mh->first; const HACCEL & hAccel = mh->second; if (!TranslateAccelerator(hWnd, hAccel, &msg)) { // message not for TranslateAccelerator. Try it with IsDialogMessage if (!IsDialogMessage(hWnd, &msg)) { // so, do default stuff return FALSE; } } // ok, message translated. Say to message-loop, to get next message return TRUE; } BOOL HandleAccelArray(HWND hActive, MSG & msg) { if (!hActive) return FALSE; // no active window. Nothing to do std::map::const_iterator mh = l_mAccelTable.find(hActive); if (mh != l_mAccelTable.end()) { // Got it! Try to translate this message for the active window return HandleAccelWindow(mh, msg); } return FALSE; }
Теперь каждое дочернее окно вправе добавить себе любимую таблицу акселераторов и спокойно ловить и обрабатывать WM_COMMAND с нужным кодом.

А что там еще об одной строчке в коде обработчика WM_COMMAND?

Описание в TranslateAccelerator гласит:
To differentiate the message that this function sends from messages sent by menus or controls, the high-order word of the wParam parameter of the WM_COMMAND or WM_SYSCOMMAND message contains the value 1.
Обычно код обработки WM_COMMAND выглядит так:

Switch(HIWORD(wParam)) { case BN_CLICKED: // command from buttons/menus { switch(LOWORD(wParam)) { case IDC_BUTTON1: DoButton1Stuff(); break; case IDC_BUTTON2: DoButton2Stuff(); break; // ... } break; } }
Теперь можно написать так:

Switch(HIWORD(wParam)) { case 1: // accelerator case BN_CLICKED: // command from buttons/menus { switch(LOWORD(wParam)) { case IDC_BUTTON1: DoButton1Stuff(); break; case IDC_BUTTON2: DoButton2Stuff(); break; // ... } break; } }
И теперь, возвращаясь к тому же fceux, добавив всего одну строчку в код обработки команд от кнопок, мы получим желаемое: управлять дебагером с клавиатуры. Достаточно добавить небольшую обертку вокруг главного цикла сообщений и новую таблицу акселераторов с нужными соответствиями VK_KEY => IDC_DEBUGGER_BUTTON.

P.S.: Мало кто знает, но можно создавать свою собственную таблицу акселераторов , а теперь и применять ее прямо налету.

P.P.S.: Т.к. DialogBox/DialogBoxParam крутит собственный цикл, то от при вызове диалога через них акселераторы работать не будут и наш цикл (или циклы) будет «простаивать».

P.P.P.S.: После вызова HandleAccelWindow мап l_mAccelTable может измениться, т.к. TranslateAccelerator или IsDialogMessage вызывают DispatchMessage, а там может встретиться AddAccelerators или DelAccel в наших обработчиках! Поэтому лучше его после этой функции не трогать.

Пощупать код можно . За основу был взят код, генерируемый из стандартного шаблона MS VS 2017.

Теги: Добавить метки

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

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

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

Соответственно, Windows API - это набор функций, являющийся частью самой операционной системы и в то же время - доступной для любого другого приложения. И в этом плане вполне оправдана аналогия с набором системных прерываний 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.

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

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

Встроенные функции реализуют лишь частный случай соответствующей API-функции. Это довольно обычный вариант.

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

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

Win API и Dynamic Link Library (DLL)

Набор Win API реализован в виде динамических DLL-библиотек.

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

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

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

Библиотеки Win32 API ядра операционной системы Windows 95/98:

KERNEL32.DLL: низкоуровневые функции управления памятью, задачами и другими ресурсами системы;

USER32.DLL: здесь в основном находятся функции управления пользовательским интерфейсом;

GDI32.DLL: библиотека Graphics Device Interface - разнообразные функции вывода на внешние устройства;

COMDLG32.DLL: функции, связанные с использованием диалоговых окон общего назначения.

Основные библиотеки с функциями расширения:

COMCTL32.DLL: набор дополнительных элементов управления Windows, в том числе Tree List и Rich Text;

MAPI32.DLL: функции работы с электронной почтой;

NETAPI32.DLL: элементы управления и функции работы с сетью;

ODBC32.DLL: функции этой библиотеки нужны для работы с различными базами данных через протокол ODBC;

WINMM.DLL: операции доступа к системным средствам мультимедиа.

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 («прерывание»).

Зачем нужен 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 и Dynamic Link Library (DLL)

Набор 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. Следите за правильным оформлением объявления DL L-процедур

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

Call DllName ([список аргументов])

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

Declare Sub ИмяПроцедуры Lib _ “ИмяБиблиотеки” _ [([СписокАргументов])]

Declare Function ИмяФункции _ Lib “ИмяБиблиотеки” _ [([СписокАргументов])]

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

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

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

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

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

Declare Function GetTempPath _ Lib “kernel32” Alias “GetTempPathA” _ (ByVal nBufferLength As Long, _ ByVal lpBuffer As String) As Long

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

Совет 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 по умолчанию), во втором - ее величина. Принципиальное отличие заключается в том, что с помощью ссылки обеспечивается возврат в вызывающую программу измененного значения передаваемого параметра.

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

Dim v As Integer v = 2 Call MyProc(v) MsgBox “v = “ & v Sub MyProc (v As Integer) v = v + 1 End Sub

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

Sub MyProc (ByVal v As Integer)

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

Sub MyProc (v As Integer) ... Call MyProc((v)) ‘ (v) - скобки указывают режим _ передачи по значению.

Однако при обращении к внутренним 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.

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

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

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

    Declare Function name& ‘ тип функции _ определяется с помощью суффикса

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

Result& = ApiName& ([СписокАргументов ]
  1. Чаще всего возвращаемое значение функции является кодом завершения операции. Причем ненулевое значение означает в данном случае нормальное завершение, нулевое - ошибку. Обычно (но не всегда) уточнить характер ошибки можно с помощью обращения к функции GetLastError. Описание этой функции имеет такой вид: Declare Function GetLastError& Lib “kernel32” ()

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

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

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

    • ERROR_INVALID_HANDLE = 6& - неверный описатель
    • ERROR_CALL_NOT_IMPLEMENTED = 120& - вызов в Windows 9x функции, доступной только для Windows NT
    • ERROR_INVALID_PARAMETER = 87& - неверное значение параметра

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

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

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

    Б) ByVal ... As String

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

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

    WinPath$ = Space$(144) ‘ резервируем строку в _ 144 символа Result& = GetWindowsDirectory& (WinTath$, 144) _ ‘заполнение буфера ‘ Result& - фактическое число символов в имени _ каталога WinPath$ = Left$(WinPath, Result&)

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

    В) ... As Any

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

    Г) ... As UserDefinedType

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

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

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

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

Declare Function lopen Lib “kernel32” _ Alias “_lopen” (_ ByVal lpFileName As String, _ ByVal wReadWrite As Long) As Long Declare Function lread Lib “kernel32” _ Alias “_lread” (_ ByVal hFile As Long, lpBuffer As Any, _ ByVal wBytes As Long) As Long

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

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

Const INVALID_HANDLE_VALUE = -1 ‘ неверное _ значение описателя lpFileName$ = “D:\calc.bas” ‘ имя файла wReadWrite& = 2 ‘ режим “чтения-записи” hFile& = lopen(lpFileName$, wReadWrite&) _ ‘ определяем описатель файла If hFile& = INVALID_HANDLE_VALUE Then _ ‘ ошибка открытия файла ‘ уточняем код ошибки CodeError& = Err.LastDllError ‘CodeError& = GetLastError _ ‘ эта конструкция не работает End If

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

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

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

Dim MyVar As Single wBytes = lread (hFile&, MyVar, Len(MyVar) ‘ чтение вещественного числа, 4 байта ‘ wBytes - число фактически прочитанных данных, ‘ -1 - ошибка... Type MyStruct x As Single i As Integer End Type Dim MyVar As MyStruct wBytes = lread (hFile&, MyVar, Len(MyVar)) ‘ чтение структуры данных, 6 байтов

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

Dim MyVar As String MyVar = Space$(10) ‘резервируем переменную для 10 символов wBytes = lread (hFile&, ByVal MyVar, Len(MyVar)) ‘ чтение символьной строки, 10 символов

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

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

Dim MyArray(1 To 10) As Byte wBytes = lread (hFile&, MyArray(1), _ Len(MyArray(1))* 10) ‘ чтение 10 элементов массива

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

WBytes = lread (hFile&, MyArray(4), _ Len(MyArray(1))* 5) ‘ чтение элементов массива с 4-го по 8-й

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

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

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

Declare Function lreadString Lib “kernel32” _ Alias “_lread” (_ ByVal hFile As Long, ByVal lpBuffer As String, _ ByVal wBytes As Long) As Long

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

WBytes = lreadString (hFile&, MyVarString, _ Len(MyVarString)) ‘

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

Declare Function lreadString Lib “kernel32” Alias “_lread” (_ ByVal hFile As Long, lpBuffer() As Byte, _ ByVal wBytes As Long) As Long

Однако обращение

WBytes = lreadArray (hFile&, MyArray(), 10)

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

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

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

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

  • Категорически нельзя использовать для обращения к Win API конструкцию следующего вида: Type MyStruct x As Single s As String ‘ строка переменной длины End Type

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

  • Можно использовать в качестве элемента структуры строку фиксированной длины: Type MyStruct x As Single s As String*8 ‘ строка фиксированной длины End Type

При этом производится соответствующее преобразование кодировок.

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

Вполне вероятно, что у вас возникнет ситуация, когда вам потребуется написать собственную библиотеку 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-функции. Но сохраняйте бдительность...

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

C WinAPI — это основной набор программных интерфейсов (API) Microsoft, доступных в операционных системах Ранняя версия носила название Win32 API.

Введение

C WinAPI — это интерфейс прикладного программирования, который используется для создания приложений Windows. Для начала работы начинающий пользователь должен загрузить SDK Windows, ранее известный как Platform SDK.

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

C WinAPI можно разделить на несколько областей:

    базовые услуги;

    безопасность;

  • пользовательский интерфейс;

    мультимедиа;

    оболочка Windows;

    сетевые службы.

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

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

Компоненты

При создании WinAPI C следует учитывать базовые возможности, предоставляемые Windows API, которые можно упорядочить в семи категориях. Рассмотрим каждую из них подробнее.

Основные услуги предоставляют доступ к базовым системным ресурсам, доступным в Windows. Примеры: файловая система, периферийные устройства, процессы, доступ к системному реестру и система управления исключениями. Эти функции хранятся в файлах kernel.exe, krnl286.exe или krnl386.exe для 16-разрядных систем и kernel32.dll и advapi32.dll для 32-разрядных систем.

Графический интерфейс обеспечивает доступ к ресурсам для отображения на мониторах, принтерах и другом периферийном оборудовании. Хранится в файле gdi.exe на 16-разрядных системах и gdi32.dll в 32-разрядных системах.

Пользовательский интерфейс отвечает за просмотр и управление основными элементами, такими как кнопки и полосы прокрутки, получение информации о клавиатуре и мыши, а также связанные с ними функции. Эти функции хранятся в файле user.exe в 16-разрядных системах и user32.dll comctl32.dll в 32-разрядных системах. Начиная с версии XP элементы управления были сгруппированы в comctl32.dll.

Общие диалоги — отображают данные для открытия и сохранения файлов, выбора цвета и шрифта. Находятся в файле comdlg.dll на 16-разрядных системах и comdlg32.dll в 32-разрядных системах.

Windows Shell — компонент WinAPI, который позволяет приложениям получать доступ к функциям, предоставляемым оболочкой операционной системы.

Сетевые службы обеспечивает доступ к различным сетевым возможностям операционной системы. Его подкомпоненты включают NetBIOS, Winsock, RPC. В старых версиях — NetDDE.

Версии

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

Win32, преемник Win16, был представлен в 1993 году в 32-разрядных продуктах семейства Windows, таких как Windows NT, 2000, 95. Этот программный интерфейс реализован тремя программными библиотеками: Kernel32.dll, User32.dll и GDI32.dll2. Те же функции Win32 доступны во всех продуктах Windows, и, в зависимости от продукта, использование определенных функций может привести к ошибке обслуживания.

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

Спецификация

C WinAPI — это абстрактная спецификация интерфейса программирования для операционной системы Windows. Состоит из объявлений функций, объединений, структур, типов данных, макросов, констант и других элементов программирования. WinAPI описывается главным и находится в заголовках Windows C. Официальная реализация функций WinAPI находится в динамических библиотеках (DLL): например, kernel32.dll, user32.dll, gdi32.dll или shell32.dll в системном каталоге. Существуют сторонние реализации Windows API: в первую очередь проект Wine и проект ReactOS.

Windows API — динамический объект. Количество функций постоянно растет с каждой новой версией ОС и новыми пакетами обновлений. Существуют также важные различия между версиями сервера и настольными версиями операционной системы. Некоторые функции официально не документированы.

Pelles C

Pelles C — бесплатная программа и лучший компилятор C и интегрированная среда разработки (IDE) для языка программирования C. Поддерживает 32-разрядную Windows (x86) и 64-разрядную Windows (x64). Реализует как стандарты C99, так и C11. Pelles C имеет встроенный редактор ресурсов, растровое изображение, значок и редактор курсоров и редактор шестнадцатеричных дампов. Он разработан шведским разработчиком Пелле Ориниусом. Название компилятора носит имя своего автора. Поставляется с SDK, поэтому программист сразу может приступить к созданию приложений без дальнейшей установки.

Ошибка целевой архитектуры

Чтобы создавать программы Windows API, необходимо включить расширения Microsoft. По умолчанию они выключены, в связи с чем компилятор выдает следующее сообщение об ошибке, которое служит примером C WinAPI с нарушенной структурой: fatal error #1014: #error: "No target architecture" («Нет целевой архитектуры»). Чтобы включить расширения Microsoft, переходим к параметрам проекта и выбираем вкладку «Компилятор». На этой вкладке активируем флажок «Включить расширения Microsoft».

MSDN

Является центральным порталом для разработки Windows. Это огромная коллекция материалов, связанных с разработкой приложений с использованием инструментов Microsoft. Это самая полная база наряду с документацией по разработке настольных приложений и список API Windows.

Применение DLL в WinAPI C

Библиотека общих элементов управления обеспечивает доступ к расширенным функциям операционной системы, таким как строки состояния, индикаторы выполнения, панели инструментов и вкладки. Эти команды находятся в библиотеке commctrl.dll в 16-разрядных системах и comctl32.dll и сгруппированы с пользовательским интерфейсом.

DLL — это формат файла динамической библиотеки ссылок, используемый для хранения нескольких кодов и процедур для программ Windows. Файлы DLL были созданы таким образом, что несколько программ могли использовать их информацию одновременно, помогая сохранить память. Позволяет пользователю редактировать кодирование сразу нескольких приложений без их изменения. Библиотеки DLL можно преобразовать в статические, используя MSIL Disassembler или DLL для Lib 3.00.

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

Прежде чем начать программирование в WinAPI, необходимо настроить среду для кода в ОС Windows. Поскольку это не дистрибутив Linux, у него нет встроенного компилятора для создания приложений. Рассмотрим следующие варианты для компиляции кода:


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