Shell Extension para Windows 32-bit e 64-bit com Delphi XE2
Com o suporte a compilação para windows 64-bit no Delphi XE2, desenvolvedores passam a poder criar Shell Extensions para Windows 64-bit. Shell Extensions são objetos COM que estendem as capacidades do sistema operacional Windows. Nesta primera parte do artigo explico como criar, compilar para 32-bit e 64-bit e registrar um shell extension. O exemplo a ser utilizado irá adicionar dois menus de contexto ao Windows Explorer, estes novos menus terão como funcionalidade permitir que os usuários façam upload de arquivos para as nuvens Microsoft Azure e Amazon S3. Na segunda parte do artigo irei explicar como fazer o upload de arquivos para o Amazon S3 e Microsoft Azure utilizando o Cloud API.
Criando o CloudUpload Shell Extension
Para criar um shell extension em Delphi o primeiro passo é criar um projeto ActiveX Library e em seguinda criar um Automation Object. O exemplo utilizado neste artigo tem como nome de projeto CloudUpload e o Automation Object se chama TCloudUploadContext. Para integrar menus de contexto ao Windows Explorer a classe TCloudUploadContext terá de implementar as interfaces IShellExtInit e IContextMenu e seus respectivos métodos.
{ IShellExtInit Methods } { Initialize the context menu if a files was selected} function IShellExtInit.Initialize = ShellExtInitialize; function ShellExtInitialize(pidlFolder: PItemIDList; lpdobj: IDataObject; hKeyProgID: HKEY): HResult; stdcall; { IContextMenu Methods } { Initializes the context menu and it decides which items appear in it, based on the flags you pass } function QueryContextMenu(Menu: HMENU; indexMenu, idCmdFirst, idCmdLast, uFlags: UINT): HResult; stdcall; { Execute the command, which will be the upload to Amazon or Azure} function InvokeCommand(var lpici: TCMInvokeCommandInfo): HResult; stdcall; { Set help string on the Explorer status bar when the menu item is selected } function GetCommandString(idCmd: UINT_PTR; uFlags: UINT; pwReserved: PUINT; pszName: LPSTR; cchMax: UINT): HResult; stdcall;
O ShellExtInitialize define se o menu de contexto será exibido ou não no Windows Explorer. Neste exemplo o menu de contexto aparece apenas e somente quando um arquivo for selecionado, a partir dai armazenamos o nome do arquivo na variável FFileName.
function TCloudUploadContextMenu.ShellExtInitialize(pidlFolder: PItemIDList; lpdobj: IDataObject; hKeyProgID: HKEY): HResult; var DataFormat: TFormatEtc; StrgMedium: TStgMedium; Buffer: array [0 .. MAX_PATH] of Char; begin Result := E_FAIL; { Check if an object was defined } if lpdobj = nil then Exit; { Prepare to get information about the object } DataFormat.cfFormat := CF_HDROP; DataFormat.ptd := nil; DataFormat.dwAspect := DVASPECT_CONTENT; DataFormat.lindex := -1; DataFormat.tymed := TYMED_HGLOBAL; if lpdobj.GetData(DataFormat, StrgMedium) <> S_OK then Exit; { The implementation now support only one file } if DragQueryFile(StrgMedium.hGlobal, $FFFFFFFF, nil, 0) = 1 then begin SetLength(FFileName, MAX_PATH); DragQueryFile(StrgMedium.hGlobal, 0, @Buffer, SizeOf(Buffer)); FFileName := Buffer; Result := NOERROR; end else begin // Don't show the Menu if more then one file was selected FFileName := EmptyStr; Result := E_FAIL; end; { http://msdn.microsoft.com/en-us/library/ms693491(v=vs.85).aspx } ReleaseStgMedium(StrgMedium); end;
Depois de inicializado o handle do menu de contexto através da interface IShellExiInit, o Windows utilizar a interface IContextMenu para chamar os outros métodos do handle do menu de contexto, neste caso irá chamar QueryContextMenu, GetCommandString e InvokeCommand.
As opções do menu de contexto (Amazon S3, Microsoft Azure) serão criadas através do método QueryContextMenu.
function TCloudUploadContextMenu.QueryContextMenu(Menu: HMENU; indexMenu, idCmdFirst, idCmdLast, uFlags: UINT): HResult; var CloudMenuItem: TMenuItemInfo; MenuCaption: String; SubMenu: HMENU; uId: UINT; begin { only adding one menu CloudMenuItem, so generate the result code accordingly } Result := MakeResult(SEVERITY_SUCCESS, 0, 3); { store the menu CloudMenuItem index } FMenuItemIndex := indexMenu; { specify what the menu says, depending on where it was spawned } if (uFlags = CMF_NORMAL) then // from the desktop MenuCaption := 'Send file from Desktop to the Cloud' else if (uFlags and CMF_VERBSONLY) = CMF_VERBSONLY then // from a shortcut MenuCaption := 'Send file from Shourtcut to the Cloud' else if (uFlags and CMF_EXPLORE) = CMF_EXPLORE then // from explorer MenuCaption := 'Send file from Explorer to the Cloud' else { fail for any other value } Result := E_FAIL; if Result <> E_FAIL then begin SubMenu := CreatePopupMenu; uId := idCmdFirst; InsertMenu(SubMenu, AmazonIndex, MF_BYPOSITION, uId, TClouds[AmazonIndex]); Inc(uId); InsertMenu(SubMenu, AzureIndex, MF_BYPOSITION, uId, TClouds[AzureIndex]); FillChar(CloudMenuItem, SizeOf(TMenuItemInfo), #0); CloudMenuItem.cbSize := SizeOf(TMenuItemInfo); CloudMenuItem.fMask := MIIM_SUBMENU or MIIM_STRING or MIIM_ID; CloudMenuItem.fType := MFT_STRING; CloudMenuItem.wID := FMenuItemIndex; CloudMenuItem.hSubMenu := SubMenu; CloudMenuItem.dwTypeData := PWideChar(MenuCaption); CloudMenuItem.cch := Length(MenuCaption); InsertMenuItem(Menu, indexMenu, True, CloudMenuItem); end; end;
Quando o usuário estiver no Windows Explorer e passar o mouse sobre um dos menus criados, uma mensagem será exibida na barra de status do Windows Explorer, isso acontece devido a implementação do método GetCommandString, o qual retorna a string a ser exibida pelo Windows Explorer.
function TCloudUploadContextMenu.GetCommandString(idCmd: UINT_PTR; uFlags: UINT; pwReserved: PUINT; pszName: LPSTR; cchMax: UINT): HResult; begin Result := E_INVALIDARG; { Set help string on the Explorer status bar when the menu item is selected } if (idCmd in [AmazonIndex, AzureIndex]) and (uFlags = GCS_HELPTEXT) then begin StrLCopy(PWideChar(pszName), PWideChar('Copy the selected file to ' + TClouds[idCmd]), cchMax); Result := NOERROR; end; end;
Assim que usuário clicar no menu referente a nuvem que deseja enviar o arquivo, o método InvokeCommand será chamado e dará inicio o processo para fazer o upload do arquivo selecionado. Neste ponto já temos o nome do arquivo e com base nos parâmetros lpici podemos identificar o qual dos items de menu o usuário clicou.
function TCloudUploadContextMenu.InvokeCommand(var lpici: TCMInvokeCommandInfo): HResult; var Item: Word; begin Result := E_FAIL; if HiWord(Integer(lpici.lpVerb)) <> 0 then Exit; { if the index matches the index for the menu, show the cloud options } Item := LoWord(Integer(lpici.lpVerb)); if Item in [AmazonIndex, AzureIndex] then begin try Upload(lpici.HWND, Item, FFileName); except on E: Exception do MessageBox(lpici.hwnd, PWideChar(E.Message), 'Cloud Upload', MB_ICONERROR); end; Result := NOERROR; end; end;
Para que o objeto COM seja criado sempre que o CloudUpload é carregado, é necessário criar uma instância da classe factory o qual irá criar uma instância do objeto shell extension. A instância da classe factory será criada na seção initialization conforme o código abaixo, veja que o código original criado pelo Delphi será substituido por este.
initialization TCloudUploadObjectFactory.Create(ComServer, TCloudUploadContextMenu, CLASS_CloudUploadContextMenu, ciMultiInstance, tmApartment); end.
Uma vez que a classe factory será responsável por registrar/desregistrar a DLL, os métodos ApproveShellExtension e UpdateRegistry serão chamados, isso vai acontecer quando você usar o regsvr32.exe.
{ the new class factory } TCloudUploadObjectFactory = class(TAutoObjectFactory) protected procedure ApproveShellExtension(&Register: Boolean; const ClsID: string); function GetProgID: string; override; public procedure UpdateRegistry(Register: Boolean); override; end; { TCloudUploadObjectFactory } { Required to registration for Windows NT/2000 } procedure TCloudUploadObjectFactory.ApproveShellExtension(&Register: Boolean; const ClsID: string); Const WinNTRegKey = 'SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved'; var Reg: TRegistry; begin Reg := TRegistry.Create; try Reg.RootKey := HKEY_LOCAL_MACHINE; if not Reg.OpenKey(WinNTRegKey, True) then Exit; { register the extension appropriately } if &Register then Reg.WriteString(ClsID, Description) else Reg.DeleteValue(ClsID); finally Reg.Free; end; end; function TCloudUploadObjectFactory.GetProgID: string; begin { ProgID not required for shell extensions } Result := ''; end; procedure TCloudUploadObjectFactory.UpdateRegistry(Register: Boolean); Const ContextKey = '*\shellex\ContextMenuHandlers\%s'; begin { perform normal registration } inherited UpdateRegistry(Register); { Registration required for Windows NT/2000 } ApproveShellExtension(Register, GUIDToString(ClassID)); { if this server is being registered, register the required key/values to expose it to Explorer } if Register then CreateRegKey(Format(ContextKey, [ClassName]), '', GUIDToString(ClassID), HKEY_CLASSES_ROOT) else DeleteRegKey(Format(ContextKey, [ClassName])); end;
Compilar para plataformas 32-bit ou 64-bit
A partir daqui preciamos apenas compilar o projeto, neste exemplo as API’s do Windows e os métodos da RTL são os mesmos para as plataformas 32-bit e 64-bit, assim sendo não precisamos fazer implementações específicas para cada plataforma. Você pode definir para qual plataforma deseja compilar utilizando o Project Manager, o default é 32-bit Windows, use o botão direito do mouse na opção Target Platforms para adicionar 64-bit Windows.
Você não pode registrar dll 32-bit em sistema operacional 64-bit, assim como não pode registrar dll 64-bit em sistema operacional 32-bit. Se você desenvolve, compila e teste seus projeto na sua máquina de desenvolvimento, compile para a plataforma compatível com o sistema operacional em uso.
Registrando o Shell Extension CloudUpload
Primeiro, você tem que executar em modo “Run as Administrator” a aplicação que será utilizada para registrar o shell extension, mesmo que seu usuário tenha direitos de Administrador.
Shell extensions 32-bit pode ser registradas através do IDE, linha de comando (cmd) pode ser usada para registrar 32-bit e 64-bit.
Abaixo os passos para registrar e cancelar o registro através de linha de comando:
– “Run as Administrator” o cmd;
– Registre a extensão utilizando a linha de comando: regsvr32 <DIRETÓRIO ONDE ESTÁ A DLL>CloudUpload.dll
– Para desregistrar a extensão utilize a seguinte linha de comando: regsvr32 <DIRETÓRIO ONDE ESTÁ A DLL >CloudUpload.dll /u
Efetuado o registro da DLL abra o Windows Explorer, selecione um arquivo e utilize o botão direito do mouse, você verá um novo menu “Send file from Explorer to the Cloud”. No screenshot abaixo você pode ver a extensão no Windows 7 64-bit.
Você pode obter o código fonte do exemplo aqui utilizado de duas formas:
- Usando o IDE do RAD Studio XE2, no menu File selecione a opção “Open from Version Control” e configure a URL para https://radstudiodemos.svn.sourceforge.net/svnroot/radstudiodemos/branches/RadStudio_XE2/Delphi/CloudAPI/CloudUpload
- Atualizando o repositório local de demos do RAD Studio XE2, case você use TortoiseSVN com o botão direito sobre a pasta C:\Users\Public\Documents\RAD Studio\9.0\Samples\Delphi selecione a opção Update;
Olá
Amigo, não consigo acessar o link, os códigos e nem mesmo encontrar o Cloud api, acabou ficando um pouco vago esta parte.
Obrigado!
Olá, onde posso conseguir esses fontes, o link não está funcionando. grato.