코딩캣: 코딩하는 고양이.
Windows 쉘 익스텐션 개발 가이드 - (1) 튜토리얼 (1/2)
API/COM
2021. 1. 16. 23:07

입문자를 위한 Windows Shell Extension 개발 가이드

본 게시물은 ‘codeproject.com’에 게시된 “The Complete Idiot's Guide to Writing Shell Extensions” 시리즈를 우리말로 번역한 것입니다.

원문의 주소는 “https://www.codeproject.com/script/Articles/MemberArticles.aspx?amid=152”입니다. 원문은 2000년에 작성되었지만 네이티브 수준에서 Windows 운영체제가 근본적으로 바뀌지 않는 이상 현재에도 여전히 유효한 내용입니다. 다만 소스코드가 Visual C++ 6.0을 기준으로 작성되었기 때문에 현재 버전의 Visual Studio에서 자동으로 생성해주는 코드의 형태와는 다소 차이가 있을 수 있음을 감안하시기 바랍니다.

또한 본 게시물은 원문을 최대한 직역하는 것을 지향하고 있으나, 우리말로 읽었을 때 보다 매끄럽게 하기 위하여 부득이 의역, 어순 조정 및 어휘 조정이 있음을 양해 바랍니다.

 

  1. 목차
  2. 쉘 익스텐션(Shell Extension)을 작성하기 위한 단계별 튜토리얼
    1. 파트 1
    2. 파트 2
  3. 여러 개의 파일에 대해 한번에 작동하는 쉘 익스텐션(Shell Extension)
    1. 파트 1
    2. 파트 2
  4. 파일에 대해 ‘팝업(Popup)’ 설명을 보여주는 쉘 익스텐션(Shell Extension)
  5. 사용자 정의 ‘드래그 앤 드롭(Drag and Drop)’ 기능을 제공하는 쉘 익스텐션(Shell Extension)
  6. 파일에 대한 ‘등록 정보’(또는 ‘속성’) 다이얼로그에 페이지를 추가하는 쉘 익스텐션(Shell Extension)
    1. 파트 1
    2. 파트 2
  7. ‘보내기(Send To)’ 메뉴에서 사용될 수 있는 쉘 익스텐션(Shell Extension)
  8. 컨텍스트 메뉴에 그림 출력하는 쉘 익스텐션(Shell Extension)
    및 디렉토리의 빈 공간에서 마우스 오른쪽 클릭에 응답하는 컨텍스트 메뉴 익스텐션(Shell Extension)
    1. 파트 1
    2. 파트 2
    3. 파트 3
  9. Windows 탐색기에서 “자세히” 보기 모드를 선택할 때 나타나는 열 항목을 추가하는 쉘 익스텐션(Shell Extension)
    1. 파트 1
    2. 파트 2
  10. 특정 형식의 파일에 대해 아이콘을 사용자화 하는 쉘 익스텐션(Shell Extension)

 

1 단계. 쉘 익스텐션(Shell Extension)을 작성하기 위한 단계별 튜토리얼

실습 프로젝트 다운로드

ShellExtGuide1_demo.zip
38.4 kB

여러분이 이 게시물의 토론 게시판에 질문하기 전 꼭 읽어주시기 바랍니다.

본래 이 시리즈는 Visual C++ 6.0 사용자를 기준으로 작성되었습니다. Visual C++ 8.0 (Visual Studio 2005)이 출시된 만큼, 필자는 본 글을 Visual C++ 7.1(Visual Studio 2003)까지 다루도록 업데이트해야 할 필요가 있음을 느꼈습니다(Visual C++ 7.1이 제공하는 자동 업데이트가 있기는 하지만 그다지 매끄럽게 작동되는 것이 아니어서 독자 여러분이 첨부된 예제 프로그램을 실습하고자 할 때 좌절하실 수 있습니다). 필자가 글의 내용을 검토하고 본 시리즈를 업데이트 하면서, Visual C++ 7.1의 새로운 기능을 반영하고자 업데이트 할 것이고, 소스 코드의 다운로드 코너에서 Visual C++ 7.1 형식의 프로젝트를 포함시킬 것입니다.

 

Visual Studio 2005 사용자를 위한 주의사항

Visual C++ 2005 Express Edition은 ATL과 MFC를 포함하고 있지 않습니다. 그러나 본 시리즈는 ATL을 사용하고 경우에 따라서 MFC도 사용할 것이기 때문에, 본 글에 첨부된 예제 프로그램을 실습하고자 하는 분은 Express Edition을 사용할 수 없습니다.

