Winapi функции. Введение в Win32 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 («прерывание»).

Зачем нужен 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

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

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

Объявление Windows API функций в Visual FoxPro

Функции Windows API скомпонованы в динамически связанные библиотеки (Dynamic Link Library, DLL ). Как правило, файлы таких библиотек имеют расширение dll . Перед тем, как использовать Windows API функцию в вашем приложении, вы должны её объявить . Для объявления функции применяется команда DECLARE..DLL:

DECLARE [cFunctionType ] FunctionName IN LibraryName ; [cParamType1 [@] ParamName1 , cParamType2 [@] ParamName2 , ...]

Параметры команды:

cFunctionType
необязательный параметр, указывает тип данных, возвращаемых функцией:

cFunctionType Размер,
байт
Описание
Short 16-ти разрядное целое число
Integer, Long 4 32-х разрядное целое число
Single 4 32-х разрядное вещественное число
Double 8 64-х разрядное вещественное число
String - Строка символов

FunctionName
имя функции в DLL-библиотеке. Имя функции чувствительно к регистру символов, то есть GetDC и GETDC - это имена совершенно разных функций.

LibraryName
наименование DLL-библиотеки, в которой находится функция. Для библиотек Kernel32.dll, Gdi32.dll, User32.dll, Mpr.dll и Advapi32.dll можно использовать синоним WIN32API.

AliasName
необязательный параметр, позволяет вместо имени функции использовать придуманный вами псевдоним. Написание псевдонима, в отличие от имени функции, не чувствительно к регистру символов. Как правило, псевдоним используется, когда имя API функции совпадает с именем встроенной (или вашей) функции Visual FoxPro.

cParamType
указывает тип данных передаваемого функции значения:

Параметр может передаваться как по значению, так и по ссылке. Для указания того, что параметр передаётся по ссылке, используется символ "@".

С точки зрения Visual FoxPro не существует никакой разницы между типами данных Long и Integer. Обычно тип Integer применяется для обозначения целых чисел со знаком, а тип Long - целых чисел без знака.

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

Все функции Windows API, как, впрочем, и сама Windows, написаны на языке программирования Си. Поэтому для того, чтобы понять, как правильно использовать API функции в Visual FoxPro (который, кстати, так же написан на Си, по крайней мере, его ядро), познакомимся, какие типы данных применяются в Си и Windows, и, что не менее важно, разберёмся с такими типами данных, как перечисления, структуры и указатели. Кроме того, вы узнаете, что такое прототипы функций в Си, и как, основываясь на описании прототипа функции в MSDN, правильно объявить её в команде DECLARE..DLL.

Базовые типы данных Си

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

Typedef int INT32;

и вот у вас новый тип INT32, который полностью соответствует типу int. Но, с точки зрения Си, это совершенно разные типы, и попытка присвоить переменной типа INT32 значение переменной типа int приведёт к ошибке!

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

    тип char - символ в формате ANSI. Имеет длину 8 разрядов (один байт).

    тип wchar - символ в формате Unicode. Имеет длину 16 разрядов (два байта).

    тип int - целые числа. Они делятся в Си на три типа: int , short int и long int . Последние обычно сокращаются до short и long . Тип short - это 16-ти разрядное, а типы int и long - 32-х разрядные целые числа.

    тип float - вещественные числа, имеющие дробную часть. Имеют длину 32 разряда (4 байта).

    тип double - вещественные числа двойной точности. Имеют длину 64 разряда (8 байт).

    тип enum - перечисляемый тип данных.

    тип void используется для обозначения величин, имеющих нулевую длину и не имеющих значения.

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

Как ни странно, строковый тип в Си отсутствует. На самом деле все строки представлены в Си как массивы символов.

Некоторые типы могут объявляться как беззнаковые. Модификатор unsigned (без знака) используется со следующими типами данных: char , short , int и long .

Например, следующее объявление переменной в Си:

Usigned int имя_переменной ;

означает, что эта переменная - целое 32-х разрядное целое без знака.

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

Перечисляемый тип enum связывает с переменной набор именованных констант, называемых перечисляемыми константами. Объявление перечисляемого типа выглядит так:

Enum поле_тега { const1 , const2 , ... } переменная ;

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

Исторически сложилось так, что тип enum равнозначен типу int - то есть переменная перечисляемого типа занимает в памяти 4 байта. Каждая перечисляемая константа имеет значение, определяемое её порядковым номером в списке; нумерация начинается с нуля. Рассмотрим перечисление CombineMode:

Enum CombineMode{ CombineModeReplace, CombineModeIntersect, CombineModeUnion, CombineModeXor, CombineModeExclude, CombineModeComplement };

В этом перечислении константа CombineModeReplace имеет значение 0, константа CombineModeIntersect имеет значение 1, и так далее; константа CombineModeComplement имеет значение 5.

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

Enum DashCap{ DashCapFlat = 0, DashCapRound = 2, DashCapTriangle = 3 };

Перечисленные типы данных покрывают 99% всех типов данных, используемых в программировании Windows API. Это звучит слишком просто, не так ли? Почему же описания API функций содержат все эти типы - HWND, HINSTANCE, POINT и им подобные?

