Menginisialisasi Handler Properti

Topik ini menjelaskan cara membuat dan mendaftarkan penangan properti untuk bekerja dengan sistem properti Windows.

Topik ini diatur sebagai berikut:

Penangan Properti

Handler properti adalah bagian penting dari sistem properti. Mereka dipanggil dalam proses oleh pengindeks untuk membaca dan mengindeks nilai properti, dan juga dipanggil oleh Windows Explorer dalam proses untuk membaca dan menulis nilai properti langsung dalam file. Handler ini perlu ditulis dan diuji dengan hati-hati untuk mencegah penurunan performa atau hilangnya data dalam file yang terpengaruh. Untuk informasi selengkapnya tentang pertimbangan khusus pengindeks yang memengaruhi implementasi handler properti, lihat Mengembangkan Penangan Properti untuk Windows Search.

Topik ini membahas sampel format file berbasis XML yang menjelaskan resep dengan ekstensi nama file .recipe. Ekstensi nama file .recipe terdaftar sebagai format filenya sendiri yang berbeda daripada mengandalkan format file .xml yang lebih umum, yang handler-nya menggunakan aliran sekunder untuk menyimpan properti. Kami menyarankan agar Anda mendaftarkan ekstensi nama file unik untuk jenis file Anda.

Sebelum Anda mulai

Handler properti adalah objek COM yang membuat abstraksi IPropertyStore untuk format file tertentu. Mereka membaca (mengurai) dan menulis format file ini dengan cara yang sesuai dengan spesifikasinya. Beberapa handler properti melakukan pekerjaan mereka berdasarkan API yang mengabstraksikan akses ke format file tertentu. Sebelum mengembangkan handler properti untuk format file, Anda perlu memahami bagaimana format file Anda menyimpan properti, dan bagaimana properti tersebut (nama dan nilai) dipetakan ke dalam abstraksi penyimpanan properti.

Saat merencanakan implementasi Anda, ingatlah bahwa handler properti adalah komponen tingkat rendah yang dimuat dalam konteks proses seperti Windows Explorer, pengindeks Windows Search, dan aplikasi pihak ketiga yang menggunakan model pemrograman item Shell. Akibatnya, handler properti tidak dapat diimplementasikan dalam kode terkelola, dan harus diimplementasikan di C++. Jika handler Anda menggunakan API atau layanan apa pun untuk melakukan pekerjaannya, Anda harus memastikan bahwa layanan tersebut dapat berfungsi dengan baik di lingkungan tempat handler properti Anda dimuat.

Catatan

Penangan properti selalu dikaitkan dengan jenis file tertentu; dengan demikian, jika format file Anda berisi properti yang memerlukan penangan properti kustom, Anda harus selalu mendaftarkan ekstensi nama file unik untuk setiap format file.

 

Menginisialisasi Handler Properti

Sebelum properti digunakan oleh sistem, properti diinisialisasi dengan memanggil implementasi IInitializeWithStream. Handler properti harus diinisialisasi dengan meminta sistem menetapkan aliran daripada meninggalkan penugasan tersebut ke implementasi handler. Metode inisialisasi ini memastikan hal-hal berikut:

  • Handler properti dapat berjalan dalam proses terbatas (fitur keamanan penting) tanpa memiliki hak akses untuk langsung membaca atau menulis file, melainkan mengakses konten mereka melalui aliran.
  • Sistem dapat dipercaya untuk menangani oplock file dengan benar, yang merupakan langkah keandalan penting.
  • Sistem properti menyediakan layanan penghematan aman otomatis tanpa fungsionalitas tambahan yang diperlukan dari implementasi handler properti. Lihat bagian Menulis Kembali Nilai untuk informasi selengkapnya tentang aliran.
  • Penggunaan IInitializeWithStream mengabstraksi implementasi Anda dari detail sistem file. Ini memungkinkan handler untuk mendukung inisialisasi melalui penyimpanan alternatif seperti folder File Transfer Protocol (FTP) atau file terkompresi dengan ekstensi nama file .zip.

