IPaper::Save
此示例代码中的主要重点在于 COPaper 如何加载其纸张数据并将其保存在复合文件中。 详细讨论了 IPaper、 Load 和 Save 方法实现。
下面是 Paper.cpp 中的 IPaper::Save 方法。
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 中,存储了多种类型的数据。
dllPaper 的 CLSID(CLSID_DllPaper)序列化并存储在名为“\001CompObj”的存储对象内的特殊 COM 控制流中。 WriteClassStg 服务函数执行此存储。 此存储的 CLSID 数据可用于将存储内容与创建的 DllPaper 组件相关联,并且可以对其进行解释。 在此示例中,根存储由 StoClien 传递,因此整个复合文件与 DllPaper 组件相关联。 以后可以通过调用 ReadClassStg 服务函数来检索此 CLSID 数据。
由于 DllPaper 处理可编辑的数据,因此也适合在存储中记录剪贴板格式。 调用 WriteFmtUserTypeStg 服务函数来存储剪贴板格式指定和格式的用户可读名称。 用户可读名称用于在选择列表中显示 GUI。 上面传递的名称使用宏CLIPBDFMT_STR,该宏在 Paper.h 中定义为“DllPaper 1.0”。 稍后可以通过调用服务函数 ReadFmtUserTypeStg 来检索此存储的剪贴板数据。 此函数返回使用任务内存分配器分配的字符串值。 调用方负责释放字符串。
接下来,保存将在 COPaper 纸张数据的存储中创建一个流。 流称为“PAPERDATA”,使用 STREAM_PAPERDATA_USTR 宏传递。 IStorage::CreateStream 方法要求此字符串位于 Unicode 中。 由于字符串在编译时是固定的,因此宏在 Paper.h 中定义为 Unicode。
#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 方法还调用 COPaper 内部 NotifySinks 方法) 中的客户端 IPaperSink (,以通知客户端保存操作已完成。 此时, Save 方法将返回到调用客户端,该客户端通常会释放 IStorage 指针。