ЭЛЕКТРОННАЯ БИБЛИОТЕКА КОАПП
Сборники Художественной, Технической, Справочной, Английской, Нормативной, Исторической, и др. литературы.



 

Часть 20

Глава 17. Отладка стандартной прикладной программы Windows
Используемые в данной главе примеры программы были написаны с использованием вызовов только стандартных функций Windows. Они не используют классы ObjectWindows, которые существенно упрощают программирование для Windows. К программам Windows, описанным в 
данной главе (как и к программам, написанным с помощью ObjectWindows), также применимы все методы, описанные в Главе 15 "Отладка стандартной прикладной программы Турбо Паскаля". 
Поскольку прикладная программа Windows использует интерфейс прикладных программ Windows (API), вызвать неправильную работу программы могут множество причин (особенно когда вы не используете ObjectWindows). В данной главе делается попытка предостеречь вас
 от упущений при программировании для Windows. Назначение этой главы состоит в том, чтобы показать вам, как можно использовать средства TDW для поиска ошибок в стандартной прикладной программе. 
Примеры программ
В TDW предусмотрено три примера программ, для каждого из которых требуется "мышь". Это программы: 
- TDWDEMO.PAS, исходный файл программы простого рисования. Данная программа позволяет вам рисовать линии, эллипсы и прямоугольники тремя цветами и тремя типами перьев, отличающимися толщиной.
- TDWDEMOA.PAS, программа, содержащая все ошибки.
- TDWDEMOB.PAS, программа, в которой исправлена первая ошибка TDWDEMOA.PAS. 
Кроме того, имеется несколько файлов, общих для всех трех версий программ, например: 
- включаемый файл TDWDEMO.INC;
- скомпилированный файл ресурсов TDWDEMO.RES;
- файл пиктограмм программы TDWDEMO.ICO.
Компиляция и компоновка демонстрационных программ
Изучение описанных в данной главе шагов облегчает то, что поставляется три версии демонстрационной программы. Если вы захотите, то с помощью Турбо Паскаля для Windows можете сами внести изменения в программу. 
Запуск TDWDEMO
Перед началом сеанса отладки вы можете немного поэкспериментировать с программой TDWDEMO, чтобы получить некоторое представление о назначении программы. Запустите Турбо Паскаль, используя команду менеджера программ FileіRun (ФайліВыполнение) выберите пик
тограмму Турбо Паскаля. Далее используйте команду FileіRun для загрузки TDWDEMO.PAS в окно Edit (Редактирования), затем нажмите для компиляции программы клавиши Ctrl-F9. 
Отладка программы TDWDEMOA
Певая программа, которую вы будете отлаживать, это программа TDWDEMOA. Из менеджера программ Windows запустите Турбо Паскаль для Windows, выбрав пиктограмму программы. После запуска Турбо Паскаля выберите команду FileіOpen (ФайліОткрытие) и введите полны
й маршрут программы TDWDEMOA.PAS, затем для загрузки программы в окно Edit нажмите клавишу Enter. 
Когда вы увидите программу в окне Edit, выберите для запуска TDW и загрузки программы команду RunіDebugger (ВыполнениеіОтладчик). (При необходимости Турбо Паскаль скомпилирует программу с включением в нее отладочной информации.) Когда выведется окно Modu
le (Модуль), нажмите для запуска программы клавишу F9. 
Когда выведется экран прикладной программы, вы, возможно захотите получить нормальный курсор в виде стрелки. Вместо этого он может принять специальную форму, указывающую на выполнение процесса. Если ваш курсор имеет форму стрелки, и вы попытаетесь рисова
ть с помощью "мыши", то курсор будет перемещаться по экрану, но при этом на экране ничего не появляется. 
Принятие решения
Когда вы вернетесь в TDW, следующим шагом будет принятие решения, как можно проверить, что в программе работает неверно. Обычно, если прикладная программа Windows "зависает" или перестает выполняться при первом запуске, вы перезагружаете ее и начинаете в
ыполнять по шагам подпрограммы инициализации. Если вы это сделаете и дойдете до цикла сообщений, то можете зарегистрировать сообщения Windows и посмотреть, какие сообщения поступают в программу. 
Поскольку начальный экран отобразился, вы знаете, что программа достигла цикла сообщений. Таким образом, вы можете пропустить пошаговое выполнение и перейти непосредственно к регистрации сообщений. Информация о том, какие сообщения обрабатываются, по кра
йней мере даст вам некоторое представление о том, какие части кода использованы. 
Регистрация сообщений
Теперь нужно указать TDW на необходимость регистрации перед запуском программы всех сообщений. Выбрав команду ViewіWindows Message (ОбзоріСообщения Windows), откройте окно Windows Messages (Сообщения Windows), затем добавьте в левую верхнюю область окна 
WndProc. Вы увидите, что в правой верхней области выведется Log all messages (Регистрация всех сообщений). Поскольку это именно то, что вы хотите, с этим окном работа закончена. 
Так как вы регистрируете неизвестное число сообщений, возможна ситуация, когда TDW перед возвратом в программу исчерпает пространство сообщений (200 сообщений). Поэтому вы должны убедиться, что эти сообщения сохраняются в файле. Сделайте следующее: 
1. Переместитесь в нижнюю область окна Windows Messages и выведите локальное меню (нажмите клавиши ALt-F10).
2. Выберите Send To Log Window (Передача в окно регистрации). Если переключатель установлен в значение No (Нет), нажмите клавишу Enter, чтобы переключить его в Yes (Да).
3. Используя команду ViewіLog (ОбзоріРегистрация) получите информацию в окне Log, затем выведите локальное меню.
4. Выберите команду Open Log File (Открытие файле регистрации) и для назначения используемого по умолчанию имени файла нажмите клавишу Enter. 
Теперь вы можете запустить программу, нажав клавишу F9. Когда появится экран, для возврата в TDW нажмите клавиши Ctrl-Alt-SysRq. 
Анализ протокола сообщений
В нижней области окна Windows Messages (Сообщения Windows) вы видите множество сообщений Wm_PAINT и, возможно, ничего больше. Чтобы увидеть все приходящие сообщения, выберите для вывода списка файлов в текущем каталоге команду ViewіFile (ОбзоріФайл). Что
бы просмотреть файл регистрации, выберите TDWDEMOA.LOG. 
При этом должен вывестись довольно короткий список сообщений инициализации окна (около 16), за которым следует длинный список сообщений WM_PAINT. Очевидно, Windows начинает посылать сообщения для установки начального экрана, но сбивается на сообщении WM_
PAINT. Этот анализ отражает то, что вы видите при запуске программы: экран появляется, но ничего не происходит. 
Поиск ошибки
Что же делать дальше? Вы можете начать просмотр исходного кода, чтобы определить, есть ли область, где обрабатывается сообщение WM_PAINT. Однако более привлекательной альтернативой является задание точки останова в нужном месте и последующее выполнение п
рограммы по шагам с целью выявления проблемы. 
Задание точки останова по сообщению отменит выход из прикладной программы с помощью Ctrl-Alt-SysRq, что сделает небезопасным выполнение программы по шагам или выход из TDW. Поскольку программа сбивается на сообщении WM_PAINT, задайте прерывание программы
 отладчиком по сообщению WM_PAINT, а затем снова запустите программу: 