Ada kasus di mana inisialisasi dengan aliran tidak dimungkinkan. Dalam situasi tersebut, ada dua antarmuka lebih lanjut yang dapat diterapkan oleh penangan properti: IInitializeWithFile dan IInitializeWithItem. Jika handler properti tidak mengimplementasikan IInitializeWithStream, pengindeks properti harus memilih untuk tidak menjalankan dalam proses terisolasi di mana pengindeks sistem akan menempatkannya secara default jika ada perubahan pada aliran. Untuk menolak fitur ini, atur nilai registri berikut.

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

Namun, jauh lebih baik untuk mengimplementasikan IInitializeWithStream dan melakukan inisialisasi berbasis aliran. Handler properti Anda akan lebih aman dan lebih dapat diandalkan sebagai hasilnya. Menonaktifkan isolasi proses umumnya hanya ditujukan untuk penangan properti warisan dan harus sangat dihindari oleh kode baru apa pun.

Untuk memeriksa implementasi handler properti secara rinci, lihat contoh kode berikut, yang merupakan implementasi dari IInitializeWithStream::Initialize. Handler diinisialisasi dengan memuat dokumen resep berbasis XML melalui penunjuk ke instans IStream terkait dokumen tersebut. Variabel _spDocEle yang digunakan di dekat akhir contoh kode didefinisikan sebelumnya dalam sampel sebagai MSXML2::IXMLDOMElementPtr.

Catatan

Contoh kode berikut dan semua berikutnya diambil dari sampel handler resep yang disertakan dalam Windows Software Development Kit (SDK). .

 

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;
                }

Â

Setelah dokumen itu sendiri dimuat, properti yang akan ditampilkan di Windows Explorer dimuat dengan memanggil metode _LoadProperties yang dilindungi, seperti yang diilustrasikan dalam contoh kode berikut. Proses ini diperiksa secara rinci di bagian berikutnya.

                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;
}

Jika aliran bersifat baca-saja tetapi parameter grfMode berisi bendera STGM_READWRITE, inisialisasi harus gagal dan mengembalikan STG_E_ACCESSDENIED. Tanpa pemeriksaan ini, Windows Explorer menunjukkan nilai properti sebagai dapat ditulis meskipun tidak, yang mengarah ke pengalaman pengguna akhir yang membingungkan.

Handler properti hanya diinisialisasi sekali dalam masa pakainya. Jika inisialisasi kedua diminta, handler harus mengembalikan HRESULT_FROM_WIN32(ERROR_ALREADY_INITIALIZED).

Penyimpanan Properti In-Memory

Sebelum melihat implementasi _LoadProperties, Anda harus memahami array PropertyMap yang digunakan dalam sampel untuk memetakan properti dalam dokumen XML ke properti yang ada dalam sistem properti melalui nilai PKEY mereka.

Anda tidak boleh mengekspos setiap elemen dan atribut dalam file XML sebagai properti. Sebaliknya, pilih hanya mereka yang Anda yakini akan berguna bagi pengguna akhir dalam organisasi dokumen mereka (dalam hal ini, resep). Ini adalah konsep penting yang perlu diingat saat Anda mengembangkan penangan properti Anda: perbedaan antara informasi yang benar-benar berguna untuk skenario organisasi, dan informasi yang termasuk dalam detail file Anda dan dapat dilihat dengan membuka file itu sendiri. Properti tidak dimaksudkan untuk menjadi duplikasi penuh dari file 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 },
};

Berikut adalah implementasi lengkap dari metode _LoadProperties yang disebut oleh 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;
}

Metode _LoadProperties memanggil fungsi pembantu Shell PSCreateMemoryPropertyStore untuk membuat penyimpanan properti dalam memori (cache) untuk properti yang ditangani. Dengan menggunakan cache, perubahan dilacak untuk Anda. Ini membebaskan Anda dari pelacakan apakah nilai properti telah diubah dalam cache tetapi belum disimpan ke penyimpanan yang bertahan. Ini juga membebaskan Anda dari nilai properti yang tidak perlu bertahan yang tidak berubah.

Metode _LoadProperties juga memanggil _LoadProperty yang implementasinya diilustrasikan dalam kode berikut) sekali untuk setiap properti yang dipetakan. _LoadProperty mendapatkan nilai properti seperti yang ditentukan dalam elemen PropertyMap di aliran XML dan menetapkannya ke cache dalam memori melalui panggilan ke IPropertyStoreCache::SetValueAndState. Bendera PSC_NORMAL dalam panggilan ke IPropertyStoreCache::SetValueAndState menunjukkan bahwa nilai properti belum diubah sejak waktu memasuki 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;
}

