Inicializando manipuladores de propriedade

Este tópico explica como criar e registrar manipuladores de propriedades para trabalhar com o sistema de propriedades do Windows.

Este tópico é organizado da seguinte maneira:

Manipuladores de propriedade

Os manipuladores de propriedades são uma parte crucial do sistema de propriedades. Eles são invocados em processo pelo indexador para ler e indexar valores de propriedade e também são invocados pelo Windows Explorer em processo para ler e gravar valores de propriedade diretamente nos arquivos. Esses manipuladores precisam ser cuidadosamente gravados e testados para evitar o desempenho degradado ou a perda de dados nos arquivos afetados. Para obter mais informações sobre considerações específicas do indexador que afetam a implementação do manipulador de propriedades, consulte Developing Property Handlers for Windows Search.

Este tópico discute um formato de arquivo baseado em XML de exemplo que descreve uma receita com uma extensão de nome de arquivo .recipe. A extensão de nome de arquivo .recipe é registrada como seu próprio formato de arquivo distinto em vez de depender do formato de arquivo de .xml mais genérico, cujo manipulador usa um fluxo secundário para armazenar propriedades. Recomendamos que você registre extensões de nome de arquivo exclusivas para seus tipos de arquivo.

Antes de começar

Manipuladores de propriedade são objetos COM que criam a abstração IPropertyStore para um formato de arquivo específico. Eles leem (analisam) e gravam esse formato de arquivo de maneira que esteja em conformidade com sua especificação. Alguns manipuladores de propriedades fazem seu trabalho com base em APIs que abstraem o acesso a um formato de arquivo específico. Antes de desenvolver um manipulador de propriedades para o formato de arquivo, você precisa entender como o formato de arquivo armazena propriedades e como essas propriedades (nomes e valores) são mapeadas para a abstração do repositório de propriedades.

Ao planejar sua implementação, lembre-se de que os manipuladores de propriedades são componentes de baixo nível carregados no contexto de processos como o Windows Explorer, o indexador do Windows Search e aplicativos de terceiros que usam o modelo de programação de itens do Shell. Como resultado, os manipuladores de propriedade não podem ser implementados no código gerenciado e devem ser implementados em C++. Se o manipulador usar apis ou serviços para fazer seu trabalho, você deverá garantir que esses serviços possam funcionar corretamente nos ambientes em que o manipulador de propriedades é carregado.

Observação

Manipuladores de propriedade são sempre associados a tipos de arquivo específicos; Portanto, se o formato de arquivo contiver propriedades que exigem um manipulador de propriedades personalizado, você sempre deverá registrar uma extensão de nome de arquivo exclusivo para cada formato de arquivo.

 

Inicializando manipuladores de propriedade

Antes que uma propriedade seja usada pelo sistema, ela é inicializada chamando uma implementação de IInitializeWithStream. O manipulador de propriedades deve ser inicializado fazendo com que o sistema atribua um fluxo a ele em vez de deixar essa atribuição para a implementação do manipulador. Esse método de inicialização garante o seguinte:

  • O manipulador de propriedades pode ser executado em um processo restrito (um recurso de segurança importante) sem ter direitos de acesso para ler ou gravar arquivos diretamente, em vez de acessar seu conteúdo por meio do fluxo.
  • O sistema pode ser confiável para lidar corretamente com os oplocks de arquivo, o que é uma medida de confiabilidade importante.
  • O sistema de propriedades fornece um serviço de salvamento seguro automático sem nenhuma funcionalidade extra necessária da implementação do manipulador de propriedades. Consulte a seção Escrevendo valores de volta para obter mais informações sobre fluxos.
  • O uso do IInitializeWithStream abstrai sua implementação dos detalhes do sistema de arquivos. Isso permite que o manipulador dê suporte à inicialização por meio de armazenamentos alternativos, como uma pasta FTP (File Transfer Protocol) ou um arquivo compactado com uma extensão de nome de arquivo .zip.

Há casos em que a inicialização com fluxos não é possível. Nessas situações, há duas interfaces adicionais que os manipuladores de propriedades podem implementar: IInitializeWithFile e IInitializeWithItem. Se um manipulador de propriedades não implementar o IInitializeWithStream, ele deverá recusar a execução no processo isolado no qual o indexador do sistema o colocaria por padrão se houvesse uma alteração no fluxo. Para recusar esse recurso, defina o valor do Registro a seguir.

HKEY_CLASSES_ROOT
   CLSID
      {66742402-F9B9-11D1-A202-0000F81FEDEE}
         DisableProcessIsolation = 1