Причиной тому является то, что Cи имеет особенность, называемую strict-typing . Однажды появившись, переменная одного типа может принимать только те значения, которые соответствуют ее типу. Вы не можете сначала сохранить в переменной строку, а затем присвоить ей число. В Visual FoxPro мы обычно стараемся симулировать подобное путем соглашения о наименованиях. Например, cName представляет собой переменную символьного типа, тогда как nCount - числовую. Strict-typing позволяет создать новый тип данных, присвоив существующему типу данных новое имя. Каждый новый тип представляется отличным от других типов, несмотря на то, что внутренне они хранят одно и то же.

Windows усложняет использование этой концепции. К примеру, тип LONG в действительности представляет собой long int, а тип UINT - unsigned int. Оба типа являются 32-разрядными целыми числами. Производные типы данных определяются в различных include-файлах (файлы с расширением.h). Если вы приобрели Visual Studio.NET, то можете найти эти файлы в папке ..\VC7\PlatformSDK\Include\ .

Типы данных Windows

Определение того, какой из базовых типов Си действительно представляет тип данных, используемый в API функции, является одной из тяжелейших задач в программировании API. Используйте следующее правило: если вы не можете найти слово float, double, char или str где-либо в имени функции или параметра, тогда это обычно 32-разрядное целое. Потребуется некоторое время для понимания и выработки навыков, но потом вы будете запросто преобразовывать типы данных. В следующей таблице приведены основные типы данных Windows и соответствующие им типы, используемые при объявлении функции в Visual FoxPro:

Тип данных
Windows
Тип в объявлении
функции
Описание
BOOL Long 32-х разрядное целое число. 0 означает false, все остальное означает true.
BOOLEAN Long тоже самое, что и BOOL.
BYTE String 8-ми разрядное целое число
CHAR String 8-ми разрядное целое число
CLSID String
COLORREF Long 32-х разрядное целое число
DWORD Long 32-х разрядное целое число
DOUBLE Double 64-х разрядное вещественное число
FLOAT Single 32-х разрядное вещественное число
GUID String 128-разрядное число (16 байт)
HANDLE Long
HBITMAP Long 32-х разрядное целое число без знака
HDC Long 32-х разрядное целое число без знака
HICON Long 32-х разрядное целое число без знака
HGLOBAL Long 32-х разрядное целое число без знака
HKL Long 32-х разрядное целое число без знака
HLOCAL Long 32-х разрядное целое число без знака
HINSTANCE Long 32-х разрядное целое число без знака
HRESULT Long 32-х разрядное целое число без знака
HWND Long 32-х разрядное целое число без знака
LONG Long 32-х разрядное целое число
LPARAM Long 32-х разрядное целое число без знака
SHORT Integer 16-ти разрядное целое число
SIZE_T Long 32-х разрядное целое число без знака
TCHAR String Соответствует типу CHAR для строк формата ANSI и WCHAR для строк формата Unicode
UCHAR String Символ в ANSI кодировке
UINT Long 32-х разрядное целое число без знака
ULONG Long 32-х разрядное целое число без знака
USHORT Integer
UUID String 128-разрядное число (16 байт)
VOID нет Не имеет значения
WCHAR String UNICODE character
WNDPROC Long 32-х разрядное целое число без знака
WORD Integer 16-ти разрядное целое число без знака
WPARAM Long 32-х разрядное целое число без знака

Указатели

Другой концепцией, широко используемой в Си, являются указатели (pointers). Указатель представляет собой переменную, которая содержит адрес области памяти, по которому хранятся данные. Тип указателя всегда определяется типом данных, на которые он указывает; его размер всегда равен четырём байтам. Например, указатель на переменную типа SHORT представляет собой 32-разрядное целое число, как и указатель на любой другой тип данных. Описание указателя, принятое в программировании Windows API, начинается с символов "LP", что означет Long Pointer, или "длинный указатель", работающий с 32-х разрядной моделью памяти. Затем может следовать символ "C" (const), указывающий, что данные не должны изменяться. Далее следует описание типа данных переменной, адрес которой хранится в указателе. Например, LPDWORD - указатель на переменную типа DWORD.

Указатели на числовые данные при объявлении Windows API функции передаются по ссылке. Как пример рассмотрим функцию GetFileSize. Вот её прототип (подробнее о прототипах функций буде рассказано ниже):