1. Снова выведите окно Windows Messages (Сообщения Windows), перейдите в правую верхнюю область, выведите локальное меню и выберите команду Add (Добавление).
2. Появляется диалоговое окно Set Message Filter (Установка фильтра сообщения), в котором уже выбрано Single Message (Отдельное сообщение), а курсор находится в поле текстового ввода Single Message Name (Имя одиночного сообщения). Введите WM_PAINT (испол
ьзуя только символы верхнего регистра, иначе TDW не найдет соответствия), затем выберите в качестве действия Break (Прерывания).
3. Для запуска программы нажмите клавишу F9.
Программа немедленно прерывает выполнение и оставляет вас на первой строке WndProc. (Вам нужно удалить с экрана окно Windows Messages, чтобы оно не мешало просматривать исходный код.) Эта подпрограмма состоит целиком из оператора case (сообщения обрабаты
ваются исключительно этой подпрограммой): 
     function WndProc(HWindow : HWnd; Message : Word;
                      wParam : Word; lParam : LongInt): LongInt; export;
     begin
        WndProc := 0;
        case Message of
           wm_Command:
               WndProc := DoWMCommand(wParam);
           wm_LButtonDown:
               DoLButtonDown(HWindow, lParam);
           wm_LButtonUp:
               DoLButtonUp(HWindow, lParam);
           wm_MouseMove:
               DoMouseMove(HWindow, lParam);
           wm_Paint:
               DoPaint(HWindow);
         else
           WndProc := DefWindowProc(HWindow, Message, wParam, LParam);
         end;
     end;
     Выполнение программы по шагам
     Для выполнения программы по шагам начните нажимать клавишу F7. Маркер текущей строки перемещается к оператору case по WM_PAINT, затем достигает подпрограммы DoPaint: 
     procedure DoPaint(HWindow: HWind);
     var
        i, SaveROP: Integer;
        HandleDC, hMemDC: HDC;
        theBitmap: HBitMap;
        ps: TPaintStruct;
     begin
        if CurrentPoint >= 0 then
        begin
          HandleDC := BeginPaint(HWindow, ps);
          { Определить, какая прямоугольная область на
            экране недопустима. Если ни одна прямоугольная
            область не отмечена, как недопустимая, то
            будет заново отображаться все окно }
           GetUpdateRect(HWindow, theRect, False);
           if IsRectEmpty(theRect) then
              GetClientRect(HWindow, theRect);
            { Создание описателя памяти и битового
              образа того же размера, что и обновляемая
              прямоугольная область }
             hMemDC := CreateCompatibleBitmap(HandleDC,
                theRect.Right - theRect.Left,
                theRect.Bottom - theRect.Top);
             SelectObject(hMemDC, theBitMap);
             { Стереть theBitmap }
              BitBlt(hMemDC, 0, 0,
                theRect.Right - theRect.Left,
                theRect.Bottom - theRect.Top,
                HandleDC, 0, 0, SRCopy);
             { Нарисовать только те фигуры, которые лежат
               в обновляемой прямоугольной области }
             for i := 0 to CurrentPoint do
             begin
                IntersectRect(DestRect, thisShape[i].Pointe, theRect);
                if not IsRectEmpty(destRect) then
                    DrawShape(hMemDC,
                       thisShape[i].Points.Left - theRect.Left,
                       thisShape[i].Points.Top - theRect.Top,
                       thisShape[i].Points.Right - theRect.Left,
                       thisShape[i].Points.Bottom - theRect.Top,
                       thisShape[i].theShape, thisShape[i].Slope);
                       { Отметим, что при вычерчивании фигуры позиция
                         фигуры была преобразована таким образом, что
                         ее начало было в верхнем левом углу
                         обновляемой прямоугольной области. Это точка
                         (0,0) в битовом образе, которая отображается
                         в (theRect.Left, theRect.Right). }
                end;
                { Наконец, скопируем битовый образ в обновляемую
                  прямоугольную область }
                BitBlt(HandleDC, theRect.Left, theRect.Top,
                  theRect.Right - theRect.Left,
                  theRect.Bottom - theRect.Top,
                  hMemDC, 0, 0, SRCopy);
                DeleteDC(hMemDC);
                DeleteObject(theBitmap);
                ReleaseDC(HWindow, HandleDC);
                EndPaint(HWindow, ps);
          end;
     end;
