IPaper::Save
Основное внимание в этом примере кода уделяется тому, как COPaper может загружать и сохранять свои бумажные данные в составных файлах. Подробно рассматриваются реализации методов IPaper, Load и Save .
Ниже приведен метод IPaper::Save из Paper.cpp.
STDMETHODIMP COPaper::CImpIPaper::Save(
SHORT nLockKey,
IStorage* pIStorage)
{
HRESULT hr = E_FAIL;
IStream* pIStream;
ULONG ulToWrite, ulWritten;
if (OwnThis())
{
if (m_bLocked && m_cLockKey == nLockKey && NULL != pIStorage)
{
// Use the COM service to mark this compound file as one
// that is handled by our server component, DllPaper.
WriteClassStg(pIStorage, CLSID_DllPaper);
// Use the COM Service to write user-readable clipboard
// format into the compound file.
WriteFmtUserTypeStg(pIStorage, m_ClipBdFmt,
TEXT(CLIPBDFMT_STR));
// Create the stream to be used for the actual paper data.
// Call it "PAPERDATA".
hr = pIStorage->CreateStream(
STREAM_PAPERDATA_USTR,
STGM_CREATE | STGM_WRITE | STGM_DIRECT | STGM_SHARE_EXCLUSIVE,
0,
0,
&pIStream);
if (SUCCEEDED(hr))
{
// Obtained a stream. Write data to it.
// First, write PAPER_PROPERTIES structure.
m_PaperProperties.lInkArraySize = m_lInkDataEnd+1;
m_PaperProperties.crWinColor = m_crWinColor;
m_PaperProperties.WinRect.right = m_WinRect.right;
m_PaperProperties.WinRect.bottom = m_WinRect.bottom;
ulToWrite = sizeof(PAPER_PROPERTIES);
hr = pIStream->Write(&m_Paper_Properties, ulToWrite,
&ulWritten);
if (SUCCEEDED(hr) && ulToWrite != ulWritten)
hr = STG_E_CANTSAVE;
if (SUCCEEDED(hr))
{
// Now, write the complete array of Ink Data.
ulToWrite = m_PaperProperties.lInkArraySize *
sizeof(INKDATA);
hr = pIStream->Write(m_paInkData, ulToWrite, &ulWritten);
if (SUCCEEDED(hr) && ulToWrite != ulWritten)
hr = STG_E_CANTSAVE;
}
// Release the stream.
pIStream->Release();
}
}
UnOwnThis();
}
// Notify all other connected clients that Paper is now saved.
if (SUCCEEDED(hr))
m_pBackObj->NotifySinks(PAPER_EVENT_SAVED, 0, 0, 0, 0);
return hr;
}
В этом отношении клиента и сервера COPaper не создает составной файл, используемый для хранения бумажных данных. Для методов Save и Load клиент передает указатель интерфейса IStorage для существующего составного файла. Затем он использует IStorage для записи и чтения данных в этом составном файле. В IPaper::Save выше хранятся данные нескольких типов.
CLSID для DllPaper, CLSID_DllPaper, сериализуется и хранится в специальном потоке, управляемом COM, в объекте хранилища с именем "\001CompObj". Это хранилище выполняется с помощью функции службы WriteClassStg . Эти сохраненные данные CLSID можно использовать для связывания содержимого хранилища с компонентом DllPaper, который создал и может интерпретировать его. В этом примере корневое хранилище передается StoClien, поэтому весь составной файл связан с компонентом DllPaper. Эти данные CLSID можно получить позже с помощью вызова функции службы ReadClassStg .
Так как DllPaper имеет дело с редактируемыми данными, также целесообразно записать формат буфера обмена в хранилище. Функция-служба WriteFmtUserTypeStg вызывается для хранения обозначения формата буфера обмена и имени, доступного для чтения пользователем. Доступное для чтения имя предназначено для отображения графического пользовательского интерфейса в списках выбора. Имя, переданное выше, использует макрос, CLIPBDFMT_STR, который определен как "DllPaper 1.0" в Paper.h. Эти сохраненные данные буфера обмена можно получить позже с помощью вызова функции службы ReadFmtUserTypeStg. Эта функция возвращает строковое значение, выделенное с помощью распределителя памяти задач. Вызывающий объект отвечает за освобождение строки.
Save next создает поток в хранилище для данных бумаги COPaper. Поток называется "PAPERDATA" и передается с помощью макроса STREAM_PAPERDATA_USTR. Метод IStorage::CreateStream требует, чтобы эта строка была в Юникоде. Так как строка фиксируется во время компиляции, макрос определяется как Юникод в Paper.h.
#define STREAM_PAPERDATA_USTR L"PAPERDATA"
Это достигается с помощью L перед строкой, указывающей long.
Метод CreateStream создает и открывает поток в указанном хранилище. Указатель интерфейса IStream для нового потока передается в переменную указателя интерфейса вызывающего объекта. Метод AddRef вызывается для этого указателя интерфейса в CreateStream, и вызывающий объект должен освободить этот указатель после его использования. Методу CreateStream передаются многочисленные флаги режима доступа, как показано ниже.
STGM_CREATE | STGM_WRITE | STGM_DIRECT | STGM_SHARE_EXCLUSIVE
STGM_CREATE создает новый поток или перезаписывает существующий поток с тем же именем. STGM_WRITE открывает поток с разрешением на запись. STGM_DIRECT открывает поток для прямого доступа, а не для доступа через транзакцию. STGM_SHARE_EXCLUSIVE открывает файл для монопольного использования вызывающей стороны.
После успешного создания потока PAPERDATA для записи в поток используется интерфейс IStream . Метод IStream::Write используется для первого хранения содержимого структуры PAPER_PROPERTIES. По сути, это заголовок свойств в передней части потока. Так как номер версии является первым в файле, его можно считывать независимо, чтобы определить, как работать с приведенными ниже данными. Если объем фактически записанных данных не равен запрошенному объему, метод Save прерывается и возвращает STG_E_CANTSAVE.
Сохранить весь массив данных рукописного ввода в потоке очень просто.
// Now write the complete array of Ink Data.
ulToWrite = m_PaperProperties.lInkArraySize * sizeof(INKDATA);
hr = pIStream->Write(m_paInkData, ulToWrite, &ulWritten);
if (SUCCEEDED(hr) && ulToWrite != ulWritten)
hr = STG_E_CANTSAVE;
Так как метод IStream::Write работает с массивом байтов, количество байтов сохраненных данных рукописного ввода в массиве вычисляется и операция записи начинается в начале массива. Если объем фактически записанных данных не равен запрошенному объему, метод Save возвращает STG_E_CANTSAVE.
После записи потока метод IPaper::Save освобождает указатель IStream , который он использовал.
Метод Save также вызывает клиент IPaperSink (во внутреннем методе COPaper NotifySinks), чтобы уведомить клиента о завершении операции сохранения. На этом этапе метод Save возвращается вызывающей клиенту, который, как правило, освобождает указатель IStorage .