Windows外殼名字空間的瀏覽 (轉)

gugu99發表於2008-05-26
Windows外殼名字空間的瀏覽 (轉)[@more@] 

外殼名字空間的瀏覽

姜偉華

Windows95/98對Dos/Win3.x作了許多重大改進,在方面,它除了採用長檔名替代Dos中的8.3檔名以外,引入外殼名字空間( Name Space)來代Dos檔案系統是其又一大突破.本文將簡要地介紹如何在Windows 95/98或4.0以上版本。

  1. 概述
  1. 簡介

      在Dos/Win3.x中,每個邏輯分割槽構成一棵目錄樹,檔案系統由這一統一的根,而且每個目錄或檔案必須一一對應於檔案系統中客觀存在的項。但Windows引入了“外殼名字空間”( Shell Name Space)的概念之後,這一切就都變了。

     外殼名字空間是Windows下的標準檔案系統,它大大擴充套件了Dos檔案系統,形成了以“桌面”(Desktop)為根的單一的檔案系統樹,原有的C盤、D盤等目錄樹變成了“我的”這一外殼名字空間子樹的下一級子樹,而像“控制皮膚”、“回收站”、“網路上的芳鄰”等應用及“印表機”等裝置也被虛擬成了外殼名字空間中的節點。另外,與DOS中物理只能和檔案系統項一一對應這一點不同的是,一個實際目錄在外殼名字空間中可以表現為不同的項。例如“我的文件”與“C;My Documents”其實都指向“C;My Documents”目錄,但它們在外殼名字空間中是不同的項。如果我們執行Windows 自帶的“Windows”看一下的話,那麼在它的左部樹型檢視中我們就可以清楚的看到整個外殼名字空間替代DOS檔案系統,Windows在檔案系統的組織與管理上終於有了質的飛躍。

      為了區別於DOS中“目錄”的概念,Windows引入了“資料夾”(Folder)的概念。“資料夾”一般是指外殼名字空間樹中的非葉節點,既可以是DOS下的目錄,也可是“控制皮膚”、“回收站”這類虛擬的目錄。但外殼名字空間中有些項本身並不是資料夾(即不具有資料夾屬性),但卻含有子資料夾,比如“網路上的芳鄰”等。以下為講座方便,我們也認為它們是資料夾。

      在下面的講座過程中我們將用“檔案系統”一詞來指代DOS檔案系統,而用“外殼名字空間”一詞來指代Windows中的外殼名字空間:另外用“檔案”一詞來指代外殼名字空間這棵樹中的葉節點(雖然它們不都是物理儲存上的檔案)。

      在Windows中,Win3.x的檔案操作,如FindFirstFile、FindNextFile、SetCurrentDirectory等,雖然仍可使用,但用它們只能瀏覽檔案系統,卻無法瀏覽與操縱整個外殼名字空間。要瀏覽Windows中的外殼名字空間,就必須使用一套全新的、基於COM(模型)基礎上的方法。

  2. 新的“路徑”PIDL

     在討論基於 COM的方法之前,我們先來介紹一下外殼名字空間中“路徑”的表示問題。DOS的字串路徑只能表示檔案系統,而無法表示整個外殼名字空間,所以外殼名字空間提供了一種“路徑”的替代物椩?乇曄鬥?斜恚?虺莆?/FONT>PIDL)。

      PIDL是一個元素型別為ITEMIDLIST結構的陣列,陣列中元素的個數是未知的,但緊接著陣列末尾的必是一個雙位元組的零。每個陣列元素代表了外殼名字空間樹中的一層(即一個資料夾或檔案),陣列中的前一元素代表的是後一元素的父資料夾。由此可見,PIDL實際上就是指向一塊由若干個順序排列的ITEMIDLIST結構組成、並在最後有一個雙位元組零的空間的指標。所以PIDL的型別就被Windows定義為ITEMIDLIST結構的指標。

      PIDL亦有“絕對路徑”與“相對路徑”的概念。表示“相對路徑”的PIDL(本文簡稱為“相對PIDL”)只有一個ITEMIDLIST結構的元素,用於標識相對於父資料夾的“路徑”;表示“絕對路徑”的PIDL(簡稱為“絕對PIDL”)有若干個ITEMIDLIST結構的元素,第一個元素表示外殼名字空間根資料夾(“桌面”)下的某一子資料夾A,第二個元素則表示資料夾A下的某一子資料夾B,其餘依此類推。這樣絕對PIDL就透過儲存一條從“桌面”下的直接子資料夾或檔案的絕對PIDL與相對PIDL是相同的,而其他的資料夾或檔案的相對PIDL就只是其絕對PIDL的最後一部分了。

      但現在就出現了一個問題:即“桌面”的表示問題。外殼名字空間中其他各項都可以用從“桌面”開始的絕對PIDL加以表示,但“桌面”的PIDL陣列顯然一個元素都沒有。這樣就只剩下PIDL陣列最後的那個雙位元組的零了。所以,“桌面”的PIDL就是一個16位的零。注意:“桌面”內容是一個雙位元組的零。另外,雖然“桌面”表示的是“C: Windows Desktop”資料夾(這裡假定Windows的系統目錄為“C: Windows”)的內容,但“桌面”與“C: Windows Desktop”資料夾的PIDL是完全不同的。這一點同樣適用於“我的文件”與“C: My Documents”等資料夾。

      DOS中的路徑是一個字串,但PIDL是一種二進位制結構,所以我們不能直接從PIDL中獲知它所代表的到底是哪個資料夾或檔案,而必須相應的函式把它轉換成代表路徑的字串。如果某絕對PIDL是檔案系統的一部分,則呼叫SHGetPathFromIDList函式即可;但如不是,就無法獲得路徑字串了,因為DOS中根本就不存在這種路徑。但很可惜的是,Windows並沒有提供一個函式來讓我們方便地把檔案系統的路徑字串轉換成PIDL。不過我們可用一個我們自己實現的函式ParsePidlFromPath()來達目的(具體函式的實現見下文)。

      PIDL的建立與釋放一般並不使用C++的new和delete操作或C語言的malloc和free函式,而必須使用專門的方法進行.首先呼叫SHGEetMallocI函式得到Malloc介面(COM介面的一種,關於COM介面下面將詳述)的指標,再呼叫該介面的Alloc方法為PIDL分配空間,或呼叫該介面的Free方法釋放某個PIDL佔用的空間。最後呼叫該介面的Release方法釋放該介面。

     除了下面將要介紹的IShellFolder、IEnumIDList等COM介面可以操作PIDL外,還有很多以SH開頭的Windows 函式也可操作PIDL,不過一般這些函式都要求使用絕對PIDL作引數。例如SHGetFileInfo函式可得到某一PIDL所指物件的各種資訊,包括名字、圖示、屬性等;SHFileOperation函式可對外殼名字空間中的項進行複製、移動、改名、刪除等操作;SHBrowseForFolder可以顯示一個讓選擇外殼名字空間中某一資料夾的瀏覽對話方塊.

  3. 基於COM的方法

  討論清楚了PIDL的概念之後,我們回過頭來討論基於COM之上的瀏覽外殼名字空間的方法。如果說PIDL是外殼的名字空間中的“路徑”的話,那麼下面所說的兩個COM介面IshellFolder與IEnumIDList就起著與Win 3.x中的FindFirstFile、FindNextFile等函式類似的功能。

  在Windows中,每個資料夾都由實現了一個派生自Iunknown介面(COM介面的最基本類)的介面IshellFolder。透過呼叫某個資料夾的該介面,即可實現對該資料夾的瀏覽,得到該資料夾中子項(子資料夾或檔案)的各種相關資訊。

  我們可以呼叫SHGetDesktopFolder函式來獲得外殼名字空間的根資料夾(即“桌面”)的IshellFolder介面。對於某個資料夾A,以它的子資料夾B的相對PIDL為引數,呼叫它的IshellFolder介面的BindTo方法即可得到子資料夾B的IshellFolder介面。如要列舉某個資料夾下的子項,則只需呼叫它的IshellFolder介面的EnumObjects方法即可獲得一個IEnumIDList介面。透過呼叫該IEnumIDList介面的Next方法我們即可列舉出該資料夾的所有子項(包括資料夾和檔案等物件),獲得它們的相對PIDL。使用父資料夾的IshellFolder介面和這些相對PIDL,我們即可獲得這些子項的各種相關資訊,包括顯示名稱、圖示、屬性等,甚至還可以獲得它的右鍵選單。例如,呼叫該介面的GetDisplayNameOf方法可獲得該資料夾下子項的顯示名稱;呼叫ParseDisplayName方法可把某個子項的用Unicode內碼錶示的字串路徑翻譯成對應的PIDL。這樣透過PIDL和這兩個介面,我們就可以遍歷和操縱整個外殼名字空間了。

  除了IshellFolder和IEnumIDList介面以外,Windows 外殼名字空間還提供了很多其他COM介面,例如IshellBrowser、IshellLink、IshellIcom、IshellView等。透過這些介面,應用於程式就可以更好的與外殼名字空間互動。由於本文篇幅有限,這些介面就不詳細介紹了,有興趣的讀者可參閱相關資料。

  值得注意的是,COM中的介面雖然在使用上與C++中的類是非常相似(事實上COM介面在C ++中就是以類的形式宣告的),但維護其正確的引用計數機制是非常重要的。每增加一個對該介面的引用,就要呼叫一次它的AddRef( )方法;而在使用完後必須呼叫它的Release( )方法釋放該介面。關於COM及COM介面的細節請參見相關資料,這裡不再贅述。

  可惜的是,雖然我們可依照上文給出的方法實現外殼名字空間的逐層展開,但外殼名字空間卻並沒有提供一種讓我們自由跳轉到某一資料夾的方法,也沒有提供返回到上一級資料夾的方法,因為我們無法方便地獲得父資料夾的IshellFolder介面。如果要返回,就必須由應用程式自己想方法獲得父資料夾的IshellFolder介面。一種可行的方法是在展開外殼名字空間時儲存每個資料夾的IShellFolder介面指標和它的絕對PIDL,這樣就可以相對容易地實現自由跳轉了。

  但無論如何,外殼名字空間提供的瀏覽和操作的方法比起DOS/ Windows 3.x的函式來還是有著巨大的飛躍的。只要我們理解清楚了這種方法的優點與不足,我們就可以揚長避短,開發出各種各樣的使用外殼名字空間的程式來。

  1. 相關介面、函式和資料結構

  對於本文所涉及的一些比較複雜的介面、函式和資料結構,以下僅列舉出作者在Visual C++6.0查到的宣告與定義,並配上相應的註釋.一些較簡單的則從略,未列出的請參見相關資料。

  1. 資料結構

