IPaper::Save
O foco principal neste código de exemplo é como o COPaper pode carregar e salvar seus dados de papel em arquivos compostos. As implementações do método IPaper, Load e Save são discutidas detalhadamente.
Veja a seguir o método IPaper::Save de 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;
}
Nessa relação de cliente e servidor, o COPaper não cria o arquivo composto usado para armazenar dados de papel. Para os métodos Salvar e Carregar , o cliente passa um ponteiro de interface IStorage para um arquivo composto existente. Em seguida, ele usa o IStorage para gravar e ler dados nesse arquivo composto. Em IPaper::Save acima, vários tipos de dados são armazenados.
O CLSID para DllPaper, CLSID_DllPaper, é serializado e armazenado em um fluxo especial controlado por COM dentro do objeto de armazenamento chamado "\001CompObj". A função de serviço WriteClassStg executa esse armazenamento. Esses dados CLSID armazenados podem ser usados para associar o conteúdo de armazenamento ao componente DllPaper que criou e pode interpretá-lo. Neste exemplo, o armazenamento raiz é passado por StoClien e, portanto, todo o arquivo composto está associado ao componente DllPaper. Esses dados CLSID podem ser recuperados posteriormente com uma chamada para a função de serviço ReadClassStg .
Como o DllPaper lida com dados editáveis, também é apropriado registrar um formato de área de transferência no armazenamento. A função de serviço WriteFmtUserTypeStg é chamada para armazenar uma designação de formato de área de transferência e um nome legível pelo usuário para o formato. O nome legível pelo usuário destina-se à exibição de GUI em listas de seleção. O nome passado acima usa uma macro, CLIPBDFMT_STR, que é definida como "DllPaper 1.0" em Paper.h. Esses dados armazenados da área de transferência podem ser recuperados posteriormente com uma chamada para a função de serviço ReadFmtUserTypeStg. Essa função retorna um valor de cadeia de caracteres alocado usando o alocador de memória da tarefa. O chamador é responsável por liberar a cadeia de caracteres.
Salvar em seguida cria um fluxo no armazenamento para os dados de papel COPaper. O fluxo é chamado de "PAPERDATA" e é passado usando a macro STREAM_PAPERDATA_USTR. O método IStorage::CreateStream exige que essa cadeia de caracteres esteja em Unicode. Como a cadeia de caracteres é corrigida em tempo de compilação, a macro é definida como Unicode em Paper.h.
#define STREAM_PAPERDATA_USTR L"PAPERDATA"
O 'L' antes da cadeia de caracteres, indicando LONG, consegue isso.
O método CreateStream cria e abre um fluxo dentro do armazenamento especificado. Um ponteiro de interface IStream para o novo fluxo é passado em uma variável de ponteiro de interface do chamador. AddRef é chamado nesse ponteiro de interface no CreateStream e o chamador deve liberar esse ponteiro depois de usá-lo. O método CreateStream é passado por vários sinalizadores de modo de acesso, da seguinte maneira.
STGM_CREATE | STGM_WRITE | STGM_DIRECT | STGM_SHARE_EXCLUSIVE
STGM_CREATE cria um novo fluxo ou substitui um existente com o mesmo nome. STGM_WRITE abre o fluxo com permissão de gravação. STGM_DIRECT abre o fluxo para acesso direto, em vez de acesso transacionado. STGM_SHARE_EXCLUSIVE abre o arquivo para uso exclusivo e não compartilhado pelo chamador.
Depois que o fluxo PAPERDATA for criado com êxito, a interface IStream será usada para gravar no fluxo. O método IStream::Write é usado para primeiro armazenar o conteúdo da estrutura PAPER_PROPERTIES. Esse é essencialmente um cabeçalho de propriedades na frente do fluxo. Como o número de versão é a primeira coisa no arquivo, ele pode ser lido independentemente para determinar como lidar com os dados a seguir. Se a quantidade de dados realmente gravada não for igual ao valor solicitado, o método Save será anulado e retornará STG_E_CANTSAVE.
Salvar toda a matriz de dados de tinta no fluxo é simples.
// 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;
Como o método IStream::Write opera em uma matriz de bytes, o número de bytes de dados de tinta armazenados na matriz é calculado e a operação de gravação começa no início da matriz. Se a quantidade de dados realmente gravados não for igual ao valor solicitado, o método Save retornará STG_E_CANTSAVE.
Depois que o fluxo é gravado, o método IPaper::Save libera o ponteiro IStream que estava usando.
O método Save também chama o cliente IPaperSink (no método NotifySinks interno do COPaper) para notificar o cliente de que a operação de salvamento está concluída. Neste ponto, o método Save retorna ao cliente de chamada, que normalmente libera o ponteiro IStorage .