여러분이 Visual C++ 6.0을 사용하고 있다면, 여러분은 Platform SDK를 업데이트할 필요가 있습니다. 웹 설치 버전을 사용할 수도 있고, 오프라인 상태에서 설치하는 경우를 위하여 CAB 파일 버전ISO 파일 버전도 마련되어 있습니다. 여러분은 Visual C++ 탐색 경로에 SDK include와 라이브러리 경로가 추가되었는지 확인해야 합니다. 이러한 경로는 “Platform SDK” 프로그램 그룹의 “Visual Studio Registration” 폴더에서 찾을 수 있습니다. 여러분이 Visual C++ 7.0 또는 8.0을 사용하고 있다 하더라도 Platform SDK를 업데이트하여 최신의 헤더 파일과 라이브러리를 유지하는 것이 좋습니다.

 

Windows® Server 2003 SP1 Platform SDK Web Install
PSDK-amd64.exe
1.29MB
PSDK-ia64.exe
1.38MB
PSDK-x86.exe
1.26MB

 

Windows® Server 2003 SP1 Platform SDK Full Download
PSDK-FULL.part01.rar
9.80MB
PSDK-FULL.part02.rar
9.80MB
PSDK-FULL.part03.rar
9.80MB
PSDK-FULL.part04.rar
9.80MB
PSDK-FULL.part05.rar
9.80MB
PSDK-FULL.part06.rar
9.80MB
PSDK-FULL.part07.rar
9.80MB
PSDK-FULL.part08.rar
9.80MB
PSDK-FULL.part09.rar
9.80MB
PSDK-FULL.part10.rar
9.80MB
PSDK-FULL.part11.rar
9.80MB
PSDK-FULL.part12.rar
9.80MB
PSDK-FULL.part13.rar
9.80MB
PSDK-FULL.part14.rar
9.80MB
PSDK-FULL.part15.rar
9.80MB
PSDK-FULL.part16.rar
9.80MB
PSDK-FULL.part17.rar
9.80MB
PSDK-FULL.part18.rar
9.80MB
PSDK-FULL.part19.rar
9.80MB
PSDK-FULL.part20.rar
9.80MB
PSDK-FULL.part21.rar
9.80MB
PSDK-FULL.part22.rar
9.80MB
PSDK-FULL.part23.rar
9.80MB
PSDK-FULL.part24.rar
9.80MB
PSDK-FULL.part25.rar
9.80MB
PSDK-FULL.part26.rar
9.80MB
PSDK-FULL.part27.rar
9.80MB
PSDK-FULL.part28.rar
9.80MB
PSDK-FULL.part29.rar
9.80MB
PSDK-FULL.part30.rar
9.80MB
PSDK-FULL.part31.rar
9.80MB
PSDK-FULL.part32.rar
9.80MB
PSDK-FULL.part33.rar
9.80MB
PSDK-FULL.part34.rar
9.80MB
PSDK-FULL.part35.rar
9.80MB
PSDK-FULL.part36.rar
9.80MB
PSDK-FULL.part37.rar
9.80MB
PSDK-FULL.part38.rar
9.80MB
PSDK-FULL.part39.rar
9.80MB
PSDK-FULL.part40.rar
9.80MB
PSDK-FULL.part41.rar
9.80MB
PSDK-FULL.part42.rar
9.80MB
PSDK-FULL.part43.rar
9.80MB
PSDK-FULL.part44.rar
2.27MB

 

Visual C++ 7.0 사용자를 위한 주의사항

여러분이 Platform DSK를 업데이트 하지 않았다면 include 경로의 기본값을 반드시 변경해야 합니다. 다음과 같이 ($VCInstallDir)include 보다 위쪽, 다시 말하면 목록의 첫 번째 줄이 $(VCInstallDir)PlatformSDK\include로 되어있는지 확인합니다.

 

[도구(T)] 메뉴의 [옵션(O)] 항목을 클릭한다.
“VC++ 디렉터리”의 “포함 파일”에서 Platform SDK의 포함 파일 경로를 맨 위로 한다.

 

