본문 바로가기
C++

Windows 탐색기 상세 정보(Details) 표시용 속성 핸들러 DLL 개발 가이드

by leo21c 2026. 6. 11.

- 특정 파일 포맷의 **헤더 일부 내용**을 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 매핑 → 탐색기 표시까지 전 과정 수록. "파일 헤더 일부를 상세 정보에 표시"하는 본 목표와 거의 동일한 구조.

LIST