typedf IshellFolder*LPSHELLFOLDER;

//IshellFolder介面指標的宣告

typedef struct _ITEMIDLIST{//ITEMIDLIST結構的定義

SHITEMID mkid;

}ITEMIDLIST, * LPITEMIDLIST;

typedef struct _SHITEMID{//ITEMIDLIST結構中元素的定義

USHORT cb;//本結構的長度(以位元組計)

BYTE abID[1];//可變長的元素識別符號

}SHITEMID, *LPSHITEMID;

typedef struct _SHFILEINFO{//FILEINFO結構的定義

HICON hicon;//檔案圖示的控制程式碼

Int ilcon;//圖示在系統影像列表中的序號

D dwAttributes;//檔案的屬性

Char szDisplayName [MAX_PATH];//顯示名稱或路徑

Char szTypeName[80];//表示檔案型別的字串

} SHFILEINFO;

2.相關介面

2.1 IshellFolder介面的方法

  1. BindToObject

    格式:HRESULT BindToObject( LPCITEMIDLLIST pidl, LPBC pbcreserved, REFIID riid, LPVOID *ppvOut);

    作用:得到本資料夾中某一子資料夾的IShellFolder介面。

    引數:Riid應為IID_IshellFolder, pbcReserved應為NNUL,pidl為表示該子資料夾的“相對路徑”的PIDL,從ppvOut中返回要求的IshellFolder介面的指標。

  2. EnumObjects