No entanto, é muito melhor implementar IInitializeWithStream e fazer uma inicialização baseada em fluxo. O manipulador de propriedades será mais seguro e confiável como resultado. A desabilitação do isolamento do processo geralmente destina-se apenas a manipuladores de propriedade herdados e deve ser evitada de forma extenuante por qualquer novo código.

Para examinar a implementação de um manipulador de propriedades em detalhes, examine o exemplo de código a seguir, que é uma implementação de IInitializeWithStream::Initialize. O manipulador é inicializado carregando um documento de receita baseado em XML por meio de um ponteiro para a instância IStream associada desse documento. A variável _spDocEle usada perto do final do exemplo de código é definida anteriormente no exemplo como MSXML2::IXMLDOMElementPtr.

Observação

Os exemplos de código a seguir e todos os exemplos de código subsequentes são obtidos do exemplo do manipulador de receitas incluído no SDK (Software Development Kit) do Windows. .

 

HRESULT CRecipePropertyStore::Initialize(IStream *pStream, DWORD grfMode)
{
    HRESULT hr = E_FAIL;
    
    try
    {
        if (!_spStream)
        {
            hr = _spDomDoc.CreateInstance(__uuidof(MSXML2::DOMDocument60));
            
            if (SUCCEEDED(hr))
            {
                if (VARIANT_TRUE == _spDomDoc->load(static_cast<IUnknown *>(pStream)))
                {
                    _spDocEle = _spDomDoc->documentElement;
                }

Â

Depois que o documento em si é carregado, as propriedades a serem exibidas no Windows Explorer são carregadas chamando o método de _LoadProperties protegido, conforme ilustrado no exemplo de código a seguir. Esse processo é examinado detalhadamente na próxima seção.

                if (_spDocEle)
                {
                    hr = _LoadProperties();
    
                    if (SUCCEEDED(hr))
                    {
                        _spStream = pStream;
                    }
                }
                else
                {
                    hr = E_FAIL;  // parse error
                }
            }
        }
        else
        {
            hr = E_UNEXPECTED;
        }
    }
    
    catch (_com_error &e)
    {
        hr = e.Error();
    }
    
    return hr;
}

Se o fluxo for somente leitura, mas o parâmetro grfMode contiver o sinalizador STGM_READWRITE, a inicialização deverá falhar e retornar STG_E_ACCESSDENIED. Sem esse marcar, o Windows Explorer mostra os valores da propriedade como graváveis mesmo que não estejam, levando a uma experiência confusa do usuário final.

O manipulador de propriedades é inicializado apenas uma vez em seu tempo de vida. Se uma segunda inicialização for solicitada, o manipulador deverá retornar HRESULT_FROM_WIN32(ERROR_ALREADY_INITIALIZED).

Repositório de Propriedades In-Memory

Antes de examinar a implementação de _LoadProperties, você deve entender a matriz PropertyMap usada no exemplo para mapear propriedades no documento XML para propriedades existentes no sistema de propriedades por meio de seus valores PKEY.

Você não deve expor todos os elementos e atributos no arquivo XML como uma propriedade. Em vez disso, selecione apenas aqueles que você acredita que serão úteis para os usuários finais na organização de seus documentos (neste caso, receitas). Esse é um conceito importante a ter em mente à medida que você desenvolve seus manipuladores de propriedades: a diferença entre informações que são realmente úteis para cenários organizacionais e informações que pertencem aos detalhes do arquivo e podem ser vistas abrindo o próprio arquivo. As propriedades não se destinam a ser uma duplicação completa de um arquivo XML.

PropertyMap c_rgPropertyMap[] =
{
{ L"Recipe/Title", PKEY_Title, 
                   VT_LPWSTR, 
                   NULL, 
                   PKEY_Null },
{ L"Recipe/Comments", PKEY_Comment, 
                      VT_LPWSTR, 
                      NULL, 
                      PKEY_Null },
{ L"Recipe/Background", PKEY_Author, 
                        VT_VECTOR | VT_LPWSTR, 
                        L"Author", 
                        PKEY_Null },
{ L"Recipe/RecipeKeywords", PKEY_Keywords, 
                            VT_VECTOR | VT_LPWSTR, 
                            L"Keyword", 
                            PKEY_KeywordCount },
};

Aqui está a implementação completa do método _LoadProperties chamado por IInitializeWithStream::Initialize.

HRESULT CRecipePropertyStore::_LoadProperties()
{
    HRESULT hr = E_FAIL;    
    
    if (_spCache)
    {
        hr = <mark type="const">S_OK</mark>;
    }
    else
    {
        // Create the in-memory property store.
        hr = PSCreateMemoryPropertyStore(IID_PPV_ARGS(&_spCache));
    
        if (SUCCEEDED(hr))
        {
            // Cycle through each mapped property.
            for (UINT i = 0; i < ARRAYSIZE(c_rgPropertyMap); ++i)
            {
                _LoadProperty(c_rgPropertyMap[i]);
            }
    
            _LoadExtendedProperties();
            _LoadSearchContent();
        }
    }
    return hr;
}