Если вы продолжите пошаговое выполнение, то единственной строкой кода, которая выполнится в DoPaint будет строка: 
if CurrentPoint >= 0
Затем управление передается циклу сообщений, где программа выбирает следующее сообщение, WM_PAINT, и снова повторяет цикл WndProc и DoPaint. Очевидно, подпрограмма DoPaint делает что-то не так, но что она должна делать? 
Анализ подпрограммы DoPaint
Назначение этой подпрограммы состоит в отображении всего экрана при первом вызове подпрограммы или повторном отображении области экрана (текущей области), если на экране что-то нарисовано. Чтобы определить, что что-то нарисовано, подпрограмма DoPaint про
веряет значение CurrentPoint, которое первоначально равно -1. (CurrentPoint указывает на число нарисованных объектов.) Если значение CurrentPoint = -1, как при первом запуске и первоначальном отображении экрана, то нет необходимости получать содержимое т
екущей прямоугольной области и заново отображать ее, поэтому она пропускает весь код в операторе if и возвращает управление, позволяя Windows отобразить весь экран. 
Если вы проверите значение CurrentPoint, используя для этого окно ViewіWathes (ОбзоріПросмотр), то увидите, что при выполнении подпрограммы это значение остается равным -1. Таким образом, вы никогда не сможете ничего нарисовать. 
Исправление ошибки
Если вы теперь откроете книгу Чарльза Пецольда "Программирование в Windows" и посмотрите, как Windows обрабатывает сообщение WM_PAINT, то причина ошибки станет ясна. Минимальным откликом, требуемым в Windows на сообщение WM_PAINT, является вызов BeginPai
nt, за которым следует вызов EndPaint. Если эти подпрограммы не вызваны, Windows никогда не узнает, что принято сообщение WM_PAINT, и продолжает посылать программе эти сообщения. 
Как видно из исходного кода, вызов BeginPAint помещен внутрь оператора if, поэтому при первом отображении экрана эта подпрограмма не вызывается. Помещение BeginPaint перед оператором должно устанить эту проблему. 
А что насчет оператора EndPaint? Он тоже находится внутри оператора if, наряду с оператором ReleaseDC, который освобождает hdc, описатель контекста текстового устройства, устанавливаемого вызовом BeginPaint. Эти две строки следует поместить после операто
ра if. 
Прерывание программы TDWDEMOA
Ошибка WM_PAINT уже исправлена в программе TDWDEMOB.PAS, следующей версии рассматриваемой программы. Перед загрузкой данной программы неплохо было бы завершить программу TDWDEMOA, чтобы освободить все системные ресурсы, которые она может использовать. По
скольку единственная проблема в этой программе состоит в том, что код внутри оператора if не выполняется не первом проходе, установка CurrentPointer в нулевое значение должно вызвать выполнение этого кода, что позволит вам выйти из программы. Завершить п
рограмму вам позволят следующие шаги: 
1. В окне Windows Message (Сообщения Windows) удалите из верхней левой области процедуру окна с именем WndProc, после чего программа не будет прерываться по сообщению WM_PAINT.
2. Нажимайте клавишу F7, пока не выведется следующая строка: 
if CurrentPointer >= 0
3. Переместите подсветку на CurrentPoint, затем для получения окна EvaluateіVodify (ВычислениеіМодификация) нажмите клавиши Ctrl-F4. 
4. Выберите Eval (Вычисление).
5. Переместите курсор в поле New Valuе и введите значение 9. Далее выберите команду Modify для изменения значения переменной, затем нажмите клавишу Esc для закрытия окна. Теперь, когда вы запустите программу, при вычислении оператора if получается значен
ие True, поэтому вызовы BeginPoint и EndPoint выполняются на первом проходе. 
6. Для запуска программы нажмите клавишу F9.
7. Для выхода из программы выберите команду Quit (Выход). Чтобы команда подействовала, вам, возможно, придется нажать клавишу, поскольку Windows может отказаться освобождать введенные от "мыши" сообщения в системной очереди. При нажатии кнопки "мыши" буд
ут освобождены все сообщения ввода от "мыши", находящиеся в данный момент в очереди. 
Отладка программы TDWDEMOB
Когда вы завершите программу TDWDEMOA, выйдите из TDW в Турбо Паскаль и загрузите TDWDEMOB в окно Edit (Редактирование), затем запустите программу снова с помощью команды RunіDebugger (ВыполнениеіОтладчик). Когда программа выведется на экран, для ее выпо
лнения нажмите клавишу F9, затем попробуйте немного поработать с ней. 
Если вы отображаете большое число объектов и особенно если вы попробуете перемещать "мышь" с нажатой правой кнопкой, вы заметите, что начинают происходить какие-то странные вещи. Вы можете заметить, что выполнение замедлится, затем будут исчезать объекты
, изменятся толщина и цвет линий, после чего изображение на экране собъется. В конце концов программа остановится, и вам придется делать "холодную" перезагрузку. 
Наиболее вероятная причина всех этих проблем состоит в неправильном использовании памяти. Чтобы проверить, что это действительно так, и начать поиск ошибки, запустите (если возможно) только менеджер программ и TDW (чтобы минимизировать использование памя
ти), а затем снова загрузите TDWDEMOB в TDW. 
Переключение из программы
Перед тем, как продолжить сеанс отладки, вам возможно захочется так организовать процесс, чтобы для переключения из прикладной программы не нужно было использовать клавиши Ctrl-Alt-SysRq. Ведь чтобы использовать этот метод, нужно очень аккуратно работать
 с TDW. Фокус состоит в том, чтобы задать одно сообщение, по которому будет выполняться прерывание - сообщение, создаваемое при работе в программе, но отсутствующее при нормальной работе. 
