코딩캣: 코딩하는 고양이.
Windows 쉘 익스텐션 개발 가이드 - (9) 아이콘 [完]
API/COM
2021. 2. 10. 12:30

입문자를 위한 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)

 

9 단계. 특정 형식의 파일에 대해 아이콘을 사용자화 하는 쉘 익스텐션(Shell Extension)

실습 프로젝트 다운로드

ShellExtGuide9_demo.zip
34.1 kB

 

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

9 단계까지 잘 오셨습니다! 본 단계도 독자 여러분께서 요청하셨던 사항이었고, 특정 형식의 파일(여기에서는 텍스트 파일) 각각에 대해 사용자화된 아이콘을 보여주는 방법에 대해 다루어 보겠습니다. 첨부된 예제 프로젝트는 모든 버전의 Windows에서 실행될 것입니다.

Visual C++ 7.0 또는 8.0 사용자는 이전과 같이 1 단계의 본 시리즈에 들어가며......를 참고하여 컴파일하기 전 몇 가지 설정을 변경해야 함을 기억하시기 바랍니다.

Windows 탐색기에서 각각의 유형의 파일들은 특정한 아이콘으로 표현됨을 모두들 아실 것입니다. 비트맵 이미지는 페인트 브러시 아이콘으로 표시되고, HTML 페이지는 인터넷 익스플로러 아이콘이 그려진 종이 모양의 아이콘으로 표시됩니다. Windows 탐색기는 파일에 대해 사용자에게 어떤 아이콘으로 보여줄 것인지 여부를 레지스트리를 찾아가며 결정합니다. 그리고 파일 유형에 따라 그러한 아이콘 정보를 가지고 있는 레지스트리 키는 HKEY_CLASSES_ROOT의 하위 키입니다. 이 방식은 특정한 형식의 모든 파일이 전부 같은 모양의 아이콘으로 보여지는 결과를 갖습니다.

그러나 아이콘을 지정하는 방식이 이것뿐만은 아닙니다. 아이콘 핸들러(icon handler)라는 쉘 익스텐션을 작성함으로써 Windows 탐색기는 우리가 각각의 파일마다 아이콘을 다르게 사용자화할 수 있게 만들어 줍니다. 사실, 파일 별로 아이콘을 다르게 나타나게 하는 예는 이미 Windows 운영체제 자체에 내장되어 있습니다. Windows 디렉토리 또는 .exe파일들이 많은 디렉토리를 Windows 탐색기로 열어 보시기 바랍니다. 여러분은 각각의 .exe 파일이 자신만의 아이콘으로 표시되고 있음을 알 수 있습니다. 단 이 경우 .exe 파일에 자체 아이콘 리소스를 가지고 있는 경우에 한하며, 그렇지 않은 .exe 파일들은 일반적은 아이콘으로 나타납니다. .ico.cur 등의 파일도 각 파일마다 서로 다르게 아이콘으로 나타납니다.

이 단계에서 다룰 예제 프로젝트는 텍스트 파일에 대해 사이즈별로 각기 다른 4개의 아이콘을 보여주는 기능을 합니다. 화면에 표시된 아이콘은 다음과 같습니다.

 

쉘 익스텐션의 실행 결과.

 

확장 인터페이스

여러분은 프로젝트를 생성하는 과정에 이미 익숙해져 있을 것입니다. 그러므로 필자는 Visual C++ 마법사 단계에 대해서는 생략하겠습니다. ATL COM 프로젝트를 생성하는 데, 그 이름은 TxtFileIcons로 정하고, C++ 구현 클래스의 이름은 CTxtIconShlExt로 정합니다.

아이콘 핸들러는 두 개의 인터페이스를 구현하고 있습니다. 하나는 IPersistFile이고 다른 하나는 IExtractIcon입니다. IShellExtInit는 한 번에 여러 파일이 선택된 상태에서 쉘 익스텐션을 초기화하는 반면, IPersistFile은 한 번에 하나의 파일에 대해서만 쉘 익스텐션을 초기화함을 기억하시기 바랍니다.