DWORD GetFileSize(HANDLE hFile, // дескриптор файла LPDWORD lpFileSizeHigh // указатель);

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

Объявление этой функции в Visual FoxPro:

DECLARE GetFileSize IN WIN32API Long hFile, Long @ FileSizeHight

Как видите, параметр FileSizeHight передаётся функции по ссылке , потому что передача по ссылке - это и есть передача указателя.

Сложнее обстоит дело со строками. Как уже говорилось, символьные строки в Cи - это массивы, поэтому в программировании API функций применяется тип str , определяющий массив символов типа CHAR (соответственно, тип wstr определяет массив символов типа WCHAR). В следующей таблице показаны типы указателей на символьные строки:

Тип указателя
на строку
Описание
LPSTR Указатель на модифицируемую нуль-терминированную строку ANSI-формата. Передаётся по ссылке
LPCSTR Указатель на немодифицируемую нуль-терминированную строку ANSI-формата. Передаётся по значению
LPTSTR Соответствует типу LPSTR для строк формата ANSI и типу LPWSTR для строк формата UNICODE. Передаётся по ссылке.
LPCTSTR Соответствует типу LPSTR для строк формата ANSI и типу LPWSTR для строк формата UNICODE. Передаётся по значению.
LPWSTR Указатель на модифицируемую нуль-терминированную строку UNICODE. Передаётся по ссылке
LPCWSTR Указатель на немодифицируемую нуль-терминированную строку UNICODE. Передаётся по значению

Указатели на символьные данные могут передаваться как по ссылке, так и по значению - тип String в объявлении функции в Visual FoxPro всегда передаёт указатель на переменную, содержащую символьные данные. Используйте передачу символьных данных по ссылке только тогда, когда API функция должна изменить значение параметра.

Структуры

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

Struct поле_тега { тип_элемента элемент1 ; тип_элемента элемент2 ; ..... тип_элемента элементN ; };

Возможно и такое объявление структуры:

Struct { тип_элемента элемент1 ; тип_элемента элемент2 ; ..... тип_элемента элементN ; } переменная ;

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

Struct { WORD wYear ; WORD wMonth ; WORD wDayOfWeek ; WORD wDay ; WORD wHour ; WORD wMinute ; WORD wSecond ; WORD wMilliseconds ; } SYSTEMTIME, *PSYSTEMTIME;

Структуры очень похожи на записи таблиц Visual FoxPro. Так, если запись таблицы personal содержит поля fio , address , tlfnumber и email , то для обращения к полю tlfnumber используется следующий синтаксис:

Personal.tlfnumber

Так же выглядит и обращение к полю структуры:

SYSTEMTIME.wMinute

Для формирования структур в Visual FoxPro используются переменные, содержащие строки символов. Например, для рассмотренной выше структуры SYSTEMTIME вам понадобится переменная длиной 16 байт. В первые два байта этой переменной заносится значение поля wYear , в следующие два байта - значение поля wMonth , в следующие два байта - значение поля wDayOfWeek , и так далее, пока структура не будет полностью сформирована. А при объявлении API функции в Visual FoxPro тип параметра, в котором передаётся переменная, содержащая структуру, должен быть типа String. Как записать в строковую переменную числовые данные, вы узнаете чуть позже.

При программировании Windows API на Си описание указателя на структуру начинается с символов LP (Long Pointer), за которыми следует наименование структуры. Так, указатель нас структуру SYSTEMTIME будет иметь тип LPSYSTEMTIME, указатель на структуру POINT будет иметь тип LPPOINT, и так далее. Как видите, ничего сложного, но, благодаря этой концепции, существует чрезвычайно большое количество типов указателей на структуры.

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

CONST имя_стуктуры *

Здесь модификатор CONST означает, что данные в структуре не должны меняться, а символ (*) после имени структуры означает, что вся эта строка есть описание указателя на структуру. В следующем примере показан прототип функции CopyRect, которая копирует одну структуру в другую:

BOOL CopyRect(LPRECT lprcDst , CONST RECT * lprcSrc );

Описание прототипов функций в MSDN

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

Согласно стандарта ANSI, все функции в Си должны иметь прототипы. Прототип функции достаточно прост:

возвращаемый_тип имя_функции(тип_параметра(ов) имя_параметра(ов) );

Если в прототипе указан тип VOID как возвращаемый_тип , то это означает, что функция не возвращает никаких значений. Если тип VOID указан как тип_параметра , то это означает, что функция не имеет параметров.

Информацию о прототипах Windows API функций, включенных в библиотеки Kernel32.dll, Gdi32.dll, User32.dll, Mpr.dll и Advapi32.dll, в MSDN для Visual Studio.NET вы можете найти, последовательно открывая следующие разделы оглавления (Contents) справки:

MSDN Library

Windows Development Win32 API SDK Documentacion Reference

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

Вот ещё один адрес в MSDN, по которому так же имеется информация об API функциях:

MSDN Library

User Interface Design and Development SDK Documentacion Windows Shell Shell Reference Shell Functions

На следующем рисунке показан фрагмент окна справочной системы MSDN:

Вот как, например, описана в MSDN функция CopyRect:

CopyRect

The CopyRect function copies the coordinates of one rectangle to another.

BOOL CopyRect(
LPRECT lprcDst , // destination rectangle
CONST RECT* lprcSrc // source rectangle
);

Parameters

lprcDst
Pointer to the RECT structure that receives the logical coordinates of the source rectangle.
lprcSrc
Pointer to the RECT structure whose coordinates are to be copied in logical units.

Return Values

If the function succeeds, the return value is nonzero.
If the function fails, the return value is zero.
Windows NT/2000/XP: To get extended error information, call GetLastError.

Remarks

Because applications can use rectangles for different purposes, the rectangle functions do not use an explicit unit of measure. Instead, all rectangle coordinates and dimensions are given in signed, logical values. The mapping mode and the function in which the rectangle is used determine the units of measure.

Example Code

For an example, see Using Rectangles .

Requirements

Windows NT/2000/XP: Included in Windows NT 3.1 and later.
Windows 95/98/Me: Included in Windows 95 and later.
Header: Declared in Winuser.h; include Windows.h.
Library: Use User32.lib.

Как видите, информация достаточно исчерпывающая. Функция возвращает значение типа BOOL, ей передаются два параметра: типа LPRECT и CONST RECT* - указатели на структуры типа RECT. При объявлении этой функции в Visual FoxPro вы должны указать, что первый параметр передаётся по ссылке, а второй - по значению:

DECLARE Long CopyRect IN User32.dll String @ Dst , String Src

А как я определил, что эта функция находится в библиотеке User32.dll? Очень просто. В разделе рекомендаций (Requirements ) пункт Library гласит: Use User32.lib. Подставьте вместо расширения lib расширение dll - и всё! Кстати, там же, в пункте Header, сообщается, в каком include-файле содержится описание прототипа функции.

Но это ещё не всё! Так как функция работает со структурами, то в её описании присутствует гиперссылка на структуру RECT. Щёлкните мышью по этой гиперссылке, и на экране появится подробное описание структуры.

Формирование структур в Visual FoxPro

В девятой версии Visual FoxPro существенно расширены возможности встроенных функций BINTOC и CTOBIN. Теперь эти функции можно применять для преобразования числовых данных в формат, пригодный для использования в структурах. Напомню, что функция BINTOC выполняет преобразование числа в строку, а функция CTOBIN - строки в число.

Синтаксис функции BINTOC:

BINTOC(nExpression , eFlag )

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

Синтаксис функции CTOBIN:

CTOBIN(cExpression , eFlag )

Возможные значения параметра eFlag :

Ниже показаны примеры использования этих функций:

CTOBIN(BINTOC(1000.55,"4RS"), "4RS") && Результат: 1000 ? CTOBIN(BINTOC(-1000.55,"4RS"), "4RS") && Результат: -1000 ? CTOBIN(BINTOC(1000.55,"F"), "4N") && Результат: 1000.549987929 ? CTOBIN(BINTOC(-1000.55,"F"), "4N") && Результат: -1000.549987929 ? CTOBIN(BINTOC(1000.55,"B"), "8N") && Результат: 1000.55 ? CTOBIN(BINTOC(-1000.55,"B"), "8N") && Результат: -1000.55

В качестве примера запишем в переменную Visual FoxPro структуру RECT. Эта структура используется в рассмотренной ранее функции CopyRect для описания координат прямоугольной области. Вот как эта структура описывается в MSDN:

Typedef struct _RECT { LONG left; LONG top; LONG right; LONG bottom; } RECT, *PRECT;

Как видите, структура RECT содержит четыре поля, в каждом из которых хранится значение типа LONG. Для её формирования в Visual FoxPro понадобится строка длиной 16 байт.

Ниже показан код, в котором объявляется функция CopyRect, формируются структуры Dst и Src для передачи их как параметров функции, и затем выполняется копирование одной структуры в другую. В примере используется функция BINTOC для преобразования числа в строку:

DECLARE Long CopyRect IN WIN32API String @ Dst, String Src * Формируем структуру Src cSrc = BINTOC(nLeft,"4RS") + BINTOC(nTop,"4RS") + ; BINTOC(nRight,"4RS") + BINTOC(nBottom,"4RS") * Подготавливаем место для структуры Dst cDst = REPLICATE(CHR(0),16) nResult = CopyRect(@cDst, cSrc) && Копирование

В следующем примере показано, как, используя функцию CTOBIN, можно "разобрать" структуру, получив числовые значения её полей:

NLeft = CTOBIN(SUBSTR(cDst,1,4), "4RS") && RECT.left nTtop = CTOBIN(SUBSTR(cDst,5,4), "4RS") && RECT.top nRight = CTOBIN(SUBSTR(cDst,9,4), "4RS") && RECT.right nBottom = CTOBIN(SUBSTR(cDst,13,4), "4RS") && RECT.bottom

Структуры, содержащие указатели

Достаточно часто встречается ситуация, когда передаваемая Windows API функции структура содержит указатели. В качестве примера рассмотрим функцию StartDoc, создающую документ для печати на принтере. Вот её прототип:

Int StartDoc(HDC hdc , // handle to DC CONST DOCINFO* lpdi // contains file names);

Как видите, второй передаваемый функции параметр - это указатель на структуру DOCINFO. Вот эта структура:

Typedef struct { int cbSize ; LPCTSTR lpszDocName ; LPCTSTR lpszOutput ; LPCTSTR lpszDatatype ; DWORD fwType ; } DOCINFO, *LPDOCINFO;

Первое поле структуры, cbSize , содержит значение длины структуры в байтах. А вот следующие три поля - это указатели на переменные, содержащие символьные данные. В частности, поле lpszDocName содержит указатель на строку с наименованием печатаемого документа (это то самое имя документа, которое вы видите, просматривая очередь печатаемых документов).

В Visual FoxPro достаточно сложно сформировать структуру, содержащую указатели. Во-первых, нужно выделить блок памяти и получить указатель на него. Во-вторых, необходимо переписать в эту память значение переменной Visual FoxPro - таким образом, у нас будет полностью реализован механизм указателей. Последнее, что остаётся сделать - это поместить значение указателя в структуру. При этом нужно выполнить одно существенное требование: выделенная память не должна быть перемещаемой - иначе может оказаться, что наш указатель в какой-то момент будет показывать на область, к которой наши данные не имеют никакого отношения!

Есть несколько возможностей получить блок памяти. Можно взять "кусочек" как из общей памяти Windows, так и из памяти, выделенной процессу (то есть вашему приложению). Второй способ имеет более высокое быстродействие, тем не менее здесь мы рассмотрим способ работы с памятью Windows как более простой.

Функция GlobalAlloc получает у Windows блок памяти указанного размера и возвращает указатель на него. Вот прототип этой функции:

HGLOBAL GlobalAlloc(UINT uFlags , // атрибуты распределения памяти SIZE_T dwBytes // размер в байтах);

Параметр uFlags определяет, как будет распределяться память. В MSDN написано, что он может принимать одно из следующих значений:

Из таблицы следует, что для параметра uFlags следует использовать значение GPTR. Но как узнать, какое это значение? Найдите в MSDN описание функции GlobalAlloc и в разделе Requirements посмотрите, в каком include-файле находится её прототип. Это файл Winbase.h. Именно в нём и следует искать описание значений констант. Вот фрагмент этого файла, в котором определяются перечисленные в таблице константы:

/* Global Memory Flags */ #define GMEM_FIXED 0x0000 #define GMEM_MOVEABLE 0x0002 #define GMEM_NOCOMPACT 0x0010 #define GMEM_NODISCARD 0x0020 #define GMEM_ZEROINIT 0x0040 #define GMEM_MODIFY 0x0080 #define GMEM_DISCARDABLE 0x0100 #define GMEM_NOT_BANKED 0x1000 #define GMEM_SHARE 0x2000 #define GMEM_DDESHARE 0x2000 #define GMEM_NOTIFY 0x4000 #define GMEM_LOWER GMEM_NOT_BANKED #define GMEM_VALID_FLAGS 0x7F72 #define GMEM_INVALID_HANDLE 0x8000 #define GHND (GMEM_MOVEABLE | GMEM_ZEROINIT) #define GPTR (GMEM_FIXED | GMEM_ZEROINIT)

Следовательно, GPTR = GMEM_FIXED + GMEM_ZEROINIT = 0x0000 + 0x0040 = 0x0040.

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

uFlags , Long dwBytes cDocumentName = "Имя печатаемого документа" && Имя документа nLenDocumentName = LEN(cDocumentName) && Длина строки hGlobal = GlobalAlloc(GPTR, nLenDocumentName)

Следующая задача - переписать содержимое переменной cDocumentName в полученный блок памяти. Воспользуемся для этого встроенной функцией Visual FoxPro SYS(2600). Вот её синтаксис:

SYS(2600, dwAddress , nLenght [, cNewString ])

Функция ведёт себя по разному в зависимости от того, указан параметр cNewString или нет.

Если параметр cNewString указан , то функция копирует nLenght байт из переменной cNewString в память по адресу, указанному в dwAddress .

Если параметр cNewString не указан , то функция возвращает nLenght байт из памяти по адресу, указанному в dwAddress .

Как видите, параметр dwAddress - это указатель .

Запишем содержимое строки cDocumentName в выделенную функцией GlobalAlloc память:

SYS(2600, hGlobal, nLenDocumentName, cDocumentName)

Вот полный код формирования структуры DOCINFO:

#DEFINE GPTR 0x0040 DECLARE Long GlobalAlloc IN WIN32API Long uFlags , Long dwBytes cDocumentName = "Имя печатаемого документа" nLenDocumentName = LEN(cDocumentName) hGlobal = GlobalAlloc(GPTR, nLenDocumentName) SYS(2600, dwAddress , nLenght [, cNewString ]) * Начинаем формировать структуру DOCINFO * cDocInfo - переменная, в которой формируется структура cDocInfo = BINTOC(20,"4RS") && DOCINFO.cbSize cDocInfo = cDocInfo + BINTOC(hGlobal,"4RS") && DOCINFO.lpszDocName cDocInfo = cDocInfo + REPLICATE(CHR(0),12) && Остальные поля структуры * Конец формирования структуры

Структура формируется в переменной cDocInfo . В первые четыре байта записываем число 20 - это размер структуры (поле cbSize ). Следующие четыре байта, добавляемые в переменную, - это указатель на область памяти, в которую переписано содержимое переменной cDocumentName - наименование документа. Затем к переменной добавляются ещё двенадцать нулевых байтов - это поля структуры lpszOutput , lpszDatatype и fwType , которые, согласно документации, могут игнорироваться; это означает, что поля должны иметь нулевые значения. Таким образом, получилась строка длиной 20 байтов - что и требовалось.

Особенности использования памяти

Применяя в ваших приложениях функции Windows API, вы должны помнить о том, Visual FoxPro не может управлять памятью, резервируемой этими функциями. Поэтому, если вы распределяете память, например, при помощи функции GlobalAlloc, то вы обязательно должны после использования вернуть эту память Windows, вызвав функцию GlobalFree. Для каждой API функции, резервирующей память, имеется функция - антипод, освобождающая зарезервированную память.

Вот прототип функции GlobalFree:

HGLOBAL GlobalFree(HGLOBAL hMem // указатель на блок памяти);

Функция получает только один параметр - указатель на блок памяти. Вот её объявление и использование в Visual FoxPro:

DECLARE Long GlobalFree IN WIN32API Long hGlobal GlobalFree(hGlobal)

здесь hGlobal - указатель на блок памяти, возвращаемый функцией GlobalAlloc.

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

Передача в функцию массивов

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

Массив Visual FoxPro имеет совершенно другую организацию, которая позволяет хранить в его элементах совершенно разные типы данных. Поэтому невозможно передать массив из Visual FoxPro в Windows API функцию, просто указав его имя как параметр. Более того, в команде DECLARE..DLL нет такого типа данных, как массивы.

Тем не менее выход из этого положения есть. Как и для структур, для передачи массивов используются символьные переменные. Числовые данные из массива должны быть преобразованы в строку, которую вы и передаёте как параметр в Windows API функцию. В следующем примере показан код функции ArrayToString, формирующей строку из массива. Функция получает получает массив taArray (по ссылке) и флаг tlTypeOfValue , указывающий, как нужно преобразовать значения элементов массива - как целые (tlTypeOfValue= .F.) или вещественные (tlTypeOfValue= .T.) числа:

FUNCTION ArrayToString PARAMETERS taArray, tlTypeOfValue EXTERNAL ARRAY taArray LOCAL lnLenArray, lnIndex, lcStruct, lcType lcType = IIF(tlTypeOfValue = .t., "F", "4RS") lnLenArray = ALEN(taArray) && Количество элементов в массиве lcStruct = "" FOR lnIndex = 1 TO lnLenArray lcStruct = lcStruct + BINTOC(taArray, lcType) ENDFOR RETURN lcStruct ENDFUNC

Функция работает как с одномерными, так и с двумерными массивами.

Кодировка символьных данных: форматы ANSI и UNICODE

В кодировке ANSI (применяемой в Visual FoxPro) каждый символ определяется одним байтом, то есть максимальное количество символов равно 256, что, конечно, очень мало. Например, при русификации часть стандартных символов ANSI заменяется на символы кириллицы. А в некоторых алфавитах, например, японской кане, столько символов, что одного байта для их кодировки просто недостаточно. Для поддержки таких языков, и, что более важно, для облегчения "перевода" программ на другие языки, была разработана кодировка Unicode. Каждый символ в Unicode состоит из двух байтов, что позволило расширить набор допустимых символов до 65536.

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

Встроенная функция Visual FoxPro STRCONV выполняет конвертирование строк как из формата ANSI в UNICODE, так и обратно. Вот её синтаксис:

STRCONV(cExpression , nConversionSetting [, nRegionalIdentifier [, nRegionalIDType ]])

Параметр cExpression - это строка, которую необходимо конвертировать. Параметр nConversionSetting указывает характер конвертирования. Из всех его возможных значений нас интересуют только два:

  • 5 - конвертирование из ANSI в UNICODE
  • 6 - конвертирование из UNICODE в ANSI

Необязательные параметры nRegionalIdentifier и nRegionalIDType определяют дополнительные региональные настройки и могут быть безболезненно проигнорированы.

Ниже показаны примеры использования функции STRCONV:

CUnicodeString = STRCONV(cANSIString, 5) && Конвертирование в Unicode cANSIString = STRCONV(cUnicodeString, 6) && Конвертирование в ANSI

Возможно, при прочтении этого раздела у вас сложилось впечатление, что работать с Windows API функциями в Visual FoxPro достаточно просто. И да, и нет. Например, некоторые API функции используют типы данных, не поддерживаемые в команде DECLARE..DLL, например, 64-х разрядные целые числа. Так же есть ряд функций, называемых функциями обратного вызова. В прототипе такой функции перед описанием возвращаемого типа присутствует модификатор CALLBACK. К сожалению, вы не можете использовать такие функции, по крайней мере, напрямую. Так же бывает, что структура содержит указатель на функцию - такие API в Visual FoxPro так же нельзя использовать.

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

В данной статье я предполагаю что вы уже достаточно ознакомились с С++, что бы уметь создавать классы и перегружать для них различные операторы и что вы уже «прятали» какие-то свои механизмы в класс.

Создание и использование консоли

Для отладки Win32 приложения или просто для того что посмотреть как оно там всё внутри происходит я всегда пользуюсь консолью.
Так как вы создаете GUI приложение, а не консольное, то консоль не подключается. Для того что бы её вызвать в недрах интернета был найден вот этот код

If (AllocConsole())
{



std::ios::sync_with_stdio();
}
Для удобства советую обернуть его в функцию. Например:
void CreateConsole()
{
if (AllocConsole())
{
int hCrt = _open_osfhandle((long)GetStdHandle(STD_OUTPUT_HANDLE), 4);
*stdout = *(::_fdopen(hCrt, "w"));
::setvbuf(stdout, NULL, _IONBF, 0);
*stderr = *(::_fdopen(hCrt, "w"));
::setvbuf(stderr, NULL, _IONBF, 0);
std::ios::sync_with_stdio();
}

Вызванная консоль работает только в режиме вывода и работает он также как и в консольных приложениях. Выводите информацию как и обычно - cout/wcout.
Для работоспособности данного кода необходимо включить в прект следующие файлы:
#include
#include #include
и включить пространство имен std в глобальное пространство имён:
using namespace std;
Конечно же, если вы не хотите этого делать, то просто допишите std:: ко всем сущностям которые в ней находятся.

Наследование объектов для вывода и арифм. операций

При создании и изучении самих «окошек» мне всегда требовалось выводить в консоль какое-нибудь значение.
Например:
Вы получаете размер клиентской области окна с помощью функции GetClientRect куда как параметр передается адрес объекта структуры RECT, что бы заполнить этот объект данными. Если вам нужно узнать размер полученной клиентский области вы просто можете вывести его в уже подключённую консоль

Cout<

Но делать так каждый раз (особенно если вам часто приходиться делать что-то подобное) очень неудобно.
Здесь нам на помощь приходит наследование.
Создайте класс который открыто наследуется от структуры RECT и перегрузите оператор вывода << так, как вам угодно.
Например:

Class newrect:public RECT
{
public:
friend ostream& operator<<(ostream &strm,newrect &rect)
{
strm<<"Prtint RECT object:\n";
strm< return strm;
}
};

Теперь просто выводите обьект с помощью cout/wcout:

Cout<

И вам в удобном виде будет выводиться всё так, как вам требуется.
Так же вы можете сделать с любыми нужными вам операторами.
Например, если надо сравнивать или присваивать структуры (допустим тот же RECT или POINT) - перегрузите operator==() и operator=() соответственно.
Если хотите реализовать оператор меньше < что бы быстро сравнивать размеры окна и т.д. перегрузите operator<().
Так вы можете делать, я предполагаю, почти с любыми структурами и самое главное, что все функции которые работают с обычным объектом структуры RECT так же хорошо будут работать и с его наследником.
И еще рекомендую всю эту красоту вынести в отдельный подключаемый файл и использовать при необходимости.

Свой класс

Не знаю как у остальных, но так как я совсем зелёный, я решил для каждой изученной функции или для каждой главы/под главы книги создавать новый проект, что бы всё было по полочкам и можно было в любой момент вернуться и освежить в памяти необходимые моменты.
Так как в WinAPI даже для создания простейшего окна нужно заполнить структуру класса, зарегистрировать её и написать тривиальную оконную процедуру, я после третьего или четвертого проекта вспомнил что я всё таки на С++ пишу.
В итоге я всё спрятал в простенький класс. Хендл окна, его имя, имя класса, адрес оконной процедуры, класс окна (WNDCLASS) всё спрятано в private секцию класса.
Для их получения достаточно описать простенькие методы-Get"еры, к примеру:
HWND GetHWND()
LPCTSTR GetClsName() и т.д.
Заполнение и регистрация оконного класса, создание самого окна и его показ производиться в конструкторе.
Для удобства можно перегрузить конструктор, а заполнение и регистрацию оконного класса спрятать в отдельную private функцию класса и вызывать в каждом из конструкторов. Удобство перегрузки состоит в том, что мне иногда необходимо создать совсем простенькое окно и я вызываю конструктор с двумя параметрами - имя окна и hinstance приложения.
В другой раз мне нужно создать окно с особыми размерами, не с дефолтной процедурой окна и с каким-то другим определённым стилем - я вызываю конструктор с сопутствующими параметрами.
Этот класс у меня определён в отдельно подключаемом файле, который лежит в include папке IDE.
Шаблон такого класса:
class BaseWindow
{
WNDCLASSEX _wcex;
TCHAR _className;
TCHAR _windowName;
HWND _hwnd;
bool _WindowCreation();
public:
BaseWindow(LPCTSTR windowName,HINSTANCE hInstance,DWORD style,UINT x,UINT y,UINT height,UINT width);
BaseWIndow(LPCTSTR windowName,HINSTANCE hInstance);
const HWND GetHWND()const{return HWND;}
LPCTSTR GetWndName()const{return _windowName;}
};

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

P.S.

Всё описанное справедливо для:
Платформа - Windows 7 32 bit
IDE - Visual Studio 2010
Может у кого-то эти советы будут вызывать смех и иронию, но всё-таки мы все когда-то в чём-то были новичками/стажерами/junior"ами.
Прошу к посту отнестись с понимаем. Конструктивная критика, конечно же приветствуется.

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 и связанных с ним технологий.

Интерфейс прикладного программирования WindowsAPI (applicationprogramminginterface) является интерфейсом системного программирования в пользовательском режиме для семейства операционных систем Windows. До выхода 64-разрядных версий Windows программный интерфейс для 32-разрядных версий операционных систем Windows назывался Win32 API, чтобы его можно было отличить от исходной 16-разрядной версии Windows API (которая служила интерфейсом программирования для начальных 16-разрядных версий Windows).

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

  • Базовыеслужбы (Base Services).
  • Службыкомпонентов (Component Services).
  • Службы пользовательского интерфейса (User Interface Services).
  • Графические и мультимедийные службы (Graphics and Multimedia Services).
  • Обмен сообщениями и совместная работа (Messaging and Collaboration).
  • Сеть (Networking).
  • Веб-службы (Web Services).

Описание WindowsAPI можно найти в документации по набору инструментальных средств разработки программного обеспечения - WindowsSoftwareDevelopmentKit (SDK). Эта документация доступна на веб-сайте www.msdn.microsoft.com. Она также включена со всеми уровнями подписки в сеть MicrosoftDeveloperNetwork (MSDN), предназначенную для разработчиков.

Microsoft .NET Framework состоит из библиотеки классов под названием Framework Class Library (FCL) и управляемой среды выполнения кода -Common Language Runtime (CLR). CLR обладает функциями своевременной компиляции, проверки типов, сборки мусора и обеспечения безопасности доступа к коду. Предлагая эти функции, CLR предоставляет среду разработки, повышающую производительность работы программистов и сокращающую количество наиболее распространенных ошибок программирования.

Среда CLR реализована, как классический COM-сервер, код которого находится в стандартной Windows DLL-библиотеке, предназначенной для работы в пользовательском режиме. Фактически все компоненты.NET Framework реализованы как стандартные Windows DLL-библиотеки пользовательского режима, наложенные поверх неуправляемых функций Windows API. (Ни один из компонентов.NET Framework не работает в режиме ядра.) Взаимоотношения между этими компонентами показаны на рисунке.

Службы, функции и стандартные программы.

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

  • Функции Windows API . Документированные, вызываемые подпрограммы в WindowsAPI. Например, CreateProcess, CreateFile и GetMessage.
  • Собственные системные службы (или системные вызовы) . Недокументированные базовые службы в операционной системе, вызываемые при работе в пользовательском режиме. Например, NtCreateUserProcess является внутренней службой, которую функция Windows CreateProcess вызывает для создания нового процесса.
  • Функции поддержки ядра (или подпрограммы) . Подпрограммы внутри операционной системы Windows, которые могут быть вызваны только из режима ядра. Например, ExAllocatePoolWithTag является подпрограммой, вызываемой драйверами устройств для выделения памяти из системных динамически распределяемых областей Windows (называемых пулами).
  • Службы Windows. Процессы, запускаемые Диспетчером управления службами (Windowsservicecontrolmanager). Например, служба Диспетчер задач запускается в виде процесса, работающего в пользовательском режиме, в котором поддерживается команда at (аналогичная UNIX-командам at или cron).
  • Библиотеки DLL (dynamic-link libraries - динамически подключаемые библиотеки) . Набор вызываемых подпрограмм, связанных вместе в виде двоичного файла, который может быть загружен в динамическом режиме приложениями, которые используют эти подпрограммы. В качестве примера можно привести Msvcrt.dll (библиотеку времени выполнения для приложений, написанных на языке C) и Kernel32.dll (одну из библиотек подсистемы Windows API). DLL-библиотеки широко используются компонентами и приложениями Windows, которые работают в пользовательском режиме. Преимущество, предоставляемое DLL-библиотеками по сравнению со статическими библиотеками, заключается в том, что они могут использоваться сразу несколькими приложениями, и Windows обеспечивает наличие в памяти только одной копии кода DLL-библиотеки для тех приложений, в которых имеются ссылки на эту библиотеку. Следует заметить, что неисполняемые.NET-сборки компилируются как DLL-библиотеки, но без каких-либо экспортируемых подпрограмм. CLR анализирует скомпилированные метаданные для доступа к соответствующим типам и элементам классов.

История Win32 API.

Интересно, что Win32 не планировался в качестве исходного интерфейса программирования для той системы, которая в ту пору называлась Windows NT. Поскольку проект Windows NT запускался в качестве замены для OS/2 версии 2, первоначальным интерфейсом программирования был 32-разрядный OS/2 PresentationManagerAPI. Но через год после запуска проекта произошел взлет поступившей в продажу Microsoft Windows 3.0. В результате этого Microsoft сменила направление и сделала Windows NT будущей заменой семейства продуктов Windows, а не заменой OS/2. В связи с этим и возникла необходимость выработать спецификацию Windows API - до этого, в Windows 3.0, API существовал только в виде 16-разрядного интерфейса.

Хотя в Windows API намечалось введение множества новых функций, недоступных в Windows 3.1, Microsoft решила сделать новый API, по возможности, максимально совместимым по именам, семантике и используемым типам данных с 16-разрядным Windows API, чтобы облегчить бремя переноса существующих 16-разрядных Windows-приложений в Windows NT. Этим, собственно, и объясняется тот факт, что многие имена функций и интерфейсов могут показаться непоследовательными: так нужно было для обеспечения совместимости нового Windows API со старым 16-разрядным Windows API.


Top