Point winapi c
Пишем на WinAPI с «нуля»
Автор: Яковлев Игорь Сергеевич
Источник: RSDN Magazine #4-2005
Опубликовано: 30.12.2005
Исправлено: 16.03.2006
Версия текста: 1.0
От редакции
Данная статья является введением в программирование на WinAPI. Важно понимать, что она носит скорее информационный характер, чем служит примером кода для реальных приложений. Дело в том, что WinAPI создавался для языка С, и имеет целый ряд недостатков в применении, как-то: невысокая безопасность, большой объем ручного кодирования для решения простейших задач, С-стиль, плохо выглядящий в C++-приложениях. Практически любое средство разработки на языке C++ для Windows включает те или иные высокоуровневые C++-библиотеки (MFC, ATL/WTL для Visual C++, VCL для C++ Builder), значительно упрощающие разработку Windows-приложений, и делающие ее более безопасной.
Предисловие
Эта статья посвящена описанию программирования приложений на «чистом» Win32 API. Она написана в основном для начинающих программистов, пишущих программы на Visual C++ 6 с использованием библиотеки MFC, но я надеюсь, может пригодиться и более опытным людям.
First Blood
После создания нового проекта Win32 Application, в зависимости от выбранных опций, мастер генерирует стартовый код. Из этого кода программисту впоследствии, и придется писать программу. Создавая новый проект Win32 Application, выберите в окне мастера опцию An empty project и добавьте в раздел Source Files новый файл с расширением .cpp.
В этом файле добавьте функцию WinMain вида:
Вот и готова первая программа на WinAPI. Она выводит сообщение, после чего завершает свою работу. Обратите внимание на параметры функции WinMain:
- HINSTANCE hInstance – дескриптор экземпляра приложения. Этот дескриптор содержит адрес начала кода программы в ее адресном пространстве. Дескриптор hInstance чаще всего требуется функциям, работающим с ресурсами программы.
- HINSTANCE hPrevInstance – дескриптор предыдущего экземпляра приложения. Этот дескриптор остался от старых версий Windows — скорее всего, вам он никогда не пригодится.
- LPSTR lpCmdLine – указатель на начало командной строки, введенной при запуске программы.
- int nCmdShow – это значение содержит желаемый вид окна (например, свернутый или развернутый)
Значение, которое возвращается функцией WinMain (тип int) – код завершения программы. Принято, что если программа завершила свое выполнение без ошибок, возвращается 0.
Функция WinMain – первая функция, которая выполнятся в программе (ее еще называют «точка входа» или «entry point»). С нее все начинается, и ею (желательно) все должно закончиться.
Функция WinMain – это первая функция, которую вы можете увидеть и заполнить кодом. На самом деле до этой функции выполняется достаточно много кода из библиотеки C++
You have a Message!
Программисты, незнакомые с программированием на WinAPI, спросят: «Что с этим делать?!», — или: «Где создавать CDialog?». В данном случае ответ прост – нигде! В нашем проекте нет класса CDialog или, предположим, CButton – ведь эта статья посвящена тому, как обойтись без них.
В Windows при каждом событии, произошедшем в системе, отсылается «сообщение Windows» («windows message»). Эти сообщения уведомляют программу о событиях в системе, а программа в свою очередь, может на них реагировать. Сообщения может отсылать не только Windows, но и сами приложения. Это является одним из способов организации связи между процессами в системе. Конечно, программа может отсылать сообщения и самой себе.
Сообщение можно отослать функцией SendMessage или ее асинхронным аналогом PostMessage.
Для приема сообщений в программе должен находиться «цикл сообщений» («message loop») который обычно выглядит так:
Функция GetMessage принимает следующие параметры:
- LPMSG lpMsg – указатель на структуру сообщения, в которую GetMessage вернет результат.
- HWND hWnd – описатель окна, от которого GetMessage примет сообщение (NULL означает, что GetMessage принимает сообщения от всех окон, принадлежащих потоку).
- UINT wMsgFilterMin – наименьший идентификатор сообщения, которое примет GetMessage.
- UINT wMsgFilterMax – наибольший идентификатор сообщения, которое примет GetMessage (если в значениях параметров wMsgFilterMin и wMsgFilterMax передать 0, функция будет принимать ВСЕ сообщения).
Функция GetMessage не отдает управление программе, пока не придет какое-либо сообщение. Если пришедшее сообщение – WM_QUIT, функция GetMessage вернет . Тогда цикл прервется, и программа завершит свою работу. При любом другом сообщении функция GetMessage возвращает значение больше нуля, и начинатся выполнение тела цикла. При ошибке GetMessage возвращает -1.
Сообщение WM_QUIT лучше посылать с помощью специальной функции PostQuitMessage(int iExitCode). Эта функция отошлет сообщение WM_QUIT, а в параметре wParam передаст код завершения программы, указанный в iExitCode.
Функция DispatchMessage должна вызвать «функцию обработки сообщений». В простейшем варианте она выглядит так:
При вызове этой функции ей передаются следующие параметры:
- HWND hWnd – описатель окна, от которого пришло сообщение.
- UINT message – идентификатор сообщения.
- WPARAM wParam и LPARAM lParam – параметры сообщения.
Функция обработки сообщений не обязательно должна иметь имя WndProc. Таких функций в программе может быть несколько, но их прототипы обязательно должны выглядеть так:
При вызове этой функции DispatchMessage передает в параметре message идентификатор сообщения. По этому идентификатору производится выборка и выполняется какое-либо действие («реакция на сообщение»).
В Windows существует очень много сообщений! Писать обработчики для всех сообщений – нереальная задача. Чтобы Windows сама обработала бесполезное для вас сообщение, необходимо вызвать функцию DefWindowProc:
Желательно передавать все необработанные сообщения этой функции, а результат ее выполнения возвращать при выходе из WndProc. Это очень важно, так как от обработки некоторых сообщений Windows ждет возврата конкретных результатов или действий.
Одной функцией обработки сообщений могут пользоваться несколько окон, но для одного окна может существовать только одна функция обработки сообщений! Как же система определяет, какой именно функцией обработки сообщения пользоваться для конкретного окна и где она находится?! За это отвечает «класс окна» («window class»).
CLASSные окна
При создании нового окна ему присваивается «Класс окна» (window class). Класс окна задает оконную функцию, используемую по умолчанию. Кроме этого, класс окна задает другие параметры окна, такие, как стиль, меню окна, цвет рабочей области и т.д. Разные классы окон могут указывать на одну и ту же функцию обработки сообщений. Для создания класса его необходимо зарегистрировать.
Итак, регистрация! За нее отвечает функция RegisterClass. В ее параметре необходимо передать указатель на структуру WNDCLASS. Обычно для заполнения структуры и вызова RegisterClass создают отдельную функцию. Но это — дело вкуса.
Вот простейший пример такой функции:
- WNDPROC lpfnWndProc – адрес функции обработки сообщений.
- HINSTANCE hInstance – уже знакомая переменная, описывающая экземпляр.
- LPCTSTR lpszClassName – имя нового класса.
- HICON hCursor – описатель курсора мыши.
- HBRUSH hbrBackground – цвет рабочей области окна.
Функция RegisterClass возвращает уникальный «описатель класса окна» типа ATOM. Если при регистрации класса произошла ошибка, это значение будет равно нулю. Чтобы узнать, что произошло, можно вызвать функцию GetLastError().
Существует также функция RegisterClassEx. Это аналог функции RegisterClass с возможностью присвоения окнам маленькой иконки. При работе с этой функцией необходимо пользоваться структурой WNDCLASSEX.
Если вы решились работать с GUI Windows вручную, то пользоваться нужно именно RegisterClassEx, поскольку приложение, не имеющее маленькой иконки, сегодня выглядит в Windows как минимум странно. – прим.ред.
Следите, чтобы имя вашего класса не совпадало с именами системных классов (например: button или edit).
Я описал не всю структуру. Все незаполненные поля, которых нет в примере, сейчас равны нулю. Об их значениях можно узнать из MSDN.
Сообщения от окон, созданных на базе класса, зарегистрированного описанной выше функцией RegMyWindowClass, будут обрабатываться функцией с именем WndProc. Чтобы функция WndProc поняла, от какого именно окна пришло сообщение, ей передается уникальный описатель окна HWND.
Our Windows
На вашем месте у меня возникло бы желание увидеть те самые пресловутые окна, из-за которых столько шума. Окно в Windows создается функцией CreateWindow. Вот ее прототип:
Как видите, у функции множество параметров:
- LPCTSTR lpClassName – имя класса для создаваемого окна (это имя использовалось при регистрации класса).
- LPCTSTR lpWindowName – имя окна.
- DWORD dwStyle – стиль окна.
- int x – позиция по горизонтали верхнего левого угла окна.
- int y – позиция по вертикали.
- int nWidth – ширина окна.
- int nHeight – высота окна.
- HWND hWndParent – используется для создания «дочернего окна» («child window»). Сюда передается описатель «родительского окна» («parent window»).
- HMENU hMenu – описатель меню (если hMenu равно нулю, используется меню класса, указанного в lpClassName).
- HINSTANCE hInstance – экземпляр приложения.
- LPVOID lpParam – указатель на пользовательский параметр окна. Этот указатель со всеми остальными параметрами функции CreateWindow будет занесен в структуру CREATESTRUCT. В сообщениях WM_CREATE или WM_NCCREATE параметр lParam будет содержать указатель на эту структуру.
Функция CreateWindow возвращает уникальный описатель окна HWND. Если функция вернула ноль, значит, во время создания окна произошла ошибка. Какая именно, можно узнать, вызвав функцию GetLastError.
Существует также функция CreateWindowEx, в которой дополнительно присутствует параметр dwExStyle. С его помощью можно создать окно с дополнительными стилями.
План полета
Итак, сейчас я упрощенно расскажу, что же произойдет, если щелкнуть по окну левой кнопкой мыши.
- Пользователь нажимает левую кнопку мыши в то время когда курсор мыши находится над рабочей областью окна.
- Windows помещает сообщение WM_LBUTTONDOWN в очередь потока.
- Цикл обработки сообщения должен вынуть сообщение с помощью функции GetMessage и передать его на обработку функции DispatchMessage.
- Функция DispatchMessage находит окно, которому предназначено сообщение и помещает сообщение в его очередь.
- Функция окна обрабатывает сообщение WM_LBUTTONDOWN и возвращает результат.
- Тело цикла заканчивается, и управление снова передается функции GetMessage для ожидания новых сообщений.
Итого
WinMain, регистрация класса, цикл сообщений, функция обработки сообщений, создание окна. Как все это связать?! Вот код, который объединяет все написанное выше в одну программу:
Вот, в принципе, и все! Это полноценное приложение на WinAPI.
Программа регистрирует класс, создает окно этого класса и обслуживает сообщение WM_LBUTTONUP (оно приходит по событию отпускания левой кнопки мыши), показывает окно, и после обработки сообщения снова возвращается в цикл сообщений, находящийся в WinMain.
Признаю, что данная статья и программа опускает очень много деталей! Многие вещи были не раскрыты (например, остальные переменные структуры WNDCLASS). Все это сделано для того, чтобы максимально упростить статью и уменьшить код программы.
Вызов функций Windows API
Из книги C#. Советы программистам (в сокращении)
Программный код, который выполняется под управлением CLR (Common Language Runtime, т. е. общая среда выполнения языков), называется управляемым (managed) кодом. Программный код, выполняющийся вне среды выполнения CLR, называется неуправляемым (unmanaged) кодом. Примером неуправляемого программного кода служат функции Win32 API, компоненты COM, интерфейсы ActiveX. Несмотря на большое количество классов .NET Framework, содержащих множество методов, программисту все равно приходится иногда прибегать к неуправляемому коду. Надо сказать, что число вызовов неуправляемого кода уменьшается с выходом каждой новой версии .NET Framework. Microsoft надеется, что наступит такое время, когда весь код можно будет сделать управляемым и безопасным. Но пока реальность такова, что без вызовов функций Windows API нам пока не обойтись. Но сначала немного теории.
Управляемый код .NET Framework может вызывать неуправляемую функцию из DLL (функцию Windows API) при помощи специального механизма Platform Invoke (сокр. P/Invoke). Для того чтобы обратиться к какой-нибудь неуправлямойнеуправляемой библиотеке DLL, вы должны преобразовать .NET-объекты в наборы struct, char* и указателей на функции, как того требует язык C. Как сказали бы программисты на своем жаргоне — вам нужно маршалировать параметры. Более подробно о маршалинге (Marshalling) вам следует почитать в документации. Чтобы вызвать DLL-функцию из C#, сначала ее необходимо объявить (программисты, имеющие опыт работы с Visual Basic 6.0, уже знакомы с этим способом). Для этого используется атрибут DllImport:
Иногда в примерах вы можете также встретить такой способ (длинный и неудобный): [System.Runtime.InteropServices.DllImport(«User32.Dll»)]. , но это на любителя.
Атрибут DllImport сообщает компилятору, где находится точка входа, что позволяет далее вызывать функцию из нужного места. Вы должны всегда использовать тип IntPtr для HWND, HMENU и любых других описателей. Для LPCTSTR используйте String, а сервисы взаимодействия (interop services) выполнят автоматический маршаллинг System.String в LPCTSTR до передачи в Windows. Компилятор ищет указанную выше функцию SetWindowText в файле User32.dll и перед ее вызовом автоматически преобразует вашу строку в LPTSTR (TCHAR*). Почему это происходит? Для каждого типа в C# определен свой тип, используемый при маршалинге по умолчанию (default marshaling type) . Для строк это LPTSTR.
Вызов функций Windows API, имеющих выходной строковый параметр char*
Предположим, нам необходимо вызвать функцию GetWindowText, у которой имеется строковый выходной параметр char*. По умолчанию, для строк используется LPTSTR, но если мы будем использовать System.String, как было сказано выше, то ничего не произойдет, так как класс System.String не позволяет модифицировать строку. Вам необходимо использовать класс StringBuilder, который позволяет изменять строки.
Тип, используемый для маршашлинга StringBuilder по умолчанию, — тоже LPTSTR, зато теперь GetWindowText может модифицировать саму вашу строку:
Таким образом, ответом на вопрос, как вызывать функцию, у которой есть выходной строковый параметр, будет — используйте класс StringBuilder.
Изменение типа, применяемого для маршалинга по умолчанию
Например, мы хотим вызвать функцию GetClassName, который принимает параметр LPSTR (char*) даже в Unicode-версиях. Если вы передадите строку, общеязыковая исполняющая среда (CLR) преобразует ее в серию TCHAR. Но с помощью атрибута MarshalAs можно переопределить то, что предлагается по умолчанию:
Теперь, когда вы вызовете GetClassName, .NET передаст вашу строку в виде символов ANSI, а не «широких символов».
Вызов функций, требующих struct
Возьмем для примера функцию GetWindowRect, которая записывает в структуру RECT экранные координаты окна. Чтобы вызвать функцию GetWindowRect и передать ей структуру RECT нужно использовать тип struct в сочетании с атрибутом StructLayout:
Важно использовать ref, чтобы CLR передала параметр типа RECT как ссылку. В этом случае функция сможет модифицировать ваш объект, а не его безымянную копию в стеке. После такого объявления функции можно ее вызвать в коде:
Обратите внимание, что ref используется и в объявлении, и при вызове функции. Тип, по умолчанию применяемый для маршалинга типов struct — по умолчанию LPStruct, поэтому необходимости в атрибуте MarshalAs нет. Но если вы хотите использовать RECT в виде класса, а не struct, вам необходимо реализовать оболочку:
Работа с функциями обратного вызова в C#
Для использования функций, написанных на C#, в качестве функций обратного вызова Windows, нужно использовать делегаты (delegate).
Объявив свой тип делегата, можно написать оболочку для функции Windows API:
Так как в строке с delegate просто объявляется тип делегата (delegate type), сам делегат нужно предоставить в классе:
а затем передать оболочке:
Проницательные читатели заметят, что я умолчал о проблеме с lparam.
В языке C, если в EnumWindows дается LPARAM, Windows будет уведомлять вашу функцию обратного вызова этим LPARAM. Обычно lparam — указатель на некую структуру или класс, который содержит контекстную информацию, нужную вам для выполнения своих операций. Но запомните: в .NET слово «указатель» произносить нельзя! Так что же делать? Можно объявить ваш lparam как IntPtr и использовать GCHandle в качестве его оболочки:
Не забудьте вызвать Free, когда закончите свои дела! Иногда в C# приходится самому освобождать память. Чтобы получить доступ к «указателю» lparam внутри перечислителя, используйте GCHandle.Target.
Ниже показан написанный мной класс, который инкапсулирует EnumWindows в массив. Вместо того чтобы возиться со всеми этими делегатами и обратными вызовами, вы пишете:
Небольшая программа ListWin (Приложение ListWin.cs), которую я написал для перечисления окон верхнего уровня, позволяет просматривать списки HWND, имен классов, заголовков и/или прямоугольников окон, используя RECT или Rectangle. Исходный код ListWin показан не полностью; весь исходный код можно скачать по ссылке, приведенной в конце статьи.
Создание собственной управляемой библиотеки
Можно создать собственную управляемую библиотеку, из которой можно будет вызывать функции Windows API. Для этого в Visual Studio предусмотрены специальные опции. Новый проект создается как библиотека классов (Class Library). Сборка при этом автоматически получает расширение dll. Использовать управляемую библиотеку в управляемом коде просто. Для этого надо добавить ссылку (используя меню Project | Add Reference…) на библиотечную сборку, указав месторасположение сборки в соответствующем диалоговом окне. После этого Visual Studio копирует сборку в директорию, в которой располагается разрабатываемый код. Далее в коде программы используется либо оператор using, либо полное имя библиотечного модуля с точечной нотацией. Все библиотечные классы и методы готовы к использованию в коде программы.
Также можно воспользоваться командной строкой. Чтобы скомпилировать класс Win32API.cs, введите:
В результате у вас будет создан файл Win32API.dll, доступный для любого проекта на C#.
Пример создания библиотеки и использования функций Windows API находится в папке Win32 на прилагаемом к книге диске.
Примеры использования функций API
Вкратце ознакомившись с теорией, перейдем к конкретным примерам. В предыдущих главах я уже неоднократно приводил пример использования функций Windows API для решения различных проблем. Рассмотрим еще несколько полезных советов, которые не вошли в другие главы.
- Блокировка компьютера — Если вам необходимо блокировать компьютер, то вызовите функцию LockWorkStation. Результат работы примера будет аналогичен нажатию комбинации клавиш Win+L или клавиш Ctrl+Alt+Del с последующим выбором кнопки (или команды меню) Блокировка.
- Является ли текущий пользователь администратором? — Если необходимо удостовериться, что текущий пользователь имеет права администратора, то можно вызвать функцию IsUserAnAdmin.
- Мигание заголовка формы — Наверное, вам приходилось видеть, что заголовок окна вдруг начинал мигать, привлекая ваше внимание. Подобный эффект реализуется вызовом функций FlashWindow или FlashWindowsEx.
- Форматирование дисков — Чтобы вызвать стандартное диалоговое окно форматирования дисков нужно воспользоваться функцией SHFormatDrive
- Открытие и закрытие лотка привода компакт-дисков — Наверное, при работе с утилитами, прожигающими компакт-диски CD-R и CD-RW, вы замечали, что у них имеется возможность извлекать компакт-диск из привода программным путем. Неплохо бы научиться делать то же самое при помощи C#. Для этого используем функцию mciSendString в связке с специальными командами, которые и позволят нам открывать и закрывать лоток привода компакт-дисков
- Создание собственного пункта в системном меню — У большинства окон в Windows имеется так называемое системное меню. Внешний вид, как правило, у всех меню одинаков, но иногда попадаются программы, у которых в системном меню имеются свои собственные пункты. Естественно, любого программиста разбирает любопытство — а как реализовать эту функциональность в своей программе. На данный момент .NET Framework не предоставляет такого полезного свойства как Form.SystemMenu или что-то в этом роде. Поэтому придется прибегать к помощи механизма P/Invoke для вызовов функций Windows API. Опытные программисты (особенно имеющие опыт работы с языком С++) знают, что для модификации системного меню используется функция GetSystemMenu, а также вспомогательная функция AppendMenu, которая позволяет добавлять в меню разделители, картинки, галочки и сам текст.
- Извлечение значков из файлов — Функция ExtractIcon позволяет извлекать значки из ресурсов, которые зашиты в файлах EXE, DLL, CPL и др. Кроме того, функция позволяет подсчитать количество значков, находящихся в файле. В качестве испытуемого файла возьмем динамическую библиотеку shell32.dll, которая имеется в любой версии Windows.
- Вызов диалогового окна Смена значка — Существует такая функция Windows API как PickIconDlg. Долгое время она была официально не документирована, но, начиная, с Windows 2000 компания Microsoft все-таки выложила описание этой функции на сайте MSDN. Функция PickIconDlg вызывает стандартное диалоговое окно «Смена значка», позволяющее выбрать значок из модуля. Тем самым, мы можем предоставить пользователю возможность самому выбирать нужный значок, после чего и вывести его на форму (или произвести другие операции).
- Панель задач, кнопка Пуск и часы в области уведомлений — Очень часто программисты хотят получить доступ к стандартным элементам интерфейса Рабочего стола Windows. Например, разработчики хотят получить координаты панели задач, программно нажать на кнопку Пуск, спрятать и показать эту кнопку Пуск и многое другое.
- Смена обоев Рабочего стола — Если вы хотите периодически менять картинку на своем Рабочем столе, то можете это сделать программным способом прямо из своего приложения. Для смены обоев Рабочего стола вызывается одна функция Windows API SystemParametersInfo.
Заключение
Несмотря на огромное число имеющихся классов .NET Framework, программисту по-прежнему приходится прибегать к вызовам системных функций Windows API. В папке Win32Help на прилагаемом к книге компакт-диске вы найдете демо-версию справочника по функциям Windows API для .NET Framework. Если вам понравится этот справочник, то вы можете приобрести его полную версию на моем сайте.
[c++]WinApi-простая программа рисования линий
Я новичок, и я пытаюсь написать простую программу рисования линий с помощью мыши. У меня есть проблема с рисованием этих линий, потому что он оставляет следы позади.
Вот изображение моей проблемы:
И вот пример моего кода:
EDIT: теперь это работает хорошо, но если бы вы могли объяснить мне еще одну вещь, как я могу сделать так, чтобы мои старые линии оставались на клиентской области, когда я рисую новые линии? Потому что теперь я могу нарисовать только одну линию. Должен ли я использовать Bitmap для сохранения экрана или что-то еще?
EDIT:EDIT: хорошо, я использовал вектор для сохранения координат каждой строки. Спасибо вам, ребята, за помощь!
1 Ответ
Вы получаете остаточные следы, потому что вы не стираете свои старые чертежи перед рисованием новой линии или, по крайней мере, обновляете last_x и last_y при каждом перемещении, чтобы новая линия соединялась с концом предыдущей строки, как в примере Microsoft .
Но, вы действительно не должны рисовать на окне непосредственно в обработчиках сообщений мыши вообще. Как только окно нужно перекрасить по какой-либо причине, весь ваш рисунок будет потерян. Правильный способ справиться с этим-выполнить весь ваш рисунок в обработчике сообщений WM_PAINT . Используйте сообщения мыши, чтобы отслеживать информацию о линии по мере необходимости, а затем выполните все фактические чертежи только в WM_PAINT .
Это нарисует одну линию, которая начинается в точке, где мышь была сначала удержана, а затем следует за мышью, когда она перемещается.
Или, если вы хотите нарисовать несколько линий end-to-end, которые следуют за мышью, пока она удерживается, попробуйте это:
Похожие вопросы:
Как оптимизировать эту процедуру рисования линий ? Будет ли memcpy работать быстрее ? void ScreenDriver::HorizontalLine(int wXStart, int wXEnd, int wYPos, COLORVAL Color, int wWidth) < int iLen =.
Я пробираюсь через книгу Dietle C# по одной странице за раз, и я застрял. На странице 555 есть самая простая программа рисования, которую вы можете себе представить. Когда вы перемещаете мышь, она.
Нашел эту замечательную библиотеку для рисования линий на веб-странице http://www.walterzorn.de/en/jsgraphics/jsgraphics_e.htm Я на самом деле прекрасно работал для меня на странице для рисования.
Я использую один 3rd party SDK, который получает hwnd (window handle) и рисует что-то на моем окне. И я хочу указать область рисования окна (слева, справа, сверху, снизу)? Как это можно сделать ? Я.
Я импортирую много необработанных данных рисования (штрихов) и конвертирую их в изображения. Я использовал базовый HTML5 Canvas до сих пор, который работал нормально. Но я хотел реализовать алгоритм.
Я хочу создать простое приложение java для рисования только линий. Моя программа сейчас такая; Пользователь может рисовать все, перетаскивая свою мышь, но к тому времени, когда он отпустит свой.
Как создать список линий для рисования, Если у меня есть пиксельные данные для изображения, поэтому мне не нужно рисовать каждый пиксель? Подойдет любой язык, хотя я перечислил то, для чего у меня.
Я изучаю OpenGL и после прочтения нескольких учебников и книг все еще не нашел ответа на простой вопрос: есть ли легкая библиотека свободного программного обеспечения для рисования основных фигур 2D.
Я знаю, что в linux, opengl может работать с видеопамятью напрямую. Верно ли это для windows? Я уже не говорю о взаимодействии opengl и window manager. Я говорю о рисовании примитивов, образов и т.
Нужно рисовать линии, а не точки с mousebuttondown При щелчке мышью программа рисует точки, я предполагаю, что еще один цикл необходим для рисования линий с помощью удерживаемой кнопки мыши. while.
Потоки (threads) в WinAPI
Когда приложение начинает свою работу, для него создаётся процесс (process). Обычно, каждой программе соответствует один процесс.
При создании процесса для него выделяется память — виртуальное адресное пространство (virtual address space). Когда в отладчике мы смотрим на адреса переменных — мы видим адреса из этого пространства.
Потоки (Threads)
Каждый процесс имеет как минимум один поток (thread). До сих пор наши программы состояли из одного процесса и одного потока. В этом уроке мы научимся создавать дополнительные потоки в процессе.
Самый главный вопрос о потоках: для чего нужно несколько потоков? Разные потоки могут одновременно выполняться разными ядрами процессора. Например, в нашей однопоточной программе одна функция просчитывает искусственный интеллект, а другая — физику взаимодействия объектов. В этом случае сначала будет выполнена одна функция, а потом вторая. Если же мы разделим программу на два процесса и запустим программу на двухъядерном процессоре, то искусственный интеллект и физика будут просчитываться одновременно.
Все потоки имеют доступ к адресному пространству процесса. И это может стать серьёзной проблемой. Например, один поток обрабатывает данные и, одновременно, второй пытается вывести эти же данные на экран. Что произойдёт? Успеет ли первый поток обработать все данные, до того как до них доберётся второй поток? Или же второй поток обгонит первый, и часть данных пользователь увидит обработанными, а часть — нет? Эти вопросы мы обсудим в следующих уроках.
С точки зрения C++ поток — это обычная функция имеющая определённый прототип. Для создания потока используется функция CreateThread.
Создание потоков — CreateThread
Функция CreateThread возвращает описатель потока:
!1?HANDLE WINAPI CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId );?1!
1. lpThreadAttributes — данный аргумент определяет, может ли создаваемый поток быть унаследован дочерним процессом. Мы не будем создавать дочерние процессы, поэтому ставим NULL.
2. dwStackSize — размер стека в байтах. Если передать 0, то будет использоваться значение по-умолчанию (1 мегабайт).
3. lpStartAddress — адрес функции, которая будет выполняться потоком. Т.е. можно сказать, что функция, адрес которой передаётся в этот аргумент, является создаваемым потоком. Данная функция должна соответствовать определённому прототипу — рассмотрим ниже. Имя функции может быть любым — вы сами его выбираете.
4. lpParameter — указатель на переменную, которая будет передана в поток.
5. dwCreationFlags — флаги создания. Здесь можно отложить запуск выполнения потока. Мы будем запускать поток сразу же, передаём 0.
6. lpThreadId — указатель на переменную, куда будет сохранён идентификатор потока. Нам идентификатор не нужен, передаём NULL.
Давайте посмотрим на код вызова CreateThread:
!1?HANDLE thread = CreateThread(NULL,0,thread2,NULL, 0, NULL);?1!
Здесь мы сохраняем описатель потока в переменной thread. Обратите внимание на третий аргумент — адрес функции потока. thread2 — имя функции, которая и будет являться вторым потоком. Вот её код:
Функция потока должна соответствовать следующему прототипу:
!1?DWORD WINAPI ThreadProc(LPVOID lpParameter)?1!
Аргумент, который может принимать данная функция передаётся четвёртым параметров функции CreateThread. Если отбросить все переопределения типов, то данный прототип выглядит так:
!1?unsigned long __stdcall ThreadProc(void* lpParameter)?1!
Напоследок рассмотрим пример создания второго потока:
Создание программы с двумя потоками
Код программы можно скачать в начале урока. Это простая консольная программа. Для работы с потоками необходимо включить файл windows.h. Рассмотрим основной код:
!1?DWORD WINAPI thread2(LPVOID); int main() < cout
theForger’s Win32 API Programming Tutorial
Sometimes people come on IRC and ask «How do I make a window?». Well it’s not entirely that simple I’m afraid. It’s not difficult once you know what you’re doing but there are quite a few things you need to do to get a window to show up; And they’re more than can be simply explained over a chat room, or a quick note.
I always liked to do things first and learn them later. so here is the code to a simple window which will be explained shortly. For most part this is the simplest windows program you can write that actually creates a functional window, a mere 70 or so lines. If you got the first example to compile then this one should work with no problems.
Step 1: Registering the Window Class
A Window Class has NOTHING to do with C++ classes. The variable above stores the name of our window class, we will use it shortly to register our window class with the system.
This is the code we use in WinMain() to register our window class. We fill out the members of a WNDCLASSEX structure and call RegisterClassEx() .
The members of the struct affect the window class as follows: cbSize The size of the structure. style Class Styles ( CS_* ), not to be confused with Window Styles ( WS_* ) This can usually be set to 0 . lpfnWndProc Pointer to the window procedure for this window class. cbClsExtra Amount of extra data allocated for this class in memory. Usually 0 . cbWndExtra Amount of extra data allocated in memory per window of this type. Usually 0 . hInstance Handle to application instance (that we got in the first parameter of WinMain() ). hIcon Large (usually 32×32) icon shown when the user presses Alt+Tab. hCursor Cursor that will be displayed over our window. hbrBackground Background Brush to set the color of our window. lpszMenuName Name of a menu resource to use for the windows with this class. lpszClassName Name to identify the class with. hIconSm Small (usually 16×16) icon to show in the taskbar and in the top left corner of the window. Don’t worry if that doesn’t make much sense to you yet, the various parts that count will be explained more later. Another thing to remember is to not try and remember this stuff. I rarely (never) memorize structs, or function parameters, this is a waste of effort and, more importantly, time. If you know the functions you need to call then it is a matter of seconds to look up the exact parameters in your help files. If you don’t have help files, get them. You are lost without. Eventually you will come to know the parameters to the functions you use most.
We then call RegisterClassEx() and check for failure, if it fails we pop up a message which says so and abort the program by returning from the WinMain() function.
Step 2: Creating the Window
Once the class is registered, we can create a window with it. You should look up the paramters for CreateWindowEx() (as you should ALWAYS do when using a new API call), but I’ll explain them briefly here.
The first parameter ( WS_EX_CLIENTEDGE ) is the extended windows style, in this case I have set it to give it a sunken inner border around the window. Set it to 0 if you’d like to see the difference. Also play with other values to see what they do.
Next we have the class name ( g_szClassName ), this tells the system what kind of window to create. Since we want to create a window from the class we just registered, we use the name of that class. After that we specify our window name or title which is the text that will be displayed in the Caption, or Title Bar on our window.
The parameter we have as WS_OVERLAPPEDWINDOW is the Window Style parameter. There are quite a few of these and you should look them up and experiment to find out what they do. These will be covered more later.
The next four parameters ( CW_USEDEFAULT, CW_USEDEFAULT, 320, 240 ) are the X and Y co-ordinates for the top left corner of your window, and the width and height of the window. I’ve set the X and Y values to CW_USEDEFAULT to let windows choose where on the screen to put the window. Remeber that the left of the screen is an X value of zero and it increases to the right; The top of the screen is a Y value of zero which increases towards the bottom. The units are pixels, which is the smallest unit a screen can display at a given resolution.
Next ( NULL, NULL, g_hInst, NULL ) we have the Parent Window handle, the menu handle, the application instance handle, and a pointer to window creation data. In windows, the windows on your screen are arranged in a heirarchy of parent and child windows. When you see a button on a window, the button is the Child and it is contained within the window that is it’s Parent. In this example, the parent handle is NULL because we have no parent, this is our main or Top Level window. The menu is NULL for now since we don’t have one yet. The instance handle is set to the value that is passed in as the first parameter to WinMain() . The creation data (which I almost never use) that can be used to send additional data to the window that is being created is also NULL .
If you’re wondering what this magic NULL is, it’s simply defined as 0 (zero). Actually, in C it’s defined as ((void*)0) , since it’s intended for use with pointers. Therefore you will possibly get warnings if you use NULL for integer values, depending on your compiler and the warning level settings. You can choose to ignore the warnings, or just use 0 instead.
Number one cause of people not knowing what the heck is wrong with their programs is probably that they didn’t check the return values of their calls to see if they failed or not. CreateWindow() will fail at some point even if you’re an experianced coder, simply because there are lots of mistakes that are easy to make. Untill you learn how to quickly identify those mistakes, at least give yourself the chance of figuring out where things go wrong, and Always check return values!
After we’ve created the window and checked to make sure we have a valid handle we show the window, using the last parameter in WinMain() and then update it to ensure that it has properly redrawn itself on the screen.
The nCmdShow parameter is optional, you could simply pass in SW_SHOWNORMAL all the time and be done with it. However using the parameter passed into WinMain() gives whoever is running your program to specify whether or not they want your window to start off visible, maximized, minimized, etc. You will find options for these in the properties of windows shortcuts, and this parameter is how the choice is carried out.
Step 3: The Message Loop
This is the heart of the whole program, pretty much everything that your program does passes through this point of control.
GetMessage() gets a message from your application’s message queue. Any time the user moves the mouse, types on the keyboard, clicks on your window’s menu, or does any number of other things, messages are generated by the system and entered into your program’s message queue. By calling GetMessage() you are requesting the next available message to be removed from the queue and returned to you for processing. If there is no message, GetMessage() Blocks. If you are unfamiliar with the term, it means that it waits untill there is a message, and then returns it to you.
TranslateMessage() does some additional processing on keyboard events like generating WM_CHAR messages to go along with WM_KEYDOWN messages. Finally DispatchMessage() sends the message out to the window that the message was sent to. This could be our main window or it could be another one, or a control, and in some cases a window that was created behind the scenes by the sytem or another program. This isn’t something you need to worry about because all we are concerned with is that we get the message and send it out, the system takes care of the rest making sure it gets to the proper window.
Step 4: the Window Procedure
The window procedure is called for each message, the HWND parameter is the handle of your window, the one that the message applies to. This is important since you might have two or more windows of the same class and they will use the same window procedure ( WndProc() ). The difference is that the parameter hwnd will be different depending on which window it is. For example when we get the WM_CLOSE message we destroy the window. Since we use the window handle that we received as the first paramter, any other windows will not be affected, only the one that the message was intended for.
WM_CLOSE is sent when the user presses the Close Button or types Alt-F4. This will cause the window to be destroyed by default, but I like to handle it explicitly, since this is the perfect spot to do cleanup checks, or ask the user to save files etc. before exiting the program.
When we call DestroyWindow() the system sends the WM_DESTROY message to the window getting destroyed, in this case it’s our window, and then destroys any remaining child windows before finally removing our window from the system. Since this is the only window in our program, we are all done and we want the program to exit, so we call PostQuitMessage() . This posts the WM_QUIT message to the message loop. We never receive this message, because it causes GetMessage() to return FALSE , and as you’ll see in our message loop code, when that happens we stop processing messages and return the final result code, the wParam of WM_QUIT which happens to be the value we passed into PostQuitMessage() . The return value is only really useful if your program is designed to be called by another program and you want to return a specific value.
Step 5: There is no Step 5
Phew. Well that’s it! If I haven’t explained stuff clearly enough yet, just hang in there and hopefully things will become more clear as we get into more usefull programs.