필자는 아직 Visual C++ 8.0을 사용해본 적이 없기 때문에 Visual C++ 8.0(Visual Studio 2008)에서도 예제 프로그램이 컴파일되는지는 모르겠습니다. 다행히도 Visual C++ 6.0에서 7.0으로 변환하는 작업보다는 Visual C++ 7.0에서 8.0으로 변환하는 작업이 훨씬 더 수월합니다. Visual C++ 8.0에서 어려움을 겪으셨다면 이 글에 대한 포럼에 질문해주시기 바랍니다.

 

본 시리즈에 들어가며......

쉘 익스텐션(Shell Extension)은 몇 가지 기능들을 윈도우 쉘(Shell), Windows 탐색기에 추가하는 COM 객체입니다. 많은 종류의 쉘 익스텐션들이 존재하지만 이들에 대해 이해하기 쉽게 작성된 문서는 많지 않습니다. 물론 필자가 처음 글을 쓰기 시작한 이래 최근 6년 동안 상황이 나아졌다고 자부할 수 있습니다. 쉘에 대해 더 깊이 있게 알고자 하는 분들이 있다면, 필자는 Dino Esposito님이 작성하신 훌륭한 서적인 “Visual C++ Windows Shell Programming(ISBN 1861001843)”을 읽어볼 것을 강력히 권해 드립니다. 그러나 이 책을 갖고 있지 않은 분들 또는 쉘 익스텐션에 대해서만 알고 싶으신 분들을 위해 필자는 이 글을 작성합니다.

이 글을 통해 여러분은 경악할 수도 있고, 또는 쉘 익스텐션을 어떻게 작성하는지에 대해 이해할 수도 있습니다. 본 가이드는 여러분이 COM과 ATL의 기본을 익혔다고 가정할 것입니다. 독자 여러분이 COM 기본지식을 모르고 있다면, 필자의 COM 소개 글을 먼저 읽어보시길 바랍니다.

1 단계는 쉘 익스텐션의 일반적인 소개를 포함하고 있습니다. 그리고 이후 등장하는 단계들에 대한 맛보기로서 간단한 컨텍스트 메뉴 확장을 포함하고 있습니다.

“쉘 익스텐션(Shell Extension)”이라는 용어는 두 단어로 구성되어 있습니다.

쉘(shell)은 Windows 탐색기를 의미하고 익스텐션(extension)은 여러분이 작성하고, 미리 약속된 이벤트(예: .doc 파일을 선택하고 마우스 오른쪽 버튼을 클릭하는 경우)가 발생할 때 Windows 탐색기가 실행하는 프로그램 코드를 의미합니다. 그러므로 쉘 익스텐션은 Windows 탐색기에 기능을 추가하는 COM 객체의 일종입니다.

쉘 익스텐션은 ‘인 프로세스(in-process) COM 서버’로서 Windows 탐색기와의 통신을 다루는 몇 가지 인터페이스를 구현하고 있습니다. ATL은 쉘 익스텐션을 빠르게 불러와서 실행하도록 만드는 가장 쉬운 방법입니다, 왜냐하면 여러분이 QueryInterfaceAddRef를 거듭하여 작성하지 않아도 되기 때문입니다. 또한 ATL로 쉘 익스텐션을 작성하는 것은 Windows NT 기반의 운영체제에서 디버그를 할 때 좀 더 편하게 만들어 줍니다. 이에 대해서는 나중에 설명하도록 하겠습니다.

쉘 익스텐션에는 여러 종류가 있습니다. 그리고 각 유형들은 각기 다른 이벤트가 발생할 때 실행됩니다. 여기 주로 쓰이는 몇 가지 유형과 그들이 실행되는 상황에 대해 정리되어 있습니다.

 

유형 언제 실행되는가? 무엇을 하는가?
컨텍스트 메뉴 핸들러
(Context Menu Handler)
파일 또는 폴더에 대해 사용자가 마우스 오른쪽 클릭을 했을 경우에 실행됩니다. 특히 4.71 이상 버전의 쉘에서는 디렉토리 창의 여백을 마우스 오른쪽 클릭했을 때도 실행됩니다. 컨텍스트 메뉴에 항목을 추가합니다.
프로퍼티 시트 핸들러
(Property Sheet Handler)
파일에 대한 등록 정보(혹은 속성) 대화상자를 열 때 실행됩니다. 프로퍼티 시트에 페이지를 추가합니다.
드래그 앤 드롭 핸들러
(Drag and Drop Handler)
디렉토리 창 또는 바탕 화면에서 사용자가 항목들을 마우스 오른쪽 버튼을 눌러 ‘드래그 앤 드롭’ 할 때 실행됩니다. 컨텍스트 메뉴에 항목을 추가합니다.
드롭 핸들러
(Drop Handler)
사용자가 파일 위에다 항목들을 ‘드롭’ 할 때 실행됩니다. 미리 설계된 작업들을 수행합니다.
QueryInfo 핸들러
(QueryInfo Handler)
파일 또는 ‘내 컴퓨터(내 PC)’와 같은 쉘 객체에 마우스 포인터를 올려놓고 있을 때 실행됩니다. Windows 탐색기가 툴팁(Tooltip)을 통해 출력할 문자열을 반환합니다.

 