Поскольку программа простого рисования - это управляемая "мышью" графическая программа, не воспринимающая текстового ввода, то использовать клавиши нет необходимости. Таким образом, хорошим выбором будет прерывание по сообщению WM_KEYDOWN (нажата клавиша
). После задания этого сообщения в окне ViewіWindows Message (ОбзоріСообщения Windows), программа будет прерываться, когда вы нажимаете клавишу. (Поскольку программа не отвечает на нажатие правой кнопки "мыши", вы можете использовать также сообщение wM_R
BUTTONDOWN.) 
Проверка программы
Теперь для запуска программы TDWDEMOB вы можете нажать клавишу F9. Перед выполнением в программе каких-либо действий проверьте процент доступной программам Windows системной памяти (с помощью переключения в менеджер программ, выбора команд Help (Справка)
, и About Programm Manager (О программном менеджере)). Появляется информационное окно, показывающее, какая версия Windows работает, сколько имеется свободной памяти. В нижней его части показана интересующая вас статистика - процент используемых свободных
 системных ресурсов. 
Теперь вернемся в в программу простого рисования и попробуем что-нибудь нарисовать. Через некоторое время снова выведите экран About Programm Manager, и вы увидите, что объем системных ресурсов уменьшился. Если вы продолжите рисовать, то в итоге ресурсы 
сведутся к нулю, и вы получите тот же эффект, что и ранее. 
Принятие решения
Итак, что вам известно? По какой-то причине программа распределяет глобальную память, поскольку она поглощает все доступные для Windows системные ресурсы. Эта информация даст вам хорошую отправную точку: вы можете перезагрузить программу, использовать ср
едство распределения глобальной памяти TDW для сохранения списка глобальной памяти в файле регистрации, порисовать немного с помощью программы, а затем сохранить в другом файле еще один список глобальной памяти. 
Поскольку в памяти Windows между двумя списками что-то изменится, они очевидно не будут совпадать. Вам придется проходить по каждому объекту в списке и сравнивать объекты, имеющие одинаковых владельцев в обоих списках. 
Поскольку это медленный процесс, и по результатам трудно прийти к какому-то решению, то данный метод мы предлагаем только в качестве упражнения по наблюдению за глобальной памятью. Метод, который мы будем использовать на самом деле, состоит в наблюдении 
за объектами глобальной памяти, распределяемыми программой и аназизе того, освобождает ли программа выделенную для них память. 
Сравнение списков глобальной памяти
Если вы хотите получить список глобальной памяти, то нужно сделать следующее:
1. Перезапустите Windows, чтобы обеспечить очистку от объектов памяти, распределенных TDWDEMOB. Нужно также избежать запуска всех ненужных программ, чтобы сократить число глобальных объектов.
2. Если вы находитесь в TDW и программа TDWDEMOB загружена, снова задайте прерывание программы по сообщению WM_KEYDOWN.
3. Для запуска программы нажмите клавишу F9. Затем нажмите какую-либо клавишу для выхода обратно в TDW.
4. Выберите команду ViewіLog (ОбзоріРегистрация) и выведите локальное меню.
5. Выберите команду Open Log File (Открытие файла регистрации). Когда выведется диалоговое окно, введите имя файла регистрации и нажмите клавишу Enter.
6. Снова выведите локальное меню окна Log (Регистрация), выберите команду Display Windows Info (Вывод информации Windows), затем, когда выведется диалоговое окно Windows Information, нажмите клавишу Enter. При нажатии Enter будут восприняты используемые 
по умолчанию установки, а список объектов глобальной памяти начинается с вершины памяти.
7. Когда TDW завершит вывод глобальной памяти, снова выведите локальное меню и выберите для закрытия файла команду Close Log File (Закрытие файла регистрации).
8. Выберите для очистки протокола регистрации команду локального меню Erase Log (Стереть протокол).
9. Чтобы снова запустить программу, нажмите клавишу F9, затем используйте "мышь" для проверки окна About Program Manager (О программном менеджере). Обратите внимание на процент доступных системных ресурсов.
10. Нарисуйте что-нибудь, чтобы процент системных ресурсов уменьшился до 20 или 30 процентов.
11. Нажмите какую-либо клавишу для возврата в TDW, затем снова выполните шаги с 4 по 8, используя различные имена файлов регистрации.
12. Выйдите из TDW, напечатайле файлы регистрации и сравните их. 
Если вы выполните весь этот процесс, тог заметите следующее:
- Объекты памяти, которыми владеет TDWDEMO, не изменяются в размере.
- Объекты памяти интерфейса с графическими устройствами (GDI) в размере увеличиваются. 
Первый пункт отвечает в какой-то мере тому, что вы уже знаете: ошибочный код в TDWDEMOB распределяет глобальную, а не локальную память. 
Второй пункт сообщает вам что-то новое: TDWDEMOB распределяет объекты (GDI) и неп освобождает их. 
Поиск ошибки: функциональный подход
Теперь, когда вы получили представление о том, что собой представляет ошибка, вы можете начать поиск по программе и найти то место, где она распределяет объекты в памяти и не освобождает выделенную память. Полезным подходом был бы функциональный анализ в
сей программы и проверка каждой подпрограммы. 
Выбор элементов меню
Вы можете выбирать элементы меню, перемещая курсор в меню, нажимая левую кнопку "мыши" и перемещаясь вниз по меню, пока вы не сделаете выбор, который изменяет цвет, толщину пера или форму. Изменение одного из этих устанавливаемых значений приведет к пере
даче Windows подпрограмме WndProc, которая обрабатывает сообщение путем вызова DoWMCommand, сообщения WM_COMMAND. 
DopWMCommand представляет собой оператор case, который сохраняет ваш выбор в переменной программы. Эти переменные хранятся в сегменте данных программы TDWDEMO и не влияют на глобальную память. 
Отображение фигуры
Вы можете нарисовать фигуру, позиционировав курсор в окне в области пользователя, переместив "мышь" с нажатой левой кнопкой и последующим ее освобождением. При перемещении "мыши" с отжатой левой кнопкой вы можете заметить, что фигура рисуется, затем стир
ается, затем по мере меремещения "мыши" снова рисуется. Фигура постоянно остается нарисованной на экране только когда вы освободите левую кнопку. 
Нажатие левой кнопки "мыши"
Когда вы в области пользователя нажимаете левую кнопку "мыши", Windows посылает подпрограмме WndProc, которая вызывает DoButtonDown, сообщение WM_LBUTTONDOWN. Данная подпрограмма сохраняет текущую позицию "мыши" (называемую "якорем" или базой) и параметр
ы пера в структуре thisShape. Данная структура представляет собой переменную программы и влияет только на сегмент данных TDWDEMO. 
Перемещение "мыши"
Когда вы перемещаете "мышь" после нажатия ее левой кнопки в области пользователя, Windows посылает сообщение WM_MOUSEMOVE (или эквивалентное сообщение WM_MOUSEFIRST) подпрограмме WndProc, которая вызывает DoMouseMove. Данная подпрограмма для стирания фиг
уры в исходной позиции "мыши" и возврата к исходной позиции использует подпрограмму DrawShape, затем вызывает DrawShape снова для рисования фигуры от текущей позиции до базы. Единственное использование глобальной памяти в DoMouseMove состоит в получении 
контекста устройства для текущего окна, и этот контекст устройства освобождается в конце подпрограммы при обращении к ReleaseDC. 
Отображение фигуры (и поиск ошибки)
Следующая подпрограмма, которую нужно проанализировать, это DrawShape, которая дважды вызывается в DoMouseMove. DrawShape сохраняет предыдущее перо, используемое для рисования предыдущей фигуры, создает новое перо, после чего рисует линию, эллипс или пря
моугольник. Последнее, что она делает, это восстановление пера, которое было сохранено в начале подпрограммы. 
Поскольку перо - это объект графического интерфейса GDI, для которого выделяется глобальная память, DrawShape может содержать код, который вызывает проблемы с памятью. Данная подпрограмма особенно подозрительна, поскольку она дважды вызывается при каждом
 перемещении "мыши". Если она создает перо и не уничтожает его, то это довольно быстро приведет к "съеданию" памяти. 
