- 특정 파일 포맷의 **헤더 일부 내용**을 Windows 탐색기의 **상세 정보 창 / 열(Details) / 풍선 도움말(Infotip) / 검색**에 노출하는 DLL을 제작하기 위한 자료.
- 출처: Microsoft Learn (Win32 Property System, Shell Extensions) 공식 문서. 조사일: 2026-06-11.
---
## 1. 결론 — 무슨 기술인가
Windows Vista 이후 Windows에는 **확장 가능한 속성 시스템(Property System)** 이 내장되어 있다. 탐색기 상세 정보 창·열, 풍선 도움말(infotip), Windows Search 인덱서가 모두 이 시스템을 통해 파일 메타데이터를 읽는다.
내가 소유한 **고유 파일 포맷**의 헤더 내용을 탐색기에 노출하려면 → 그 파일 형식 전용 **속성 핸들러(Property Handler)** 라는 **셸 확장(Shell Extension) DLL** 을 직접 만들어 등록하면 된다.
```
[파일.myext] --(IStream)--> [Property Handler DLL] --(IPropertyStore)--> [탐색기 상세정보 / 검색 인덱서]
```
### ⚠️ 핵심 제약 (MS 명시)
- **반드시 C++(네이티브)로 작성.** 탐색기·검색 인덱서 프로세스 내부에 로드되므로 **관리 코드(.NET / C#) 불가.**
- → 본 ETIMS 프로젝트(C++/MFC, VS2017 v141) 환경과 잘 맞음.
- 속성 핸들러는 **속도가 생명**이다. 탐색기는 폴더당 수백~수만 개 파일에 대해 호출할 수 있다.
---
## 2. 구현해야 하는 COM 인터페이스
속성 핸들러는 다음 인터페이스를 구현하는 **in-proc(DLL) COM 객체**다.
| 인터페이스 | 헤더 | 역할 | 필수 |
| IInitializeWithStream | propsys.h | 파일을 **스트림(IStream)** 으로 받아 초기화 (권장 방식) | 권장 (사실상 필수) |
| IPropertyStore | propsys.h | 속성 열거/읽기/쓰기 | **필수** |
| IPropertyStoreCapabilities | propsys.h | 속성의 UI 편집 가능 여부 제어 | 선택 |
| IInitializeWithFile / IInitializeWithItem |
propsys.h / shobjidl_core.h |
스트림 초기화가 불가능할 때의 대안 | 대안 |
### 2.1 `IPropertyStore` 메서드
| 메서드 | 설명 |
| GetCount | 항목에 붙은 속성 개수 반환 |
| GetAt | 인덱스로 PROPERTYKEY 반환 |
| GetValue | 특정 속성의 값(PROPVARIANT) 반환 ← **상세정보 표시의 핵심** |
| SetValue | 속성 값 설정/교체/제거 (메모리 캐시에만 반영) |
| Commit | 변경 값을 파일에 실제 저장 |
### 2.2 읽기 전용 핸들러로 만들기 (헤더 값 표시만 할 경우)
헤더 정보를 **표시만** 하고 편집은 막으려면:
- `Initialize`가 `STGM_READWRITE` 모드로 호출되면 → `STG_E_ACCESSDENIED` 반환
- `IPropertyStoreCapabilities::IsPropertyWritable`에서 → `S_FALSE` 반환 (탐색기가 편집 컨트롤 비활성화)
- `SetValue` / `Commit`은 적절한 에러(`HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)`) 반환
---
## 3. 전형적인 구현 흐름
```cpp
// 1) 초기화: 파일 전체가 아니라 헤더만 읽는다 (성능!)
IInitializeWithStream::Initialize(IStream* pStream, DWORD grfMode)
{
if (grfMode & STGM_READWRITE) return STG_E_ACCESSDENIED; // 읽기 전용
// pStream->Seek / Read 로 파일 헤더 영역만 파싱
return _LoadProperties(pStream);
}
// 2) 인메모리 캐시에 헤더 값을 PKEY로 매핑하여 채움
HRESULT _LoadProperties(IStream* pStream)
{
PSCreateMemoryPropertyStore(IID_PPV_ARGS(&_pCache)); // 캐시 생성
// 헤더 파싱 결과를 PROPVARIANT로 변환
PROPVARIANT pv;
InitPropVariantFromString(L"...", &pv);
_pCache->SetValueAndState(PKEY_Title, &pv, PSC_NORMAL); // 매핑
PropVariantClear(&pv);
// ... 필요한 헤더 필드 반복
}
// 3) 탐색기가 값을 읽어감 → 캐시에 위임
IPropertyStore::GetValue(REFPROPERTYKEY key, PROPVARIANT* pv)
{
return _pCache->GetValue(key, pv);
}
```
### 권장 사항
- `PSCreateMemoryPropertyStore`로 **인메모리 캐시(IPropertyStoreCache)** 를 만들면 dirty 추적·저장이 자동화된다.
- **모든 헤더 요소를 속성으로 노출하지 말 것.** 사용자가 파일을 *정리/분류*하는 데 유용한 핵심 값만 선별 (속성은 파일 내용의 완전 복제가 아니다).
---
## 4. 표시할 속성(PROPERTYKEY) 선택
### 4.1 기존 시스템 속성 재사용 (간단)
`propkey.h`에 정의된 표준 속성을 그대로 쓰면 별도 스키마 등록이 불필요하다.
- `System.Title`, `System.Author`, `System.Comment`, `System.Subject`
- `System.DateModified`, `System.Keywords` 등
### 4.2 포맷 고유 커스텀 속성 (사용자 정의 열)
포맷 전용 속성이 필요하면 **`.propdesc` XML 스키마**를 작성해 등록한다.
- `PSRegisterPropertySchema` API 또는 설치 시 등록
- 사용자 정의 이름의 탐색기 열로 표시 가능 (예: `Mycompany.DeviceSerial`)
---
## 5. 등록 (레지스트리)
### 5.1 COM + 확장자 연결 (MS 공식 `.recipe` 예시 기준)
```
HKEY_CLASSES_ROOT
CLSID
{내가-생성한-GUID}
(Default) = "MyFormat Property Handler"
ManualSafeSave [REG_DWORD] = 1 ; 인플레이스 쓰기 시에만
InProcServer32
(Default) = C:\...\MyHandler.dll
ThreadingModel = Both ; 권장 (또는 Apartment)
HKEY_LOCAL_MACHINE
SOFTWARE\Microsoft\Windows\CurrentVersion\PropertySystem\PropertyHandlers
.myext
(Default) = {내가-생성한-GUID}
```
### 5.2 풍선 도움말(Infotip)·상세 보기 노출
ProgID 키에 표시할 속성을 지정한다 (`prop:` 접두사 필수).
```
HKEY_CLASSES_ROOT
<ProgID>
InfoTip = prop:System.Title;System.Author;System.Comment
PreviewDetails = prop:System.Title;System.Author
FullDetails = prop:System.Title;System.Author;System.Comment
```
### 5.3 프로세스 격리 옵트아웃 (비권장)
`IInitializeWithStream`을 구현하지 않으면 인덱서 격리 프로세스에서 동작 불가 → 다음 설정 필요(신규 코드에서는 **강력 비권장**):
```
HKEY_CLASSES_ROOT\CLSID\{GUID}\DisableProcessIsolation = 1
```
---
## 6. 성능·신뢰성 가이드라인 (MS 강조)
| 항목 | 지침 |
|---|---|
| **속성 열거 속도** | 매우 빨라야 함. 네트워크 조회·대용량 계산·파일 외 리소스 탐색 금지 |
| **부분 읽기** | 수백 KB 이상 파일은 **헤더만** seek/read. 전체를 메모리에 올리면 탐색기/인덱서 작업세트가 부풀어 오름 |
| **스레딩 모델** | `Both` 권장 — STA(탐색기) / MTA(SearchProtocolHost) 모두에서 마샬링 오버헤드 회피 |
| **동시성** | 호출은 직렬이지만 스레드는 다를 수 있음 → `CriticalSection`(또는 `TryEnterCriticalSection`)으로 보호 |
| **인플레이스 쓰기** | 헤더에 여유 공간을 두고 `ManualSafeSave` + `IDestinationStreamFactory`로 안전 저장. 쓰기 중 크래시 시 파일 손상 방지를 위해 철저히 테스트 |
### 파일 공유 모드 (GETPROPERTYSTOREFLAGS)
| 접근 모드 | 공유 모드 |
|---|---|
| Write | 다른 reader/writer 모두 거부 |
| Write (`EnableShareDenyWrite`) | reader 허용, writer 거부 |
| Read-only (기본) | reader 허용, writer 거부 |
| Read-only (`EnableShareDenyNone`) | reader/writer 모두 허용 |
---
## 7. 검증 도구
- **File Type Verifier** (Windows 7 SDK 포함): 파일 형식 핸들러가 올바르게 등록·구현되었는지 검사.
- 검사 항목: Preview / Thumbnail / **Property Handler** / Verb / IFilter / Kind / Perceived Type / 중요 속성
- **재등록 후 캐시 갱신**: 등록 변경이 탐색기에 즉시 반영 안 되면 → 탐색기 재시작 또는 `explorer.exe` 종료/재기동. 인덱서 변경은 재인덱싱 필요.
---
## 8. 개발 자료 위치 (Microsoft Learn 공식)
| 주제 | URL |
|---|---|
| **속성 핸들러 개발 개요 (시작점)** | https://learn.microsoft.com/windows/win32/properties/building-property-handlers |
| 핸들러 초기화 + 샘플 코드 | https://learn.microsoft.com/windows/win32/properties/building-property-handlers-property-handlers |
| **등록·배포 방법 (레지스트리 예시)** | https://learn.microsoft.com/windows/win32/properties/prophand-reg-dist |
| 검색용 속성 핸들러 개발 | https://learn.microsoft.com/windows/win32/search/-search-3x-wds-extidx-propertyhandlers |
| 셸 확장 전체 개요 | https://learn.microsoft.com/windows/win32/shell/shell-exts |
| 셸 확장 핸들러 만들기 | https://learn.microsoft.com/windows/win32/shell/handlers |
| File Type Verifier | https://learn.microsoft.com/windows/win32/shell/file-type-verifier |
| 미디어 파일용 커스텀 메타데이터 공급자(실전 예시) | https://learn.microsoft.com/windows/win32/medfound/custom-metadata-providers-for-media-files |
| `IInitializeWithStream::Initialize` | https://learn.microsoft.com/windows/win32/api/propsys/nf-propsys-iinitializewithstream-initialize |
| `IPropertyStore` | https://learn.microsoft.com/windows/win32/api/propsys/nn-propsys-ipropertystore |
### 샘플 코드 (가장 완전한 레퍼런스)
- **RecipePropertyHandler** — `.recipe` 확장자 예제.
- Windows SDK 및 GitHub `microsoft/Windows-classic-samples` 에 포함.
- XML 헤더 파싱 → PKEY 매핑 → 탐색기 표시까지 전 과정 수록. "파일 헤더 일부를 상세 정보에 표시"하는 본 목표와 거의 동일한 구조.
'C++' 카테고리의 다른 글
| ConvertBSTRToString 함수 메모리 누수 문제 (0) | 2023.09.14 |
|---|---|
| safearray 값을 SafeArrayAccessData 이용해서 읽어 오는 방법 (0) | 2023.09.07 |
| ProcessID로 Handle 찾기 및 WM_COPYDATA 처리 문제 (0) | 2021.04.12 |
| std::map에서 char array를 key로 사용하기 (0) | 2021.04.08 |
| memcmp 바이트 Array 비교를 할 때 사용 (0) | 2021.03.31 |