"Программирование на Visual C++. Архив рассылки" - читать интересную книгу автора (Jenter Алекс)

Программирование на Visual C++ Выпуск №5 от 28/06/2000

Приветствую!

Итак, рассылка снова с вами, уважаемые подписчики, и вы видите сейчас уже пятый выпуск. Сегодня мы поговорим о типах данных и рассмотрим ваши вопросы и ответы.

WINAPI

WinAPI – это одна из обещанных мной новых рубрик. Как следует из ее названия, в ней мы будем рассматривать вопросы, посвященные Windows API.

WinAPI: НЕ ЗАПУТАЙТЕСЬ В ТИПАХ 

Вы когда-нибудь попадали на страницу Win32 Simple Data Types в Help? В переводе с английского simple означает "простой", т.е. Microsoft хочет сказать, что это "простые типы". В C++ простыми типами были int, double и другие. При программировании для Windows эти типы никуда не деваются, но появляется очень много новых. Они, конечно, не входят в стандарт C++ и не являются его ключевыми словами (ведь на C++ программируют не только под Windows), но любой Windows-программист должен эти типы хорошо знать. И краткого описания типа, приведенного на вышеуказанной странице MSDN, часто бывает недостаточно, так что иногда приходится лезть в исходники и смотреть, что же из себя представляет тип на самом деле. Я вам предлагаю обзор самых основных и важных типов.

Типы 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. В приложении есть операция, которая требует, скажем, больше пяти минут времени, причем по некоторым причинам дальнейшее выполнение не может быть продолжено до завершения этой операции. Хотелось бы, чтобы при этом окно приложения нормально обновлялось, могло быть свернуто-развернуто и т.п.

Куканов Алексей ([email protected])

A1. А в чем проблема? Внутри, допустим цикла иногда добавляется цикл:

for( ; GetMessage(lpMsg, hWnd, 0, 0); DispatchMessage(lpmsg));

И для красоты на все "запрещенные" действия ставим флаг (который взводим/гасим по необходимости). Вот и весь велосипед.

Сергей Бойко

Мне вот только не совсем понятно, что значит "иногда добавляется"… Время от времени добавляется, что ли? ;)

A2. Я решил написать ответ на вопрос Куканова Алексея, о корректной прорисовке окна во время какого-то процесса. С MFC это решается элементарно. Пусть есть функция   LRESULT Calculation (LPVOID pParam); Не обращайте внимание на параметры объясню позже. Так вот вместо того чтобы в теле какого-то обработчика запускать эту функцию

CMyDlg::OnButtonClick() {

 Calculation();

}

и ждать когда она закончит лучше сделать так 

CMyDlg::OnButtonClick() {

 AfxBeginThread(Calculation, (LPVOID)m_hWnd,THREAD_PRIORITY_LOWEST);

}

По сути MFC запускает параллельную нить, которая никак не влияет на перерисовку всего остального. Обычно можно в качестве  pParam передать HWND окна. Потому как узнать когда закончится процесс можно только при помощи сообщений. Например в теле Calculation  

::SendMessage((HWND)pParam, WM_STOP, 0, 0);

А кто хочет узнать побольше читайте MSDN – "Worker threads".

Alex ([email protected])

Кстати, Alex пишет нам уже второй раз, хочу поблагодарить его за активность.

В принципе такой же, но более обстоятельный ответ на этот вопрос  пришел чуть позже:

A3. Самый оптимальный по-моему способ: Это запустить worker thread – второй поток (если пока только один :)) ) апликации. В качестве параметра передать туда структуру с необходимыми данными, а можно и ничего не передавать. Если все данные хранятся в наследнике CWinApp (дальше – CMyApp) , то получить доступ к объекту апликации можно с помощью функции AfxGetApp(). Единственное замечание по передаче данных из одного потока в другой заключается в том, что надо доступаться ТОЛЬКО к мемберам класса – нельзя вызывать функции класса из другого потока (вернее, можно, если они не изменяют данных класса или не обращаются к оконным функциям класса (относиться к наследникам CWnd)). В итоге имеем схему: 