格式:HRSULT EnumObjects( HWND hwndOwner, DWORD grfFlags, LPENUMIDLIST*ppenumIDList);

作用:列舉本資料夾的成員。

引數:hwndOwmer為父視窗控制程式碼,grfFlags決定列舉世聞名的內容,可為SHCONTF_FOLDERS、SHCONTF_NONFOLDERS、SHCONTF_INCLUDEHIDDEN的組合,從ppenumIDList返回IEnumIDList介面的指標。

(3)GetDisplayNameOf

格式:HRESULT GetDisplayNameOf (LPCITEMIDLIST pidl, DWORD uFlags, LPSTRRERT lpName);

作用:得到本資料夾中某一物件的顯示名稱。

引數:pidl為表示該子資料夾的“相對路徑”的PIDL, uFlags為 SHGDN_NORMAL、SHGDN_INFOLDER、SHGFI_SYSICONINDEX、SHGFI_EXETYPE、SHGFI_ATTRIBUTES、SHGFI_PIDL、SHGFI_DISPLAYNAME、SHGFI_LARGEICON等。

返回值:如uFlags包含SHGFI-EXETYPE標誌,則返回值為該可資料夾型別;如uFlags包含SHGFI_SYSICONINDEX標誌,則返回值為系統影像列表的控制程式碼。否則,如本函式呼叫成功則返回非零值,失敗則返回零。

  1. 應用舉例
  1. 幾個非常有用的函式的實現

