在Win32作業系統(包括Win9X、Windows NT、Windows 2000)不但有方便的圖形使用者(GUI)介面,微軟還為windows使用者介面保留了強大的可擴充性。其中對於Windows介面的作業環境(這裡稱為外殼Shell),微軟提供了一種稱為外殼擴充(Shell Extensions)的功能來實現檔案系統操作的可程式化性。如果你的機器中安裝了Word 7.0以上的版本,當你滑鼠右鍵單擊一個DOC檔案,在快顯功能表中選“屬性”項,在屬性頁面中不僅顯示顯示檔案的大小、建立日期等資訊,同時還增加了Doc文檔的摘要、統計等資訊;又例如安裝了winZip 6.0以上版本後,當選中一個或多個檔案或檔案夾後在單擊滑鼠右鍵,在彈出的右鍵菜單中就增加了“Add To Zip”等一個zip檔案壓縮選項。上面的這些功能都是通過Windows外殼擴充來實現的。
Windows外殼擴充是這樣實現的。首先要編寫外殼擴充程式,一個外殼擴充程式是基於COM(Component Object Model)組件模型的。外殼是通過介面(Interface)來訪問對象的。外殼擴充被設計成32位的進程中伺服器程式,並且都是以動態連結程式庫的形式為作業系統提供服務的。
寫好外殼擴充程式後,必須將它們註冊才會生效。所有的外殼擴充都必須在Windows註冊表的HKEY_CLASSES_ROOT/CLSID鍵之下進行註冊。在該鍵下面可以找到許多名字像{ACDE002F-0000-0000-C000-000000000046}的鍵,這類鍵就是全域唯一類別識別項。每一個外殼擴充都必須有一個全域唯一類別識別項,Windows正是通過此唯一類別識別項來找到外殼延伸模組處理常式的。在類別識別項之下的InProcServer32子鍵下記錄著外殼擴充動態連結程式庫在系統中的位置。
Windows系統支援以下7類的外殼擴充功能:
(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在檔案被拖放、拷貝或者粘貼時,就會被系統被調用。
本文介紹的檔案夾保護功能就是通過上面的第5類,既Copy-hook handlers來實現的。一個支援Copy-hook handlers的程式除了上面提到的要在註冊表的HKEY_CLASSES_ROOT/CLSID下註冊之外,還需要在HKEY_CLASSES_ROOT/Directory/shellex/CopyHookHandlers/下註冊伺服器程式的類。
由於Windows外殼伺服器程式是基於COM組件模型的,所以編寫外殼程式就是構造一個COM對象的過程,由於Delphi4.0以上的版本支援Windows外殼擴充和COM組件模型,所以可以利用Delphi來編寫外殼擴充程式。
利用Delphi編寫Copy-hook handle需要實現ICopyHook介面。ICopyHook是一個十分簡單的介面,要實現的只有CopyCallBack方法。ICopyHook的CopyCallBack方法的定義如下:
UINT CopyCallback(
HWND hwnd, file://Handle of the parent window for displaying UI objects
UINT wFunc, file://Operation to perform.
UINT wFlags, file://Flags that control the operation
LPCSTR pszSrcFile, file://Pointer to the source file
DWORD dwSrcAttribs, file://Source file attributes
LPCSTR pszDestFile, file://Pointer to the destination file
DWORD dwDestAttribs file://Destination file attributes
);
其中的參數hwnd是一個視窗控制代碼,Copy-hook handle以此為父視窗。參數wFunc指定要被執行的操作,其取值為下表中所列之一:
常量 取值 含義
FO_COPY $2 複製由pszSrcFile指定的檔案到由pszDestFile指定的位置。
FO_DELETE $3 刪除由pszSrcFile指定的檔案。
FO_MOVE $1 移動由pszSrcFile指定的檔案到由pszDestFile指定的位置。
FO_RENAME $4 重新命名由pszSrcFile指定的檔案到由pszDestFile指定的檔案名稱。
PO_DELETE $13 刪除pszSrcFile指定的印表機。
PO_PORTCHANGE $20 改變印表機連接埠。PszSrcFile和pszDestFile為兩個以Null結尾的字串,分別指定當前和新的印表機連接埠名。
PO_RENAME $14 重新命名由pszSrcFile指定的印表機連接埠。
PO_REN_PORT $34 PO_RENAME和PO_PORTCHANGE的組合。
參數wFlags指定操作的標誌;參數pszSrcFile和pszDestFile指定源檔案夾和目標檔案夾。參數dwSrcAttribs和dwDesAttribs指定源檔案夾和目標檔案夾的屬性。函數傳回值可以為IDYES、IDNO和IDCANCEL。分別指示Windows外殼允許操作、阻止操作,但是其他動作繼續、阻止當前操作,取消為執行的操作。
下面是具體的程式實現:
首先在Delphi的菜單中選 File|New選項,選擇其中的DLL表徵圖,按Ok鍵建立一個DLL工程檔案,在其中添加以下代碼:
library CopyHook;
uses
ComServ,
CopyMain in 'CopyMain.pas';
exports
DllGetClassObject,
DllCanUnloadNow,
DllRegisterServer,
DllUnregisterServer;
{$R *.TLB}
{$R *.RES}
begin
end.
將檔案儲存為 CopyHook.dpr。再在Delphi菜單中選File|New選項,選擇其中的Unit表徵圖,按Ok鍵建立一個Pas檔案,在其中加入以下代碼:
unit CopyMain;
interface
uses Windows, ComObj, ShlObj;
type
TCopyHook = class(TComObject, ICopyHook)
protected
function CopyCallback(Wnd: HWND; wFunc, wFlags: UINT; pszSrcFile: PAnsiChar;
dwSrcAttribs: DWORD; pszDestFile: PAnsiChar; dwDestAttribs: DWORD): UINT; stdcall;
end;
TCopyHookFactory = class(TComObjectFactory)
protected
function GetProgID: string; override;
procedure ApproveShellExtension(Register: Boolean; const ClsID: string);
virtual;
public
procedure UpdateRegistry(Register: Boolean); override;
end;
implementation
uses ComServ, SysUtils, Registry;
{ TCopyHook }
file://當Windows外殼程式執行檔案夾或者印表機連接埠操作時,CopyCallBack
file://方法就會被調用。
function TCopyHook.CopyCallback(Wnd: HWND; wFunc, wFlags: UINT;
pszSrcFile: PAnsiChar; dwSrcAttribs: DWORD; pszDestFile: PAnsiChar;
dwDestAttribs: DWORD): UINT;
const
FO_COPY = 2;
FO_DELETE = 3;
FO_MOVE = 1;
FO_RENAME = 4;
var
sOp:string;
begin
Case wFunc of
FO_COPY: sOp:=format('你確定要將 %s 拷貝到 %s 嗎?',[pszSrcFile,pszDestFile]);
FO_DELETE: sOp:=format('你確定要將 %s 刪除嗎?',[pszSrcFile]);
FO_MOVE: sOp:=format('你確定要將 %s 轉移到 %s 嗎?',[pszSrcFile,pszDestFile]);
FO_RENAME: sOp:=format('你確定要將 %s 重新命名為 %s 嗎?',[pszSrcFile,pszDestFile]);
else
sOp:=format('無法識別的作業碼 %d',[wFlags]);
end;
// 提示,讓使用者決定是否執行操作
Result := MessageBox(Wnd, PChar(sOp),
'檔案掛鈎示範', MB_YESNOCANCEL);
end;
{ TCopyHookFactory }
function TCopyHookFactory.GetProgID: string;
begin
Result := ';
end;
procedure TCopyHookFactory.UpdateRegistry(Register: Boolean);
var
ClsID: string;
begin
ClsID := GUIDToString(ClassID);
inherited UpdateRegistry(Register);
ApproveShellExtension(Register, ClsID);
if Register then
file://將clsid 加入到註冊表的CopyHookHandlers中
CreateRegKey('directory/shellex/CopyHookHandlers/' + ClassName, ',
ClsID)
else
DeleteRegKey('directory/shellex/CopyHookHandlers/' + ClassName);
end;
procedure TCopyHookFactory.ApproveShellExtension(Register: Boolean;
const ClsID: string);
const
SApproveKey = 'SOFTWARE/Microsoft/Windows/CurrentVersion/Shell Extensions/Approved';
begin
with TRegistry.Create do
try
RootKey := HKEY_LOCAL_MACHINE;
if not OpenKey(SApproveKey, True) then Exit;
if Register then WriteString(ClsID, Description)
else DeleteValue(ClsID);
finally
Free;
end;
end;
const
CLSID_CopyHook: TGUID = '{66CD5F60-A044-11D0-A9BF-00A024E3867F}';
LIBID_CopyHook: TGUID = '{D2F531A0-0861-11D2-AE5C-74640BC10000}';
initialization
TCopyHookFactory.Create(ComServer, TCopyHook, CLSID_CopyHook,
'CR_CopyHook', '檔案操作掛鈎示範',ciMultiInstance, tmApartment);
end.
將檔案儲存為CopyMain.Pas檔案,然後編譯器為CopyHook.Dll檔案,然後註冊CopyHook.Dll檔案,你可以使用Windows提供的RegSvr32.exe來註冊,註冊的方法是在Dos視窗中進入Windows的System子目錄,然後在其中輸入Regsvr32 x:/xxx/xxx/copyhook.dll ,其中x:/xxx/xxx/是編譯的CopyHook.dll所在的全路徑名。也可以在Run菜單中選擇Register ActiveX Server來註冊。
當檔案註冊成功之後,在Windows的Explore中任意改變一個檔案夾的名字或者移動一個目錄,就會有一個提示框彈出,提示使用者是否確定執行操作。:
按“是”將執行檔案夾操作,按“否”或者“取消”將取消相應的檔案夾操作。
上面介紹的只是Delphi實現Windows外殼擴充的一種,參照上面的程式和Delphi關於Windows的COM組件模型的編程,就可以編寫出十分專業化的Windows外殼擴充程式。