1. Создается worker thread (поток одной функции, при ее завершении завершается и поток). В качестве параметра функции AfxBeginThread передается указатель на необходимые данные.

2. В основном потоке создается собственное сообщение, сигнализирующее о завершении потока. Оно будет брошено рабочим потоком перед своим завершением с помощью PostMessage (при работе с потоками я предпочитаю PostMessage для обмена такого рода сообщениями, ведь SendMessage ждет завершения работы обработчика события, что часто просто не нужно).

3. Запущенный поток выполняет всю черновую работу, в то время как основной поток апликации занимается важными делами, а именно – ничего не делает, знай крутит себе цикл обработки сообщений и не жужжит.

4. По завершении работы, worker thread посылает основному потоку мессагу, мол я закончил, выкладывает результаты так, чтобы основной знал где они (как это сделать – миллионы способов :)), в частности, передать в завершающем сообщении указатель на данные результата. )

Примерный код таков.

UINT WorkerThreadFunction(WPARAM, LPARAM lpData) {

 // тутачки работаем с lpData и выполняем

 // всю необходимую работу

 // результат запихиваем в память,

 // а адрес на нее – в lpResult

 AfxGetMainWnd()-gt;PostMessage(IID_WORKER_THREAD_END, 0, lpResult);

 // возвращаем код успеха (а вообще это на ваш вкус)

 return 0;

}


void CMyApp::OnStartExecution() {

 // заполняем lpData нужными данными, и вызываем ..

 CWinThread *pThread = AfxBeginThread(WorkerThreadFunction, lpData);

 if (!pThread) {

  // Не смогли запустить поток.

  // Правда обычно этот код не выполняется :)).

  // Я до сих пор не знаю ситуации, когда поток

  // может не запуститься, кроме low memory.

  AfxMessageBox(_T("Can't start thread."));

 }

}


LRESULT CMyApp::OnWorkerThreadEnd(WPARAM wParam, LPARAM lpResult) {

 // тутачки обрабатываем завершение расчетов.

}

Замечания:

1. Объявлять обработчик сообщения ID_WORKER_THREAD_END надо через ON_MESSAGE макрос 

2. Потоков запускать можно сколько душе угодно (если хватает памяти :)) ).

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

Oleg Tselobyonok, Applied systems, Ltd.

A4. Самым естественным способом решения задачи n1 является создание дополнительного потока в процессе, который собственно и будет выполнять ту самую длительную операцию. С другой стороны – основной поток должен ожидать завершения операции. Для этого в Win32 существует функция WaitForSingleObject, одним из параметров которой задается описатель ожидаемого потока. Но в этом случае ждущий поток не может обрабатывать сообщения своего окна, так как ему система перестает выделять процессорное время. Здесь можно придумать много разных способов: во-первых, совсем можно обойтись и без функции WaitForSingleObject, создав глобальную переменную, которая при запуске потока инициализируется в false, а по его завершении – в true (или наоборот); можно, кроме того, используя функцию WaitForSingleObject, задавать ей вместо INFINITE лимит времени, по истечении которого будет возобновлено исполнение потока – вся байда проводится в цикле, при каждой итерации которого производится обработка сообщений окна;

Епрст

Ну вот, на вопрос получены очень хорошие ответы. А о  многозадачности мы еще обязательно поговорим в одном из выпусков. Главное понять – это не так сложно , как кажется! ;)

Q. Можно ли переопределенный обработчик событий сделать подставляемым (inline)? (автор тот же)

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

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

Q. Вопрос про ресурсные строки: "…при нажатии на клавишу "Пропустить" программа идет дальше и вываливается на следующей операции загрузки строки с теми же симптомами и так до тех пор, пока не будут загружены все строки. После этого выполнение программы продолжается в нормальном режиме и все остальное работает как надо (строки все-таки загружаются!). В Release-версии программа пролетает это место без спотыканий."

Евгений