O método _LoadProperties chama a função auxiliar do Shell PSCreateMemoryPropertyStore para criar um repositório de propriedades na memória (cache) para as propriedades manipuladas. Usando um cache, as alterações são controladas para você. Isso libera você de controlar se um valor de propriedade foi alterado no cache, mas ainda não foi salvo no armazenamento persistente. Ele também libera você de valores de propriedade persistentes desnecessariamente que não foram alterados.

O método _LoadProperties também chama _LoadProperty cuja implementação é ilustrada no código a seguir) uma vez para cada propriedade mapeada. _LoadProperty obtém o valor da propriedade conforme especificado no elemento PropertyMap no fluxo XML e o atribui ao cache na memória por meio de uma chamada para IPropertyStoreCache::SetValueAndState. O sinalizador PSC_NORMAL na chamada para IPropertyStoreCache::SetValueAndState indica que o valor da propriedade não foi alterado desde a hora em que ele entrou no cache.

HRESULT CRecipePropertyStore::_LoadProperty(PropertyMap &map)
{
    HRESULT hr = S_FALSE;
    
    MSXML2::IXMLDOMNodePtr spXmlNode(_spDomDoc->selectSingleNode(map.pszXPath));
    if (spXmlNode)
    {
        PROPVARIANT propvar = { 0 };
        propvar.vt = map.vt;
        
        if (map.vt == (VT_VECTOR | VT_LPWSTR))
        {
            hr = _LoadVectorProperty(spXmlNode, &propvar, map);
        }
        else
        {
            // If there is no value, set to VT_EMPTY to indicate
            // that it is not there. Do not return failure.
            if (spXmlNode->text.length() == 0)
            {
                propvar.vt = VT_EMPTY;
                hr = <mark type="const">S_OK</mark>;
            }
            else
            {
                // SimplePropVariantFromString is a helper function.
                // particular to the sample. It is found in Util.cpp.
                hr = SimplePropVariantFromString(spXmlNode->text, &propvar);
            }
        }
    
        if (S_OK == hr)
        {
            hr = _spCache->SetValueAndState(map.key, &propvar, PSC_NORMAL);
            PropVariantClear(&propvar);
        }
    }
    return hr;
}

Lidando com valores de PROPVARIANT-Based

Na implementação de _LoadProperty, um valor de propriedade é fornecido na forma de um PROPVARIANT. Um conjunto de APIs no SDK (software development kit) é fornecido para converter de tipos primitivos, como PWSTR ou int para ou de tipos PROPVARIANT . Essas APIs são encontradas em Propvarutil.h.

Por exemplo, para converter um PROPVARIANT em uma cadeia de caracteres, você pode usar PropVariantToString conforme ilustrado aqui.

PropVariantToString(REFPROPVARIANT propvar, PWSTR psz, UINT cch);

Para inicializar um PROPVARIANT de uma cadeia de caracteres, você pode usar InitPropVariantFromString.

InitPropVariantFromString(PCWSTR psz, PROPVARIANT *ppropvar);

Como você pode ver em qualquer um dos arquivos de receita incluídos no exemplo, pode haver mais de uma palavra-chave em cada arquivo. Para considerar isso, o sistema de propriedades dá suporte a cadeias de caracteres de valores múltiplos representadas como um vetor de cadeias de caracteres (por exemplo, "VT_VECTOR | VT_LPWSTR"). O método _LoadVectorProperty no exemplo usa valores baseados em vetor.

HRESULT CRecipePropertyStore::_LoadVectorProperty
                                (MSXML2::IXMLDOMNode *pNodeParent,
                                 PROPVARIANT *ppropvar,
                                 struct PropertyMap &map)
{
    HRESULT hr = S_FALSE;
    MSXML2::IXMLDOMNodeListPtr spList = pNodeParent->selectNodes(map.pszSubNodeName);
    