들어가기에 앞서......

지금쯤 여러분은 Windows 탐색기에서 쉘 익스텐션이 어떻게 생긴 것인지 궁금해 하실 것입니다. 예를 들어 WinZip은 여러 가지 유형의 쉘 익스텐션을 포함하고 있고, 그 중 하나가 컨텍스트 메뉴 핸들러입니다. 다음은 압축 파일을 선택하고 마우스 오른쪽 클릭을 하였을 때 WinZip이 어떻게 쉘(shell)을 확장하는지를 보여주고 있습니다.

 

WinZip에 의해 확장된 컨텍스트 메뉴.

 

WinZip은 메뉴 항목들을 추가하는 코드와 ‘플라이-바이’ 도움말(fly-by help: Windows 탐색기의 상태 표시줄에 나타나는 텍스트)를 포함하고 있고 사용자가 이 명령 중 하나를 선택하였을 때 적절한 작업을 수행합니다.

 

WinZip에 의해 확장된 컨텍스트 메뉴 항목을 선택하면 나타나는 플라이-바이 도움말.

 

WinZip은 또한 드래그 앤 드롭 핸들러도 포함하고 있습니다. 이 유형은 컨텍스트 메뉴 핸들러와 매우 유사하지만 사용자가 마우스 오른쪽 버튼을 누른 상태로 파일을 드래그했을 때 실행됩니다. 다음은 WinZip의 드래그 앤 드롭 핸들러가 컨텍스트 메뉴에 무엇을 추가하고 있는지를 보여주고 있습니다.

 

zip 파일을 하나 선택하고 마우스 오른쪽 버튼을 눌러 드래그하면 나타나는 메뉴.

 

이외에 더 많은 유형들이 존재하고 마이크로소프트는 각 Windows 버전마다 더 많은 것들을 추가하고 있습니다. 지금부터 우리는 작성하기에 가장 간단하고, 실행 결과를 확인하기도 쉬운 컨텍스트 메뉴 확장에 대해 살펴보겠습니다.

코딩하기에 앞서, 작업을 더 쉽게 만들어 줄 수 있는 몇 가지 팁을 알려드리겠습니다. 여러분이 쉘 익스텐션을 호출하여(번역자 주: 마우스 오른쪽 클릭을 하여) Windows 탐색기가 이를 로드(load)할 때, 쉘 익스텐션은 메모리에서 잠시 머물러 있게 됩니다. 그리고 이것은 DLL 파일을 다시 빌드(build)하는 것을 불가능하게 만듭니다. Windows 탐색기가 쉘 익스텐션을 자주 언로드(unload)하도록 하기 위하여 다음과 같은 경로의 레지스트리 키를 생성합니다.

 

HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Explorer\AlwaysUnloadDLL

 

그리고 이 키의 기본값을 1로 설정합니다. Windows 9x의 경우 이것이 여러분이 할 수 있는 최선입니다.

HKLM\Software\Microsoft\Windows\CurrentVersion\Explorer\AlwaysUnloadDLL

 

Windows NT의 경우 다음 경로의 레지스트리 키로 이동합니다.

 

HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer

그리고 이 키에 이름이 DesktopProcessDWORD를 생성하고 그 값을 1로 설정합니다. 이것은 바탕화면과 작업표시줄이 하나의 프로세스에서 작동되도록 만들고, Windows 탐색기 창이 각각 자신의 프로세스에서 실행되도록 만듭니다.

또한 이것은 여러분이 하나의 Windows 탐색기 창을 가지고 디버그하고 그 창을 닫을 때 여러분이 만들고 있는 DLL 파일도 자동으로 언로드(unload)하도록 만들어 줌으로써 “파일이 사용 중입니다.”와 같은 문제를 방지해 줍니다. 레지스트리의 변경 사항은 여러분이 로그오프(logoff)하고 다시 로그온(logon)할 때 적용됩니다.

 

HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer에 추가된 DWORD 값.

 

Windows 9x에서 쉘 익스텐션을 디버그(debug)하는 것에 대하여는 나중에 설명하겠습니다.

 

AppWizard를 사용하여 시작하기

먼저 우리는 메시지 상자를 띄움으로써 이것이 작동 중임을 확인할 수 있는 쉘 익스텐션을 만들어 보겠습니다. 우리는 이 쉘 익스텐션을 .txt 파일에 걸어 놓아서 사용자가 텍스트 파일에 대해 마우스 오른쪽 클릭을 하였을 때 실행되게 만들 것입니다.

이제 시작해 볼 시간입니다. 필자는 아직 이 쉘 익스텐션을 어떻게 사용하는지에 대해 설명도 하지 않았습니다. 하지만 걱정하지 않아도 됩니다. 필자는 과정을 따라 설명하게 될 것입니다. 필자는 개념들이 설명되었다면, 예제 프로그램이 작성되는 과정을 따라 설명하는 것이 더 낫다고 생각하였습니다. 물론 필자는 모든 것을 먼저 설명한 후, 나중에 코드를 작성하게 해 드릴 수도 있습니다. 그러나 이것은 여러분이 흡수하기 어렵습니다. 어쨌든 Visual C++을 실행하고 바로 시작해 보겠습니다.

AppWizard를 실행하고 새 ATL COM 프로그램을 생성합니다. 우리는 이것을 SimpleExt라고 부르겠습니다. AppWizard의 기본 설정을 그대로 두고 [Finish]를 클릭합니다. 이제 우리는 당장이라도 DLL로 빌드될 수 있는 빈 ATL 프로젝트가 생겼지만, 쉘 익스텐션을 할 수 있는 COM 객체를 만들어야 합니다.

ATL COM AppWizard를 선택하여 새 프로젝트를 생성한다.
모든 설정을 그대로 두고 [Finish]를 클릭한다.

DLL에 COM 개체를 추가하기 위해 “클래스 뷰(Class View)” 화면으로 이동하여 “HardLink classes” 트리 항목에 대해 마우스 오른쪽 버튼을 클릭하고 [New ATL Object] 항목을 클릭합니다. Visual C++ 7.0 이상의 버전에서는 해당 트리 항목에 대해 마우스 오른쪽 버튼을 클릭하고 “추가(D)”→”클래스 추가(C)...”를 클릭합니다. 영어 버전의 Visual Studio에서는 “Add”→”Add Class” 메뉴입니다.

 

클래스 뷰 화면에서 [New ATL Object…]를 선택한다.
Visual C++ 7.0 이상의 버전에서 COM 개체를 추가할 수 있는 메뉴.

ATL Object Wizard 화면에서 첫 번째 단계는 이미 “Simple Object”라는 것을 선택하고 있습니다. 그냥 [Next >] 버튼을 클릭합니다.

[Objects] 범주에서 [Simple Object]를 선택하고 [Next >]를 누른다.

두 번째 단계에서 “Short Name”이라 적힌 에디트 상자에 SimpleShlExt라고 적습니다. 그러면 나머지 에디트 상자들은 자동으로 내용이 채워질 것입니다.

기본적으로 이 마법사는 C에서도 사용 가능하고, OLE Automation을 통해 스크립트 기반 언어에서도 사용할 수 있는 COM 객체를 생성하게 됩니다. 우리의 쉘 익스텐션은 Windows 탐색기에서만 사용될 것이기 때문에 우리는 몇 가지 설정들에 대해 Automation 관련 기능을 제거하도록 수정할 수 있습니다. 우선 ‘Attribute’ 페이지로 이동하여 ‘Interface Type’을 ‘Custom’으로 변경 후 ‘Aggregation’에 대해 ‘No’를 선택합니다.

 

우선 [Names] 탭의 [Short Name] 항목에 “SimpleShlExt”라고 적는다.
그 다음 [Attributes] 탭에서 ‘Apartment’, ‘Custom’, ‘No’ 순으로 클릭한다.

[확인] 버튼을 누르면 마법사는 COM 객체를 구현하기 위한 기본 코드가 포함된 CSimpleShlExt 클래스를 생성하고 이를 현재의 프로젝트에 추가할 것입니다. 이 클래스에 우리는 추가적으로 코딩을 하게 될 것입니다.

 

초기화 인터페이스