IExtractIcon은 두 개의 메소드를 가지고 있는데 둘 다 Windows 탐색기에게 특정 파일에 대해 어떤 아이콘을 사용할 것인지에 대한 내용을 전달해주는 역할을 합니다.

Windows 탐색기는 보여지는 파일들 모두에 대해 COM 객체를 생성함을 기억하시기 바랍니다. 이것은 매 파일마다 C++ 클래스의 인스턴스가 생성됨을 의미합니다. 그러므로 Windows 탐색기가 버벅거리는 것을 피하기 위하여, 이번 쉘 익스텐션에서 여러분은 시간을 소비하는 작업을 피하셔야 합니다.

 

초기화 인터페이스

COM 객체에 IPersistFile 인터페이스를 추가하기 위하여 TxtIconShlExt.h를 열고 아래 표시한 내용과 같이 수정합니다.

 

#include <comdef.h>
#include <shlobj.h>
#include <atlconv.h>
 
class CTxtIconShlExt :
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CTxtIconShlExt, &CLSID_TxtIconShlExt>,
    public IPersistFile { // 새로 추가
    BEGIN_COM_MAP(CTxtIconShlExt)
        COM_INTERFACE_ENTRY(IPersistFile) // 새로 추가
    END_COM_MAP()
    
    // 이 이하는 전부 새로 추가
    
    public:
        // IPersistFile
        STDMETHOD(GetClassID)(CLSID *)       { return E_NOTIMPL; }
        STDMETHOD(IsDirty)()                  { return E_NOTIMPL; }
        STDMETHOD(Save)(LPCOLESTR, BOOL)    { return E_NOTIMPL; }
        STDMETHOD(SaveCompleted)(LPCOLESTR) { return E_NOTIMPL; }
        STDMETHOD(GetCurFile)(LPOLESTR *)    { return E_NOTIMPL; }
        STDMETHOD(Load)(LPCOLESTR wszFile, DWORD /*dwMode*/) { 
            USES_CONVERSION;
            lstrcpyn(m_szFilename, OLE2CT(wszFile), MAX_PATH);
            return S_OK;
        }
    
    protected:
        TCHAR     m_szFilename[MAX_PATH]; // 요청 받은 파일에 대한 전체 경로
        DWORDLONG m_qwFileSize; // 파일의 크기
};

 

IPersistFile을 사용하는 다른 쉘 익스텐션과 마찬가지로 구현을 필요로 하는 단 하나의 메소드는 Load 뿐입니다. 왜냐하면 이 메소드를 통해 우리가 어떤 파일에 대해 작업해야 하는 지를 Windows 탐색기가 알려줄 것이기 때문입니다. Load의 구현은 인라인(inline)으로 작성되었습니다. 나중에 사용하기 위해 현재 파일의 이름을 m_szFilename 멤버 변수로 복사만 합니다.

 

IExtractIcon 인터페이스

아이콘 핸들러는 Windows 탐색기가 파일에 대한 아이콘을 필요로 할 때 호출할 메소드가 담긴 IExtractIcon 인터페이스를 구현합니다. 우리가 만들 쉘 익스텐션은 텍스트 파일에 대한 것이기 때문에, Windows 탐색기 창 또는 시작 메뉴에서 텍스트 파일이 보여지는 매 순간마다 Windows 탐색기는 우리가 구현할 IExtractIcon을 호출할 것입니다. COM 개체에 IExtractIcon을 추가하기 위하여 TxtIconShlExt.h를 열고 다음과 같이 내용을 추가합니다.

 

class CTxtIconShlExt :
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CTxtIconShlExt, &CLSID_TxtIconShlExt>,
    public IPersistFile,
    public IExtractIcon { // 새로 추가
    BEGIN_COM_MAP(CTxtIconShlExt)
        COM_INTERFACE_ENTRY(IPersistFile)
        COM_INTERFACE_ENTRY(IExtractIcon) // 새로 추가
    END_COM_MAP()
    
    public:
        // IExtractIcon
        STDMETHODIMP GetIconLocation(UINT uFlags, LPTSTR szIconFile, UINT cchMax, int * piIndex, UINT * pwFlags);
        STDMETHODIMP Extract(LPCTSTR pszFile, UINT nIconIndex, HICON * phiconLarge, HICON * phiconSmall, UINT nIconSize);
};

 

