Web開發(fā)中遠(yuǎn)程腳本的應(yīng)用
Windows外殼擴(kuò)展的使用
在Windows下的一些軟件提供了這樣的功能:當(dāng)安裝了這些軟件之后,當(dāng)在Windows的Explore中鼠標(biāo)右鍵單擊文件或者文件夾后,在彈出菜單中就會(huì)多出與該軟件操作相關(guān)的菜單項(xiàng),點(diǎn)擊該項(xiàng)就會(huì)激活相應(yīng)的程序?qū)τ脩暨x中的文件進(jìn)行相應(yīng)的操作。例如安裝了Winzip之后,當(dāng)用戶選中一個(gè)文件夾后單擊右鍵,在彈出菜單中就會(huì)多出一個(gè)Add To Zip和一個(gè) Add To xxx.zip的選項(xiàng),其中xxx為選中的文件夾的名稱。只要單擊上面的兩個(gè)菜單項(xiàng)中的一個(gè),就可以方便的壓縮目錄了。這樣的功能稱為Windows外殼擴(kuò)展(Shell Extensions)
外殼擴(kuò)展概述
下面是與外殼擴(kuò)展相關(guān)的三個(gè)重要術(shù)語(yǔ):
(1)文件對(duì)象(File Object)
文件對(duì)象是外殼中的一項(xiàng),大家最熟識(shí)的文件對(duì)象是文件和目錄,此外,打印機(jī)、控制面板程序、共享網(wǎng)
絡(luò)等也都是文件對(duì)象。
(2)文件類(File Class)
文件類是具有某種共同特性的文件對(duì)象的集合,比如,擴(kuò)展名相同的文件屬于同一文件類。
(3)處理程序(Handler)
處理程序是具體實(shí)現(xiàn)某個(gè)外殼擴(kuò)展的代碼。
Windows支持七種類型的外殼擴(kuò)展(稱為Handler),它們相應(yīng)的作用簡(jiǎn)述如下:
(1)Context menu handlers向特定類型的文件對(duì)象增添上下文相關(guān)菜單;
(2)Drag-and-drop handlers用來支持當(dāng)用戶對(duì)某種類型的文件對(duì)象進(jìn)行拖放操作時(shí)的OLE數(shù)據(jù)傳輸;
(3)Icon handlers用來向某個(gè)文件對(duì)象提供一個(gè)特有的圖標(biāo),也可以給某一類文件對(duì)象指定圖標(biāo);
(4)Property sheet handlers給文件對(duì)象增添屬性頁(yè),屬性頁(yè)可以為同一類文件對(duì)象所共有,也可以給一個(gè)
文件對(duì)象指定特有的屬性頁(yè);
(5)Copy-hook handlers在文件夾對(duì)象或者打印機(jī)對(duì)象被拷貝、移動(dòng)、刪除和重命名時(shí),就會(huì)被系統(tǒng)調(diào)用,
通過為Windows增加Copy-hook handlers,可以允許或者禁止其中的某些操作;
(6)Drop target handlers在一個(gè)對(duì)象被拖放到另一個(gè)對(duì)象上時(shí),就會(huì)被系統(tǒng)被調(diào)用;
(7)Data object handlers在文件被拖放、拷貝或者粘貼時(shí),就會(huì)被系統(tǒng)被調(diào)用。
Windows的所有外殼擴(kuò)展都是基于COM(Component Object Model) 組件模型的,外殼是通過接口(Interface)來訪問對(duì)象的。外殼擴(kuò)展被設(shè)計(jì)成32位的進(jìn)程中服務(wù)器程序,并且都是以動(dòng)態(tài)鏈接庫(kù)的形式為操作系統(tǒng)提供服務(wù)的。因此,如果要對(duì)Windows的用戶界面進(jìn)行擴(kuò)充的話,則具備寫COM對(duì)象的一些知識(shí)是十分必要的。
寫好外殼擴(kuò)展程序后,必須將它們注冊(cè)才能生效。所有的外殼擴(kuò)展都必須在Windows注冊(cè)表的HKEY_CLASSES_ROOTCLSID鍵之下進(jìn)行注冊(cè)。在該鍵下面可以找到許多名字像{0000002F-0000-0000-C000-000000000046}的鍵,這類鍵就是全局唯一類標(biāo)識(shí)符。每一個(gè)外殼擴(kuò)展都必須有一個(gè)全局唯一類標(biāo)識(shí)符,Windows正是通過此唯一類標(biāo)識(shí)符來找到外殼擴(kuò)展處理程序的。在類標(biāo)識(shí)符之下的InProcServer32子鍵下記錄著外殼擴(kuò)展動(dòng)態(tài)鏈接庫(kù)在系統(tǒng)中的位置。與某種文件類型關(guān)聯(lián)的外殼擴(kuò)展注冊(cè)在相應(yīng)類型的shellex主鍵下。如果所處的Windows操作系統(tǒng)為Windows NT,則外殼擴(kuò)展還必須在注冊(cè)表中的HKEY_LOCAL_MACHINESoftwareMicrosoftWindowsCurrentVersionShellExtensionsApproved主鍵下登記。
注冊(cè)表HKEY_CLASSES_ROOT主鍵下有幾個(gè)特殊的子鍵,如*、Folder、Drive以及Printer。如果把外殼擴(kuò)展注冊(cè)在*子鍵下,那么這個(gè)外殼擴(kuò)展將對(duì)Windows中所有類型的文件有效;如果把外殼擴(kuò)展注冊(cè)在Folder子鍵下,則對(duì)所有目錄有效。
上面提到的在Windows Explore中在鼠標(biāo)右鍵菜單中添加菜單項(xiàng)(我們成為上下文相關(guān)菜單)的操作屬于外殼擴(kuò)展的第一類,即Context menu handlers向特定類型的文件對(duì)象增添上下文相關(guān)菜單。要?jiǎng)討B(tài)地在上下文相關(guān)菜單中增添菜單項(xiàng),可以通過寫Context Menu Handler來實(shí)現(xiàn)。
編寫Context Menu Handler必須實(shí)現(xiàn)IShellExtInit和IContextMenu兩個(gè)接口。除了IUnknown接口所定義的函數(shù)之外,Context Menu Handler還需要用到QueryContextMenu、InvokeCommand和GetCommandString這三個(gè)非常重要的成員函數(shù)。
(1)QueryContextMenu函數(shù):每當(dāng)系統(tǒng)要顯示一個(gè)文件對(duì)象的上下文相關(guān)菜單時(shí),它首先要調(diào)用該函數(shù)。為了在上下文相關(guān)菜單中添加菜單
項(xiàng),我們?cè)谠摵瘮?shù)中調(diào)用InsertMenu函數(shù)。
(2)InvokeCommand函數(shù):當(dāng)用戶選定了某個(gè)Context Menu Handler登記過的菜單項(xiàng)后,該函數(shù)將會(huì)被調(diào)用,系統(tǒng)將會(huì)傳給該函數(shù)一個(gè)指向
LPCMINVOKECOMMANDINFO結(jié)構(gòu)的指針。在該函數(shù)中要執(zhí)行與所選菜單項(xiàng)相對(duì)應(yīng)的操作。
(3)GetCommandString函數(shù):當(dāng)鼠標(biāo)指針移到一個(gè)上下文相關(guān)菜單項(xiàng)上時(shí),在當(dāng)前窗口的狀態(tài)條上將會(huì)出現(xiàn)與該菜單項(xiàng)相關(guān)的幫助信息,此
信息就是系統(tǒng)通過調(diào)用該函數(shù)獲取的。
下面我通過具體的例程來說明編寫一個(gè)比較完整的上下文菜單程序,這個(gè)程序是一個(gè)文件操作程序,當(dāng)安裝并注冊(cè)了外殼擴(kuò)展的服務(wù)器動(dòng)態(tài)連接庫(kù)之后,當(dāng)選擇一個(gè)或者多個(gè)文件并單擊鼠標(biāo)右鍵后,在右鍵菜單中就會(huì)多出一個(gè)“執(zhí)行文件操作”的上下文菜單,點(diǎn)擊菜單就會(huì)彈出相應(yīng)的程序執(zhí)行文件操作。
在整個(gè)程序的編寫中,外殼擴(kuò)展的服務(wù)器動(dòng)態(tài)連接庫(kù)是有Delphi4.0編寫的,而動(dòng)態(tài)連接庫(kù)調(diào)用的文件操作程序是由VB6編寫的。下面首先介紹服務(wù)器動(dòng)態(tài)連接庫(kù)的編寫:
服務(wù)器動(dòng)態(tài)連接庫(kù)的工程文件內(nèi)容如下:
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。
服務(wù)器動(dòng)態(tài)連接庫(kù)的單位文件內(nèi)容如下:
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}';
{全局唯一標(biāo)識(shí)符(GUID)是一個(gè)16字節(jié)(128為)的值,它唯一地標(biāo)識(shí)一個(gè)接口(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
//如果lpdobj等于Nil,則本調(diào)用失敗
if (lpdobj = nil) then begin
Result := E_INVALIDARG;
Exit;
end;
//首先初始化并清空FileList以添加文件
FileList:=TStringList.Create;
FileList.Clear;
//初始化剪貼版格式文件
with FormatEtc do begin
cfFormat := CF_HDROP;
ptd := nil;
dwAspect := DVASPECT_CONTENT;
lindex := -1;
tymed := TYMED_HGLOBAL;
end;
Result := lpdobj.GetData(FormatEtc, StgMedium);
if Failed(Result) then Exit;
//首先查詢用戶選中的文件的個(gè)數(shù)
FileNumber := DragQueryFile(StgMedium.hGlobal,$FFFFFFFF,nil,0);
//循環(huán)讀取,將所有用戶選中的文件保存到FileList中
for i:=0 to FileNumber-1 do begin
DragQueryFile(StgMedium.hGlobal, i, FFileName, SizeOf(FFileName));
FileList.Add(FFileName);
Result := NOERROR;
end;
ReleaseStgMedium(StgMedium);
end;
function TContextMenu.QueryContextMenu(Menu: HMENU; indexMenu, idCmdFirst,
idCmdLast, uFlags: UINT): HResult;
begin
Result := 0;
if ((uFlags and $0000000F) = CMF_NORMAL) or
((uFlags and CMF_EXPLORE) <> 0) then begin
// 往Context Menu中加入一個(gè)菜單項(xiàng)
InsertMenu(Menu, indexMenu, MF_STRING or MF_BYPOSITION, idCmdFirst,
PChar('執(zhí)行文件操作'));
// 返回增加菜單項(xiàng)的個(gè)數(shù)
Result := 1;
end;
end;
function TContextMenu.InvokeCommand(var lpici: TCMInvokeCommandInfo): HResult;
var
// sFile:TFileStream;
charSavePath:array[0..1023]of char;
sSaveFile:String;
i:Integer;
F: TextFile;
FirstLine: string;
begin
// 首先確定該過程是被系統(tǒng)而不是被一個(gè)程序所調(diào)用
if (HiWord(Integer(lpici.lpVerb)) <> 0) then
begin
Result := E_FAIL;
Exit;
end;
// 確定傳遞的參數(shù)的有效性
if (LoWord(lpici.lpVerb) <> 0) then begin
Result := E_INVALIDARG;
Exit;
end;
//建立一個(gè)臨時(shí)文件保存用戶選中的文件名
GetTempPath(1024,charSavePath);
sSaveFile:=charSavePath+'chen0001.tmp';
AssignFile(F,sSaveFile); { next file in Files property }
ReWrite(F);
//將文件名保存到臨時(shí)文件中
for i:= 0 to FileList.Count -1 do begin
FirstLine:=FileList.Strings;
Writeln(F,FirstLine); { Read the first line out of the file }
end;
CloseFile(F);
//調(diào)用文件操作程序?qū)τ脩暨x中的文件進(jìn)行操作
ShellExecute(0,nil,'c:FileOP.exe',PChar(sSaveFile),charSavePath,SW_NORMAL);
Result := NOERROR;
end;
function TContextMenu.GetCommandString(idCmd, uType: UINT; pwReserved: PUINT;
pszName: LPSTR; cchMax: UINT): HRESULT;
begin
if (idCmd = 0) then begin
if (uType = GCS_HELPTEXT) then
{返回該菜單項(xiàng)的幫助信息,此幫助信息將在用戶把鼠標(biāo)移動(dòng)到該菜單項(xiàng)時(shí)出現(xiàn)在狀態(tài)條上。}
StrCopy(pszName, PChar('點(diǎn)擊該菜單項(xiàng)將執(zhí)行文件操作'));
Result := NOERROR;
end
else
Result := E_INVALIDARG;
end;
type
TContextMenuFactory = class(TComObjectFactory)
public
procedure UpdateRegistry(Register: Boolean); override;
end;
procedure TContextMenuFactory.UpdateRegistry(Register: Boolean);
var
ClassID: string;
begin
if Register then begin
inherited UpdateRegistry(Register);
ClassID := GUIDToString(Class_ContextMenu);
CreateRegKey('*shellex', '', '');
CreateRegKey('*shellexContextMenuHandlers', '', '');
CreateRegKey('*shellexContextMenuHandlersOpenWithWordPad', '', ClassID);
//如果操作系統(tǒng)為Windows NT的話
if (Win32Platform = VER_PLATFORM_WIN32_NT) then
with TRegistry.Create do
try
RootKey := HKEY_LOCAL_MACHINE;
OpenKey('SOFTWAREMicrosoftWindowsCurrentVersionShell Extensions', True);
OpenKey('Approved', True);
WriteString(ClassID, 'Context Menu Shell Extension');
finally
Free;
end;
end
else begin
DeleteRegKey('*shellexContextMenuHandlersFileOpreation');
DeleteRegKey('*shellexContextMenuHandlers');
// DeleteRegKey('*shellex');
inherited UpdateRegistry(Register);
end;
end;
initialization
TContextMenuFactory.Create(ComServer, TContextMenu, Class_ContextMenu,
'', 'Context Menu Shell Extension', ciMultiInstance,tmApartment);
end.
將該單位文件保存為unit2.pas,文件同contextmenu.dpr位于同一個(gè)目錄下。
打開Delphi,選菜單中的 file | open project 打開contextmenu.dpr文件,然后選 Project | build contextmenu菜單項(xiàng)編譯連接程序,如果編譯成功的話,會(huì)建立一個(gè)contextmenu.dll的動(dòng)態(tài)連接庫(kù)文件,這個(gè)文件就是服務(wù)器動(dòng)態(tài)連接庫(kù)。
下面來建立文件操作程序。打開VB,建立一個(gè)新的工程文件,在Form1中加入一個(gè)ListBox控件和三個(gè)CommandButton控件,將ListBox的MultiSelect屬性設(shè)置為2。然后在Form1的代碼窗口中加入以下代碼:
Option Explicit
Private Type BrowseInfo
hwndOwner As Long
pIDLRoot As Long
pszDisplayName As Long
lpszTitle As Long
ulFlags As Long
lpfnCallback As Long
lParam As Long
iImage As Long
End Type
Private Type SHFILEOPSTRUCT
hwnd As Long
wFunc As Long '對(duì)文件的操作指令
pFrom As String '源文件或路徑
pTo As String '目的文件或路徑
fFlags As Integer '操作標(biāo)志
fAnyOperationsAborted As Long
hNameMappings As Long
lpszProgressTitle As String
End Type
Const FO_COPY = &H2
Const FO_DELETE = &H3
Const FO_MOVE = &H1
Const FO_RENAME = &H4
Const FOF_ALLOWUNDO = &H40
Const BIF_RETURNONLYFSDIRS = 1
Const MAX_PATH = 260
Private Declare Function ShellAbout Lib "shell32.dll" Alias _
"ShellAboutA" (ByVal hwnd As Long, ByVal szApp As _
String, ByVal szOtherStuff As String, ByVal hIcon As Long) _
As Long
Private Declare Sub CoTaskMemFree Lib "ole32.dll" (ByVal hMem As Long)
Private Declare Function lstrcat Lib "kernel32" Alias _
"lstrcatA" (ByVal lpString1 As String, ByVal lpString2 _
As String) As Long
Private Declare Function SHBrowseForFolder Lib "shell32" (lpbi _
As BrowseInfo) As Long
Private Declare Function SHGetPathFromIDList Lib "shell32" _
(ByVal pidList As Long, ByVal lpBuffer As String) As Long
Private Declare Function SHFileOperation Lib "shell32" _
(lpFileOp As SHFILEOPSTRUCT) As Long
Private Declare Function GetWindowsDirectory _
Lib "kernel32" Alias "GetWindowsDirectoryA" _
(ByVal lpBuffer As String, ByVal nSize As _
Long) As Long
Dim DirString As String
Dim sFile As String
Sub UpdateList()
'UpdateList函數(shù)檢查列表框中的文件是否存在,如果不存在,就將其
'從文件列表中刪除
Dim bEndList As Boolean
Dim i As Integer
bEndList = True
i = 0
While bEndList
'檢查文件是否存在,如果不存在就刪除
If Dir$(List1.List(i)) = "" Then
List1.RemoveItem (i)
Else '如果文件存在就轉(zhuǎn)移到下一個(gè)列表項(xiàng)
i = i + 1
If i > List1.ListCount - 1 Then
bEndList = False
End If
End If
Wend
Command1.Enabled = False
Command2.Enabled = False
Command3.Enabled = False
End Sub
Function BrowseForFolder(hwndOwner As Long, sPrompt As String) As String
Dim iNull As Integer
Dim lpIDList As Long
Dim lResult As Long
Dim sPath As String
Dim udtBI As BrowseInfo
'初試化udtBI結(jié)構(gòu)
With udtBI
.hwndOwner = hwndOwner
.lpszTitle = lstrcat(sPrompt, "")
.ulFlags = BIF_RETURNONLYFSDIRS
End With
'彈出文件夾查看窗口
lpIDList = SHBrowseForFolder(udtBI)
If lpIDList Then
sPath = String$(MAX_PATH, 0)
lResult = SHGetPathFromIDList(lpIDList, sPath)
Call CoTaskMemFree(lpIDList)
iNull = InStr(sPath, vbNullChar)
If iNull Then sPath = Left$(sPath, iNull - 1)
End If
BrowseForFolder = sPath
End Function
Private Sub Command1_Click() '執(zhí)行文件拷貝操作
Dim sPath As String
Dim tCopy As SHFILEOPSTRUCT
Dim i As Integer
'選擇拷貝到的文件夾
sPath = BrowseForFolder(Form1.hwnd, "選擇拷貝到的文件夾")
If sPath <> "" Then
With tCopy
.hwnd = Form1.hwnd
.lpszProgressTitle = "正在拷貝"
.pTo = sPath
.fFlags = FOF_ALLOWUNDO
.wFunc = FO_COPY
End With
For i = 0 To List1.ListCount - 1
If List1.Selected(i) Then '如果文件被選中則拷貝文件
tCopy.pFrom = List1.List(i)
SHFileOperation tCopy
End If
Next i
UpdateList
End If
Kill sFile
End Sub
Private Sub Command2_Click() '執(zhí)行文件移動(dòng)操作
Dim sPath As String
Dim tCopy As SHFILEOPSTRUCT
Dim i As Integer
'選擇移動(dòng)到的文件夾
sPath = BrowseForFolder(Form1.hwnd, "選擇轉(zhuǎn)移到的文件夾")
If sPath <> "" Then
With tCopy
.hwnd = Form1.hwnd
.lpszProgressTitle = "正在移動(dòng)"
.pTo = sPath
.fFlags = FOF_ALLOWUNDO
.wFunc = FO_MOVE
End With
For i = 0 To List1.ListCount - 1
If List1.Selected(i) Then '如果文件被選中則拷貝文件
tCopy.pFrom = List1.List(i)
SHFileOperation tCopy
End If
Next i
UpdateList
End If
Kill sFile
End Sub
Private Sub Command3_Click() '執(zhí)行文件刪除操作
Dim sPath As String
Dim tCopy As SHFILEOPSTRUCT
Dim i As Integer
With tCopy
.hwnd = Form1.hwnd
.lpszProgressTitle = "正在刪除"
.pTo = sPath
.fFlags = FOF_ALLOWUNDO
.wFunc = FO_DELETE
End With
For i = 0 To List1.ListCount - 1
If List1.Selected(i) Then
tCopy.pFrom = List1.List(i)
SHFileOperation tCopy
End If
Next i
UpdateList
Kill sFile
End Sub
Private Sub Form_Load()
Dim hFileHandle As Long
Dim TextLine As String
Command1.Caption = "拷貝"
Command2.Caption = "移動(dòng)"
Command3.Caption = "刪除"
Command1.Enabled = False
Command2.Enabled = False
Command3.Enabled = False
'sFile接受由Windows外殼擴(kuò)展庫(kù)contextmenu.dll傳遞過來的文件參數(shù)
sFile = Command$
hFileHandle = FreeFile
Open sFile For Input As hFileHandle
Do While Not EOF(hFileHandle)
Line Input #1, TextLine
If Dir$(TextLine) <> "" Then
List1.AddItem TextLine
End If
Loop
Close hFileHandle
End Sub
Private Sub Form_Unload(Cancel As Integer)
If Dir$(sFile) <> "" Then
Kill sFile
End If
End Sub
Private Sub List1_Click()
If Not Command1.Enabled Then
Command1.Enabled = True
Command2.Enabled = True
Command3.Enabled = True
End If
End Sub
保存文件并將工程文件編譯為FileOP.exe文件,將文件拷貝到C盤根目錄下。然后注冊(cè)contextmenu.dll,注冊(cè)的方法是,在DOS窗口中進(jìn)入Windowssystem子目錄,輸入 Regsvr32 x:xxxxxcontextmenu.dll 。其中x:xxxxx為Contextmenu.dll文件所在的驅(qū)動(dòng)器和目錄。如果注冊(cè)成功,系統(tǒng)會(huì)彈出對(duì)話框,顯示 DllRegisterServer in ..xxxcontextmenu.dll Success 提示注冊(cè)成功。
注冊(cè)成功后,再選擇文件并單擊右鍵,就會(huì)發(fā)現(xiàn)在彈出菜單中多了一個(gè)“執(zhí)行文件操作”的菜單項(xiàng),點(diǎn)擊該項(xiàng),系統(tǒng)就會(huì)調(diào)用FileOP.exe執(zhí)行文件操作,在窗口的列表框中會(huì)出現(xiàn)用戶選擇的文件名,點(diǎn)擊相應(yīng)的文件并點(diǎn)擊“拷貝”、“移動(dòng)”或“刪除”按鈕就可以對(duì)列表框中的選中的文件進(jìn)行相應(yīng)的操作。
上面介紹的只是Context Menu Handler的應(yīng)用的一個(gè)框架,各位讀者可以根據(jù)需要在上面的程序的基礎(chǔ)上做修改,為自己的程序建立上下文菜單擴(kuò)展。
上面的程序Pascal部分在Delphi4.0下,Basic部分在VB6.0英文版下,在Windows98中文版下編譯運(yùn)行通過。