    if (spList)
    {
        UINT cElems = spList->length;
        ppropvar->calpwstr.cElems = cElems;
        ppropvar->calpwstr.pElems = (PWSTR*)CoTaskMemAlloc(sizeof(PWSTR)*cElems);
    
        if (ppropvar->calpwstr.pElems)
        {
            for (UINT i = 0; (SUCCEEDED(hr) && i < cElems); ++i)
            {
                hr = SHStrDup(spList->item[i]->text, 
                              &(ppropvar->calpwstr.pElems[i]));
            }
    
            if (SUCCEEDED(hr))
            {
                if (!IsEqualPropertyKey(map.keyCount, PKEY_Null))
                {
                    PROPVARIANT propvarCount = { VT_UI4 };
                    propvarCount.uintVal = cElems;
                    
                    _spCache->SetValueAndState(map.keyCount,
                                               &propvarCount, 
                                               PSC_NORMAL);
                }
            }
            else
            {
                PropVariantClear(ppropvar);
            }
        }
        else
        {
            hr = E_OUTOFMEMORY;
        }
    }
    
    return hr;
}

Se um valor não existir no arquivo, não retorne um erro. Em vez disso, defina o valor como VT_EMPTY e retorne S_OK. VT_EMPTY indica que o valor da propriedade não existe.

Suporte a metadados abertos

Este exemplo usa um formato de arquivo baseado em XML. Seu esquema pode ser estendido para dar suporte a propriedades que não foram consideradas durante o developmet, por exemplo. Esse sistema é conhecido como metadados abertos. Este exemplo estende o sistema de propriedades criando um nó sob o elemento Recipe chamado ExtendedProperties, conforme ilustrado no exemplo de código a seguir.

<ExtendedProperties>
    <Property 
        Name="{65A98875-3C80-40AB-ABBC-EFDAF77DBEE2}, 100"
        EncodedValue="HJKHJDHKJHK"/>
</ExtendedProperties>

Para carregar propriedades estendidas persistentes durante a inicialização, implemente o método _LoadExtendedProperties , conforme ilustrado no exemplo de código a seguir.