Berurusan dengan Nilai PROPVARIANT-Based

Dalam implementasi _LoadProperty, nilai properti disediakan dalam bentuk PROPVARIANT. Sekumpulan API dalam kit pengembangan perangkat lunak (SDK) disediakan untuk mengonversi dari jenis primitif seperti PWSTR atau int ke atau dari jenis PROPVARIANT . API ini ditemukan di Propvarutil.h.

Misalnya, untuk mengonversi PROPVARIANT menjadi string, Anda dapat menggunakan PropVariantToString seperti yang diilustrasikan di sini.

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

Untuk menginisialisasi PROPVARIANT dari string, Anda dapat menggunakan InitPropVariantFromString.

InitPropVariantFromString(PCWSTR psz, PROPVARIANT *ppropvar);

Seperti yang Anda lihat dalam salah satu file resep yang disertakan dalam sampel, mungkin ada lebih dari satu kata kunci di setiap file. Untuk mempertimbangan hal ini, sistem properti mendukung string multinilai yang direpresentasikan sebagai vektor string (misalnya "VT_VECTOR | VT_LPWSTR"). Metode _LoadVectorProperty dalam contoh menggunakan nilai berbasis vektor.

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;
}

Jika nilai tidak ada dalam file, jangan mengembalikan kesalahan. Sebagai gantinya, atur nilai ke VT_EMPTY dan kembalikan S_OK. VT_EMPTY menunjukkan bahwa nilai properti tidak ada.

Mendukung Metadata Terbuka

Contoh ini menggunakan format file berbasis XML. Skemanya dapat diperluas untuk mendukung properti yang tidak terpikirkan selama developmet, misalnya. Sistem ini dikenal sebagai metadata terbuka. Contoh ini memperluas sistem properti dengan membuat node di bawah elemen Recipe yang disebut ExtendedProperties, seperti yang diilustrasikan dalam contoh kode berikut.

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

Untuk memuat properti yang diperluas yang bertahan selama inisialisasi, terapkan metode _LoadExtendedProperties , seperti yang diilustrasikan dalam contoh kode berikut.

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);
                    }

API serialisasi yang dideklarasikan dalam Propsys.h digunakan untuk menserialisasikan dan mendeserialisasi jenis PROPVARIANT ke dalam blob data, lalu pengodean Base64 digunakan untuk menserialisasikan blob tersebut ke dalam string yang dapat disimpan di XML. String ini disimpan dalam atribut EncodedValue dari elemen ExtendedProperties . Metode utilitas berikut, yang diimplementasikan dalam file Util.cpp sampel, melakukan serialisasi. Ini dimulai dengan panggilan ke fungsi StgSerializePropVariant untuk melakukan serialisasi biner, seperti yang diilustrasikan dalam contoh kode berikut.

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

Selanjutnya, fungsi CryptBinaryToStringÂ, dinyatakan dalam Wincrypt.h, melakukan konversi 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>;}

Fungsi DeserializePropVariantFromString , juga ditemukan di Util.cpp, membalikkan operasi, mendeserialisasi nilai dari file XML.

Untuk informasi tentang dukungan untuk metadata terbuka, lihat "Jenis File yang Mendukung Metadata Terbuka" di Jenis File.

Isi Full-Text

Penangan properti juga dapat memfasilitasi pencarian teks lengkap konten file, dan mereka adalah cara mudah untuk menyediakan fungsionalitas tersebut jika format file tidak terlalu rumit. Ada cara alternatif yang lebih kuat untuk menyediakan teks lengkap file melalui implementasi antarmuka IFilter .

Tabel berikut ini meringkas manfaat dari setiap pendekatan menggunakan IFilter atau IPropertyStore.

Kemampuan IFilter IPropertyStore
Perbolehkan menulis kembali ke file? Tidak Ya
Menyediakan campuran konten dan properti? Ya Ya
Multibahasa? Ya Tidak
MIME/Tertanam? Ya Tidak
Batas teks? Kalimat, paragraf, bab Tidak ada
Implementasi yang didukung untuk SPS/SQL Server? Ya Tidak
Implementasi Kompleks Sederhana

 

