[WinAPI] 구현 Namespace Extension

탐색기가 우리의 익스텐션의 파일 및 폴더를 표시하기 위해서, 우리는 Shell namespace상에서 root 폴더가 어디에 위치할지를 명시해야 합니다. 이러한 위치는 접합지점이라고 부릅니다. 아래의 URL에서 자세한 정보를 얻을 수 있습니다.

https://msdn.microsoft.com/en-us/library/cc144096(v=vs.85).aspx



Shell SDK Samples

---------------------------------
레지스트리 설정 부분을 보면
1. NSE COM 등록은 아래위치에 명시
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\{MyCLSID}
여기서 들어가는 기본값이 %DESCRIPTION%이다.

HKCR(#Wow6432Node#)\CLSID\{MyCLSID}\
var LocalizedString MyDllPath,-201
InprocServer32 MyDllPath
   var ThreadingModle 'Apartment'
DefaultIcon MyDllPath
ProgID RegFolder(%PROJECTNAME%)
Implemented Categories\{00021490-0000-0000-C000-000000000046}  Browsable Shell Extension
ShellFolder
   var Attributes REG_DWORD %FOLDERATTRIBS%
   var PinToNameSpaceTree s''
ShellEx

2. 어디에 표시할지 아래위치에 명시
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\MyComputer\
NameSpace\{MyCLSID} 여기서 들어가는 기본값이 "DisplayName"이다.

-----------------------------------

윈도우즈 탐색기 관점에서 Namespace 호출부분 간략히 요점정리해 보면
루트가 desktop folder로 가정해 보면

1. SHGetDesktopFolder(&pSHFolder) <- 데스크톱 LPSHELLFOLDER 포인터 얻어옴
2. TreeEnumObjects(트리뷰윈도우, 데스크톱hItem, 데스크톱IShellFolder, 데스크톱pidlFQ)
  2.1. (데스크톱)IShellFolder::EnumObjects(&pEnum) <- LPENUMIDLIST를 요청하고
  2.2. 요청된 EnumIDLIst로 아이템 하나씩 LPITEMIDLIST를 얻어온다
  2.3. (데스크톱)IShellFolder::AddRef() <- TVN_DELETEITEM시 Release() 수행
  2.4. 요청된 아이템의 pidlFQ 계산 <- 데스크톱pidlFQ | pidlTemp를 합쳐준다
  2.5. pSHFolderParent <- 데스크톱 IShellFolder를 가리킴
  2.6. (데스크톱)IShellFolder::GetAttributes(1, &pidlTemp, &dwAttribs)
  2.7. dwAttribs값을 보고 하위 아이템 유무를 확인가능
3. Optional(TreeCompareProc 수행)
4. pSHFolder->Release()

다시 데스크톱->내PC 요청하면
1. (데스크톱)IShellFolder::BindToObject(내PC pidlRel, NULL, IShellFolder, &pSHFolder) <- 내PC LPSHELLFOLDER 포인터 얻어옴
2. TreeEnumObjects(트리뷰윈도우, 내PC hItem, 내PC IShellFolder, 내PC pidlFQ)
 2.1. (내PC)IShellFolder::EnumObjects(&pEnum)
 2.2. 요청된 EnumIDList로 아이템 하나씩 LPITEMIDLIST 얻어옴
 2.3. (내PC)IShellFolder::AddRef()
 2.4. 요청된 아이템의 pidlFQ 계산 <- 내PC pidlFQ | pidlTemp
 2.5. pSHFolderParent <- 내PC IShellFolder를 가리킴
 2.6. (내PC)IShellFolder::GetAttributes(1, &pidlTemp, &dwAttribs)
 2.7. dwAttribs값 보고 하위 아이템 유무 확인
3. TreeCompareProc 수행
4. pSHFolder->Release()

다시 데스크톱->내PC->FolderView  요청하면
데스크톱
   -> 내PC (14 00 1f 50 ~)
      -> FolderView (14 00 2e 80 ~)
pItem->pParentFolder->BindToObject(pidlRel, ..., (LPVOID*)&pParentFolder)
  --> pItem->pParentFolder ; 내PC ShellFolder
  --> pidlRel ; FolderView Sample
  --> 그냥 FolderView::Initialize()만 호출이 됨
  --> pParentFolder ; FolderView ShellFolder를 가리킴

다시 데스크톱->내PC->FolderView->one 요청하면
pItem->pParentFolder->BindToObject(pidlRel, ..., (LPVOID*)&pParentFolder)
  --> pItem->pParentFolder ; FolderView ShellFolder
  --> pidlRel ; one1
  --> FolderView::BindToObject() 호출됨

여기서의 FolderView::BindToObject()를 의사코드로 기술해보면
1. new FolderView ShellFolder 인스턴스 생성
2. FolderView::Initialize(pidl) ;  입력된 pidl에 대해서 ILCloneFirst()통해서 나온값과
3. 원래의 FolderView의 m_pidl과 2번에 나온 값을 합쳐준다 ; m_pidl은 (내PC->FolderView->one) 된다

여전히 풀리지 않는 지점으로 FolderView를 Expand할때 입력은 (14 00 2e 80 ~)으로 입력했는데 FolderView::Initialize(pidl)로 들어올때는 그 상위의 값(14 00 1f 50 ~)으로 들어온다. <- 이 부분은 탐색기의 BindToObject()에서 그렇게 연산해주는것으로 이해하자.
어쨌든 이쪽으로 들어오는 pidlFQ를 갖고서 m_pidl를 구성하면 되는거다


내PC 항목 IContextMenu 요청은 다음과 같이 한다.
psfFolder <- 내PC의상위 폴더인 (데스크톱)IShellFolder를 의미
IContextMenu * pcm;
psfFolder->GetUIObjectOf(hwndParent, 1, (LPCITEMIDLIST*)&pidlRel, IID_IContextMenu, NULL, (LPVOID*)&pcm);

아래부터는 Namespace Extention을 이해하는데 필요한 기초적인 내용을 정리한 부분이다.
The-Complete-Idiot-s-Guide-to-Writing-Namespace-Ex

It does nothing to explain (namespace상에서 events 내부동작을 설명하는데 역할을 하지 않는다)
namespace extention 내부구조체는 컴파일러와 프로그래밍 언어에 의존적임. 중요 항목이 있는데 ID List의 포인터를 나타내는 PIDL(탐색기가 TreeView에 Item과 SubFolder를 조직하는데 사용됨)

extention 주요 부분은 반드시 구현해야 하는 COM interface가 있다.
{IShellFolder, IEnumIDList, IShellView}

PIDL의 구조는 다음과 같다.
PIDL = ITEMIDLIST1, ITEMIDLIST2, ITEMIDLIST3
각각의 ITEMIDLIST는
USHORT cb;   2바이트 포함한 전체데이터가리키고
BYTE abID[1]; 의미있는 어떻한값도 허용되나, 같은 폴더 두객체는 동일 데이터 갖을수없다


https://www.codeproject.com/KB/shell/namespcextguide1/simplepidl.gif

IEnumIDList
1. Shell view가 무엇을 출력할지 알기 위해 폴더내용을 열거하는데 필요하다.
2. 탐색기는 트리뷰 펼치기 위해 폴더내의 서브폴더를 열거하는데 필요하다.

IShellFolder
탐색기가 extention과 초기화하고 통신하는데 사용하는 인터페이스이다. 탐색기가 extention 자신의 view window를 만들때 IShellFolder 메서드를 호출합니다.
IShellFolder는 extention의 가상 폴더 내용을 열거하고, 정렬 목적으로 폴더내 두 항목을 비교하는 메서드를 가지고 있다.
탐색기가 우리의 namespace extention을 만들때, 가장 먼저 IShellFolder 오브젝트를 인스턴스한다.
- BindToObject() ; namespace의 우리 파트 폴더가 브라우즈될때 호출된다. 이것의 JOB은 IShellFolder 오브젝트를 만들고, 브라우즈되는 폴더의 PIDL로 초기화 한다. 그리고 shell에게 새로운 오브젝트를 리턴해 준다.
- CompareIDs() ; 두 PIDL을 비교하고 상대순서를 반환하는 책임이 있다
- CreateViewObject() ; 탐색기가 shell view를 만들때 호출된다. 새로운 IShellView를 만들고 탐색기에게 그것을 리턴해 준다
- EnumObjects() ; 가상폴더 내용을 열거할 수 있는 새로운 PIDL 열거자를 만든다
view object가 디스플레이하는 폴더 내용을 알고자 할때 호출된다. 여기에는 명확한 기능분리가 있음을 주목하십시요(Notice the clear separation of functionality here). shell folder는 내용을 알지만 UI code를 갖고 있지 않고, shell view는 UI를 다루지만, 본질적으로 폴더내용을 알지 못한다.
- GetUIObjectOf() ; 가상폴더의 아이템과 연관된(가령, 컨텍스트메뉴) UI 엘리먼트를 구현하는 COM object를 리턴한다.

IShellView
탐색기가 extention에게 UI 관련 이벤트를 알려주는 인터페이스이다. IShellView는 extention이 view window를 만들고 삭제할때, display를 갱신할때 알려주는 메서드를 갖고 있다.
여기서는 레포트 모드로 list control을 만든다.
- GetCurrentInfo() ; FOLDERSETTINGS 구조체안에 우리 view의 current view 세팅을 리턴해 준다
- Refresh() ; shell view의 내용을 갱신해야 할때 호출됨
- UIActivate() ; view가 포커스를 얻거나 잃을때 호출됨.

IOleCommandTarget
탐색기가 view에게 명령을 보낼때 사용이 된다.
탐색기가 extention과 통신하는 방법은 해당 인터페이스를 사용하는 거다
- QueryState() ; 탐색기가 extention이 지원하는 표준 명령을 결정하기 위해 호출
S_OK 리턴하면, extention이 명령을 지원함을 의미하며, 탐색기가 Exec()를 호출하여 명령에 응답하도록 할수 있습니다.
내가 테스트하는 동안 사용되고 있는 것을 본 세개의 그룹이 있다. (There is thee groups that I saw being used during my testing)

- Exec() ; user가 탐색기에 명령을 호출할때 호출됨
명령과 관련한 문서가 거의 없다. 명령이 무엇에 사용되는지 또는 ID가 무엇인지 관련해(There is little documentation regarding the commands, what they are used for, or even what their IDs are)

IShellBrower
탐색기가 노출하는 인터페이스이며, extention이 탐색기 윈도우를 조작하도록 해준다. 메뉴, 툴바, 상태바를 변경할 수 있는 메서드를 가지고 있다. 뿐만 아니라 탐색기의 컨트롤로 일반적 메시지를 보낼수도 있다.

RGS 파일은 연결지점을 만듭니다. 탐색기가 extention을 사용하도록 지정하는 방법과 namespace에서 표시되어야 하는 위치.

[FolderViewImpl Sample]
Shell namespace상에서 System Folder View Object를 사용하기 위한 필요한 모든 단계 설명을 시도한다.
MS에서 System Folder View Object로 알려져 있는 IShellView를 제공하고 있다.
아이템 상세정보를 표시하는데 Windows 탐색기가 사용하는 view object와 동일하다.
System Folder View Object는 표준 list view 기능을 둘러싼 랩퍼이다.

1. Shell이 System Folder View Object를 만들기 위해 IShellFolder::CreateViewObject가 호출된다.
2. System Folder View Object는 IShellFolder2를 질의해서 표시정보를 가져옵니다.
3. view의 그룹화는 ICategoryProvider 구현을 통해서 이루어지며(attained) 차례로 각 그룹집합이 필요한 만큼 ICategorizer 인스턴스가 만들어진다.
4. IShellFolder::GetUIObjectOf 와 IShellFolder::CreateViewObject가 컨텍스트메뉴를 인스턴스하는데 사용되며, IContextMenu 인터페이스를 리턴해 줍니다.

IShellFolder::CreateViewObject Windows 탐색기가 view object 표시를 요청할때 호출된다.
IShellView 구현 대신에 System Folder View Object 사용하기 위해서 SHCreateShellFolderView(Ex)를 호출해라. CreateViewObject에서  인스턴스화된 IContextMenu는 view 자체 배경에 대한 컨텍스트 메뉴를 나타냅니다. view상의 각 아이템에 대한 컨텍스트 메뉴 요청은 IShellFolder::GetUIObjectOf 으로 이동합니다.

Shell이 컬럼개수, 타이틀, 기본 포맷팅 정보를 얻기 위해 null PIDL를 사용해서 IShellFolder2::GetDetailsOf를 처음 호출합니다. 컬럼 초기 열거하기전에 IShellFolder2::GetDefaultColumnState 호출합니다. 최소 한개 컬럼이 보여지는것을 보장합니다. 각각의 컬럼이 열거된 후에 역시나 GetDefaultColumnState 호출됩니다.GetDetailsOf(Ex) 호출하면 특정 프로퍼티를 가져오는것이 느려질수 있습니다. 백그라운드 스레드로 가져오는 작업을 이동하기 위해 SHCOLSTATE_SLOG 플래그를 입력하면 됩니다.

System Folder View Object가 컬럼을 열거한 후에, 계속해서 유효 PIDLs과 컬럼수로 데이터를 채우기 위해 GetDetailsOf 호출을 합니다.

formatID, GUID 결합이 프로퍼티 그룹을 식별하는데 사용되고, PID(property ID)는 컬럼을 정의합니다. 비스타 이상에서는(in Vista and beyond) SCID가 PEKY로 참조됩니다(is now referred to as a PKEY)
FolderViewImpl 샘플에서는 컬럼번호를 SHCOLUMNID 구조체로 변환하기 위해 IShellFolder2::MapColumnToSCID 구현을 사용합니다. 이후 _GetColumnDisplayName 함수를 호출하는데, PIDL과 SHCOLUMNID를 인자로 받고 컬럼라벨로 사용할 유저-프렌들리 문자열을 리턴해 줍니다.

보다 일반적인 컬럼이름에는 표준셋 ID와 프로퍼티ID가 제공됩니다(have been given) 자신이 정의하는 것 대신에 정의할때 이러한 ID를 사용해야 합니다.

컬럼헤더를 클릭하는 것은 컬럼에 기반해 view가 정렬이 되는데, IShellFolder::CompareIDs 메서드를 통해서 수행됩니다. 컬럼번호는 메서드 lParam의 하위워드에 들어있습니다. 이 메서드로 비교되는 두개의 PIDL은 단일 레벨이 아니며 전체 PIDL의 비교가 필요할 수 있음을 유의해야 합니다.(It should be noted that) 멀티 레벨 PIDL은 각 단계 IShellFolder로 바인딩한 다음 그 IShellFolder 인스턴스의 CompareIDs 메서드가 비교를 하도록 비교되어야 합니다. _ILCompareRelIDs 함수가 그 목적으로 제공됩니다.


https://www.codeproject.com/Articles/11909/Tips-in-Writing-Namespace-Extensions-I-Implements



PIDL의 구조는 다음과 같다.
PIDL의 구조는 다음과 같다.

댓글

이 블로그의 인기 게시물

[WinAPI] 모달리스 다이얼로그 설명

[WinDbg] Debugging a stack overflow

[WinDbg] first-chance, second-chance Exception