Вполне вероятно, что в начале работы DrawShape распределяет перо с помощью вызова SelectObject, но в конце для удаления пера не вызывает DeleteObject. Чтобы исправить эту ситуацию, вам пришлось бы заменить последнюю строку кода следующей: 
DeleteObject(SelectObject(handleDC, saveObject);
Возможно, вы нашли сейчас единственную причину вазникающих проблем с памятью, но чтобы убедиться в этом, нужно рассмотреть остальной процесс рисования. 
Освобождение левой кнопки
Когда вы освобождаете левую кнопку, программа TDWDEMOB последний раз рисует фигуру и оставляет ее на экране. Освобождение кнопки приведет к тому, что Windows будет передавать подпрограмме WndProc, которая вызывает DoButtonUp, сообщение WM_LBUTTONUP. Данн
ая подпрограмма сохраняет текущий прямоугольник в области пользователя с текущей фигурой в массиве thisShape, вызывает InvalidateRect для добавления области к обновляемой области окна, затем вызывает подпрограмму UpdateWindow, которая посылает непосредст
венно основному окну сообщение WM_PAINT. Данная подрограмма не использует никакой глобальной памяти. 
При выходе из DoLButton сообщение WM_PAINT помещается в очередь и готово для обработки WndProc. 
Рисование на экране
Когда подпрограмма WndProc получает сообщение WM_PAINT, она вызывает подпрограмму DoPaint, заново отображающую соответствующую область экрана (это было описано ранее). При отображении области экрана DoPaint вызывает две подпрограммы Windows, которые влия
ют на глобальную память: CreateCompatibleDC и SelectObject. В конце DoPaint имеются вызовы DeleteDC и DeleteObject, по которым освобождается глобальная память, выделенная в начале подпрограммы. 
Итоги
Поскольку вы просмотрели в программе каждую подпрограмму, то можете быть в достаточной степени уверены, что нашли ошибку, вызывающую проблемы с памятью. Такой подход был принят, поскольку вы не были знакомы с программой. Конечно, если вы писали программу
, то найдете операторы выделения памяти гораздо быстрее. 
Дополнительная проверка также была бы полезна. При достаточном тестировании и знании программы вы могли бы пропустить при поиске ту часть, где происходит работа с меню, поскольку сбой происходит просто при перемещении "мыши" по экрану. Кроме того, если б
ы вы знали, что проблема возникает при простом перемещении "мыши" с нажатой левой кнопкой в области пользователя (что можно обнаружить, нажав левую кнопку и перемещая "мышь", пока программа не остановится), вы могли бы быстро прийти к выводу, что подпрог
рамма DoMouseMove - это именно та подпрограмма, на которой следует сконцентрировать усилия. 


Яндекс цитирования