Windows 탐색기에게 아이콘을 반환하는 방법에는 두 가지가 있습니다.

먼저 GetIconLocation 메소드는 아이콘을 포함하고 있는 파일의 이름 및 그 인덱스 쌍을 반환할 수 있고 이 때 인덱스는 해당 파일 내에서 아이콘을 특정하기 위해 0부터 시작하는 정수입니다. 예를 들어 C:\Windows\system32\shell32.dll/9와 같은 문자열이 반환될 수 있는데 이는 Windows 탐색기에게 shell32.dll 파일의 9번째 아이콘(0부터 셌을 때 9번째)을 알려줍니다. 다만 이와 같은 문자열은 해당 아이콘의 리소스 아이디가 9번이라는 뜻을 의미하는 것이 아닙니다. 리소스 아이디 순서(작은 값에서 큰 값 순으로)대로 볼 때 9번째 항목이라는 뜻입니다.

Extract 메소드는 아무 작업도 하지 않지만, S_FALSE를 반환하여 Windows 탐색기가 직접 아이콘을 추출하라고 알리게 됩니다.

특별한 사항이 있다면, GetIconLocation이 값을 반환한 뒤에 Windows 탐색기는 Extract 메소드를 호출할 수도 있고 그렇지 않을 수도 있다는 것입니다. Windows 탐색기는 최근에 사용한 아이콘들을 유지하고 있는 ‘아이콘 캐시(icon cache)’를 가지고 있습니다. GetIconLocation이 최근에 사용된 파일 이름 및 인덱스 쌍을 반환하고 아이콘 캐시에 이에 해당하는 아이콘이 있다면, Windows 탐색기는 Extract를 호출하지 않고 캐시된 아이콘을 사용합니다.

이 메소드는 또한 “캐시를 찾지 말 것”을 의미하는 플래그(flag)를 반환할 수도 있습니다. 이 경우 Windows 탐색기는 항상 Extract 메소드를 호출하게 되는데, 이렇게 하면 Extract는 Windows 탐색기가 보여줄 아이콘에 대한 핸들을 반환하기 위해 직접 아이콘을 로드(load)해야 합니다.

 

첫 번째 아이콘 추출 방법

처음으로 호출되는 IExtractIcon 메소드는 GetIconLocation입니다.

이 메소드는 IPersistFile::Load에서 보관한 파일 이름을 사용하여, 파일을 직접 읽은 후 앞서 설명한 파일 이름 및 인덱스 쌍을 반환합니다. GetIconLocation의 원형은 다음과 같습니다.

 

HRESULT IExtractIcon::GetIconLocation(UINT uFlags, LPTSTR szIconFile, UINT cchMax, int * piIndex, UINT * pwFlags);

 

각 매개 변수(parameter)의 의미는 다음과 같습니다.

 

uFlags

쉘 익스텐션의 작동을 바꿀 수 있는 옵션들입니다. GIL_ASYNC는 아이콘 추출 작업이 오래 걸릴 것인지 물어보기 위해 전달되는 플래그(flag)로, 만일 오래 걸린다면 쉘 익스텐션은 Windows 탐색기에게 아이콘 추출 작업을 백 그라운드에서 수행해 줄 것을 요청할 수 있습니다. 이렇게 하면 Windows 탐색기 창 자체가 버벅거리는 일이 없습니다.

다른 플래그(flag)인 GIL_FORSHELLGIL_OPENICON은 네임스페이스 확장에서 의미 있습니다. 우리가 작업할 프로젝트에서는 실행 시간이 오래 걸리는 코드가 없으므로, 이러한 플래그(flag)들을 고려하지 않아도 됩니다.

szIconFile, cchMax

