속성 처리기 초기화
이 항목에서는 Windows 속성 시스템에서 작동하도록 속성 처리기를 만들고 등록하는 방법을 설명합니다.
이 항목은 다음과 같이 구성됩니다.
- 속성 처리기
- 시작하기 전에
- 속성 처리기 초기화
- 메모리 내 속성 저장소
- PROPVARIANT-Based 값 처리
- Open Metadata 지원
- 전체 텍스트 내용
- 속성 값 제공
- 다시 쓰기 값
- IPropertyStoreCapabilities 구현
- 속성 처리기 등록 및 배포
- 관련 항목
속성 처리기
속성 처리기는 속성 시스템의 중요한 부분입니다. 인덱서가 속성 값을 읽고 인덱싱하기 위해 in-process로 호출되며 Windows Explorer in-process에서 호출되어 파일에서 직접 속성 값을 읽고 씁니다. 이러한 처리기는 성능 저하 또는 영향을 받는 파일의 데이터 손실을 방지하기 위해 신중하게 작성하고 테스트해야 합니다. 속성 처리기 구현에 영향을 주는 인덱서 관련 고려 사항에 대한 자세한 내용은 Windows Search용 속성 처리기 개발을 참조하세요.
이 항목에서는 .recipe 파일 이름 확장명을 사용하는 레시피를 설명하는 샘플 XML 기반 파일 형식에 대해 설명합니다. .recipe 파일 이름 확장명은 처리기가 보조 스트림을 사용하여 속성을 저장하는 보다 일반적인 .xml 파일 형식에 의존하지 않고 고유한 파일 형식으로 등록됩니다. 파일 형식에 대해 고유한 파일 이름 확장명을 등록하는 것이 좋습니다.
시작하기 전에
속성 처리기는 특정 파일 형식에 대한 IPropertyStore 추상화 를 만드는 COM 개체입니다. 해당 사양을 준수하는 방식으로 이 파일 형식을 읽고(구문 분석) 작성합니다. 일부 속성 처리기는 특정 파일 형식에 대한 액세스를 추상화하는 API를 기반으로 작업을 수행합니다. 파일 형식에 대한 속성 처리기를 개발하기 전에 파일 형식에서 속성을 저장하는 방법과 해당 속성(이름 및 값)이 속성 저장소 추상화에 매핑되는 방법을 이해해야 합니다.
구현을 계획할 때 속성 처리기는 Windows Explorer, Windows Search 인덱서 및 셸 항목 프로그래밍 모델을 사용하는 타사 애플리케이션과 같은 프로세스의 컨텍스트에서 로드되는 하위 수준 구성 요소입니다. 따라서 속성 처리기는 관리 코드에서 구현할 수 없으며 C++에서 구현되어야 합니다. 처리기가 API 또는 서비스를 사용하여 작업을 수행하는 경우 해당 서비스가 속성 처리기가 로드된 환경에서 제대로 작동할 수 있는지 확인해야 합니다.
참고
속성 처리기는 항상 특정 파일 형식과 연결됩니다. 따라서 파일 형식에 사용자 지정 속성 처리기가 필요한 속성이 포함된 경우 항상 각 파일 형식에 대해 고유한 파일 이름 확장명을 등록해야 합니다.
속성 처리기 초기화
시스템에서 속성을 사용하려면 먼저 IInitializeWithStream의 구현을 호출하여 초기화됩니다. 속성 처리기는 해당 할당을 처리기 구현에 맡기지 않고 시스템에서 스트림을 할당하도록 하여 초기화해야 합니다. 이 초기화 방법은 다음과 같은 사항을 보장합니다.
- 속성 처리기는 스트림을 통해 콘텐츠에 액세스하는 대신 파일을 직접 읽거나 쓸 수 있는 액세스 권한 없이 제한된 프로세스(중요한 보안 기능)에서 실행할 수 있습니다.
- 시스템을 신뢰하여 파일 oplock을 올바르게 처리할 수 있으며 이는 중요한 안정성 측정값입니다.
- 속성 시스템은 속성 처리기 구현에서 필요한 추가 기능 없이 자동 안전 저장 서비스를 제공합니다. 스트림에 대한 자세한 내용은 뒤로 값 쓰기 섹션을 참조하세요.
- IInitializeWithStream을 사용하면 파일 시스템 세부 정보에서 구현이 추상화됩니다. 이렇게 하면 처리기가 FTP(파일 전송 프로토콜) 폴더 또는 .zip 파일 이름 확장명을 가진 압축 파일과 같은 대체 스토리지를 통해 초기화를 지원할 수 있습니다.
스트림을 사용하여 초기화할 수 없는 경우가 있습니다. 이러한 상황에서는 속성 처리기가 구현할 수 있는 두 가지 인터페이스인 IInitializeWithFile 및 IInitializeWithItem이 있습니다. 속성 처리기가 IInitializeWithStream을 구현하지 않는 경우 스트림이 변경된 경우 시스템 인덱서가 기본적으로 배치하는 격리된 프로세스에서 실행을 옵트아웃해야 합니다. 이 기능을 옵트아웃하려면 다음 레지스트리 값을 설정합니다.
HKEY_CLASSES_ROOT
CLSID
{66742402-F9B9-11D1-A202-0000F81FEDEE}
DisableProcessIsolation = 1
그러나 IInitializeWithStream을 구현하고 스트림 기반 초기화를 수행하는 것이 훨씬 낫습니다. 따라서 속성 처리기가 더 안전하고 안정적입니다. 프로세스 격리를 사용하지 않도록 설정하는 것은 일반적으로 레거시 속성 처리기에만 사용되며 새 코드에서 강력하게 피해야 합니다.
속성 처리기의 구현을 자세히 검토하려면 IInitializeWithStream::Initialize의 구현인 다음 코드 예제를 참조하세요. 처리기는 해당 문서의 연결된 IStream instance 대한 포인터를 통해 XML 기반 레시피 문서를 로드하여 초기화됩니다. 코드 예제의 끝부분에서 사용되는 _spDocEle 변수는 샘플의 앞부분에서 MSXML2::IXMLDOMElementPtr로 정의됩니다.
참고
다음 및 모든 후속 코드 예제는 Windows 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;
}
Â
문서 자체가 로드되면 다음 코드 예제와 같이 보호된 _LoadProperties 메서드를 호출하여 Windows Explorer 표시할 속성이 로드됩니다. 이 프로세스는 다음 섹션에서 자세히 검토됩니다.
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;
}
스트림이 읽기 전용이지만 grfMode 매개 변수에 STGM_READWRITE 플래그가 포함된 경우 초기화가 실패하고 STG_E_ACCESSDENIED 반환해야 합니다. 이 검사 없으면 Windows Explorer 속성 값이 쓰기 가능으로 표시되어 최종 사용자 환경이 혼란스럽습니다.
속성 처리기는 수명 동안 한 번만 초기화됩니다. 두 번째 초기화가 요청되면 처리기는 를 반환 HRESULT_FROM_WIN32(ERROR_ALREADY_INITIALIZED)
해야 합니다.
In-Memory 속성 저장소
_LoadProperties 구현을 살펴보기 전에 샘플에서 XML 문서의 속성을 PKEY 값을 통해 속성 시스템의 기존 속성에 매핑하는 데 사용되는 PropertyMap 배열을 이해해야 합니다.
XML 파일의 모든 요소와 특성을 속성으로 노출해서는 안 됩니다. 대신 문서의 organization 최종 사용자에게 유용할 것으로 생각되는 항목만 선택합니다(이 경우 레시피). 이는 속성 처리기를 개발할 때 유의해야 할 중요한 개념입니다. 조직 시나리오에 실제로 유용한 정보와 파일의 세부 정보에 속하며 파일 자체를 열어 볼 수 있는 정보의 차이입니다. 속성은 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 },
};
다음은 IInitializeWithStream::Initialize에서 호출하는 _LoadProperties 메서드의 전체 구현입니다.
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;
}
_LoadProperties 메서드는 Shell 도우미 함수 PSCreateMemoryPropertyStore를 호출하여 처리된 속성에 대한 메모리 내 속성 저장소(캐시)를 만듭니다. 캐시를 사용하면 변경 내용이 추적됩니다. 이렇게 하면 속성 값이 캐시에서 변경되었지만 아직 지속형 스토리지에 저장되지 않은지 여부를 추적할 수 있습니다. 또한 변경되지 않은 속성 값을 불필요하게 유지하지 않도록 합니다.
또한 _LoadProperties 메서드는 매핑된 각 속성에 대해 구현이 다음 코드에 설명된 _LoadProperty)를 한 번 호출합니다. _LoadProperty XML 스트림의 PropertyMap 요소에 지정된 대로 속성 값을 가져오고 IPropertyStoreCache::SetValueAndState 호출을 통해 메모리 내 캐시에 할당합니다. IPropertyStoreCache::SetValueAndState 호출의 PSC_NORMAL 플래그는 캐시를 입력한 이후 속성 값이 변경되지 않음을 나타냅니다.
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;
}
PROPVARIANT-Based 값 처리
_LoadProperty 구현에서 속성 값은 PROPVARIANT 형식으로 제공됩니다. PWSTR 또는 int와 같은 기본 형식에서 PROPVARIANT 형식으로 또는 PROPVARIANT 형식으로 변환하기 위해 SDK(소프트웨어 개발 키트)의 API 집합이 제공됩니다. 이러한 API는 Propvarutil.h에서 찾을 수 있습니다.
예를 들어 PROPVARIANT 를 문자열로 변환하려면 여기에 설명된 대로 PropVariantToString을 사용할 수 있습니다.
PropVariantToString(REFPROPVARIANT propvar, PWSTR psz, UINT cch);
문자열에서 PROPVARIANT를 초기화하려면 InitPropVariantFromString을 사용할 수 있습니다.
InitPropVariantFromString(PCWSTR psz, PROPVARIANT *ppropvar);
샘플에 포함된 레시피 파일에서 볼 수 있듯이 각 파일에 둘 이상의 키워드(keyword) 있을 수 있습니다. 이를 설명하기 위해 속성 시스템은 문자열의 벡터로 표시되는 다중 값 문자열을 지원합니다(instance "VT_VECTOR | VT_LPWSTR"). 예제의 _LoadVectorProperty 메서드는 벡터 기반 값을 사용합니다.
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;
}
파일에 값이 없으면 오류를 반환하지 마세요. 대신 값을 VT_EMPTY 설정하고 S_OK 반환합니다. VT_EMPTY 속성 값이 없음을 나타냅니다.
Open Metadata 지원
이 예제에서는 XML 기반 파일 형식을 사용합니다. 예를 들어, 스키마를 확장하여 개발 중에는 생각하지 못한 속성을 지원할 수 있습니다. 이 시스템을 개방형 메타데이터라고 합니다. 이 예제에서는 다음 코드 예제와 같이 ExtendedProperties라는 Recipe 요소 아래에 노드를 만들어 속성 시스템을 확장합니다.
<ExtendedProperties>
<Property
Name="{65A98875-3C80-40AB-ABBC-EFDAF77DBEE2}, 100"
EncodedValue="HJKHJDHKJHK"/>
</ExtendedProperties>
초기화 중에 지속형 확장 속성을 로드하려면 다음 코드 예제와 같이 _LoadExtendedProperties 메서드를 구현합니다.
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);
}
Propsys.h에 선언된 Serialization API는 PROPVARIANT 형식을 데이터 Blob으로 직렬화 및 역직렬화하는 데 사용되며, Base64 인코딩을 사용하여 해당 Blob을 XML에 저장할 수 있는 문자열로 직렬화합니다. 이러한 문자열은 ExtendedProperties 요소의 EncodedValue 특성에 저장됩니다. 샘플의 Util.cpp 파일에 구현된 다음 유틸리티 메서드는 serialization을 수행합니다. 다음 코드 예제와 같이 StgSerializePropVariant 함수를 호출하여 이진 serialization을 수행하는 것으로 시작합니다.
HRESULT SerializePropVariantAsString(const PROPVARIANT *ppropvar, PWSTR *pszOut)
{
SERIALIZEDPROPERTYVALUE *pBlob;
ULONG cbBlob;
HRESULT hr = StgSerializePropVariant(ppropvar, &pBlob, &cbBlob);
다음으로, Wincrypt.h에서 선언된 CryptBinaryToString 함수는 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>;}
Util.cpp에도 있는 DeserializePropVariantFromString 함수는 XML 파일에서 값을 역직렬화하여 작업을 반대로 합니다.
열려 있는 메타데이터 지원에 대한 자세한 내용은 파일 형식의 "열기 메타데이터를 지원하는 파일 형식"을 참조하세요.
Full-Text 콘텐츠
속성 처리기는 파일 내용의 전체 텍스트 검색을 용이하게 할 수도 있으며 파일 형식이 지나치게 복잡하지 않은 경우 해당 기능을 쉽게 제공할 수 있습니다. IFilter 인터페이스 구현을 통해 파일의 전체 텍스트를 제공하는 보다 강력한 대안이 있습니다.
다음 표에서는 IFilter 또는 IPropertyStore를 사용하여 각 방법의 이점을 요약합니다.
기능 | IFilter | IPropertyStore |
---|---|---|
파일에 다시 쓸 수 있나요? | 아니요 | 예 |
콘텐츠와 속성의 혼합을 제공합니까? | Yes | Yes |
다국어? | Yes | 아니요 |
MIME/Embedded? | Yes | 아니요 |
텍스트 경계? | 문장, 단락, 장 | 없음 |
SPS/SQL Server 지원되는 구현 | Yes | 아니요 |
구현 | 복합 | 단순 |
레시피 처리기 샘플에서 레시피 파일 형식에는 복잡한 요구 사항이 없으므로 전체 텍스트 지원을 위해 IPropertyStore 만 구현되었습니다. 전체 텍스트 검색은 다음 배열에서 명명된 XML 노드에 대해 구현됩니다.
const PWSTR c_rgszContentXPath[] = {
L"Recipe/Ingredients/Item",
L"Recipe/Directions/Step",
L"Recipe/RecipeInfo/Yield",
L"Recipe/RecipeKeywords/Keyword",
};
속성 시스템에는 인덱서에 System.Search.Contents
전체 텍스트 콘텐츠를 제공하기 위해 만들어진 (PKEY_Search_Contents) 속성이 포함되어 있습니다. 이 속성의 값은 UI에 직접 표시되지 않습니다. 위의 배열에 명명된 모든 XML 노드의 텍스트가 단일 문자열로 연결됩니다. 그런 다음 이 문자열은 다음 코드 예제와 같이 IPropertyStoreCache::SetValueAndState 호출을 통해 레시피 파일의 전체 텍스트 콘텐츠로 인덱서에 제공됩니다.
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;}
속성 값 제공
값을 읽는 데 사용되는 경우 속성 처리기는 일반적으로 다음 이유 중 하나로 호출됩니다.
- 모든 속성 값을 열거하려면
- 특정 속성의 값을 가져옵니다.
열거형의 경우 속성 처리기는 인덱싱하는 동안 또는 속성 대화 상자에서 기타 그룹에 표시할 속성을 요청할 때 해당 속성을 열거하도록 요청합니다. 인덱싱은 백그라운드 작업으로 계속 진행됩니다. 파일이 변경될 때마다 인덱서에 알림이 표시되고 속성 처리기에 해당 속성을 열거하도록 요청하여 파일을 다시 인덱싱합니다. 따라서 속성 처리기를 효율적으로 구현하고 가능한 한 빨리 속성 값을 반환하는 것이 중요합니다. 모든 컬렉션과 마찬가지로 값이 있는 모든 속성을 열거하지만 메모리 집약적 계산 또는 검색 속도가 느려질 수 있는 네트워크 요청을 포함하는 속성을 열거하지 않습니다.
속성 처리기를 작성할 때 일반적으로 다음 두 가지 속성 집합을 고려해야 합니다.
- 기본 속성: 파일 형식이 기본적으로 지원하는 속성입니다. 예를 들어 EXIF(Exchangeable Image File) 메타데이터의 사진 속성 처리기는 기본적으로 를 지원합니다
System.Photo.FNumber
. - 확장 속성: 파일 형식이 열려 있는 메타데이터의 일부로 지원하는 속성입니다.
샘플은 메모리 내 캐시를 사용하기 때문에 다음 코드 예제와 같이 IPropertyStore 메서드를 구현하는 것은 해당 캐시를 위임하는 문제일 뿐입니다.
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); }
메모리 내 캐시에 위임하지 않도록 선택하는 경우 메서드를 구현하여 다음과 같은 예상 동작을 제공해야> 합니다.
- IPropertyStore::GetCount: 속성이 없으면 이 메서드는 S_OK 반환합니다.
- IPropertyStore::GetAt: iProp 이 cProps보다 크거나 같으면 이 메서드는 E_INVALIDARG 반환하고 pkey 매개 변수가 가리키는 구조체는 0으로 채워집니다.
- IPropertyStore::GetCount 및 IPropertyStore::GetAt은 속성 처리기의 현재 상태를 반영합니다. PROPERTYKEY가 IPropertyStore::SetValue를 통해 파일에 추가되거나 제거되는 경우 이 두 메서드는 다음에 호출될 때 해당 변경 내용을 반영해야 합니다.
- IPropertyStore::GetValue: 이 메서드가 존재하지 않는 값을 묻는 메시지가 표시되면 값이 VT_EMPTY 보고된 S_OK 반환합니다.
뒤로 값 쓰기
속성 처리기가 IPropertyStore::SetValue를 사용하여 속성 값을 쓰는 경우 IPropertyStore::Commit 이 호출될 때까지 파일에 값을 쓰지 않습니다. 메모리 내 캐시는 이 체계를 구현하는 데 유용할 수 있습니다. 샘플 코드에서 IPropertyStore::SetValue 구현은 단순히 메모리 내 캐시의 새 값을 설정하고 해당 속성의 상태를 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;
}
모든 IPropertyStore 구현에서는 IPropertyStore::SetValue에서 다음 동작이 필요합니다.
- 속성이 이미 있는 경우 속성 값이 설정됩니다.
- 속성이 없으면 새 속성이 추가되고 해당 값이 설정됩니다.
- 속성 값을 지정된 것과 동일한 정확도로 유지할 수 없는 경우(instance 경우 파일 형식의 크기 제한으로 인해 잘림) 값은 가능한 한 설정되고 INPLACE_S_TRUNCATED 반환됩니다.
- 속성 처리기
HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)
에서 속성이 지원되지 않는 경우 가 반환됩니다. - 파일이 잠겨 있거나 ACL(액세스 제어 목록)을 통해 편집할 수 있는 권한이 없는 등 속성 값을 설정할 수 없는 또 다른 이유가 있는 경우 STG_E_ACCESSDENIED 반환됩니다.
스트림을 샘플로 사용할 때의 주요 이점 중 하나는 안정성입니다. 속성 처리기는 치명적인 오류가 발생할 경우 파일을 일관되지 않은 상태로 둘 수 없다는 점을 항상 고려해야 합니다. 사용자의 파일 손상은 분명히 피해야 하며 이 작업을 수행하는 가장 좋은 방법은 "쓰기에 복사" 메커니즘을 사용하는 것입니다. 속성 처리기가 스트림을 사용하여 파일에 액세스하는 경우 이 동작이 자동으로 수행됩니다. 시스템은 스트림에 변경 내용을 기록하여 커밋 작업 중에만 파일을 새 복사본으로 바꿉니다.
이 동작을 재정의하고 파일 저장 프로세스를 수동으로 제어하려면 여기에 설명된 대로 처리기의 레지스트리 항목에서 ManualSafeSave 값을 설정하여 안전한 저장 동작을 옵트아웃할 수 있습니다.
HKEY_CLASSES_ROOT
CLSID
{66742402-F9B9-11D1-A202-0000F81FEDEE}
ManualSafeSave = 1
처리기가 ManualSafeSave 값을 지정하면 초기화되는 스트림은 트랜잭션된 스트림(STGM_TRANSACTED)이 아닙니다. 처리기 자체는 저장 작업이 중단된 경우 파일이 손상되지 않도록 안전 저장 함수를 구현해야 합니다. 처리기가 현재 위치 쓰기를 구현하는 경우 제공된 스트림에 씁니다. 처리기가 이 기능을 지원하지 않는 경우 IDestinationStreamFactory::GetDestinationStream을 사용하여 파일의 업데이트된 복사본을 작성할 스트림을 검색해야 합니다. 처리기 쓰기가 완료되면 원래 스트림에서 IPropertyStore::Commit 을 호출하여 작업을 완료하고 원래 스트림의 내용을 파일의 새 복사본으로 바꿔야 합니다.
Stream으로 처리기를 초기화하지 않으면 ManualSafeSave도 기본 상황입니다. 임시 스트림의 콘텐츠를 받을 원본 스트림이 없으면 ReplaceFile 을 사용하여 원본 파일의 원자성 대체를 수행해야 합니다.
1MB보다 큰 파일을 생성하는 방식으로 사용되는 대용량 파일 형식은 현재 위치 속성 쓰기에 대한 지원을 구현해야 합니다. 그렇지 않으면 성능 동작이 속성 시스템의 클라이언트에 대한 기대치를 충족하지 않습니다. 이 시나리오에서는 속성을 작성하는 데 필요한 시간이 파일 크기의 영향을 받지 않아야 합니다.
매우 큰 파일(예: 1GB 이상의 비디오 파일)의 경우 다른 솔루션이 필요합니다. 파일에서 현재 위치 쓰기를 수행할 공간이 충분하지 않은 경우 현재 위치 속성 쓰기를 위해 예약된 공간의 양이 모두 소진된 경우 처리기가 속성 업데이트에 실패할 수 있습니다. 이 오류는 2GB의 IO(읽기 1개, 쓰기 1개)에서 발생하는 성능 저하를 방지하기 위해 발생합니다. 이러한 잠재적 오류로 인해 이러한 파일 형식은 현재 위치 속성 쓰기에 충분한 공간을 예약해야 합니다.
파일에 메타데이터를 작성할 수 있는 충분한 공간이 있고 해당 메타데이터를 작성해도 파일이 증가하거나 축소되지 않는 경우 바로 작성하는 것이 안전할 수 있습니다. 시작점으로 64KB를 사용하는 것이 좋습니다. 바로 쓰기는 IPropertyStore::Commit 구현에서 ManualSafeSave를 요청하고 IStream::Commit을 호출하는 처리기와 동일하며 쓰기에 복사하는 것보다 훨씬 더 나은 성능을 줍니다. 속성 값 변경으로 인해 파일 크기가 변경되는 경우 비정상적인 종료 시 손상된 파일이 발생할 수 있으므로 현재 위치에 쓰기를 시도해서는 안 됩니다.
참고
성능상의 이유로 ManualSafeSave 옵션을 100KB 이상의 파일로 작업하는 속성 처리기와 함께 사용하는 것이 좋습니다.
IPropertyStore::Commit의 다음 샘플 구현에 설명된 것처럼 ManualSafeSave에 대한 처리기는 수동 안전 저장 옵션을 설명하기 위해 등록됩니다. _SaveCacheToDom 메서드는 메모리 내 캐시에 저장된 속성 값을 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);
다음으로, pecified가 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);_
다음으로 데이터를 안전한 방식으로 원래 파일에 다시 쓰는 원래 스트림을 커밋합니다.
if (SUCCEEDED(hr))
{
// Commit the real output stream.
_pStream->Commit(STGC_DEFAULT);
}
}
pStreamCommit->Release();
}
pSafeCommit->Release();
}
}
}
}
}
그런 다음 _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;
다음으로 메모리 내 캐시에 저장된 속성 수를 가져옵니다.
HRESULT hr = _pCache->GetCount(&cProps);
이제 속성을 반복하여 속성 값이 메모리에 로드된 이후 변경되었는지 여부를 확인합니다.
for (UINT i = 0; SUCCEEDED(hr) && (i < cProps); ++i)
{
PROPERTYKEY key;
hr = _pCache->GetAt(i, &key);
IPropertyStoreCache::GetState 메서드는 캐시에 있는 속성의 상태를 가져옵니다. IPropertyStore::SetValue 구현에서 설정된 PSC_DIRTY 플래그는 속성을 변경된 것으로 표시합니다.
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);
eg_rgPropertyMap 배열에 지정된 대로 속성을 XML 노드에 매핑합니다.
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;
}
속성이 맵에 없는 경우 Windows Explorer 설정한 새 속성입니다. 열려 있는 메타데이터가 지원되므로 XML의 ExtendedProperties 섹션에 새 속성을 저장합니다.
// Otherwise, save as an extended property.
if (!fIsNativeProperty)
{
hr = _SaveExtendedProperty(key, propvar);
}
PropVariantClear(&propvar);
}
}
}
}
return hr;
IPropertyStoreCapabilities 구현
IPropertyStoreCapabilities 는 셸 UI에서 특정 속성을 편집할 수 있는지 여부를 셸 UI에 알립니다. 이는 속성에서 IPropertyStore::SetValue 를 성공적으로 호출할 수 있는지 여부가 아니라 UI에서 속성을 편집하는 기능과만 관련이 있다는 점에 유의해야 합니다. IPropertyStoreCapabilities::IsPropertyWritable에서 S_FALSE 반환 값을 유발하는 속성은 여전히 애플리케이션을 통해 설정할 수 있습니다.
interface IPropertyStoreCapabilities : IUnknown
{
HRESULT IsPropertyWritable([in] REFPROPERTYKEY key);
}
IsPropertyWritable은 최종 사용자가 속성을 직접 편집할 수 있어야 함을 나타내는 S_OK 반환합니다. S_FALSE 해서는 안 됨을 나타냅니다. S_FALSE 애플리케이션이 사용자가 아닌 속성을 작성해야 한다는 것을 의미할 수 있습니다. 셸은 이 메서드에 대한 호출 결과에 따라 편집 컨트롤을 적절하게 사용하지 않도록 설정합니다. IPropertyStoreCapabilities를 구현하지 않는 처리기는 속성 작성 지원을 통해 열린 메타데이터를 지원하는 것으로 간주됩니다.
읽기 전용 속성만 처리하는 처리기를 빌드하는 경우 Initialize 메서드(IInitializeWithStream, IInitializeWithItem 또는 IInitializeWithFile)를 구현하여 STGM_READWRITE 플래그를 사용하여 호출할 때 STG_E_ACCESSDENIED 반환해야 합니다.
일부 속성에는 isInnate 특성이 true로 설정되어 있습니다. 타고난 속성에는 다음과 같은 특성이 있습니다.
- 속성은 일반적으로 어떤 식으로든 계산됩니다. instance
System.Image.BitDepth
경우 는 이미지 자체에서 계산됩니다. - 파일을 변경하지 않으면 속성을 변경하는 것은 의미가 없습니다. instance 경우 변경
System.Image.Dimensions
하면 이미지의 크기가 조정되지 않으므로 사용자가 변경하도록 허용하는 것은 의미가 없습니다. - 경우에 따라 이러한 속성은 시스템에서 자동으로 제공됩니다. 예를 들어
System.DateModified
파일 시스템에서 제공하는 및System.SharedWith
사용자가 파일을 공유하는 사용자를 기반으로 하는 가 있습니다.
이러한 특성으로 인해 IsInnate 로 표시된 속성은 셸 UI의 사용자에게 읽기 전용 속성으로만 제공됩니다. 속성이 IsInnate로 표시되면 속성 시스템은 속성 처리기에 해당 속성을 저장하지 않습니다. 따라서 속성 처리기는 구현에서 이러한 속성을 설명하기 위해 특수 코드가 필요하지 않습니다. IsInnate 특성의 값이 특정 속성에 대해 명시적으로 명시되지 않은 경우 기본값은 false입니다.
속성 처리기 등록 및 배포
구현된 속성 처리기를 사용하여 등록해야 하며 처리기와 연결된 파일 이름 확장명이어야 합니다. 자세한 내용은 속성 처리기 등록 및 배포를 참조하세요.
관련 항목