このサンプル コードの主な焦点は、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 コンポーネントに関連付けられます。 この CLSID データは、後で ReadClassStg サービス関数を呼び出して取得できます。
DllPaper は編集可能なデータを処理するため、クリップボード形式をストレージに記録することも適切です。 WriteFmtUserTypeStg サービス関数は、クリップボード形式の指定と、書式のユーザーが判読できる名前の両方を格納するために呼び出されます。 ユーザーが読み取り可能な名前は、選択リストでの GUI 表示を目的とします。 上記で渡された名前は、Paper.h で "DllPaper 1.0" と定義されているマクロCLIPBDFMT_STRを使用します。 この保存されたクリップボード データは、後で 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 メソッドは、クライアント IPaperSink (COPaper 内部 NotifySinks メソッド内) を呼び出して、保存操作が完了したことをクライアントに通知します。 この時点で、Save メソッドは呼び出し元のクライアントに戻り、通常は IStorage ポインターを解放します。