szIconFile은 쉘(shell)이 제공하는 버퍼로서 우리는 사용하고자 하는 아이콘이 포함된 파일의 이름을 이 곳에 보관하면 됩니다. cchMax는 쉘(shell)이 제공한 버퍼의 크기로서, 단위는 문자 수 입니다.

piIndex

우리가 사용하고자 하는 아이콘이 szIconFile에서 몇 번째 인덱스에 해당하는지를 지정할 정수에 대한 포인터입니다(번역자 주: 이것도 쉘(shell)이 제공하는 변수를 가리키는 유효한 포인터입니다).

pwFlags

쉘(shell)이 제공한 UINT형 변수를 가리키는 유효한 포인터로서, 반환하는 플래그(flag)에 따라 Windows 탐색기의 작동을 우리가 변경할 수 있습니다. 이 곳에 들어갈 수 있는 플래그의 종류에 대하여는 잠시 후 설명하겠습니다.

GetIconLocation 메소드는 szIconFilepiIndex 매개 변수(parameter)에 내용을 채운 다음 S_OK를 반환합니다. 우리가 커스텀 아이콘을 제공하지 않기로 결정하였다면 S_FALSE를 반환합니다. 이 때 Windows 탐색기는 일반적인 아이콘(알 수 없는 파일)으로 표시해 줄 것입니다.

pwFlags를 통해 반환할 수 있는 플래그(flag)들에는 다음과 같은 종류가 있습니다.

GIL_DONTCACHE

Windows 탐색기가 szIconFile/piIndex로 지정된 파일이 최근에 사용되었는지 여부를 확인하기 위해 캐시를 검색하는 것을 못하게 합니다. 그 결과 IExtractIcon::Extract 메소드가 항상 호출됩니다. ‘두 번째 아이콘 추출 방법’에서 이 플래그에 대해 많은 이야기를 하겠습니다.

GIL_NOTFILENAME

MSDN에 따르면 GetIconLocation이 값을 반환할 때 이 플래그가 있으면 Windows 탐색기가 szIconFile/piIndex를 무시한다고 합니다. 명백하기도 이 플래그(flag)는 쉘 익스텐션이 어떻게 Windows 탐색기로 하여금 IExtractIcon::Extract를 호출하게 만드는지를 보여주는 플래그임이 확실한데, 실제로는 GetIconLocation이 값을 반환할 때 이 플래그는 Windows 탐색기의 작동에 아무 영향을 미치지 못합니다. 이와 관련해서는 나중에 설명하겠습니다.

GIL_SIMULATEDOC

이 플래그(flag)는 Windows 탐색기가 쉘 익스텐션에서 반환된 아이콘을 가져가서, “한 쪽 귀퉁이가 접힌 종이” 모양의 아이콘과 합친 다음에, 그 합성된 아이콘을 해당 파일에 적용해서 보여주라는 의미를 갖습니다. 필자는 잠시 후 이 플래그의 결과를 보여드리겠습니다.

 

첫 번째 아이콘 추출 방법에서, 우리가 작성할 쉘 익스텐션의 GetIconLocation 함수가 텍스트 파일의 크기를 계산한 다음, 그 크기에 따라 0, 1, 2, 3의 인덱스를 반환할 것입니다. 이러한 방법은 한 가지 결함을 가지고 있습니다. 여러분은 각 아이콘들의 리소스 아이디들을 추적하고 이들 리소스가 올바른 순서로 놓여 있는지를 확인해야 합니다. 우리의 쉘 익스텐션은 4개의 아이콘만 다루기 때문에 ‘회계장부정리’와 같은 이러한 작업이 어렵지는 않습니다. 그러나 여러분이 더 많은 수의 아이콘을 다루거나, 프로젝트에서 아이콘을 추가 또는 제거한 경우라면 리소스 아이디를 주의하여 관리해야 합니다.

우리가 구현할 GetIconLocation 메소드는 다음과 같습니다. 먼저 우리는 파일을 열고 그 크기를 측정합니다. 이 과정에서 오류가 발생하면 Windows 탐색기가 기본 아이콘을 적용할 수 있도록 S_FALSE를 반환합니다.

 