(Полностью  вопрос Евгения см. в предыдущем выпуске, который можно найти по адресу http://subscribe.ru/archive/comp.prog.visualc)

A1. Скорее всего, приложение преобразовано из обычного в MFC и преобразовано некорректно. Нельзя использовать ресурсные функции CString в не-MFC приложении, потому что они требуют специальной инициализации. Другие функции будут работать, а эти нет. Инициализация всех внутренних переменных происходит при инициализации приложения в вызовах AfxWinInit() и прочих служебных функций в AfxWinMain(). Можно посоветовать или сгенерировать приложение заново или использовать код вида:

CString s;

::LoadString(g_hInstance, 12345, s.GetBuffer(256), 256);

s.ReleaseBuffer();

Дмитрий Дулепов, MCSE

A2. По вопросу о CString::LoadString: Скорее всего, эта функция вызывается до статической инициализации библиотеки MFC. Например, это может произойти в конструкторе объекта, объявленного статическим. Все тот же пресловутый theApp…. :-) Микрософт рекомендует переносить все действия по инициализации в InitInstance, собственно для чего эта ф-ция и предназначена.

Sergey Emantayev

A3. А по поводу вопроса в рубрике "В поисках истины" – похоже, что зачитка данных идет в конструкторе CMyApp, а не в InitInstance. Предположение насчет некорректной инициализации похоже правильно.

Oleg Tselobyonok, Applied systems, Ltd.

Огромное всем, кто не поленился  написать ответ, особенно  Олегу – он у нас сегодня рекордсмен!

Ну вот, кажется на все вопросы получены ответы. Полная идиллия… только вот уже поступили новые вопросы… ;)

В ПОИСКАХ ИСТИНЫ

Q. Просто кульно, что ты взялся за эту рассылку, а то, как ты и говорил, в инете нет рассылок по MSVC++. Сам я за ним сижу уже два года, и всё более углубляясь внутрь, возникают всё новые вопросы ;) Но я ещё маленький ;) Хочу задать тебе вопрос: как делать окна нестандартной формы? Например, круг (как у диска Компьютерры – там окно обычное, но с помощью прозрачности виден только круг, так?)

eFi

Если никто не ответит на этот вопрос, я отвечу сам в следующем выпуске. Но от этого может пострадать тема выпуска – я все не успеваю, а по этому вопросу мне надо будет еще кое-что уточнить – поэтому кто знает или делал такие окна, напишите! 

Должен констатировать, что проблема курсоров не закрыта – пришел еще один вопрос:

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

1.Что касается курсоров – как все-таки загружать 256-цветный курсор в приложении? Т.е. проблема в том что в редакторе ресурсов можно сделать либо только черно-белый курсов, либо еще и цветной, но при этом LoadCursor загружет только ч.б. Скорее всего ларчик просто открывается, но все же?

2. Еще вопрос, скорее всего тоже очень популярный. Версия Debug работает без проблем, а при запуске версии Release появляется сообщение о недопустимой операции. Хотелось бы знать в чем проблема и пути ее решения.

George V. Samodumov

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

Q. Спасибо за рассылку – наконец-то свершилось! А то по VB их уже несколько, а по VC++ не было до недавнего времени ни одной. У меня есть вопрос по обработке события WM_KEYUP. Играя с диалогом, обнаружил, что он сам никак не реагирует на нажатия клавы. Как решение, использовал следующий способ: для каждого типа контрола делал свой класс, который реагирует на WM_KEYUP, и в обработчике этого события пересылал сообщение окну диалога. Например, для кнопок создал класс CMyButton, наследуемый от CButton, и в функции CMyButton::OnKeyDown() пересылаю сообщение родительскому окну как GetParent()-gt;SendMessage(…). То же самое для других типов контролов по аналогии.

Но такой способ отдаёт некоторой горбатостью, может быть существует какое-то более элегантное решение?

Роман Коновалов

Жду ваших писем с ответами!

Пока все.

Всего вам доброго!

©Алекс Jenter mailto:[email protected] Красноярск, 2000.