explorer.exe 進程HKCR\CLSID\{GUID}、HKCR\*\shellex\ContextMenuHandlers、NT 系統額外 Approved 列表regsvr32.exe 或 Delphi Run → Register ActiveX Server 註冊必須實作三個介面:
IShellExtInit:初始化,取得使用者選取的檔案清單IContextMenu:建構右鍵選單項目、處理點擊命令、提供說明文字IComObjectFactory:COM 物件工廠 (Delphi TComObjectFactory 封裝)ContextMenu.dpr:DLL 專案,匯出 DllGetClassObject、DllCanUnloadNow、DllRegisterServer、DllUnregisterServerContextMenuHandle.pas:核心 COM 物件實作OpWindow.pas:檔案操作視窗表單 (ListBox + Copy/Move 按鈕)
function TContextMenu.SEIInitialize(pidlFolder: PItemIDList; lpdobj: IDataObject; hKeyProgID: HKEY): HResult;
var
StgMedium: TStgMedium;
FormatEtc: TFormatEtc;
FileCount, I: Integer;
begin
if lpdobj = nil then Exit(E_INVALIDARG);
FileList := TStringList.Create; FileList.Clear;
FormatEtc.cfFormat := CF_HDROP; // 檔案拖放格式
FormatEtc.ptd := nil; FormatEtc.dwAspect := DVASPECT_CONTENT;
FormatEtc.lindex := -1; FormatEtc.tymed := TYMED_HGLOBAL;
Result := lpdobj.GetData(FormatEtc, StgMedium);
if Failed(Result) then Exit;
FileCount := DragQueryFile(StgMedium.hGlobal, $FFFFFFFF, nil, 0);
for I := 0 to FileCount - 1 do begin
DragQueryFile(StgMedium.hGlobal, I, FFileName, SizeOf(FFileName));
FileList.Add(FFileName);
end;
ReleaseStgMedium(StgMedium);
Result := NOERROR;
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
InsertMenu(Menu, indexMenu, MF_STRING or MF_BYPOSITION, idCmdFirst, '檔案操作');
Result := 1; // 回傳新增項目數
end;
end;
function TContextMenu.InvokeCommand(var lpici: TCMInvokeCommandInfo): HResult;
var
Frm: TForm1;
begin
if HiWord(Integer(lpici.lpVerb)) <> 0 then Exit(E_FAIL); // 非選單命令
if LoWord(lpici.lpVerb) <> 0 then Exit(E_INVALIDARG); // 非第一個命令
Frm := TForm1.Create(nil);
Frm.ListBox1.Items := FileList; // 傳遞檔案清單
Result := NOERROR;
end;
function TContextMenu.GetCommandString(idCmd, uType: UINT; pwReserved: PUINT; pszName: LPSTR; cchMax: UINT): HRESULT;
begin
if (idCmd = 0) and (uType = GCS_HELPTEXT) then begin
StrCopy(pszName, '點擊該選單項將執行檔案操作');
Result := NOERROR;
end else Result := E_INVALIDARG;
end;
procedure TContextMenuFactory.UpdateRegistry(Register: Boolean);
var ClassID: string;
begin
if Register then begin
inherited UpdateRegistry(Register);
ClassID := GUIDToString(Class_ContextMenu);
CreateRegKey('*\shellex', '', '');
CreateRegKey('*\shellex\ContextMenuHandlers', '', '');
CreateRegKey('*\shellex\ContextMenuHandlers\FileOperation', '', ClassID);
if Win32Platform = VER_PLATFORM_WIN32_NT then
with TRegistry.Create do try
RootKey := HKEY_LOCAL_MACHINE;
OpenKey('SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved', True);
WriteString(ClassID, 'Context Menu Shell Extension');
finally Free; end;
end else begin
DeleteRegKey('*\shellex\ContextMenuHandlers\FileOperation');
inherited UpdateRegistry(Register);
end;
end;
procedure TForm1.Button1Click(Sender: TObject); // 複製
var
Path: string;
FS: SHFILEOPSTRUCT;
I: Integer;
begin
Path := InputBox('檔案操作', '輸入複製路徑', 'C:\Windows');
if Path <> '' then
for I := 0 to ListBox1.Items.Count - 1 do begin
FillChar(FS, SizeOf(FS), 0);
FS.Wnd := Handle;
FS.wFunc := FO_COPY;
FS.fFlags := FOF_ALLOWUNDO;
FS.pFrom := PChar(ListBox1.Items[I] + #0); // 雙重 Null 結尾
FS.pTo := PChar(Path + #0);
FS.lpszProgressTitle := '複製檔案';
if SHFileOperation(FS) <> 0 then ShowMessage('複製失敗');
end;
end;
ContextMenu.dllregsvr32 ContextMenu.dllHKCR\*\shell\YourCommand + command 值指向 EXE 參數 %1,無需 DLL 注入Shell Context Menu Handler 是經典 COM 技術展示。完整實作涵蓋 IShellExtInit 初始化取檔 → IContextMenu 構建選單/執行/提示 → COM Factory 註冊。雖現代 Windows 推薦輕量替代方案,但理解此架構對維護舊系統、深度整合 Explorer 仍具不可替代價值。