우리가 만들고 있는 쉘 익스텐션이 로드(load)되면, Windows 탐색기는 QueryInterface 함수를 호출하여 IShellExtInit 인터페이스에 대한 포인터를 얻어갈 것입니다. 이 인터페이스에는 단 하나의 메소드만 들어있습니다. Initialize로서 그 원형은 다음과 같습니다.

 

HRESULT IShellExtInit::Initialize(
    LPCITEMIDLIST pidlFolder,
    LPDATAOBJECT pDataObj,
    HKEY hProgID);

 

Windows 탐색기는 위 메소드를 호출함으로써 우리에게 다양한 정보를 전달합니다.

pidlFolder는 현재 선택된 파일들이 포함된 폴더의 PIDL입니다. PIDL은 ‘pointer to an ID list’의 약어로서 쉘에 있는 어느 객체이든, 그것이 파일시스템 객체이든 아니든 관계없이 유일하게 식별하도록 해 주는 데이터 구조체입니다.

pDataObj는 현재 선택된 파일들의 이름을 얻을 수 있는 IDataObject 인터페이스형 포인터입니다.

hProgID는 우리의 등록 정보가 포함된 레지스트리 키에 접근할 수 있는 HKEY입니다.

이 예제는 간단하기 때문에 우리는 단지 pDataObj 매개 변수(parameter)만을 사용하게 될 것입니다.

우리가 만들고 있는 COM 객체에 IShellExtInit를 추가하기 위해서, SimpleShlExt.h 파일을 열고 다음과 같이 굵게 표시된 줄을 추가하시기 바랍니다. 우리는 프로젝트가 생성되는 과정에서 자동으로 생성된 인터페이스를 사용하지 않을 것이므로, 마법사가 생성한 일부 COM 관련 코드들은 필요하지 않을 것입니다. 그래서 필자는 삭제될 수 있는 코드를 취소선으로 표시하였습니다.

 

#include <shlobj.h> // 새로 추가
#include <comdef.h> // 새로 추가
 
class ATL_NO_VTABLE CSimpleShlExt :
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CSimpleShlExt, &CLSID_SimpleShlExt>,
    public ISimpleShlExt, // 이 줄은 삭제해도 됨
    public IShellExtInit {
    
    BEGIN_COM_MAP(CSimpleShlExt)
        COM_INTERFACE_ENTRY(ISimpleShlExt)
        COM_INTERFACE_ENTRY(IShellExtInit)
    END_COM_MAP()

 

COM_MAP은 ATL이 QueryInterface를 어떻게 구현할 것인지에 대한 것입니다. 이것은 우리가 만든 COM 객체를 다른 프로그램이 어떤 형식의 인터페이스로서 캐스팅하여 가져갈 수 있는지를 ATL에게 알려줍니다.

클래스 선언 내부에 Initialize에 대한 원형을 추가합니다. 우리는 또한 파일 이름을 보관할 버퍼가 필요합니다. 이를 선언합니다.

 

    // ...
    
    protected:
    TCHAR m_szFile[MAX_PATH];
    
    public:
    // IShellExtInit
    STDMETHODIMP Initialize(LPCITEMIDLIST, LPDATAOBJECT, HKEY);
    
    // ...

 

그 다음 SimpleShlExt.cpp 파일에서 이 함수의 정의를 추가합니다.

 

STDMETHODIMP CSimpleShlExt::Initialize(
    LPCITEMIDLIST pidlFolder,
    LPDATAOBJECT pDataObj,
    HKEY hProgID) {
    
    // ...
    
    }

 

우리가 해야 할 것은 마우스 오른쪽 클릭된 파일의 이름을 가져오고 그 이름을 메시지 상자로 보여주는 것입니다. 선택된 파일이 하나 이상이면, 여러분은 선택된 파일 모두를 pDataObj 인터페이스 포인터를 통해 접근할 수 있습니다, 그러나 우리는 이 단계에서 예제 프로그램을 단순하게 유지하기 위하여, 첫 번째 파일 이름만을 보도록 하겠습니다.

파일 이름들은 WS_EX_ACCEPTFILES 스타일이 적용된 윈도우에 파일을 드래그 앤 드롭 할 때와 동일한 형식으로 보관됩니다. 이것은 우리가 파일 이름을 얻고자 할 때 DragQueryFile과 똑같은 API를 사용한다는 뜻입니다. 우리는 IDataObject에 포함된 데이터에 대한 핸들을 얻는 것으로 시작하겠습니다.

HRESULT CSimpleShlExt::Initialize(...) {
    FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
    STGMEDIUM stg = { TYMED_HGLOBAL };
    HDROP     hDrop;
    
    // 데이터 객체에서 CF_HDROP 데이터를 찾습니다. 
    // 그러한 데이터가 없다면 Windows 탐색기로 오류 코드를 반환합니다.
    if (FAILED(pDataObj->GetData(&fmt, &stg)))
        return E_INVALIDARG;
    
    // 실제 데이터에 대한 핸들을 얻습니다.
    hDrop = (HDROP)GlobalLock(stg.hGlobal);
    
    // 이것이 정상 작동하는지 확인합니다.
    if (hDrop == NULL)
        return E_INVALIDARG;
    
    // ...
    
}

 

모든 것을 오류 체크하고, 특히 포인터를 체크하는 것은 필수적으로 중요합니다. 우리의 쉘 익스텐션은 Windows 탐색기의 처리 공간에서 작동되기 때문에, 우리가 만들고 있는 앱이 충돌하면 Windows 탐색기도 함께 강제 종료하게 됩니다. Windows 9x에서는 그러한 충돌 때문에 컴퓨터를 재부팅해야 할 수도 있습니다.

HDROP 핸들을 만들었기 때문에 지금부터 우리는 우리가 원하는 파일의 이름을 가져올 수 있습니다.

 

HRESULT CSimpleShlExt::Initialize(...) {
    FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
    STGMEDIUM stg = { TYMED_HGLOBAL };
    HDROP     hDrop;
    
    // ...
    
    // 상태 확인 - 적어도 하나 이상의 파일 이름을 가지고 있는지 확인한다.
    UINT uNumFiles = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0);
    HRESULT hr = S_OK;
    
    if (uNumFiles == 0) {
        GlobalUnlock(stg.hGlobal);
        ReleaseStgMedium(&stg);
        return E_INVALIDARG;
    }
    
    // 첫 번째 파일의 이름을 얻어와서 멤버변수 m_szFile에 보관합니다.
    if (DragQueryFile(hDrop, 0, m_szFile, MAX_PATH) == 0)
        hr = E_INVALIDARG;
    
    GlobalUnlock(stg.hGlobal);
    ReleaseStgMedium(&stg);
    
    return hr;
}

 