Dalam sampel handler resep, format file resep tidak memiliki persyaratan yang kompleks, jadi hanya IPropertyStore yang telah diimplementasikan untuk dukungan teks lengkap. Pencarian teks lengkap diimplementasikan untuk simpul XML bernama dalam array berikut.

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

Sistem properti berisi System.Search.Contents properti (PKEY_Search_Contents), yang dibuat untuk menyediakan konten teks lengkap ke pengindeks. Nilai properti ini tidak pernah ditampilkan langsung di UI; teks dari semua node XML yang dinamai dalam array di atas digabungkan ke dalam satu string. String tersebut kemudian diberikan kepada pengindeks sebagai konten teks lengkap file resep melalui panggilan ke IPropertyStoreCache::SetValueAndState seperti yang diilustrasikan dalam contoh kode berikut.

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;}

Menyediakan Nilai untuk Properti

Saat digunakan untuk membaca nilai, handler properti biasanya dipanggil karena salah satu alasan berikut:

  • Untuk menghitung semua nilai properti.
  • Untuk mendapatkan nilai properti tertentu.

Untuk enumerasi, handler properti diminta untuk menghitung propertinya baik selama pengindeksan atau ketika kotak dialog properti meminta properti untuk ditampilkan di grup Lain . Pengindeksan berlangsung terus-menerus sebagai operasi latar belakang. Setiap kali file berubah, pengindeks diberi tahu, dan mengindeks ulang file dengan meminta handler properti untuk menghitung propertinya. Oleh karena itu, sangat penting bahwa penangan properti diimplementasikan secara efisien dan mengembalikan nilai properti secepat mungkin. Hitung semua properti yang Anda miliki nilainya, seperti yang Anda lakukan untuk koleksi apa pun, tetapi jangan menghitung properti yang melibatkan penghitungan intensif memori atau permintaan jaringan yang dapat membuatnya lambat untuk diambil.

Saat menulis handler properti, Anda biasanya perlu mempertimbangkan dua set properti berikut.

  • Properti utama: Properti yang didukung jenis file Anda secara asli. Misalnya, handler properti foto untuk metadata Exchangeable Image File (EXIF) secara asli mendukung System.Photo.FNumber.
  • Properti yang diperluas: Properti yang didukung jenis file Anda sebagai bagian dari metadata terbuka.

Karena sampel menggunakan cache dalam memori, menerapkan metode IPropertyStore hanyalah masalah mendelegasikan ke cache tersebut, seperti yang diilustrasikan dalam contoh kode berikut.

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); }

Jika Anda memilih untuk tidak mendelegasikan ke cache dalam memori, Anda harus menerapkan metode Anda untuk memberikan> perilaku yang diharapkan berikut:

Menulis Kembali Nilai

Ketika handler properti menulis nilai properti menggunakan IPropertyStore::SetValue, itu tidak menulis nilai ke file sampai IPropertyStore::Commit dipanggil. Cache dalam memori dapat berguna dalam menerapkan skema ini. Dalam kode sampel, implementasi IPropertyStore::SetValue hanya mengatur nilai baru dalam cache dalam memori dan mengatur status properti tersebut ke 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;
}

Dalam implementasi IPropertyStore apa pun, perilaku berikut diharapkan dari IPropertyStore::SetValue:

  • Jika properti sudah ada, nilai properti diatur.
  • Jika properti tidak ada, properti baru ditambahkan dan nilainya ditetapkan.
  • Jika nilai properti tidak dapat dipertahankan pada akurasi yang sama seperti yang diberikan (misalnya, pemotokan karena keterbatasan ukuran dalam format file), nilai diatur sejauh mungkin dan INPLACE_S_TRUNCATED dikembalikan.
  • Jika properti tidak didukung oleh handler properti, HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED) dikembalikan.
  • Jika ada alasan lain bahwa nilai properti tidak dapat diatur, seperti file yang dikunci atau kurangnya hak untuk diedit melalui daftar kontrol akses (ACL), maka STG_E_ACCESSDENIED dikembalikan.

