Программирование на Visual C++. Архив рассылки
Шрифт:
• WinAPI: не запутайтесь в типах
Ну вот, видите какой большой получился выпуск, хотя в нем, фактически, не было ничего кроме ваших писем. Думаю, это служит доказательством целесообразности нового режима выхода рассылки. Не бойтесь, что рассылка станет просто большой конференцией – я постараюсь этого не допустить! ;) Все хорошо в меру. Все ваши замечания и предложения с благодарностью принимаются.
До новых встреч. Всего хорошего!
Программирование на Visual C++
Выпуск №5 от 28/06/2000
Приветствую!
Итак, рассылка снова с вами, уважаемые подписчики, и вы видите сейчас уже пятый выпуск. Сегодня мы поговорим о типах данных и рассмотрим ваши вопросы и ответы.
WinAPI – это одна из обещанных мной новых рубрик. Как следует из ее названия, в ней мы будем рассматривать вопросы, посвященные Windows API.
Вы когда-нибудь попадали
Типы Win32 гораздо легче понять, если знать некоторые соглашения. Например, названия типов, по своей природе являющихся указателями, начинается с префикса P или LP. Кстати, LP означает Long Pointer (дальний указатель) и остался в наследие от Windows 3.1, когда указатели еще делились на ближние (содержащие только смещение в сегменте) и дальние (содержащие как сегмент, так и смещение). Префикс H означает HANDLE — это типы, используемые для описания различных объектов, а префикс U — что тип беззнаковый.
С типами INT, UINT, LONG, ULONG, WORD, DWORD, VOID, SHORT, USHORT, CHAR, FLOAT. BYTE, BOOL(BOOLEAN), у вас не должно быть никаких проблем, и было бы глупо их тут расписывать. Эти типы дублируют встроенные типы C++, и единственное, на что здесь нужно обращать пристальное внимание — это размер типа. Эти типы рекомендуется использовать вместо встроенных в C++ для улучшения переносимости приложения, т.к. в разных системах встроенные типы имеют различные размеры.
Очень интересен тип WINAPI. По-хорошему это все-таки не тип. Если вы посмотрите в файл windef.h, то увидите следующую строку: "#define WINAPI __stdcall". __stdcall – это ключевое слово языка C++, оно, в частности, влияет на механизм передачи параметров функции. Суть механизма, определяемого __stdcall состоит в том, что 1) аргументы передаются справа налево; 2) аргументы из стека выбирает вызываемая функция; 3) аргументы передаются по значению (by value), а не по ссылке (by reference), т.е. функции передаются копии переменных; 4) определяет соглашение по декорированию имени функции, т.е. включению в имя дополнительной информации, используемой компоновщиком; 5) регистр символов не изменяется.
То есть оказалось, что WINAPI – это не вовсе тип, а указание о том, что функция использует соглашение __stdcall. Кстати, имейте в виду, что описатель PASCAL и __pascal — это то же самое, что и WINAPI. Но этот описатель является устаревшим, оставлен лишь для совместимости, и Microsoft рекомендует повсеместно использовать вместо него WINAPI.
Использование соглашения __сdecl вместо __stdcall иногда оправданно, но приводит к увеличению размера исполняемого модуля из-за того, что имя функции декорируется в этих соглашениях по-разному.
…продолжение следует…
Я очень рад, что мой расчет оказался верным и нашлись знающие люди, готовые ответить на заданные в предыдущем выпуске вопросы. Огромное им спасибо!
Q. В приложении есть операция, которая требует, скажем, больше пяти минут времени, причем по некоторым причинам дальнейшее выполнение не может быть продолжено до завершения этой операции. Хотелось бы, чтобы при этом окно приложения нормально обновлялось, могло быть свернуто-развернуто и т.п.
A1. А в чем проблема? Внутри, допустим цикла иногда добавляется цикл:
И для красоты на все "запрещенные" действия ставим флаг (который взводим/гасим по необходимости). Вот и весь велосипед.
Мне вот только не совсем понятно, что значит "иногда добавляется"… Время от времени добавляется, что ли? ;)
A2. Я решил написать ответ на вопрос Куканова Алексея, о корректной прорисовке окна во время какого-то процесса. С MFC это решается элементарно. Пусть есть функция LRESULT Calculation (LPVOID pParam); Не обращайте внимание на параметры объясню позже. Так вот вместо того чтобы в теле какого-то обработчика запускать эту функцию
и ждать когда она закончит лучше сделать так
По сути MFC запускает параллельную нить, которая никак не влияет на перерисовку всего остального. Обычно можно в качестве pParam передать HWND окна. Потому как узнать когда закончится процесс можно только при помощи сообщений. Например в теле Calculation
А кто хочет узнать побольше читайте MSDN – "Worker threads".
Кстати, Alex пишет нам уже второй раз, хочу поблагодарить его за активность.
В принципе такой же, но более обстоятельный ответ на этот вопрос пришел чуть
позже:A3. Самый оптимальный по-моему способ: Это запустить worker thread – второй поток (если пока только один :)) ) апликации. В качестве параметра передать туда структуру с необходимыми данными, а можно и ничего не передавать. Если все данные хранятся в наследнике CWinApp (дальше – CMyApp) , то получить доступ к объекту апликации можно с помощью функции AfxGetApp. Единственное замечание по передаче данных из одного потока в другой заключается в том, что надо доступаться ТОЛЬКО к мемберам класса – нельзя вызывать функции класса из другого потока (вернее, можно, если они не изменяют данных класса или не обращаются к оконным функциям класса (относиться к наследникам CWnd)). В итоге имеем схему:
1. Создается worker thread (поток одной функции, при ее завершении завершается и поток). В качестве параметра функции AfxBeginThread передается указатель на необходимые данные.
2. В основном потоке создается собственное сообщение, сигнализирующее о завершении потока. Оно будет брошено рабочим потоком перед своим завершением с помощью PostMessage (при работе с потоками я предпочитаю PostMessage для обмена такого рода сообщениями, ведь SendMessage ждет завершения работы обработчика события, что часто просто не нужно).
3. Запущенный поток выполняет всю черновую работу, в то время как основной поток апликации занимается важными делами, а именно – ничего не делает, знай крутит себе цикл обработки сообщений и не жужжит.
4. По завершении работы, worker thread посылает основному потоку мессагу, мол я закончил, выкладывает результаты так, чтобы основной знал где они (как это сделать – миллионы способов :)), в частности, передать в завершающем сообщении указатель на данные результата. )
Примерный код таков.
Замечания:
1. Объявлять обработчик сообщения ID_WORKER_THREAD_END надо через ON_MESSAGE макрос
2. Потоков запускать можно сколько душе угодно (если хватает памяти :)) ).
3. Повторюсь: важно понимать, что доступ к данным может быть одновременным из разных потоков. Поэтому необходима синхронизация. чтобы не получилось ситуации, что один поток пишет, а второй читает в одно время – как результат, можно разрушить логическую целостность данных. Поэтому в приведенных примерах лучше сбрасывать данные в отдельный блок памяти и передавать указатель на него. И функция рабочего потока, и обработчик сообщения завершения должны освобождать передаваемую им память.
A4. Самым естественным способом решения задачи n1 является создание дополнительного потока в процессе, который собственно и будет выполнять ту самую длительную операцию. С другой стороны – основной поток должен ожидать завершения операции. Для этого в Win32 существует функция WaitForSingleObject, одним из параметров которой задается описатель ожидаемого потока. Но в этом случае ждущий поток не может обрабатывать сообщения своего окна, так как ему система перестает выделять процессорное время. Здесь можно придумать много разных способов: во-первых, совсем можно обойтись и без функции WaitForSingleObject, создав глобальную переменную, которая при запуске потока инициализируется в false, а по его завершении – в true (или наоборот); можно, кроме того, используя функцию WaitForSingleObject, задавать ей вместо INFINITE лимит времени, по истечении которого будет возобновлено исполнение потока – вся байда проводится в цикле, при каждой итерации которого производится обработка сообщений окна;
Ну вот, на вопрос получены очень хорошие ответы. А о многозадачности мы еще обязательно поговорим в одном из выпусков. Главное понять – это не так сложно , как кажется! ;)
Q. Можно ли переопределенный обработчик событий сделать подставляемым (inline)? (автор тот же)
На этот вопрос пришел только один ответ, но, на мой взгляд, исчерпывающий, причем тоже от Олега, который умудрился ответить на все заданные в предыдущем выпуске вопросы:
A. Естественно, нельзя. Дело в том, что инлайновые функции не имеют собственного указателя – они похожи на макросы в этом смысле. А диспетчеру обработчиков (если так можно выразиться) надо давать адрес обработчика.