이 메소드가 E_INVALIDARG를 반환한다면 Windows 탐색기는 마우스 오른쪽 버튼 클릭을 재차 하더라도 우리가 만드는 쉘 익스텐션을 실행하지 않을 것입니다.

S_OK를 반환할 때 Windows 탐색기는 QueryInterface 메소드를 다시 호출하여, IContextMenu라는 또 다른 인터페이스 포인터를 받아갈 것입니다.

 

컨텍스트 메뉴와 상호작용하기 위한 인터페이스

Windows 탐색기는 우리가 만든 쉘 익스텐션이 일단 초기화 되었다면, 우리가 컨텍스트 메뉴에 항목을 추가하고 플라이-바이 도움말을 제공하고, 사용자의 선택에 따른 작업을 수행할 수 있도록 IContextMenu 인터페이스의 메소드들을 호출할 것입니다.

우리가 만들고 있는 쉘 익스텐션에 IContextMenu 인터페이스를 추가하는 것은 IShellExtInit를 추가했던 것과 비슷합니다. SimpleShlExt.h를 열고 아래와 같이 줄을 추가합니다. 그리고 IContextMenu 메소드의 원형들을 추가합니다.

 

class ATL_NO_VTABLE CSimpleShlExt : 
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CSimpleShlExt, &CLSID_SimpleShlExt>,
    public IShellExtInit,
    public IContextMenu { // 새로 추가
    
    BEGIN_COM_MAP(CSimpleShlExt)
        COM_INTERFACE_ENTRY(IShellExtInit)
        COM_INTERFACE_ENTRY(IContextMenu)
    END_COM_MAP()
    
    // ...
    
    // IContextMenu에 있는 메소드 원형 추가
    public:
    STDMETHODIMP GetCommandString(UINT, UINT, UINT*, LPSTR, UINT);
    STDMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO);
    STDMETHODIMP QueryContextMenu(HMENU, UINT, UINT, UINT, UINT);
}

 

컨텍스트 메뉴 수정하기

IContextMenu는 세 가지 메소드를 가지고 있습니다.