HRESULT CRecipePropertyStore::_LoadExtendedProperties()
{
    HRESULT hr = S_FALSE;
    MSXML2::IXMLDOMNodeListPtr spList = 
                  _spDomDoc->selectNodes(L"Recipe/ExtendedProperties/Property");
    
    if (spList)
    {
        UINT cElems = spList->length;
        
        for (UINT i = 0; i < cElems; ++i)
        {
            MSXML2::IXMLDOMElementPtr spElement;
            
            if (SUCCEEDED(spList->item[i]->QueryInterface(IID_PPV_ARGS(&spElement))))
            {
                PROPERTYKEY key;
                _bstr_t bstrPropName = spElement->getAttribute(L"Name").bstrVal;
    
                if (!!bstrPropName &&
                    (SUCCEEDED(PropertyKeyFromString(bstrPropName, &key))))
                {
                    PROPVARIANT propvar = { 0 };
                    _bstr_t bstrEncodedValue = 
                               spElement->getAttribute(L"EncodedValue").bstrVal;
                   
                    if (!!bstrEncodedValue)
                    {
                        // DeserializePropVariantFromString is a helper function
                        // particular to the sample. It is found in Util.cpp.
                        hr = DeserializePropVariantFromString(bstrEncodedValue, 
                                                              &propvar);
                    }

As APIs de serialização declaradas em Propsys.h são usadas para serializar e desserializar tipos PROPVARIANT em blobs de dados e, em seguida, a codificação Base64 é usada para serializar esses blobs em cadeias de caracteres que podem ser salvas no XML. Essas cadeias de caracteres são armazenadas no atributo EncodedValue do elemento ExtendedProperties . O método utilitário a seguir, implementado no arquivo Util.cpp do exemplo, executa a serialização. Ele começa com uma chamada para a função StgSerializePropVariant para executar a serialização binária, conforme ilustrado no exemplo de código a seguir.

HRESULT SerializePropVariantAsString(const PROPVARIANT *ppropvar, PWSTR *pszOut)
{
    SERIALIZEDPROPERTYVALUE *pBlob;
    ULONG cbBlob;
    HRESULT hr = StgSerializePropVariant(ppropvar, &pBlob, &cbBlob);

Em seguida, a função CryptBinaryToStringÂ, declarada em Wincrypt.h, executa a conversão Base64.

    if (SUCCEEDED(hr))
    {
        hr = E_FAIL;
        DWORD cchString;
        
        if (CryptBinaryToString((BYTE *)pBlob, 
                                cbBlob,
                                CRYPT_STRING_BASE64, 
                                NULL, 
                                &cchString))
        {
            *pszOut = (PWSTR)CoTaskMemAlloc(sizeof(WCHAR) *cchString);
    
            if (*pszOut)
            {
                if (CryptBinaryToString((BYTE *)pBlob, 
                                         cbBlob,
                                         CRYPT_STRING_BASE64,
                                         *pszOut, 
                                         &cchString))
                {
                    hr = <mark type="const">S_OK</mark>;
                }
                else
                {
                    CoTaskMemFree(*pszOut);
                }
            }
            else
            {
                hr = E_OUTOFMEMORY;
            }
        }
    }
    
    return <mark type="const">S_OK</mark>;}

A função DeserializePropVariantFromString , também encontrada em Util.cpp, inverte a operação, desserializando valores do arquivo XML.

Para obter informações sobre o suporte para metadados abertos, consulte "Tipos de arquivo que dão suporte a metadados abertos" em Tipos de Arquivo.

Conteúdo do Full-Text

Os manipuladores de propriedades também podem facilitar uma pesquisa de texto completo do conteúdo do arquivo e eles são uma maneira fácil de fornecer essa funcionalidade se o formato de arquivo não for excessivamente complicado. Há uma maneira alternativa e mais poderosa de fornecer o texto completo do arquivo por meio da implementação da interface IFilter .

A tabela a seguir resume os benefícios de cada abordagem usando IFilter ou IPropertyStore.

Funcionalidade Ifilter Ipropertystore
Permite a gravação de volta em arquivos? Não Sim
Fornece uma combinação de conteúdo e propriedades? Sim Sim
Multilingue? Sim Não
MIME/Embedded? Sim Não
Limites de texto? Frase, parágrafo, capítulo Nenhum
Implementação com suporte para SPS/SQL Server? Sim Não
Implementação Complex Simples

 

No exemplo do manipulador de receitas, o formato do arquivo de receita não tem requisitos complexos, portanto, somente IPropertyStore foi implementado para suporte de texto completo. A pesquisa de texto completo é implementada para os nós XML nomeados na matriz a seguir.

const PWSTR c_rgszContentXPath[] = {
    L"Recipe/Ingredients/Item",
    L"Recipe/Directions/Step",
    L"Recipe/RecipeInfo/Yield",
    L"Recipe/RecipeKeywords/Keyword",
};

O sistema de propriedades contém a System.Search.Contents propriedade (PKEY_Search_Contents), que foi criada para fornecer conteúdo de texto completo ao indexador. O valor dessa propriedade nunca é exibido diretamente na interface do usuário; o texto de todos os nós XML nomeados na matriz acima são concatenados em uma única cadeia de caracteres. Essa cadeia de caracteres é então fornecida ao indexador como o conteúdo de texto completo do arquivo de receita por meio de uma chamada para IPropertyStoreCache::SetValueAndState , conforme ilustrado no exemplo de código a seguir.

HRESULT CRecipePropertyStore::_LoadSearchContent()
{
    HRESULT hr = S_FALSE;
    _bstr_t bstrContent;
    
    for (UINT i = 0; i < ARRAYSIZE(c_rgszContentXPath); ++i)
    {
        MSXML2::IXMLDOMNodeListPtr spList = 
                                  _spDomDoc->selectNodes(c_rgszContentXPath[i]);
    
        if (spList)
        {
            UINT cElems = spList->length;
            
            for (UINT elt = 0; elt < cElems; ++elt)
            {
                bstrContent += L" ";
                bstrContent += spList->item[elt]->text;
            }
        }
    }
    
    if (bstrContent.length() > 0)
    {
        PROPVARIANT propvar = { VT_LPWSTR };
        hr = SHStrDup(bstrContent, &(propvar.pwszVal));
    
        if (SUCCEEDED(hr))
        {
            hr = _spCache->SetValueAndState(PKEY_Search_Contents, 
                                            &propvar, 
                                            PSC_NORMAL);
            PropVariantClear(&propvar);
        }
    }
    
    return hr;}

Fornecendo valores para propriedades

Quando usados para ler valores, os manipuladores de propriedade geralmente são invocados por um dos seguintes motivos:

  • Para enumerar todos os valores de propriedade.
  • Para obter o valor de uma propriedade específica.

Para enumeração, um manipulador de propriedades é solicitado a enumerar suas propriedades durante a indexação ou quando a caixa de diálogo propriedades solicita que as propriedades sejam exibidas no grupo Outros . A indexação continua constantemente como uma operação em segundo plano. Sempre que um arquivo é alterado, o indexador é notificado e indexa novamente o arquivo solicitando ao manipulador de propriedades para enumerar suas propriedades. Portanto, é fundamental que os manipuladores de propriedade sejam implementados com eficiência e retornem valores de propriedade o mais rápido possível. Enumerar todas as propriedades para as quais você tem valores, assim como faria para qualquer coleção, mas não enumerar propriedades que envolvam cálculos com uso intensivo de memória ou solicitações de rede que possam torná-los lentos para recuperar.

Ao escrever um manipulador de propriedades, você geralmente precisa considerar os dois conjuntos de propriedades a seguir.

  • Propriedades primárias: propriedades que o tipo de arquivo dá suporte nativamente. Por exemplo, um manipulador de propriedades de foto para metadados exif (arquivo de imagem trocável) dá suporte System.Photo.FNumbernativo a .
  • Propriedades estendidas: propriedades compatíveis com o tipo de arquivo como parte de metadados abertos.

Como o exemplo usa cache na memória, implementar métodos IPropertyStore é apenas uma questão de delegar a esse cache, conforme ilustrado no exemplo de código a seguir.

IFACEMETHODIMP GetCount(__out DWORD *pcProps)
{ return _spCache->GetCount(pcProps); }

IFACEMETHODIMP GetAt(DWORD iProp, __out PROPERTYKEY *pkey)
{ return _spCache->GetAt(iProp, pkey); }

IFACEMETHODIMP GetValue(REFPROPERTYKEY key, __out PROPVARIANT *pPropVar)
{ return _spCache->GetValue(key, pPropVar); }

Se você optar por não delegar ao cache na memória, deverá implementar seus métodos para fornecer> o seguinte comportamento esperado:

Gravando valores de volta

Quando o manipulador de propriedades grava o valor de uma propriedade usando IPropertyStore::SetValue, ele não grava o valor no arquivo até que IPropertyStore::Commit seja chamado. O cache na memória pode ser útil na implementação desse esquema. No código de exemplo, a implementação IPropertyStore::SetValue simplesmente define o novo valor no cache na memória e define o estado dessa propriedade como PSC_DIRTY.

HRESULT CRecipePropertyStore::SetValue(REFPROPERTYKEY key, const PROPVARIANT *pPropVar)
    {
    
    HRESULT hr = E_FAIL;
    
    if (IsEqualPropertyKey(key, PKEY_Search_Contents))
    {
        // This property is read-only
        hr = HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);  
    }
    else
    {
        hr = _spCache->SetValueAndState(key, pPropVar, PSC_DIRTY);
    }
    
    return hr;
}

Em qualquer implementação do IPropertyStore , o seguinte comportamento é esperado de IPropertyStore::SetValue:

  • Se a propriedade já existir, o valor da propriedade será definido.
  • Se a propriedade não existir, a nova propriedade será adicionada e seu valor definido.
  • Se o valor da propriedade não puder ser persistido na mesma precisão fornecida (por exemplo, truncamento devido a limitações de tamanho no formato de arquivo), o valor será definido na medida do possível e INPLACE_S_TRUNCATED será retornado.
  • Se a propriedade não for compatível com o manipulador de propriedades, HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED) será retornado.
  • Se houver outro motivo para que o valor da propriedade não possa ser definido, como o arquivo bloqueado ou a falta de direitos de edição por meio de ACLs (listas de controle de acesso), STG_E_ACCESSDENIED será retornado.

Uma das principais vantagens de usar fluxos, como o exemplo, é a confiabilidade. Os manipuladores de propriedades sempre devem considerar que não podem deixar um arquivo em um estado inconsistente no caso de uma falha catastrófica. A corromper os arquivos de um usuário obviamente deve ser evitada e a melhor maneira de fazer isso é com um mecanismo de "copiar na gravação". Se o manipulador de propriedades usar um fluxo para acessar um arquivo, você obterá esse comportamento automaticamente; o sistema grava as alterações no fluxo, substituindo o arquivo pela nova cópia somente durante a operação de confirmação.

Para substituir esse comportamento e controlar o processo de salvamento de arquivos manualmente, você pode recusar o comportamento de salvamento seguro definindo o valor ManualSafeSave na entrada do registro do manipulador, conforme ilustrado aqui.

HKEY_CLASSES_ROOT
   CLSID
      {66742402-F9B9-11D1-A202-0000F81FEDEE}
         ManualSafeSave = 1

Quando um manipulador especifica o valor ManualSafeSave, o fluxo com o qual ele é inicializado não é um fluxo transacionado (STGM_TRANSACTED). O próprio manipulador deve implementar a função de salvamento segura para garantir que o arquivo não esteja corrompido se a operação de salvamento for interrompida. Se o manipulador implementar a gravação in-loco, ele gravará no fluxo fornecido. Se o manipulador não der suporte a esse recurso, ele deverá recuperar um fluxo com o qual gravar a cópia atualizada do arquivo usando IDestinationStreamFactory::GetDestinationStream. Depois que o manipulador terminar de gravar, ele deverá chamar IPropertyStore::Commit no fluxo original para concluir a operação e substituir o conteúdo do fluxo original pela nova cópia do arquivo.

ManualSafeSave também é a situação padrão se você não inicializar seu manipulador com um fluxo. Sem um fluxo original para receber o conteúdo do fluxo temporário, você deve usar ReplaceFile para executar uma substituição atômica do arquivo de origem.

Formatos de arquivo grandes que serão usados de uma maneira que produza arquivos maiores que 1 MB devem implementar suporte para gravação de propriedade in-loco; caso contrário, o comportamento de desempenho não atende às expectativas dos clientes do sistema de propriedades. Nesse cenário, o tempo necessário para gravar propriedades não deve ser afetado pelo tamanho do arquivo.

Para arquivos muito grandes, por exemplo, um arquivo de vídeo de 1 GB ou mais, uma solução diferente é necessária. Se não houver espaço suficiente no arquivo para executar a gravação in-loco, o manipulador poderá falhar na atualização da propriedade se a quantidade de espaço reservado para gravação de propriedade in-loco tiver sido esgotada. Essa falha ocorre para evitar um desempenho ruim decorrente de 2 GB de E/S (1 para leitura, 1 para gravação). Devido a essa possível falha, esses formatos de arquivo devem reservar espaço suficiente para gravação de propriedade in-loco.

Se o arquivo tiver espaço suficiente em seu cabeçalho para gravar metadados e se a gravação desses metadados não fizer com que o arquivo cresça ou diminua, talvez seja seguro gravar in-loco. Recomendamos 64 KB como ponto de partida. Escrever no local é equivalente ao manipulador que solicita ManualSafeSave e chamar IStream::Commit na implementação de IPropertyStore::Commit e tem um desempenho muito melhor do que copiar na gravação. Se o tamanho do arquivo for alterado devido a alterações no valor da propriedade, a gravação in-loco não deverá ser tentada devido ao potencial de um arquivo corrompido no caso de uma terminação anormal.

Observação

Por motivos de desempenho, recomendamos que a opção ManualSafeSave seja usada com manipuladores de propriedade que trabalham com arquivos de 100 KB ou maiores.

 

Conforme ilustrado na implementação de exemplo a seguir de IPropertyStore::Commit, o manipulador de ManualSafeSave é registrado para ilustrar a opção de salvamento seguro manual. O método _SaveCacheToDom grava os valores de propriedade armazenados no cache na memória no objeto XMLdocument.

HRESULT CRecipePropertyStore::Commit()
{
    HRESULT hr = E_UNEXPECTED;
    if (_pCache)
    {
        // Check grfMode to ensure writes are allowed.
        hr = STG_E_ACCESSDENIED;
        if (_grfMode & STGM_READWRITE)
        {
            // Save the internal value cache to XML DOM object.
            hr = _SaveCacheToDom();
            if (SUCCEEDED(hr))
            {
                // Reset the output stream.
                LARGE_INTEGER liZero = {};
                hr = _pStream->Seek(liZero, STREAM_SEEK_SET, NULL);

Em seguida, pergunte se o pecified dá suporte a IDestinationStreamFactory.

                        if (SUCCEEDED(hr))
                        {
                            // Write the XML out to the temprorary stream and commit it.
                            VARIANT varStream = {};
                            varStream.vt = VT_UNKNOWN;
                            varStream.punkVal = pStreamCommit;
                            hr = _pDomDoc->save(varStream);
                            if (SUCCEEDED(hr))
                            {
                                hr = pStreamCommit->Commit(STGC_DEFAULT);_

Em seguida, confirme o fluxo original, que grava os dados de volta no arquivo original de maneira segura.

                        if (SUCCEEDED(hr))
                                {
                                    // Commit the real output stream.
                                    _pStream->Commit(STGC_DEFAULT);
                                }
                            }

                            pStreamCommit->Release();
                        }

                        pSafeCommit->Release();
                    }
                }
            }
        }
    }

Em seguida, examine a implementação do _SaveCacheToDom .

// Saves the values in the internal cache back to the internal DOM object.
HRESULT CRecipePropertyStore::_SaveCacheToDom()
{
    // Iterate over each property in the internal value cache.
    DWORD cProps;  

Em seguida, obtenha o número de propriedades armazenadas no cache na memória.

HRESULT hr = _pCache->GetCount(&cProps);          
            

Agora itera pelas propriedades para determinar se o valor de uma propriedade foi alterado desde que foi carregado na memória.

    for (UINT i = 0; SUCCEEDED(hr) && (i < cProps); ++i)
    {
        PROPERTYKEY key;
        hr = _pCache->GetAt(i, &key); 

O método IPropertyStoreCache::GetState obtém o estado da propriedade no cache. O sinalizador PSC_DIRTY, que foi definido na implementação IPropertyStore::SetValue , marca uma propriedade como alterada.

        if (SUCCEEDED(hr))
        {
            // check the cache state; only save dirty properties
            PSC_STATE psc;
            hr = _pCache->GetState(key, &psc);
            if (SUCCEEDED(hr) && psc == PSC_DIRTY)
            {
                // get the cached value
                PROPVARIANT propvar = {};
                hr = _pCache->GetValue(key, &propvar); 

Mapeie a propriedade para o nó XML conforme especificado na matriz eg_rgPropertyMap .

if (SUCCEEDED(hr))
                {
                    // save as a native property if the key is in the property map
                    BOOL fIsNativeProperty = FALSE;
                    for (UINT i = 0; i < ARRAYSIZE(g_rgPROPERTYMAP); ++i)
                    {
                        if (IsEqualPropertyKey(key, *g_rgPROPERTYMAP[i].pkey))
                        {
                            fIsNativeProperty = TRUE;
                            hr = _SaveProperty(propvar, g_rgPROPERTYMAP[i]);
                            break;
                        }     

Se uma propriedade não estiver no mapa, ela será uma nova propriedade que foi definida pelo Windows Explorer. Como há suporte para metadados abertos, salve a nova propriedade na seção ExtendedProperties do XML.

                    
                    // Otherwise, save as an extended property.
                    if (!fIsNativeProperty)
                    {
                        hr = _SaveExtendedProperty(key, propvar);
                    }

                    PropVariantClear(&propvar);
                }
            }
        }
    }

    return hr;    

Implementando IPropertyStoreCapabilities

IPropertyStoreCapabilities informa à interface do usuário do Shell se uma propriedade específica pode ser editada na interface do usuário do Shell. É importante observar que isso está relacionado apenas à capacidade de editar a propriedade na interface do usuário, não se você pode chamar IPropertyStore::SetValue com êxito na propriedade . Uma propriedade que provoca um valor retornado de S_FALSE de IPropertyStoreCapabilities::IsPropertyWritable ainda pode ser capaz de ser definida por meio de um aplicativo.

interface IPropertyStoreCapabilities : IUnknown
{
    HRESULT IsPropertyWritable([in] REFPROPERTYKEY key);
}

IsPropertyWritable retorna S_OK para indicar que os usuários finais devem ter permissão para editar a propriedade diretamente; S_FALSE indica que não devem. S_FALSE pode significar que os aplicativos são responsáveis por gravar a propriedade, não os usuários. O Shell desabilita a edição de controles conforme apropriado com base nos resultados das chamadas para esse método. Supõe-se que um manipulador que não implemente IPropertyStoreCapabilities dê suporte a metadados abertos por meio do suporte para a gravação de qualquer propriedade.

Se você estiver criando um manipulador que manipula apenas propriedades somente leitura, deverá implementar seu método Initialize (IInitializeWithStream, IInitializeWithItem ou IInitializeWithFile) para que ele retorne STG_E_ACCESSDENIED quando chamado com o sinalizador STGM_READWRITE.

Algumas propriedades têm seu atributo isInnate definido como true. As propriedades inatas têm as seguintes características:

  • A propriedade geralmente é calculada de alguma forma. Por exemplo, System.Image.BitDepth é calculado a partir da própria imagem.
  • Alterar a propriedade não faria sentido sem alterar o arquivo. Por exemplo, alterar System.Image.Dimensions não redimensionaria a imagem, portanto, não faz sentido permitir que o usuário a altere.
  • Em alguns casos, essas propriedades são fornecidas automaticamente pelo sistema. Os exemplos incluem System.DateModified, que é fornecido pelo sistema de arquivos e System.SharedWith, que se baseia em com quem o usuário está compartilhando o arquivo.

Devido a essas características, as propriedades marcadas como IsInnate são fornecidas ao usuário na interface do usuário do Shell apenas como propriedades somente leitura. Se uma propriedade for marcada como IsInnate, o sistema de propriedades não armazenará essa propriedade no manipulador de propriedades. Portanto, os manipuladores de propriedades não precisam de código especial para considerar essas propriedades em suas implementações. Se o valor do atributo IsInnate não for declarado explicitamente para uma propriedade específica, o valor padrão será false.

Registrando e distribuindo manipuladores de propriedade

Com o manipulador de propriedades implementado, ele deve ser registrado e sua extensão de nome de arquivo associada ao manipulador. Para obter mais informações, consulte Registrando e distribuindo manipuladores de propriedade.

Noções básicas sobre manipuladores de propriedade

Usando nomes de tipo

Usando listas de propriedades

Registrando e distribuindo manipuladores de propriedade

Práticas recomendadas e perguntas frequentes do manipulador de propriedades