Salah satu keuntungan utama menggunakan aliran, sebagai sampel, adalah keandalan. Handler properti harus selalu mempertimbangkan bahwa mereka tidak dapat meninggalkan file dalam keadaan tidak konsisten jika terjadi kegagalan bencana. Merusak file pengguna jelas harus dihindari, dan cara terbaik untuk melakukan ini adalah dengan mekanisme "copy-on-write". Jika handler properti Anda menggunakan aliran untuk mengakses file, Anda mendapatkan perilaku ini secara otomatis; sistem menulis perubahan apa pun pada aliran, mengganti file dengan salinan baru hanya selama operasi penerapan.

Untuk mengambil alih perilaku ini dan mengontrol proses penyimpanan file secara manual, Anda dapat memilih keluar dari perilaku penyimpanan yang aman dengan mengatur nilai ManualSafeSave di entri registri handler Anda seperti yang diilustrasikan di sini.

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

Ketika handler menentukan nilai ManualSafeSave, aliran yang diinisialisasi bukan aliran yang ditransaksikan (STGM_TRANSACTED). Handler itu sendiri harus menerapkan fungsi penyimpanan yang aman untuk memastikan bahwa file tidak rusak jika operasi penyimpanan terganggu. Jika handler menerapkan penulisan di tempat, handler akan menulis ke aliran yang diberikan. Jika handler tidak mendukung fitur ini, maka harus mengambil stream untuk menulis salinan file yang diperbarui menggunakan IDestinationStreamFactory::GetDestinationStream. Setelah handler selesai menulis, seharusnya memanggil IPropertyStore::Commit pada aliran asli untuk menyelesaikan operasi, dan mengganti konten stream asli dengan salinan baru file.

ManualSafeSave juga merupakan situasi default jika Anda tidak menginisialisasi handler Anda dengan streaming. Tanpa aliran asli untuk menerima konten aliran sementara, Anda harus menggunakan ReplaceFile untuk melakukan penggantian atom file sumber.

Format file besar yang akan digunakan dengan cara yang menghasilkan file yang lebih besar dari 1 MB harus menerapkan dukungan untuk penulisan properti di tempat; jika tidak, perilaku performa tidak memenuhi harapan klien sistem properti. Dalam skenario ini, waktu yang diperlukan untuk menulis properti tidak boleh dipengaruhi oleh ukuran file.

Untuk file yang sangat besar, misalnya file video 1 GB atau lebih, diperlukan solusi yang berbeda. Jika tidak ada cukup ruang dalam file untuk melakukan penulisan di tempat, handler mungkin gagal pembaruan properti jika jumlah ruang yang dicadangkan untuk penulisan properti di tempat telah habis. Kegagalan ini terjadi untuk menghindari performa buruk yang timbul dari 2 GB IO (1 untuk dibaca, 1 untuk menulis). Karena potensi kegagalan ini, format file ini harus mencadangkan cukup ruang untuk penulisan properti di tempat.

Jika file memiliki cukup ruang di header untuk menulis metadata, dan jika penulisan metadata tersebut tidak menyebabkan file bertambah atau menyusut, mungkin aman untuk menulis di tempat. Kami merekomendasikan 64 KB sebagai titik awal. Menulis di tempat setara dengan handler yang meminta ManualSafeSave dan memanggil IStream::Commit dalam implementasi IPropertyStore::Commit, dan memiliki performa yang jauh lebih baik daripada copy-on-write. Jika ukuran file berubah karena nilai properti berubah, penulisan di tempat tidak boleh dicoba karena potensi file yang rusak jika terjadi penghentian abnormal.

Catatan

Untuk alasan performa, kami menyarankan agar opsi ManualSafeSave digunakan dengan penangan properti yang bekerja dengan file berukuran 100 KB atau lebih besar.

 

Seperti yang diilustrasikan dalam contoh implementasi IPropertyStore::Commit berikut, handler untuk ManualSafeSave terdaftar untuk mengilustrasikan opsi penyimpanan aman manual. Metode _SaveCacheToDom menulis nilai properti yang disimpan dalam cache dalam memori ke objek 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);

Selanjutnya, tanyakan apakah pecified mendukung 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);_

Selanjutnya, terapkan aliran asli, yang menulis data kembali ke file asli dengan cara yang aman.

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

                            pStreamCommit->Release();
                        }

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