그 중 첫 번째 메소드인 QueryContextMenu는 우리에게 컨텍스트 메뉴를 수정할 수 있게 해 줍니다. QueryContextMenu의 원형은 다음과 같습니다.

 

HRESULT IContextMenu::QueryContextMenu(
    HMENU hmenu,
    UINT uMenuIndex,
    UINT uidFirstCmd,
    UINT uidLastCmd,
    UINT uFlags);

 

hmenu는 컨텍스트 메뉴에 대한 핸들입니다.

uMenuIndex는 우리가 메뉴 항목을 추가하게 되는 컨텍스트 메뉴 위치(순서)입니다.

uidFirstCmduidLastCmd는 우리가 컨텍스트 메뉴 각 항목에 부여할 수 있는 Command ID의 범위입니다.

uFlags는 Windows 탐색기가 왜 QueryContextMenu를 호출하였는지 이유를 나타내고 있습니다. 이것에 대해서는 나중에 설명하겠습니다.

반환 값은 여러분이 누구에게 물어보는지에 따라 다르게 문서화되어 있습니다. Dino Esposito의 책에 따르면 이 반환 값은 QueryContextMenu에 따라 추가된 메뉴 항목의 개수입니다. Visual C++ 6.0 및 그 이후 버전에 부속되는 MSDN에 따르면, 우리가 가장 마지막으로 추가한 메뉴 항목의 Command ID입니다.

온라인 버전의 MSDN에서는 이렇게 설명하고 있습니다:

성공하면 심각도는 SEVERITY_SUCCESS로 설정되고 코드 값은 (여러분이) 사용한 Command ID 값 중 (uidFirstCmd로부터) 가장 큰 값에 대한 오프셋(offset)으로 설정된 HRESULT입니다.

예를 들어 idCmdFirst5이고, 여러분이 컨텍스트 메뉴에 5, 7, 8의 Command ID를 갖는 항목을 추가했다면, 이 반환 값은 MAKE_HRESULT(SEVERITY_SUCCESS, 0, 8 - 5 + 1)이어야 합니다. 그 외의 경우 OLE 오류 값을 반환합니다.

필자는 지금까지 Dino의 설명을 따라 소스 코드를 작성해왔고, 문제 없이 작동되었습니다. 사실 이 저자가 반환 값을 구성하는 방법은 uidFirstCmd부터 1씩 증가해가며 Command ID를 사용하는 한 온라인 MSDN과 동일합니다.

우리가 만들고 있는 예제는 단 하나의 항목만을 컨텍스트 메뉴에 추가할 것이므로, QueryContextMenu 함수도 다음과 같이 간단하게 작성될 수 있습니다.

 

HRESULT CSimpleShlExt::QueryContextMenu(
    HMENU hmenu, UINT uMenuIndex, UINT uidFirstCmd, UINT uidLastCmd, UINT uFlags) {
    // uFlags가 CMF_DEFAULTONLY를 포함하고 있다면 우리는 특별히 할 것이 없습니다.
    if (uFlags & CMF_DEFAULTONLY)
        return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 0);

    InsertMenu(hmenu, uMenuIndex, MF_BYPOSITION, 
    uidFirstCmd, _T("SimpleShlExt Test Item"));

    return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 1);
}

 

먼저 우리가 확인해야 할 것은 uFlags입니다. 여러분은 MSDN을 통해 이 값이 될 수 있는 전체 목록을 확인할 수 있습니다만, 컨텍스트 메뉴 확장에 한한다면 CMF_DEFAULTONLY 하나만이 중요합니다. 이 설정은 네임스페이스 확장(namespace extension)에게 기본 메뉴 항목만을 추가할 것을 알려줍니다. 이 설정이 되어 있다면 쉘 익스텐션은 컨텍스트 메뉴에 어떤 항목도 새로 추가해서는 안 됩니다. 이것이 uFlagsCMF_DEFAULTONLY를 포함하고 있을 때 즉시 0을 반환하는 이유입니다.

이 설정이 없을 경우 우리는 hmenu로 전달된 컨텍스트 메뉴에 항목을 추가하고, 우리가 1개의 항목을 새로 추가하였음을 쉘(shell)에게 알리기 위해 1을 반환합니다.

 

계속 읽기

다음 게시글: Windows 쉘 익스텐션 개발 가이드 - (1) 튜토리얼 (2/2)

 

'API/COM' 카테고리의 다른 글
더 보기...
태그 : 
댓글