Windows外殼擴展編程 www.applevb.com
在Windows下的一些軟件提供了這樣的功能:當安裝了這些軟件之後,當在Windows的Explore中鼠標右鍵單擊文件或者文件夾後,在彈出菜單中就會多出與該軟件操作相關的菜單項,點擊該項就會激活相應的程序對用戶選中的文件進行相應的操作。例如安裝了Winzip之後,當用戶選中一個文件夾後單擊右鍵,在彈出菜單中就會多出一個Add To Zip和一個 Add To xxx.zip的選項,其中xxx為選中的文件夾的名稱。只要單擊上面的兩個菜單項中的一個,就可以方便的壓縮目錄了。這樣的功能稱為Windows外殼擴展(Shell Extensions)
外殼擴展概述
下面是與外殼擴展相關的三個重要術語:
(1)文件對象(File Object)
文件對象是外殼中的一項,大家最熟識的文件對象是文件和目錄,此外,打印機、控制面板程序、共享網
絡等也都是文件對象。
(2)文件類(File Class)
文件類是具有某種共同特性的文件對象的集合,比如,擴展名相同的文件屬於同一文件類。
(3)處理程序(Handler)
處理程序是具體實現某個外殼擴展的代碼。
Windows支持七種類型的外殼擴展(稱為Handler),它們相應的作用簡述如下:
(1)Context menu handlers向特定類型的文件對象增添上下文相關菜單;
(2)Drag-and-drop handlers用來支持當用戶對某種類型的文件對象進行拖放操作時的OLE數據傳輸;
(3)Icon handlers用來向某個文件對象提供一個特有的圖標,也可以給某一類文件對象指定圖標;
(4)Property sheet handlers給文件對象增添屬性頁,屬性頁可以為同一類文件對象所共有,也可以給一個
文件對象指定特有的屬性頁;
(5)Copy-hook handlers在文件夾對象或者打印機對象被拷貝、移動、刪除和重命名時,就會被系統調用,
通過為Windows增加Copy-hook handlers,可以允許或者禁止其中的某些操作;
(6)Drop target handlers在一個對象被拖放到另一個對象上時,就會被系統被調用;
(7)Data object handlers在文件被拖放、拷貝或者粘貼時,就會被系統被調用。
Windows的所有外殼擴展都是基於COM(Component Object Model) 組件模型的,外殼是通過接口(Interface)來訪問對象的。外殼擴展被設計成32位的進程中服務器程序,並且都是以動態鏈接庫的形式為操作系統提供服務的。因此,如果要對Windows的用戶界面進行擴充的話,則具備寫COM對象的一些知識是十分必要的。
寫好外殼擴展程序後,必須將它們注冊才能生效。所有的外殼擴展都必須在Windows注冊表的HKEY_CLASSES_ROOTCLSID鍵之下進行注冊。在該鍵下面可以找到許多名字像{0000002F-0000-0000-C000-000000000046}的鍵,這類鍵就是全局唯一類標識符。每一個外殼擴展都必須有一個全局唯一類標識符,Windows正是通過此唯一類標識符來找到外殼擴展處理程序的。在類標識符之下的InProcServer32子鍵下記錄著外殼擴展動態鏈接庫在系統中的位置。與某種文件類型關聯的外殼擴展注冊在相應類型的shellex主鍵下。如果所處的Windows操作系統為Windows NT,則外殼擴展還必須在注冊表中的HKEY_LOCAL_MACHINESoftwareMicrosoftWindowsCurrentVersionShellExtensionsApproved主鍵下登記。
注冊表HKEY_CLASSES_ROOT主鍵下有幾個特殊的子鍵,如*、Folder、Drive以及Printer。如果把外殼擴展注冊在*子鍵下,那麼這個外殼擴展將對Windows中所有類型的文件有效;如果把外殼擴展注冊在Folder子鍵下,則對所有目錄有效。
上面提到的在Windows Explore中在鼠標右鍵菜單中添加菜單項(我們成為上下文相關菜單)的操作屬於外殼擴展的第一類,即Context menu handlers向特定類型的文件對象增添上下文相關菜單。要動態地在上下文相關菜單中增添菜單項,可以通過寫Context Menu Handler來實現。
編寫Context Menu Handler必須實現IShellExtInit和IContextMenu兩個接口。除了IUnknown接口所定義的函數之外,Context Menu Handler還需要用到QueryContextMenu、InvokeCommand和GetCommandString這三個非常重要的成員函數。
(1)QueryContextMenu函數:每當系統要顯示一個文件對象的上下文相關菜單時,它首先要調用該函數。為了在上下文相關菜單中添加菜單
項,我們在該函數中調用InsertMenu函數。
(2)InvokeCommand函數:當用戶選定了某個Context Menu Handler登記過的菜單項後,該函數將會被調用,系統將會傳給該函數一個指向
LPCMINVOKECOMMANDINFO結構的指針。在該函數中要執行與所選菜單項相對應的操作。
(3)GetCommandString函數:當鼠標指針移到一個上下文相關菜單項上時,在當前窗口的狀態條上將會出現與該菜單項相關的幫助信息,此
信息就是系統通過調用該函數獲取的。
下面我通過具體的例程來說明編寫一個比較完整的上下文菜單程序,這個程序是一個文件操作程序,當安裝並注冊了外殼擴展的服務器動態連接庫之後,當選擇一個或者多個文件並單擊鼠標右鍵後,在右鍵菜單中就會多出一個“執行文件操作”的上下文菜單,點擊菜單就會彈出相應的程序執行文件操作。
在整個程序的編寫中,外殼擴展的服務器動態連接庫是有Delphi4.0編寫的,而動態連接庫調用的文件操作程序是由VB6編寫的。下面首先介紹服務器動態連接庫的編寫:
服務器動態連接庫的工程文件內容如下:
library contextmenu;
uses
ComServ,
ContextMenuHandler in Unit2.pas;
// contmenu_TLB in contmenu_TLB.pas;
exports
DllGetClassObject,
DllCanUnloadNow,
DllRegisterServer,
DllUnregisterServer;
{$R *.TLB}
{$R *.RES}
begin
end.
將工程文件保存為contextmenu.dpr。
服務器動態連接庫的單位文件內容如下:
unit ContextMenuHandler;
interface
uses Windows,ActiveX,ComObj,ShlObj,Classes;
type
TContextMenu = class(TComObject,IShellExtInit,IContextMenu)
private
FFileName: array[0..MAX_PATH] of Char;
protected
function IShellExtInit.Initialize = SEIInitialize; // Avoid compiler warning
function SEIInitialize(pidlFolder: PItemIDList; lpdobj: IDataObject;
hKeyProgID: HKEY): HResult; stdcall;
function QueryContextMenu(Menu: HMENU; indexMenu, idCmdFirst, idCmdLast,
uFlags: UINT): HResult; stdcall;
function InvokeCommand(var lpici: TCMInvokeCommandInfo): HResult; stdcall;
function GetCommandString(idCmd, uType: UINT; pwReserved: PUINT;
pszName: LPSTR; cchMax: UINT): HResult; stdcall;
end;
const
Class_ContextMenu: TGUID = {19741013-C829-11D1-8233-0020AF3E97A9};
{全局唯一標識符(GUID)是一個16字節(128為)的值,它唯一地標識一個接口(interface)}
var
FileList:TStringList;
Buffer:array[1..1024]of char;
implementation
uses ComServ, SysUtils, ShellApi, Registry,UnitForm;
function TContextMenu.SEIInitialize(pidlFolder: PItemIDList; lpdobj: IDataObject;
hKeyProgID: HKEY): HResult;
var
StgMedium: TStgMedium;
FormatEtc: TFormatEtc;
FileNumber,i:Integer;
begin
file://如果lpdobj等於Nil,則本調用失敗
if (lpdobj = nil) then begin
Result := E_INVALIDARG;
Exit;
end;
file://首先初始化並清空FileList以添加文件
FileList:=TStringList.Create;
FileList.Clear;
file://初始化剪貼版格式文件
with FormatEtc do begin
cfFormat := CF_HDROP;
ptd := nil;
dwAspect := DVASPECT_CONTENT;
lindex := -1;
tymed := TYMED_HGLOBAL;
end;
Result := lpdobj.GetData(FormatEtc,