Kemudian periksa implementasi _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;  

Selanjutnya, dapatkan jumlah properti yang disimpan dalam cache dalam memori.

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

Sekarang ulangi melalui properti untuk menentukan apakah nilai properti telah diubah sejak dimuat ke dalam memori.

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

Metode IPropertyStoreCache::GetState mendapatkan status properti di cache. Bendera PSC_DIRTY, yang diatur dalam implementasi IPropertyStore::SetValue , menandai properti sebagai diubah.

        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); 

Petakan properti ke simpul XML seperti yang ditentukan dalam array 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;
                        }     

Jika properti tidak ada di peta, properti ini adalah properti baru yang ditetapkan oleh Windows Explorer. Karena metadata terbuka didukung, simpan properti baru di bagian ExtendedProperties xml.

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

                    PropVariantClear(&propvar);
                }
            }
        }
    }

    return hr;    

Menerapkan IPropertyStoreCapabilities

IPropertyStoreCapabilities menginformasikan UI Shell apakah properti tertentu dapat diedit di antarmuka pengguna Shell. Penting untuk dicatat bahwa ini hanya berkaitan dengan kemampuan untuk mengedit properti di UI, bukan apakah Anda dapat berhasil memanggil IPropertyStore::SetValue pada properti . Properti yang memprovokasi nilai pengembalian S_FALSE dari IPropertyStoreCapabilities::IsPropertyWritable mungkin masih mampu diatur melalui aplikasi.

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

IsPropertyWritable mengembalikan S_OK untuk menunjukkan bahwa pengguna akhir harus diizinkan untuk mengedit properti secara langsung; S_FALSE menunjukkan bahwa mereka seharusnya tidak. S_FALSE dapat berarti bahwa aplikasi bertanggung jawab untuk menulis properti, bukan pengguna. Shell menonaktifkan kontrol pengeditan yang sesuai berdasarkan hasil panggilan ke metode ini. Handler yang tidak mengimplementasikan IPropertyStoreCapabilities diasumsikan untuk mendukung metadata terbuka melalui dukungan untuk penulisan properti apa pun.

Jika Anda membangun handler yang hanya menangani properti baca-saja, maka Anda harus menerapkan metode Inisialisasi Anda (IInitializeWithStream, IInitializeWithItem, atau IInitializeWithFile) sehingga kembali STG_E_ACCESSDENIED saat dipanggil dengan bendera STGM_READWRITE.

Beberapa properti memiliki atribut isInnate yang diatur ke true. Properti bawaan memiliki karakteristik berikut:

  • Properti biasanya dihitung dalam beberapa cara. Misalnya, System.Image.BitDepth dihitung dari gambar itu sendiri.
  • Mengubah properti tidak akan masuk akal tanpa mengubah file. Misalnya, mengubah System.Image.Dimensions tidak akan mengubah ukuran gambar, sehingga tidak masuk akal untuk memungkinkan pengguna mengubahnya.
  • Dalam beberapa kasus, properti ini disediakan secara otomatis oleh sistem. Contohnya termasuk System.DateModified, yang disediakan oleh sistem file, dan System.SharedWith, yang didasarkan pada dengan siapa pengguna berbagi file.

Karena karakteristik ini, properti yang ditandai sebagai IsInnate diberikan kepada pengguna di antarmuka pengguna Shell hanya sebagai properti baca-saja. Jika properti ditandai sebagai IsInnate, sistem properti tidak menyimpan properti tersebut di handler properti. Oleh karena itu, penangan properti tidak memerlukan kode khusus untuk mempertangungjawabkan properti ini dalam implementasinya. Jika nilai atribut IsInnate tidak secara eksplisit dinyatakan untuk properti tertentu, nilai defaultnya adalah false.

Mendaftarkan dan Mendistribusikan Penangan Properti

Dengan handler properti yang diimplementasikan, itu harus didaftarkan dan ekstensi nama file yang terkait dengan handler. Untuk informasi selengkapnya, lihat Mendaftarkan dan Mendistribusikan Penangan Properti.

Memahami Handler Properti

Menggunakan Nama Jenis

Menggunakan Daftar Properti

Mendaftarkan dan Mendistribusikan Penangan Properti

Praktik Dan FAQ Terbaik Handler Properti