Операционная система 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 |