1.1ParsePidlFromPath

描述:將檔案系統路徑翻譯成對應的PIDL。LPITEMIDLIST ParsePidlFromPath(LPCSTR path)

{

//存放以Unicode內碼錶示的路徑字串的緩衝區

OLECHAR szOleChar[MAX_PATH];

//“桌面“的IshellFolder介面指標

LPSHELLFOLDER IpsfDeskTop;

//返回的PIDL

LPITEMIDLIST Ipifq;

ULONG ulEaten, ulAttribs;

HRESULT hres;

//得到“桌面”的IshellFolderr 介面指標

SHGetDesktopFolder(&lpsfDeskTop);

//將Ansi字符集的路徑字串轉換成Unicode字串,

存入szOleChar

MultiByteToWChar(CP_ACP,MB_PRECOMPOSED,

Path,-1,szOleChar,sizeof(szOleChar));

//將szOleChar,中的路徑徑字串翻譯成相應的PIDL,存入lpifq

hres=lpsfDeskTop->Release( );

//如果翻譯失敗,則返回NULL

if(FAILED(hres))return NULL;

return lpifq;

1.2 GetItemIcon

描述:返回lpi這個絕對PIDL所指項的圖示在系統影像列表中的序號,uFlags為要求的圖示型別。

Int Getltemlcon(LPITEMIDLIST lpi, UINT uFlags)

{

//存放檔案資訊的結構

SHFILEINFO sfi;

//給uFlags增加一些公共標誌(lpi為PIDL、要求返回系統影像列表、要求小圖示)

uFlags|=SHGFI-PIDL |SHGFI_SYSICONINDEX |SHGFI_SMALLICON;

獲得圖示

SHGetFileinfo( (LPCSTR) lpi, 0, & sfi , sizeof(SHFILEINFO),uFlags);

//返回圖示在系統影像列表中的序號

return sfi,ilcon;

}

1.3 GetName

描述:lpio lpsf所指的IshellFolder介面代表的資料夾下的相對PIDL,本函式獲得lpi所指項的顯示名稱,dwFlags表明欲得到的顯示名稱型別,lpFriendlyName為存放顯示名稱的緩衝區。

BOOL GetName(LPSHELLFOLDER lpsf,LPITEMIDLIST lpi,DWORD dwFlags,LPSTR lpFriendlyName)

{

STRRET str;

//得到顯示名稱

if(NOERROR!=lpsf->GetDisplayNameOf()lpi,dwFlags,&str))

return FALSE;

//根據返回值進行轉換

switch(str uType)

{

//如為Unicode字串,則轉成Ansi字符集的字串case STRRET_WSTR:

WideCharToMultiByte(CP_ACP,0,str.pOleStr,-1,ipFriendlyName,sizeof(lpFriendlyName),NULL,NULL);

Break;

//如為偏移量,則去除偏移量

case STRRET_OFFSET:

lstrcpy(lpFriendlyName,(LPSTR)lpi+str.uOffset);

break;

如為Ansi字串,則直接複製

case STRRET_CSTR:

Lstrcpy(lpFriendlyName,(LPSTR)str.cStr);

Break;

//情況

default:

return FALSE;

}

return TRUE;

  1. 一個例項

以下我們將用Visual C++6.0製作一個例子來演示外殼名了空間的瀏覽。具體為使用Ctreer View,展開外殼名字空間中的“桌面”資料夾,列舉出該資料夾下的所有子資料夾。

在這個專案中,CtreeView 的影像列表我們使用Windows 的系統影像列表,而不是自己建立一個。

首先,用AppWizard新建一個專案,型別為MFC AppWizard(exe),專案名為Test;在第一步中選擇Single document;在第六步中將CtestView的基類改為CtreeView。其它均使用預設設定。

其次,在CtestView中加一個私有成員變數m_ImageList,型別為CimageList,用於儲存系統列表。(Windows中所有的圖示都儲存在系統影像列表中,我們可以在程式中得到這個影像列表)。

第三步,將上文提到的GetName 和GetItemIcon 這兩個函式的實現複製到CtestView.cpp的較開頭的位置。

第四步,在CtestView的OnInitialUpdate( )函式中加入以下程式碼:

//系統影像列表的控制程式碼

HIMAGELIST himlSmall;

//存放檔案資訊的結構

SHFILEINFO sfi;

//存放樹型中的節點的資訊

TV_INEM tvi;

//向樹型控制元件中插入節點時使用的結構

TV_INSERTSTRUCT tvis;

//欲插入節點的前一節點的控制程式碼

HTREEITEM hParent=TVI_FIRST;

//欲節點的父節點的控制程式碼

HTREEITEM hParent=TV_;

//某一資料夾的IshellFolder介面指標

LPSHELLFOLDER lpsf=0;

//IenumiDList介面的指標

LPENUMIDLIST lpe=0;

//lpi為一PIDL

LPITEMIDLIST lpi=0;

//IMalloc介面的指標

LPMALLOC lpMalloc=0;

//列舉的個數

ULONG ulFetched;

//存放顯示名稱的緩衝區

char szBuff[MAX_PATH];

//獲得系統影像列表,並把它賦給CtestView的CtreeCtrl控制元件

himlsmall=(HIMAGELIST)SHGetFileinfo(“C:”,0,&

sfi, sizeof(SHFIEINFO), SHGFI_SYSICONINDEX|SHGFI_SMALLICON);

m_lmageList.Attach(himlsmall);

GetTreeCtrl().SetlmageList(&m_imageList,TVSIL_NORMAL);

//獲得Imalloc介面的指標

SHGetMalloc(&lpMalloc);

//獲得“桌面”資料夾的IshellFolder介面指標

SHGetDesktopFloder(&lpsf);

//建立一個“桌面”的絕對PIDL

lpi=(LPITEMIDLIST)lpMalloc->Alloc(sizeof(USHORT0));

*((USHORT*)lpi)=0

//設定要插入的樹節點資訊

tvi,mask=TVIF_TEXT|TVIF_IMAGE|TVIF_EDIMAGE|

TVIF_CHILDREN;

tvi.cchTextMax=MAX_PATH;

//設定顯示名稱

tvi.pszText=_T(“桌面”);

//獲得標準圖示和展開時的圖示

tvi.ilmage=Tetltemlcon(lpi,NULL);

tvi.iSelectedlmage=Getltemlcon(lpi,SHGFI_OPENICON);

//設定插入位置

tvis.item=tvi;

tvis.hlnsertAfter=TVI_FIRST;

tvis.hParent=TVI_ROOT;

//插入根節點

hpParent=GetTreeCtrl().Instrtltem(& tvis);

//釋放lpi所佔的空間

lpMalloc->Free(lpi);

//獲得“桌面”資料夾的IenumiDList介面指標lpe

lpsf->EnumObjects(m_hWnd, SHCONTF_FOLDERS |

SHCONTF_NONFOLDERS,& lpe);

//列舉“桌面”下的各個子資料夾

while(S_OK= =lpe-> Next(1,&lpi,&ulFetched))

{

//獲得lpi表示的子資料夾的顯示名稱

GetName(lpsf,lpi,SHGDN_NORMAL,szBuff);

tvi.pszText=szBuff;

// 獲得該項的圖示

//由於是“桌面”下的直接子項,所以它的相對PIDL與絕對PIDL是一致的

tvi.ilmage=Getltemlcon(lpi,NULL);

tvi.iSelectedimage=Getltemlcon(lpi,SHGFI_OPENICON);

//設定插入位置

tvis.item=tvi;

tvis.hinsertAfter=hPrev;

tvis.hParent=hParent;

//插入節點

hPrev=GetTreeCtrl(). insertltem(& tvis);

//釋放lpi所佔的空間

ipMalloc->Free(lpi);

}

//釋放Imalloc和IsshellFolder介面

lpMalloc->Release();

lpsf->Release();

//對生成的節點進行排序

GetTreeCtrl( ).SortChildren(hParent);

//將CtestView中的“桌面”節點展開

GetTreeCtrl( ).Selectltem(hParent);

GetTreeCtrl( ).Expand(hParent,TVE_EXPAND);

最後,響應CtestView的WM_DESTROY訊息,加入以下程式碼:

//由於使用了系統影像列表,退出時必須釋放對它的所有權

//否則,退出後Windows將一個圖示沒有

m_imageList.Detach( );

這個演示程式的效果如下圖所示:

  1. 後記

  由於篇幅的關係,本文所舉的例子只能非常簡單的演示一下外殼名字空間的瀏覽,很多較複雜的方法都沒有表現出來。最後,希望本文能夠起到拋磚引玉的作用,讓更多的開發者認識與使用外殼名字空間,開發出更好的程式來。

參考文獻

  1. Corporation. Microsoft Windows95程式設計師指南,清華大學出版社,1996
  2. StefamoMaruzzi.Windows95開發者必讀,電子工業出版社,1997

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10748419/viewspace-1004607/,如需轉載,請註明出處,否則將追究法律責任。

相關文章