Операционная система Microsoft Windows 3.1 для программиста. Дополнительные главы© Александр Фролов, Григорий ФроловТом 17, М.: Диалог-МИФИ, 1994, 287 стр. 2.4. Отложенная записьМы уже говорили о том, что приложения, как правило, записывают данные в Clipboard одновременно в нескольких форматах. В некоторых случаях это может привести к неэкономному расходованию оперативной памяти. Например, если приложение копирует битовое изображение в форматах CF_DIB, CF_BITMAP вместе с CF_PALETTE, CF_METAFILEPICT и еще в нескольких собственных форматах (о собственных форматах читайте ниже), общий объем израсходованной для записи памяти может оказаться значительным. В то же время, если пользователь скопировал изображение в Clipboard только для того чтобы, например, вставить его в Paintbrush, может оказаться достаточным использование формата метафайла. В этом разделе мы расскажем о том, как организовать отложенную запись (delayed rendering) в Clipboard и приведем исходные тексты приложения, выполняющего такую операцию с текстом. Выполнение отложенной записиСам по себе процесс выполнения отложенной записи предельно прост: достаточно открыть и очистить Clipboard, вызвать функцию SetClipboardData, указав в первом параметре формат данных, а во втором - значение NULL, и закрыть Clipboard. Так, отложенная запись текстовых данных выглядит следующим образом: OpenClipboard(hwnd); EmptyClipboard(); SetClipboardData(CF_TEXT, NULL); CloseClipboard(); Выполняя отложенную запись данных в Clipboard, приложение как бы объявляет, что оно способно "по первому требованию" других приложений предоставить данные в указанном формате. Сами данные на момент объявления могут не существовать. Как только какое-либо приложение попытается прочитать записанные подобным образом данные, Windows обнаружит, что данных нет в памяти, так как для идентификатора соответствующего блока памяти указано значение NULL. В этом случае приложение, выполнившее отложенную запись, получит сообщение WM_RENDERFORMAT, в ответ на которое оно должно выполнить реальную запись данных. Параметр wParam сообщения WM_RENDERFORMAT содержит код формата данных, который потребовался приложению, выполняющему чтение данных из Clipboard. Обработчик сообщения WM_RENDERFORMAT должен, не открывая и не очищая Clipboard, предоставить данные в нужном формате, вызвав функцию SetClipboardData. На этот раз необходимо указать реальный идентификатор незафиксированного блока памяти, содержащего копируемые данные: SetClipboardData(wParam, hglbTextCopyBuf); После выполнения операции не следует закрывать Clipboard. Теперь рассмотрим следующую ситуацию. Пусть пользователь скопировал данные из приложения в Clipboard с использованием отложенной записи, а затем попытался закрыть это приложение, и пусть в момент копирования не было активно ни одно окно динамического просмотра Clipboard. В данной ситуации данные объявлены, но реально их нет в памяти. Следовательно, перед завершением приложения требуется выполнить запись данных в Clipboard во всех возможных форматах. Если приложение объявило форматы данных в Clipboard, "взяв на себя обязательство" обеспечить их в случае необходимости, оно не может уйти со сцены, не выполнив реальной записи данных, так как в противном случае записывать объявленные данные будет некому. Эта проблема имеет простое решение. Перед завершением работы приложение, объявившее в Clipboard данные, но не предоставившее их, получит от Windows сообщение WM_RENDERALLFORMATS. В ответ на это сообщение оно должно выполнить реальную запись данных. Вот возможная реализация обработчика сообщения WM_RENDERALLFORMATS: case WM_RENDERALLFORMATS: { OpenClipboard(hwnd); EmptyClipboard(); SendMessage(hwnd, WM_RENDERFORMAT, CF_TEXT, 0L); SendMessage(hwnd, WM_RENDERFORMAT, CF_BITMAP, 0L); SendMessage(hwnd, WM_RENDERFORMAT, CF_PALETTE, 0L); CloseClipboard(); return 0; } Приложение несколько раз посылает само себе сообщение WM_RENDERFORMAT, указывая все возможные для него форматы данных. При этом обработчик сообщения WM_RENDERFORMAT каждый раз будет выполнять реальную запись данных в Clipboard. Возможна такая ситуация, когда приложение выполнило отложенную запись и затем предоставила реальные данные, заказав для их хранения блок глобальной памяти, а другое приложение вскоре после этого очистило Clipboard и записало туда свои данные. Теперь первое приложение хранит в глобальной памяти никому не нужные данные, которые могут занимать много места. В указанной ситуации ваше приложение получит сообщение WM_DESTROYCLIPBOARD. Обработчик этого сообщения может уничтожить ненужный блок памяти. Приложение CLIPRNDRДля иллюстрации метода отложенной записи мы изменили приложение CLIPTXT, создав на его основе приложение CLIPRNDR. Внешний вид главного окна приложения и выполняемые им функции не изменились, однако теперь это приложение способно выполнять более экономную отложенную запись данных в Clipboard. Основной файл исходных текстов приведен в листинге 2.11. Листинг 2.11. Файл cliprndr/cliprndr.cpp // ---------------------------------------- // Отложенная запись данных в Clipboard // ---------------------------------------- #define STRICT #include <windows.h> #include <mem.h> #include "cliprndr.hpp" BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); char const szClassName[] = "ClipRndrClass"; char const szWindowTitle[] = "Delayed Rendering Demo"; char const szClipboardText[] = "Этот текст будет записан\r\n" "в универсальный буфер обмена Clipboard\r\n" "приложением CLIPRNDR"; // ===================================== // Функция WinMain // ===================================== #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения if(!hPrevInstance) if(!InitApp(hInstance)) return FALSE; hwnd = CreateWindow( szClassName, szWindowTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, NULL); if(!hwnd) return FALSE; ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); while(GetMessage(&msg, 0, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } // ===================================== // Функция InitApp // Выполняет регистрацию класса окна // ===================================== BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна memset(&wc, 0, sizeof(wc)); wc.lpszMenuName = "APP_MENU"; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(hInstance, "APP_ICON"); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszClassName = (LPSTR)szClassName; aWndClass = RegisterClass(&wc); return (aWndClass != 0); } // ===================================== // Функция WndProc // ===================================== LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; RECT rc; static HGLOBAL hglbTextCopyBuf; LPSTR lpTextCopy; static HGLOBAL hglbTextPasteBuf; LPSTR lpTextPaste; static HGLOBAL hglbClipBuf; LPSTR lpClipBuf; switch (msg) { case WM_CREATE: { hglbTextPasteBuf = GlobalAlloc(GHND, 1); if(hglbTextPasteBuf == NULL) return -1; lpTextPaste = (LPSTR)GlobalLock(hglbTextPasteBuf); if(hglbTextPasteBuf == NULL) return -1; lpTextPaste[0] = '\0'; GlobalUnlock(hglbTextPasteBuf); return 0; } case WM_PAINT: { hdc = BeginPaint(hwnd, &ps); lpTextPaste = (LPSTR)GlobalLock(hglbTextPasteBuf); if(lpTextPaste != NULL) { GetClientRect(hwnd, &rc); DrawText(hdc, lpTextPaste, -1, &rc, DT_LEFT | DT_EXPANDTABS); GlobalUnlock(hglbTextPasteBuf); } EndPaint(hwnd, &ps); } case WM_COMMAND: { switch (wParam) { // Выполняем отложенное копирование данных case CM_EDITCOPY: { OpenClipboard(hwnd); EmptyClipboard(); SetClipboardData(CF_TEXT, NULL); CloseClipboard(); return 0; } // Чтение текстовых данных из Clipboard case CM_EDITPASTE: { OpenClipboard(hwnd); hglbClipBuf = GetClipboardData(CF_TEXT); if(hglbClipBuf != NULL) { lpClipBuf = (LPSTR)GlobalLock(hglbClipBuf); if(lpClipBuf != NULL) { hglbTextPasteBuf = GlobalReAlloc(hglbTextPasteBuf, GlobalSize(hglbClipBuf), GMEM_NODISCARD); lpTextPaste = (LPSTR)GlobalLock(hglbTextPasteBuf); if(lpTextPaste != NULL) { lstrcpy(lpTextPaste, lpClipBuf); InvalidateRect(hwnd, NULL, TRUE); GlobalUnlock(hglbTextPasteBuf); } else MessageBox(hwnd, "Мало памяти", (LPSTR)szWindowTitle, MB_OK | MB_ICONHAND); GlobalUnlock(hglbClipBuf); } else MessageBox(hwnd, "Мало памяти", (LPSTR)szWindowTitle, MB_OK | MB_ICONHAND); } else MessageBox(hwnd, "Формат CF_TEXT недоступен", (LPSTR)szWindowTitle, MB_OK | MB_ICONHAND); CloseClipboard(); return 0; } case CM_HELPABOUT: { MessageBox(hwnd, "Приложение CLIPTXT\n(C) Фролов А.В., 1995", (LPSTR)szWindowTitle, MB_OK | MB_ICONINFORMATION); return 0; } case CM_FILEEXIT: { DestroyWindow(hwnd); return 0; } default: return 0; } } // Копируем данные во всех форматах case WM_RENDERALLFORMATS: { OpenClipboard(hwnd); EmptyClipboard(); // Инициируем копирование в текстовом формате SendMessage(hwnd, WM_RENDERFORMAT, CF_TEXT, 0L); CloseClipboard(); return 0; } // Копируем данные в нужном формате case WM_RENDERFORMAT: { // Работаем только с текстовым форматом if(wParam != CF_TEXT) return 0; hglbTextCopyBuf = GlobalAlloc(GHND, sizeof(szClipboardText) + 1); if(hglbTextCopyBuf != NULL) { lpTextCopy = (LPSTR)GlobalLock(hglbTextCopyBuf); if(lpTextCopy != NULL) { lstrcpy(lpTextCopy, szClipboardText); GlobalUnlock(hglbTextCopyBuf); // Фактическая запись данных SetClipboardData(wParam, hglbTextCopyBuf); } else MessageBox(hwnd, "Мало памяти", (LPSTR)szWindowTitle, MB_OK | MB_ICONHAND); } else MessageBox(hwnd, "Мало памяти", (LPSTR)szWindowTitle, MB_OK | MB_ICONHAND); return 0; } case WM_DESTROY: { if(hglbTextPasteBuf != NULL) GlobalFree(hglbTextPasteBuf); PostQuitMessage(0); return 0; } default: break; } return DefWindowProc(hwnd, msg, wParam, lParam); } В приложении CLIPRNDR использованы рассмотренные нами приемы. Так как данные, предназначенные для записи в Clipboard, определены статически, приложение не освобождает занимаемую ими память и, соответственно, не обрабатывает сообщение WM_DESTROYCLIPBOARD. Файл cliprndr.hpp содержит определения констант (листинг 2.12). Листинг 2.12. Файл cliprndr/cliprndr.hpp #define CM_HELPABOUT 24000 #define CM_EDITPASTE 24001 #define CM_EDITCOPY 24002 #define CM_FILEEXIT 24003 Файл описания ресурсов приведен в листинге 2.13. Листинг 2.13. Файл cliprndr/cliprndr.rc #include "cliprndr.hpp" APP_MENU MENU BEGIN POPUP "&File" BEGIN MENUITEM "E&xit", CM_FILEEXIT END POPUP "&Edit" BEGIN MENUITEM "&Copy", CM_EDITCOPY MENUITEM "&Paste", CM_EDITPASTE END POPUP "&Help" BEGIN MENUITEM "&About...", CM_HELPABOUT END END APP_ICON ICON "cliprndr.ico" Файл определения модуля вы найдете в листинге 2.14. Листинг 2.14. Файл cliprndr/cliprndr.def NAME CLIPRNDR DESCRIPTION 'Приложение CLIPRNDR, (C) 1995, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple |