IPaper::Save

此示例代码中的主要重点在于 COPaper 如何加载其纸张数据并将其保存在复合文件中。 详细讨论了 IPaperLoadSave 方法实现。

下面是 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 不会创建用于存储纸张数据的复合文件。 对于 SaveLoad 方法,客户端为现有复合文件传递 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 指针。