|
"Графика для Windows средствами DirectDraw" Глава 7. Проблема курсора Если вы пытались
работать с мышью в полноэкранном приложении DirectDraw, скорее всего,
проблема с курсором вам уже знакома. Ввод от мыши нетрудно получить
и использовать в программе, пока не приходится отображать курсор на
экране. Но попробуйте-ка вывести стандартный курсор Windows — и проблема
заявит о себе.
Типичное приложение DirectDraw (наподобие
тех, что рассматривались в предыдущих главах) заранее строит весь кадр
во вторичном буфере и затем переключает страницы. Эта методика работает
быстро (переключение страниц обычно происходит почти мгновенно) и не
вызывает мерцания (построение каждого кадра завершается до его вывода).
Итак, курсор мыши должен обновляться
прямо на первичной поверхности. При очередном перемещении курсора необходимо
выполнить два действия:
И все же такой подход связан с некоторыми ограничениями. Он хорошо работает, если старая область курсора не накладывается на новую. Но если области перекрываются, курсор мерцает, потому что стирание происходит поблизости от места рисования. Чтобы полностью избавиться от мерцания, мы должны одновременно обновлять старую и новую области расположения курсора, а описанный выше алгоритм можно использовать для неперекрывающихся областей курсора. Прежде чем продолжать, я хотел бы заметить, что чаще встречаются именно перекрывающиеся области. Старая и новая области курсора перекрываются при любом медленном перемещении мыши, а мышь обычно перемещается быстро лишь из одного края экрана в другой. Во всех остальных случаях при выборе конкретного участка экрана курсор перемещается медленно. Следовательно, борьба с мерцанием становится очень важной задачей. Чтобы справиться с мерцанием, можно обновлять изображение на внеэкранной поверхности. Мы копируем в нее обе области курсора (старую и новую), обновляем изображение, а затем копируем обе области обратно на первичную поверхность как единое целое. Алгоритм состоит из пяти этапов:
Используя оба алгоритма (из трех и
пяти этапов), мы всегда сможем обновить курсор без мерцания и разрушения
основного изображения. До сих пор мы рассматривали обновление
курсора мыши без переключения страниц, но ведь приложение должно переключать
страницы для обновления экрана. Что же произойдет с нашим тщательно
подготовленным курсором после переключения? Он исчезнет. Мы можем нарисовать
его заново, но это вызовет мерцание.
Теперь курсор можно обновлять при переключении страниц или без него, причем не вызывая мерцания. Однако мы лишь подходим к решению проблемы — нужно придумать, как запрограммировать это решение.
Когда все внимание сосредоточено на
курсоре мыши, нетрудно забыть, что курсор — всего лишь часть нашего
приложения. После появления курсора приложение не должно принципиально
отличаться от рассмотренных выше, так что было бы нежелательно вставлять
код ввода от мыши и обновления курсора в середину приложения. И даже
если согласиться на это, как будет выглядеть этот код? Он должен постоянно
проверять наличие новых данных от мыши. При обнаружении данных он обновляет
курсор мыши; в противном случае продолжает свою нормальную работу. Постоянный
опрос мыши замедлит приложение и усложнит его структуру. Более удачное
решение — разделить приложение на две подзадачи, использовав многопоточность.
Сознаете вы это или нет, но вы уже
знакомы с потоками и процессами. Каждый раз при запуске программы создается
новый процесс. Процесс обеспечивает программу всем, что ей нужно для
работы, включая один поток (thread). Этот стандартный поток (также называемый
основным потоком — primary thread) используется для выполнения
кода программы. Основной поток типичного процесса начинает работу с
точки входа (для Windows-программ это функция WinMain() ) и
продолжает выполняться в соответствии со всеми циклами, условными операторами
и вызовами функций. Основной поток завершается вместе с завершением
процесса. Многопоточность приносит пользу при
наличии нескольких задач, которые могут (хотя бы частично) работать
одновременно. Код правильно написанного многопоточного приложения выглядит
просто, потому что каждый поток выполняет свою конкретную задачу. Добавить новый поток в программу несложно —
намного сложнее организовать его выполнение и завершение, поэтому многие
функции многопоточных API предназначены именно для синхронизации потоков.
В этом разделе мы кратко рассмотрим такие средства синхронизации.
Класс CWinThread представляет
отдельный поток. Он присутствует во всех приложениях, потому что класс
CWinApp (базовый для класса DirectDrawApp ) является
производным от CWinThread . Этот экземпляр класса CWinThread
представляет основной поток приложения; чтобы добавить новые рабочие
потоки, следует создать объекты CWinThread . Теперь мы знаем все необходимое и можем сосредоточиться на решении проблемы курсора. Чтобы курсор мыши обновлялся независимо от основного потока, мы воспользуемся отдельным рабочим потоком (я буду называть его потоком ввода ). Прежде всего давайте поговорим о основном потоке. Основной поток программы Cursor ведет
себя почти так же, как и основные потоки всех остальных программ, рассмотренных
нами. Он инициализирует DirectDraw, создает поверхности приложения,
строит очередной кадр во вторичном буфере и переключает страницы для
его отображения. Чтобы обеспечить работу потока ввода, нам придется
возложить на основной поток следующие дополнительные задачи:
Поток ввода обладает более узкой специализацией
по сравнению с основным потоком. Он должен делать следующее:
Для получения ввода от мыши могут применяться
две схемы: опрос и оповещение. Опрос плохо подходит для нашего случая,
потому что поток ввода постоянно остается активным, даже если пользователь
не работает с мышью. С другой стороны, если поток ввода блокируется
до поступления новых данных от мыши, он почти не расходует лишнего процессорного
времени. С помощью имеющегося в DirectInput механизма оповещения можно
заблокировать поток ввода до тех пор, пока DirectInput не сообщит о
поступлении новых данных. В начале этой главы мы решили создать
курсор, который не мерцает и мгновенно реагирует на перемещения мыши
при любой частоте вывода кадров. Нам удалось спроектировать (не считая
собственно кодирования) решение, удовлетворяющее этим критериям. Многопоточность
позволила отделить ввод мыши от приложения. Если не считать исходного
запуска потока ввода и синхронизации, наш основной поток решает общие
задачи приложения и не занимается получением ввода.
Программа Cursor использует описанную
выше методику и выводит на экран изображение вращающейся спирали, меню
задержки и курсор мыши. По умолчанию программа выводит кадры максимально
часто, но меню задержки позволяет уменьшить частоту вывода за счет задержки
в основном потоке (максимальная задержка равна 500 миллисекундам,
при этом приложение замедляется до 2 FPS). Если бы курсор не управлялся
отдельным потоком, его обновление происходило бы лишь с выводом очередного
кадра. Но поскольку курсор мыши не зависит от основного потока, он нормально
реагирует на действия пользователя при любой частоте вывода. Программа
Cursor изображена на рис. 7.1.
По умолчанию приложение работает в 8-битном видеорежиме и соответственно с 8-битным курсором. Многое зависит от вашего графического редактора, но, скорее всего, вы избавитесь от проблем с палитрой, если воспользуетесь файлом cursor_08.bmp с CD-ROM как шаблоном для создания нестандартного курсора. С курсором формата True Color дело обстоит проще, но, чтобы воспользоваться им, придется слегка подправить функцию SelectInitialDisplayMode() , чтобы активизировать беспалитровый видеорежим вместо палитрового. Программа Cursor, как и все остальные программы этой книги, построена на базе структурных классов DirectDrawWin и DirectDrawApp . Эти классы остались неизменными, а вся специфика приложения реализуется классом CursorWin . На практике функциональность курсора мыши, вероятно, следовало бы встроить в структурный класс. И все же для наглядности я объединил код для работы с курсором со специфическим кодом приложения. Класс CursorWin приведен в листинге 7.1. class CursorWin : public DirectDrawWin { public: CursorWin(); protected: //{{AFX_MSG(CursorWin) afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); afx_msg void OnDestroy(); afx_msg void OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized); //}}AFX_MSG DECLARE_MESSAGE_MAP() private: int SelectDriver(); int SelectInitialDisplayMode(); BOOL CreateCustomSurfaces(); void DrawScene(); void RestoreSurfaces(); private: BOOL InitMouse(); BOOL InitKeyboard(); BOOL UpdateDelaySurface(); private: //------- Функции потока ввода ------ static DWORD MouseThread(LPVOID); BOOL UpdateCursorSimpleCase(int curx, int cury, int oldcurx, int oldcury); BOOL UpdateCursorComplexCase(int curx, int cury, int oldcurx, int oldcury); private: //------- Данные мыши ------- static LPDIRECTINPUTDEVICE mouse; static CCriticalSection critsection; static CWinThread* mousethread; static CEvent* mouse_event[2]; static int cursor_width; static int cursor_height; static LPDIRECTDRAWSURFACE cursor; static LPDIRECTDRAWSURFACE cursor_under; static LPDIRECTDRAWSURFACE cursor_union; static int curx, cury; static int oldcurx, oldcury; static CList<MouseClickData, MouseClickData> mouseclickqueue; private: //------- Данные приложения ------- LPDIRECTINPUT dinput; LPDIRECTINPUTDEVICE keyboard; LPDIRECTDRAWSURFACE coil[coil_frames]; LPDIRECTDRAWSURFACE dm_surf; int dm_index; DWORD menubarfillcolor; HFONT largefont, smallfont; }; Класс CursorWin объявляет
три обработчика сообщений: OnCreate(), OnDestroy() и OnActivate()
. Функция OnCreate() инициализирует DirectDraw, DirectInput
и поток ввода. Функция OnDestroy() освобождает интерфейсы DirectX
и завершает поток ввода. Функция OnActivate() обеспечивает
захват мыши и клавиатуры на период активности приложения.
Затем объявляются функции InitMouse() и InitKeyboard() . Эти функции используются функцией OnCreate() и отвечают за инициализацию объектов DirectInput, предназначенных для работы с мышью и клавиатурой. Функция InitKeyboard() совпадает с одноименными функциями программ Qwerty и Smear из главы 6 , поэтому она также не рассматривается. Однако функция InitMouse() помимо инициализации мыши запускает поток ввода. Вскоре мы рассмотрим эту функцию. Функция UpdateDelaySurface() готовит к выводу поверхность меню задержки. Она выводит текст меню и выделяет текущую задержку. Далее в классе CursorWin объявляются три функции потока мыши:
Функция MouseThread() реализует
поток ввода. Когда основной поток создает поток ввода, он передает указатель
на статическую функцию MouseThread() . Созданный поток использует
эту функцию в качестве точки входа и продолжает выполнять ее до возврата
из функции или вызова функции AfxEndThread() . Функция
MouseThread() обновляет изображение курсора с помощью функций
UpdateCursorSimpleCase() и UpdateCursorComplexCase() .
Наше знакомство с программой Cursor
начинается с функции OnCreate() , которая отвечает за инициализацию
DirectDraw, DirectInput и потока ввода. Функция OnCreate()
приведена в листинге 7.2. Листинг 7.2 . Функция CursorWin::OnCreate() int CursorWin::OnCreate(LPCREATESTRUCT lpCreateStruct) { HRESULT r=DirectInputCreate( AfxGetInstanceHandle(), DIRECTINPUT_VERSION, &dinput, 0 ); if (r!=DI_OK) { AfxMessageBox("DirectInputCreate() failed"); return -1; } if (InitMouse()==FALSE) return -1; if (InitKeyboard()==FALSE) return -1; if (DirectDrawWin::OnCreate(lpCreateStruct) == -1) return -1; mousethread->ResumeThread(); return 0; } Сначала OnCreate() инициализирует
DirectInput функцией DirectInputCreate() . Затем мышь и клавиатура
инициализируются функциями InitMouse() и InitKeyboard()
, после чего вызывается функция DirectDrawWin::OnCreate()
. Функция InitMouse() , которую мы рассмотрим чуть ниже, создает
поток ввода, доступ к которому осуществляется через указатель mousepointer
. Однако поток ввода создается в приостановленном состоянии, чтобы
он не пытался преждевременно обращаться к первичной поверхности. Поток
будет запущен лишь после инициализации DirectDraw. Приостановленный
поток активизируется функцией CWinThread::ResumeThread() .
Листинг 7.3 . Функция InitMouse() BOOL CursorWin::InitMouse() { HRESULT r; r = dinput->CreateDevice( GUID_SysMouse, &mouse, 0 ); if (r!=DI_OK) { TRACE("CreateDevice(mouse) failed\n"); return FALSE; } r = mouse->SetDataFormat( &c_dfDIMouse ); if (r!=DI_OK) { TRACE("mouse->SetDataFormat() failed\n"); return FALSE; } r = mouse->SetCooperativeLevel( GetSafeHwnd(), DISCL_NONEXCLUSIVE | DISCL_FOREGROUND ); if (r!=DI_OK) { TRACE("mouse->SetCooperativeLevel() failed\n"); return FALSE; } DIPROPDWORD property; property.diph.dwSize=sizeof(DIPROPDWORD); property.diph.dwHeaderSize=sizeof(DIPROPHEADER); property.diph.dwObj=0; property.diph.dwHow=DIPH_DEVICE; property.dwData=64; r = mouse->SetProperty( DIPROP_BUFFERSIZE, &property.diph ); if (r!=DI_OK) { TRACE("mouse->SetProperty() failed (buffersize)\n"); return FALSE; } mouse_event[mouse_event_index]=new CEvent; mouse_event[quit_event_index]=new CEvent; r = mouse->SetEventNotification( *mouse_event[mouse_event_index] ); if (r!=DI_OK) { TRACE("mouse->SetEventNotification() failed\n"); return FALSE; } mousethread=AfxBeginThread( (AFX_THREADPROC)MouseThread, this, THREAD_PRIORITY_TIME_CRITICAL, 0, CREATE_SUSPENDED ); return TRUE; } Функция InitMouse() состоит
из семи этапов:
На этапах 1-4 происходит нормальная
инициализация DirectInput, подробно рассмотренная в
главе 6 , поэтому основное внимание будет уделено этапам 5,
6 и 7. mousethread=AfxBeginThread( (AFX_THREADPROC)MouseThread, this, THREAD_PRIORITY_TIME_CRITICAL, 0, CREATE_SUSPENDED ); Существуют и другие способы создания
потоков, но функция AfxBeginThread() является самым простым
вариантом. Она получает шесть аргументов, однако последние четыре имеют
значения по умолчанию, так что обязательными являются лишь два аргумента.
В нашем случае передается пять аргументов. Функция DrawScene() отвечает
за подготовку нового кадра во вторичном буфере, обновление курсора и
переключение страниц. Функция DrawScene() выполняется в основном
потоке, поэтому она должна синхронизировать доступ к первичной поверхности
и очереди событий мыши с потоком ввода. Функция DrawScene()
приведена в листинге 7.4. Листинг 7.4 . Функция DrawScene() void CursorWin::DrawScene() { //------ Проверить клавишу ESCAPE ------- static char key[256]; keyboard->GetDeviceState( sizeof(key), &key ); if ( key[DIK_ESCAPE] & 0x80 ) PostMessage( WM_CLOSE ); //------ Обычные задачи ------ ClearSurface( backsurf, 0 ); BltSurface( backsurf, dm_surf, 539, 0 ); static coil_idx; BltSurface( backsurf, coil[coil_idx], coilx, coily ); coil_idx=(coil_idx+1)%coil_frames; //------ Начало синхронизированной секции ------ critsection.Lock(); //------ Сохранить область вторичного буфера под курсором RECT src; src.left=curx; src.top=cury; src.right=curx+cursor_width; src.bottom=cury+cursor_height; cursor_under->BltFast( 0, 0, backsurf, &src, DDBLTFAST_WAIT ); //------ Нарисовать курсор во вторичном буфере backsurf->BltFast( curx, cury, cursor, 0, DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT ); primsurf->Flip( 0, DDFLIP_WAIT ); while (primsurf->GetFlipStatus(DDGFS_ISFLIPDONE)!=DD_OK) // ничего не делать (ждать, пока закончится // переключение страниц) int x, y; BOOL newclick=FALSE; int count=mouseclickqueue.GetCount(); while (count--) { MouseClickData mc=mouseclickqueue.RemoveTail(); if (mc.button==0) { x=mc.x; y=mc.y; newclick=TRUE; } } critsection.Unlock(); //------ Конец синхронизированной секции ------- //------ Сделать паузу в соответствии с выбранной задержкой ---- if ( delay_value[dm_index]!=0) Sleep( delay_value[dm_index] ); //------ Обновить меню задержки -------- if (newclick) { int max_index=sizeof(delay_value)/sizeof(int)-1; int menux=screen_width-dm_width+dm_margin; int menuw=dm_width-dm_margin*2; if (x>=menux && x<=menux+menuw) { int index=(y-dm_header)/dm_entrysize; if (index>=0 && index<=max_index && index!=dm_index) { dm_index=index; UpdateDelaySurface(); } } } } Функция DrawScene() состоит
из семи этапов:
Первый этап выполняется функцией
GetDeviceState() интерфейса DirectInputDevice . Если будет
обнаружено нажатие клавиши Escape , функция посылает сообщение
WM_CLOSE , сигнализируя о завершении приложения. Если не считать двух вспомогательных
функций, весь поток ввода реализован в виде одной функции. Функция
MouseThread() приведена в листинге 7.5. Листинг 7.5 . Функция MouseThread() DWORD CursorWin::MouseThread(LPVOID p) { TRACE("starting mouse thread\n" ); CursorWin* win=(CursorWin*)p; while(TRUE) { CMultiLock mlock( (CSyncObject**)mouse_event, 2 ); DWORD event=mlock.Lock( INFINITE, FALSE ); if (event-WAIT_OBJECT_0==quit_event_index) { TRACE("got quit message: quitting mouse thread\n"); return 0; } critsection.Lock(); oldcurx=curx; oldcury=cury; BOOL buffer_empty=FALSE; while (!buffer_empty) { DIDEVICEOBJECTDATA data; DWORD elements=1; if (mouse==0) { TRACE("invalid pointer: quitting mouse thread\n"); return 0; } HRESULT r=mouse->GetDeviceData( sizeof(data), &data, &elements, 0 ); if (r==DI_OK && elements==1) { static MouseClickData mc; switch(data.dwOfs) { case DIMOFS_X: curx+=data.dwData; break; case DIMOFS_Y: cury+=data.dwData; break; case DIMOFS_BUTTON0: if (data.dwData & 0x80) { mc.x=curx; mc.y=cury; mc.button=0; mouseclickqueue.AddHead( mc ); } break; case DIMOFS_BUTTON1: if (data.dwData & 0x80) { mc.x=curx; mc.y=cury; mc.button=1; mouseclickqueue.AddHead( mc ); } break; } } else buffer_empty=TRUE; } if (curx<0) curx=0; if (cury<0) cury=0; if (curx>=screen_width-cursor_width) curx=screen_width-cursor_width-1; if (cury>=screen_height-cursor_height) cury=screen_height-cursor_height-1; if (curx==oldcurx && cury==oldcury) { //----- обновление курсора не требуется ------ goto nevermind; } else if (abs(curx-oldcurx) >= cursor_width || abs(cury-oldcury) >= cursor_height) { //----- простой случай: прямоугольники нового // и старого курсора не перекрываются ----- win->UpdateCursorSimpleCase( curx, cury, oldcurx, oldcury ); } else { //----- сложный случай: прямоугольники нового // и старого курсора перекрываются ----- win->UpdateCursorComplexCase( curx, cury, oldcurx, oldcury ); } nevermind:; critsection.Unlock(); } TRACE("leaving mouse thread\n"); return 0; }; Функция MouseThread() имеет
один параметр — значение, передаваемое функции AfxBeginThread()
при создании потока (см. листинг 7.3
). Мы передавали указатель this , поэтому сейчас сможем присвоить
его значение указателю на класс CursorWin (переменная win
). В функции MouseThread() указатель win будет
использоваться для доступа к членам класса CursorWin . Листинг 7.6 . Функция UpdateCursorSimpleCase() BOOL CursorWin::UpdateCursorSimpleCase(int curx, int cury, int oldcurx, int oldcury) { RECT src; HRESULT r; //------ Блиттинг 1: стирание старого курсора ---------- r=primsurf->BltFast( oldcurx, oldcury, cursor_under, 0, DDBLTFAST_WAIT ); if (r!=DD_OK) { TRACE("Blt 1 failed\n"); CheckResult(r); } //------ Блиттинг 2: сохранение области под новым курсором ------ src.left=curx; src.top=cury; src.right=curx+cursor_width; src.bottom=cury+cursor_height; r=cursor_under->BltFast( 0, 0, primsurf, &src, DDBLTFAST_WAIT ); if (r!=DD_OK) { TRACE("Blt 2 failed\n"); CheckResult(r); } //------ Блиттинг 3: рисование нового курсора ---------- r=primsurf->BltFast( curx, cury, cursor, 0, DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT ); if (r!=DD_OK) { TRACE("Blt 3 failed\n"); CheckResult(r); } return TRUE; } С помощью трех последовательных вызовов
функции BltFast() интерфейса DirectDrawSurface , функция
UpdateCursorSimpleCase() стирает существующий курсор, сохраняет
область под новым курсором и рисует новый курсор. Листинг 7.7 . Функция UpdateCursorComplexCase() BOOL CursorWin::UpdateCursorComplexCase(int curx, int cury, int oldcurx, int oldcury) { RECT src; HRESULT r; int unionx=min(curx, oldcurx); int uniony=min(cury, oldcury); int unionw=max(curx, oldcurx)-unionx+cursor_width; int unionh=max(cury, oldcury)-uniony+cursor_height; //----- Блиттинг 1: копирование объединяющего прямоугольника // во вспомогательный буфер -------- src.left=unionx; src.top=uniony; src.right=unionx+unionw; src.bottom=uniony+unionh; r=cursor_union->BltFast( 0, 0, primsurf, &src, DDBLTFAST_WAIT ); if (r!=DD_OK) { TRACE("Blt 1 failed\n"); CheckResult(r); } //------ Блиттинг 2: стирание старого курсора // во вспомогательном буфере --------- r=cursor_union->BltFast( oldcurx-unionx, oldcury-uniony, cursor_under, 0, DDBLTFAST_WAIT ); if (r!=DD_OK) { TRACE("Blt 2 failed\n"); CheckResult(r); } //------ Блиттинг 3: сохранение области под новым курсором ----- src.left=curx-unionx; src.top=cury-uniony; src.right=src.left+cursor_width; src.bottom=src.top+cursor_height;r r=cursor_under->BltFast( 0, 0, cursor_union, &src, DDBLTFAST_WAIT ); if (r!=DD_OK) { TRACE("Blt 3 failed\n"); CheckResult(r); } //------ Блиттинг 4: рисование нового курсора // во вспомогательном буфере --------- r=cursor_union->BltFast( curx-unionx, cury-uniony, cursor, 0, DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT ); if (r!=DD_OK) { TRACE("Blt 4 failed\n"); CheckResult(r); } //------- Блиттинг 5: копирование вспомогательного буфера // на первичную поверхность -------- src.left=0; src.top=0; src.right=unionw; src.bottom=unionh; r=primsurf->BltFast( unionx, uniony, cursor_union, &src, DDBLTFAST_WAIT ); if (r!=DD_OK) { TRACE("Blt 5 failed\n"); CheckResult(r); } return TRUE; } Пользуясь одной из этих двух функций, поток ввода обновляет курсор. При этом удается избежать мерцания и разрушения текущего изображения на первичной поверхности. Осталось лишь поговорить о том, как завершается работа приложения. Эта тема неоднократно рассматривалась, и ее можно было бы пропустить, но для программы Cursor она важна из-за наличия дополнительного потока. Мы должны не только послать потоку ввода сигнал о завершении, но и проследить за тем, чтобы поток завершился до уничтожения объекта устройства мыши и поверхностей DirectDraw. В противном случае он может попытаться обратиться к мыши или обновить первичную поверхность после того, как соответствующие объекты перестанут существовать. Функция OnDestroy() выглядит так: void CursorWin::OnDestroy() { critsection.Lock(); DirectDrawWin::OnDestroy(); if (mouse) { TRACE("mouse->Unacquire()\n"); mouse->Unacquire(); TRACE("sending mouse quit message...\n"); mouse_event[quit_event_index]->SetEvent(); Sleep(100); // дать потоку мыши возможность ответить TRACE("Releasing mouse pointer...\n"); mouse->Release(), mouse=0; delete mouse_event[mouse_event_index]; delete mouse_event[quit_event_index]; } if (keyboard) keyboard->Release(), keyboard=0; if (dinput) dinput->Release(), dinput=0; critsection.Unlock(); } Когда MFC вызывает функцию OnDestroy()
, основной поток заведомо не обновляет экран, потому что он занят
выполнением этой функции. Тем не менее мы не знаем, не обновляется ли
экран потоком ввода. Чтобы поток ввода закончил последнее обновление,
мы блокируем критическую секцию. Вывод курсора в DirectDraw — одна из тех досадных проблем, которые часто возникают перед разработчиками. Однако частичное обновление экрана и многопоточность пригодятся вам и в других ситуациях. |
Дизайн и техническая
поддержка: Мишин Олег (mishinoleg@mail.ru)
При использовании информации ссылка на автора обязательна. Коммерческое использование информации без согласия автора запрещается. |