STDMETHODIMP CTxtIconShlExt::GetIconLocation(UINT uFlags, LPTSTR szIconFile, UINT cchMax, int * piIndex, UINT * pwFlags) {
    DWORD     dwFileSizeLo, dwFileSizeHi;
    DWORDLONG qwSize;
    HANDLE    hFile;
    
    hFile = CreateFile(m_szFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    
    if (hFile == INVALID_HANDLE_VALUE)
        return S_FALSE;    // 쉘에게 기본 아이콘을 사용하도록 알림
    
    dwFileSizeLo = GetFileSize(hFile, &dwFileSizeHi);
    
    CloseHandle(hFile);
    
    if (dwFileSizeLo == (DWORD)-1 &&  GetLastError() != NO_ERROR)
        return S_FALSE;    // 쉘에게 기본 아이콘을 사용하도록 알림
    
    qwSize = (DWORDLONG(dwFileSizeHi) << 32) | dwFileSizeLo;
    
    // ...

 

다음으로 우리가 사용할 아이콘들은 DLL 파일에 포함되어 있으므로, 이 DLL 파일의 경로를 가져와야 합니다. DLL 파일의 경로는 szIconFile 버퍼에 복사됩니다.

 

    // ...
    
    TCHAR szModulePath[MAX_PATH];
    
    GetModuleFileName(_Module.GetResourceInstance(), szModulePath, MAX_PATH);
    
    lstrcpyn(szIconFile, szModulePath, cchMax);
    
    // ...

 

다음으로 우리는 파일의 크기를 확인하고 그 크기에 맞추어 piIndex가 가리키는 위치의 값을 적절한 인덱스로 설정합니다(사용되는 아이콘들에 대해서는 본 게시글의 맨 윗부분을 참고 바랍니다).

 

    // ...
    
    if (qwSize == 0)
        *piIndex = 0;
    else if (qwSize < 4096)
        *piIndex = 1;
    else if (qwSize < 8192)
        *piIndex = 2;
    else
        *piIndex = 3;
    
    // ...

 

다음으로 우리는 Windows 탐색기로부터 기본 작동을 얻기 위하여 pwFlags0으로 설정합니다. 즉 Windows 탐색기는 szIconFile/piIndex로 특정된 아이콘의 캐시 여부를 아이콘 캐시를 통해 확인하라는 의미입니다. 이와 같이 설정하면, IExtractIcon::Extract 메소드는 호출되지 않을 것입니다. 그리고 나서 우리는 S_OK를 반환하여 GetIconLocation 메소드가 성공했음을 알립니다.

 

    // ...
    
    *pwFlags = 0;
    
    return S_OK;
}

 

우리는 Windows 탐색기에게 아이콘을 어디에서 찾을 수 있는지 말했기 때문에, 우리가 직접 구현하는 Extract 메소드는 항상 S_FALSE를 반환할 것입니다. 이는 Windows 탐색기에게 아이콘을 직접 추출하라는 의미입니다. 다음 절에서 필자는 Extract의 매개 변수(parameter)에 대해 설명하겠습니다.

 

STDMETHODIMP CTxtIconShlExt::Extract(LPCTSTR pszFile, UINT nIconIndex, HICON * phiconLarge, HICON * phiconSmall, UINT nIconSize) {
    return S_FALSE;    // 쉘에게 아이콘 추출을 직접 하라고 알립니다.
}

 

본 쉘 익스텐션의 작동 결과는 다음과 같습니다. (번역자 주: 아이콘이 바뀌지 않는다면, 작업 관리자에서 explorer.exe 프로세스만 강제 종료한 뒤, 다시 실행해 봅니다.)

'아이콘' 보기(1).

 

'자세히' 보기(2).

 

'큰 아이콘' 보기(3).

 

여러분이 GetIconLocation 메소드에서 pwFlags 위치가 가리키는 값을 GIL_SIMULATEDOC으로 설정하면, 아이콘은 다음과 같이 보여질 것입니다.

'아이콘' 보기(2).

 

'자세히' 보기(2).

 

'큰 아이콘' 보기(2).

 

아이콘 보기와 큰 아이콘 보기 화면에 주목하시기 바랍니다. 우리가 제공하는 ‘작은’ 크기(16 * 16 버전)의 아이콘이 사용되었습니다. 작은 아이콘 보기에서, Windows 탐색기는 원래 작은 아이콘을 더욱 작게 축소시키고 있는데 이는 별로 예뻐 보이지 않습니다.

 

두 번째 아이콘 추출 방법

두 번째 방법은 우리의 쉘 익스텐션이 스스로 아이콘을 추출하여 Windows 탐색기의 아이콘 캐시를 우회하는 것을 포함하고 있습니다.

IExtraction::Extract 메소드는 항상 호출됩니다. 그리고 이 메소드는 아이콘들을 로드해서 큰 아이콘과 작은 아이콘이라는 두 개의 HICON을 Windows 탐색기에게 반환해야 합니다. 이 방법의 이점이 있다면 리소스 아이디를 순서에 맞추어 유지하는 것을 염려하지 않아도 된다는 것입니다. 다만 쉘 익스텐션이 Windows 탐색기의 아이콘 캐시를 우회하기 때문에 많은 수의 텍스트 파일이 들어있는 디렉토리를 탐색할 때 그 탐색 속도가 느려질 수 있습니다.

GetIconLocation은 첫 번째 방법과 유사합니다. 그러나 파일의 크기를 가져오기만 하면 되기 때문에 이 메소드에서 해야 할 일이 약간 줄어들었습니다.

 

STDMETHODIMP CTxtIconShlExt::GetIconLocation(UINT uFlags, LPTSTR szIconFile, UINT cchMax, int * piIndex, UINT * pwFlags) {
    DWORD  dwFileSizeLo, dwFileSizeHi;
    HANDLE hFile;
    
    hFile = CreateFile(m_szFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    
    if (hFile == INVALID_HANDLE_VALUE)
        return S_FALSE;    // 쉘에게 기본 아이콘을 사용할 것을 말합니다.
    
    dwFileSizeLo = GetFileSize(hFile, &dwFileSizeHi);
    
    CloseHandle(hFile);
    
    if (dwFileSizeLo == (DWORD)-1  &&  GetLastError() != NO_ERROR)
        return S_FALSE;    // 쉘에게 기본 아이콘을 사용할 것을 말합니다.
    
    m_qwFileSize = ((DWORDLONG)dwFileSizeHi) << 32 | dwFileSizeLo;
    // ...

 

일단 파일의 크기가 보관되었으므로 Windows 탐색기에게 아이콘 캐시를 확인하지 말 것을 알리기 위해 pwFlags가 가리키는 위치의 값을 GIL_DONTCARE로 변경합니다.

우리는 szIconFile/piIndex을 통해 아이콘을 특정하지 않을 것이므로 Windows 탐색기가 szIconFile/piIndex를 무시할 수 있도록, 이 플래그를 반드시 설정해야만 합니다.

비록 필자가 테스트하고 있는 현재 버전의 쉘(shell)에서는 별 효과가 없다고 하더라도 GIL_NOTFILENAME 플래그도 포함되어 있습니다. 이 플래그의 문서화된 목적은 Windows 탐색기에게 우리가 szIconFile/piIndex의 내용을 채우지 않을 것임을 알리는 것인데, 이 플래그를 전달하는 것 자체는 의미가 없으므로(달리 말하면 어차피 우리는 Windows 탐색기에게 추출해갈 대상을 전해주지 않으므로), Windows 탐색기는 내부적으로 이 플래그의 존재 여부를 평가조차 하지 않는 듯 합니다. 뭐 어쨌든 차기 버전의 쉘(shell)에서는 GIL_NOTFILENAME 여부를 평가할 수도 있으므로 이 플래그를 명시적으로 넣어주는 것은 좋은 생각입니다.

 

    *pwFlags = GIL_NOTFILENAME | GIL_DONTCACHE;
    return S_OK;
}

 

이제 Extract 메소드를 좀 더 깊게 들여다 볼 차례입니다. 원형은 다음과 같습니다.

 

HRESULT IExtractIcon::Extract(LPCTSTR pszFile, UINT nIconIndex, HICON * phiconLarge, HICON* phiconSmall, UINT nIconSize);

 

매개 변수(parameter)는 다음과 같습니다.

pszFile/nIconIndex

아이콘이 담긴 파일과 그 내부의 인덱스 번호입니다. 이 두 값은 GetIconLocation에서 반환된 값과 같습니다.

phiconLarge, phiconSmall

작은 아이콘과 큰 아이콘을 사용하기 위해 Extract 메소드가 설정해야 하는 각각의 HICON입니다. 이들 포인터는 NULL인 채로 전달될 수 있습니다.

nIconSize

아이콘의 예상 크기입니다. 상위 워드(HIWORD)는 작은 아이콘의 디멘션(dimension, 어차피 같을 것이므로 가로 폭과 세로 높이를 아우르는 값)이고, 하위 워드(LOWORD)는 큰 아이콘의 디멘션입니다. 일반적인 조건에서 작은 아이콘은 16이고, 큰 아이콘은 32 또는 48인데 Windows 탐색기의 보기 모드가 무엇에 선택되어 있는지에 달려 있습니다. 큰 아이콘 보기는 32이고, 타일 보기 모드에서는 48입니다.

 

우리의 쉘 익스텐션에서는 GetIconLocation 메소드에서 파일이름 및 인덱스 쌍을 채워 넣지 않았으므로 pszFilenIconIndex를 무시할 수 있습니다. 우리는 파일 크기에 따른 큰 아이콘과 작은 아이콘을 로드(load)하여 Windows 탐색기에게 반환하면 됩니다.

 

STDMETHODIMP CTxtIconShlExt::Extract(LPCTSTR pszFile, UINT nIconIndex, HICON * phiconLarge, HICON * phiconSmall, UINT nIconSize) {
    UINT uIconID;
    
    // 파일 크기에 따라 어떤 아이콘을 사용할 것인지를 결정합니다.
    if (m_qwFileSize == 0)
        uIconID = IDI_ZERO_BYTES;
    else if (m_qwFileSize < 4096)
        uIconID = IDI_UNDER_4K;
    else if (m_qwFileSize < 8192)
        uIconID = IDI_UNDER_8K;
    else 
        uIconID = IDI_OVER_8K;
    
    // 아이콘을 로드합니다!
    if (phiconLarge != NULL) {
        *phiconLarge = (HICON)LoadImage(_Module.GetResourceInstance(), MAKEINTRESOURCE(uIconID), IMAGE_ICON, wLargeIconSize, wLargeIconSize, LR_DEFAULTCOLOR);
    }
     
    if (phiconSmall != NULL) {
        *phiconSmall = (HICON)LoadImage(_Module.GetResourceInstance(), MAKEINTRESOURCE(uIconID), IMAGE_ICON,
wSmallIconSize, wSmallIconSize, LR_DEFAULTCOLOR);
    }
      
    return S_OK;
}

 

이제 됐습니다! Windows 탐색기는 우리가 반환하는 아이콘을 보여주게 될 것입니다. 한 가지 주목할 것은, 두 번째 방법을 사용하면서 GetIconLocation 메소드에 GIL_SIMULATEDOC 플래그를 사용하는 것은 아무 효과가 없다는 것입니다.

 

쉘 익스텐션을 등록하기

아이콘 핸들러는 해당 유형의 파일을 다루는 레지스트리 키의 하위키로 등록됩니다. 우리의 경우 텍스트 파일을 다루고 있으므로 HKCR\txtfile의 하위키로 등록됩니다. 다른 쉘 익스텐션에서 보았듯이 txtfile 레지스트리 키에는 ShellEx 하위키가 있습니다. 여기에 다시 IconHandler 하위키가 놓입니다. 이 레지스트리 키의 기본 값은 우리가 작성한 쉘 익스텐션의 GUID입니다. 드롭 핸들러와 마찬가지로 특정한 한 형식의 파일에는 하나의 아이콘 핸들러만이 올 수 있습니다. IconHandler의 하위 키로 놓는 대신에 우리는 또한 DefaultIcon 레지스트리 키의 기본 값을 %1로 바꾸어 우리가 작성한 아이콘 핸들러가 호출되도록 만들 수 있습니다.

우리가 작성한 쉘 익스텐션을 등록하는 RGS 스크립트는 다음과 같습니다.

 

HKCR {
     NoRemove txtfile {
          NoRemove DefaultIcon = s '%%1'
          NoRemove ShellEx {
               ForceRemove IconHandler = s '{DF4F5AE4-E795-4C12-BC26-7726C27F71AE}'
          }
     }
}

 

%1이라는 문자열을 지정하기 위해, 우리는 RGS 스크립트에서 %%1이라 적어야 함을 주목하시기 바랍니다. 왜냐하면 %는 대체 가능한 매개 변수(parameter)를 지시하는 데 사용되는 특수문자이기 때문입니다(예: %MODULE%).

DefaultIcon 값을 덮어쓰기하고 있는 사실은 중요한 문제를 야기합니다. 우리가 만들 쉘 익스텐션이 제거될 때 어떻게 하면 예전의 DefaultIcon 값을 되돌릴 수 있을까요? 답은, DllRegisterServer에서 DefaultIcon 기존의 값을 보관하고 DllUnregisterServer에서 이 값을 복원하는 것입니다.

우리는 “반드시” 이 작업을 수행하여 깨끗하게 제거되고 텍스트 파일 아이콘이 우리의 쉘 익스텐션이 설치되기 전으로 되돌려 놓아야 합니다.

등록과 등록 해제가 어떻게 이루어지는지는 첨부된 프로젝트의 소스 코드를 확인하시기 바랍니다. RGS 스크립트가 처리되기 위해 ATL이 호출되기 전에 백업이 수행되고 있음을 주목합니다. 반대로 작업을 수행(RGS 스트립트를 처리한 이후에 백업)한다면 우리가 백업할 기회를 잡기도 전에 DefaultIcon은 덮어쓰기 될 것이기 때문입니다.

 

원 저자의 저작권

(C) 2000-2006 Michael Dunn. 본 시리즈는 저작권을 갖는 저작물입니다. 필자는 이 글이 인터넷 상에서 퍼지는 것을 막을 수는 없음을 알고 있습니다. 어쨌든 필자는 말할 필요가 있습니다. 여러분이 이 글을 번역하고자 한다면, 필자에게 이메일을 보내어 알려 주시기 바랍니다. 누군가 번역하는 것을 막지는 않겠습니다만, 필자는 본 글에 링크를 게시하기 위해 번역되었음을 알고자 합니다.

본 글에 첨부된 예제 소스 코드는 공공 도메인(public domain)을 동반합니다. 필자는 이와 같이 함으로써 모두에게 소스 코드가 유용하게 쓰이길 바랍니다. (다만 본 글에 대해서는 공공 도메인을 원하지 않습니다. 왜냐하면 CodeProject에서만 열람 가능하도록 하는 것이 필자의 존재감과 CodeProject 사이트에 이익이 되기 때문입니다.) 여러분이 본 글에 첨부된 예제 프로그램을 여러분이 진행중인 프로젝트에 활용하고자 할 때 필자에게 이메일을 보내어 알려주신다면 매우 감사하겠습니다(이것은 단순히 필자의 소스 코드가 타인들에게 유용한지 그렇지 않은지를 알고 싶은 개인의 호기심 충족을 위해서입니다), 다만 이것은 필수 사항은 아닙니다. 여러분의 소스 코드에 필자에 대한 감사 표시를 해 주시면 좋습니다만, 이것도 필수 사항은 아닙니다.

 

시리즈의 끝

이전 게시글: Windows 쉘 익스텐션 개발 가이드 - (8) 자세히 모드 (2/2)

 

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