Bagikan melalui


Menginisialisasi Handler Properti

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

Topik ini diatur sebagai berikut:

Pengelola Properti

Pengelola 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 pengindeksan yang memengaruhi penerapan penangan properti, lihat Mengembangkan Penangan Properti untuk Windows Search.

Topik ini membahas contoh format file berbasis XML yang menjelaskan resep dengan ekstensi nama file .recipe. Ekstensi nama file .recipe terdaftar sebagai format file yang terpisah dan unik, bukan mengandalkan format file .xml yang lebih umum, yang pengelolanya 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 menciptakan abstraksi IPropertyStore untuk format file tertentu. Mereka mengurai dan menulis format file ini sesuai dengan spesifikasinya. Beberapa handler properti melakukan pekerjaan mereka berdasarkan API yang mengabstraksi 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.

Nota

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

 

Menginisialisasi Pengendali 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 data.
  • 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 Nilai Kembali 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 data tidak dimungkinkan. Dalam situasi tersebut, ada dua antarmuka lebih lanjut yang dapat diterapkan oleh handler properti: IInitializeWithFile dan IInitializeWithItem. Jika handler properti tidak mengimplementasikan IInitializeWithStream, itu harus memilih untuk tidak berjalan 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 menerapkan IInitializeWithStream dan melakukan inisialisasi berbasis aliran. Penangan properti Anda akan lebih aman dan lebih dapat diandalkan sebagai hasilnya. Menonaktifkan isolasi proses umumnya hanya ditujukan untuk penangan properti lama dan harus dihindari dengan keras 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 pointer ke instance IStream yang terkait dengan dokumen tersebut. Variabel _spDocEle yang digunakan di dekat akhir contoh kode didefinisikan sebelumnya dalam sampel sebagai MSXML2::IXMLDOMElementPtr.

Nota

Contoh kode berikut dan semua contoh kode berikutnya diambil dari contoh modul penangan resep yang terdapat 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 flag 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.

Pengendali properti hanya diinisialisasi sekali selama masa hidupnya. 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. Sebagai gantinya, pilih hanya mereka yang Anda yakini yang 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. Sebuah properti tidak dimaksudkan sebagai salinan lengkap 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 dipanggil 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 harus menyimpan nilai properti yang tidak berubah secara tidak perlu.

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

Menangani 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 mengonversiPROPVARIANTmenjadi string, Anda dapat menggunakanPropVariantToString 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 memperhitungkan hal ini, sistem properti mendukung string multinilai yang diwakili 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.

Dukungan untuk Metadata Terbuka

Contoh ini menggunakan format file berbasis XML. Skema ini dapat diperluas untuk mendukung properti yang tidak dipikirkan selama pengembangan, sebagai contoh. Sistem ini dikenal sebagai metadata terbuka. Contoh ini memperluas sistem properti dengan membuat simpul di bawah elemen Resep 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 dipertahankan 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 PROPVARIANT jenis ke dalam blob data, lalu pengodean Base64 digunakan untuk menserialisasikan blob tersebut ke dalam string yang dapat disimpan di XML. String-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 fungsiStgSerializePropVariant 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Â, yang 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 dengan 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 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-batas teks? Kalimat, paragraf, bab Tidak
Implementasi yang didukung untuk SPS/SQL Server? Ya Tidak
Pelaksanaan Kompleks Sederhana

 

Dalam sampel handler resep, format file resep tidak memiliki persyaratan kompleks, jadi hanya IPropertyStore yang telah diimplementasikan untuk dukungan teks lengkap. Pencarian teks lengkap diimplementasikan untuk simpul-simpul XML yang disebutkan 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 properti System.Search.Contents (PKEY_Search_Contents), yang dibuat untuk menyediakan konten teks lengkap ke pengindeks. Nilai properti ini tidak pernah ditampilkan langsung di UI; teks dari semua simpul XML bernama dalam array di atas digabungkan menjadi satu string. String tersebut kemudian disediakan untuk 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

Ketika 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 lainnya. 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 handler 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 diambil.

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

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

Karena sampel menggunakan cache dalam memori, menerapkan metode IPropertyStore hanya 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 implementasiIPropertyStore, 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, pemotongan 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 mengedit melalui daftar kontrol akses (ACL), maka STG_E_ACCESSDENIED dikembalikan.

Salah satu keuntungan utama menggunakan aliran, sebagai sampel, adalah keandalan. Pengelola properti harus selalu mempertimbangkan bahwa mereka tidak dapat meninggalkan file dalam keadaan tidak konsisten dalam kasus kegagalan yang bersifat 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 menolak 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 mengimplementasikan penulisan di tempat, handler tersebut akan menulis ke dalam aliran yang diberikan. Jika handler tidak mendukung fitur ini, maka harus mengambil aliran untuk menulis salinan file yang diperbarui menggunakan IDestinationStreamFactory::GetDestinationStream. Setelah handler selesai menulis, itu harus memanggil IPropertyStore::Commit pada aliran asli untuk menyelesaikan operasi, dan mengganti konten aliran asli dengan salinan baru file.

ManualSafeSave juga merupakan situasi default jika Anda tidak menginisialisasi handler Anda dengan aliran. Tanpa adanya stream utama yang dapat menerima konten dari stream sementara, Anda harus menggunakan ReplaceFile untuk melakukan penggantian file sumber secara atomik.

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 memperbarui 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 ditulis). Karena potensi kegagalan ini, format file ini harus mencadangkan cukup ruang untuk penulisan properti di tempat.

Jika file memiliki cukup ruang di headernya 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 secara langsung setara dengan handler yang meminta ManualSafeSave dan memanggil IStream::Commit dalam implementasi IPropertyStore::Commit, dan memiliki kinerja yang jauh lebih baik daripada copy-on-write. Jika ukuran file berubah karena perubahan nilai properti, penulisan langsung pada lokasi tidak boleh dicoba karena kemungkinan terjadinya kerusakan file jika terjadi penghentian tak terduga.

Nota

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

 

Seperti yang diilustrasikan dalam contoh implementasi IPropertyStore::Commit, 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 iterasi 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 dalam cache. Penanda PSC_DIRTY, yang diatur dalam implementasi IPropertyStore::SetValue, menandai properti telah 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 menghasilkan nilai pengembalian S_FALSE dari IPropertyStoreCapabilities::IsPropertyWritable mungkin masih dapat 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. Berdasarkan hasil panggilan ke metode ini, Shell menonaktifkan kontrol pengeditan yang sesuai. Handler yang tidak menerapkan 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 (IInitializeWithStream, IInitializeWithItem, atau IInitializeWithFile) sehingga mengembalikan STG_E_ACCESSDENIED saat dipanggil dengan bendera STGM_READWRITE.

Beberapa properti memiliki atribut isInnate yang diset menjadi benar. Properti bawaan memiliki karakteristik berikut:

  • Properti biasanya dihitung dengan 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 disediakan untuk 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 memperhitungkan properti ini dalam implementasinya. Jika nilai atribut IsInnate tidak secara eksplisit dinyatakan untuk properti tertentu, nilai defaultnya adalah false.

Mendaftar dan Mendistribusikan Pengelola Properti

Setelah handler properti diimplementasikan, itu harus terdaftar dan ekstensinya nama file harus dikaitkan dengan handler. Untuk informasi selengkapnya, lihat Mendaftarkan dan Mendistribusikan Pengelola Properti.

Memahami Pengelola Properti

Menggunakan Nama Jenis

Menggunakan Daftar Properti

Mendaftarkan dan Mendistribusikan Penanganan Properti

Praktik Terbaik Pengelola Properti dan Tanya Jawab Umum