sábado, 8 de setembro de 2007

Criando Links Usando COM - The easiest way

Shell Links parecem algo mágico: Vc clica com o botão direito no desktop, cria um novo atalho, e algo acontece que faz um ícone aparecer no desktop. Este atalho criado é comumente conhecido como Shell Link. Ele é um arquivo com a extensão .lnk que reside em alguma pasta em particular.

Para a criação de um Shell Link é necessário utilizar alguns conceitos muito conhecidos da programação Windows: As interfaces COM. Estas interfaces COM permitem que um determinado objeto possa ser mapeado em outros aplicativos, independente de qualquer linguagem ou implementação específica, desde que a linguagem que se está usando para desenvolvimento ofereça suporte a estes objetos.
IShellLink = IShellLinkA;

IShellLinkA = interface(IUnknown) ['{000214EE-0000-0000-C000-000000000046}]
function GetPath(pszFile: PAnsiChar; cchMaxPath: Integer; var pfd: TWin32FindData; fFlags: DWORD): HResult; stdcall;
function GetIDList (var ppidl: PItemIDList): HResult; stdcall;
function SetIDList (pidl: PItemIDList): HResult; stdcall;
function GetDescription (pszName: PAnsiChar; cchMaxName: Integer): HResult; stdcall;
function SetDescription (pszName: PAnsiChar): HResult; stdcall;
function GetWorkingDirectory (pszDir: PAnsiChar; cchMaxPath: Integer): HResult; stdcall;
function SetWorkingDirectory (pszDir: PAnsiChar): HResult; stdcall;
function GetArguments (pszArgs: PAnsiChar; cchMaxPath: Integer): HResult; stdcall;
function SetArguments (pszArgs: PAnsiChar): HResult; stdcall;
function GetHotkey (var pwHotkey: Word): HResult; stdcall;
function SetHotkey (wHotkey: Word): HResult; stdcall;
function GetShowCmd (out piShowCmd: Integer): HResult; stdcall;
function SetShowCmd (iShowCmd: Integer): HResult; stdcall;
function GetIconLocation (pszIconPath: PAnsiChar; cchIconPath: Integer; out piIcon: Integer): HResult; stdcall;
function SetIconLocation (pszIconPath: PAnsiChar; iIcon: Integer): HResult; stdcall;
function SetRelativePath (pszPathRel: PAnsiChar; dwReserved: DWORD): HResult; stdcall;
function Resolve (Wnd: HWND; fFlags: DWORD): HResult; stdcall;
function SetPath (pszFile: PAnsiChar): HResult; stdcall;
end;


A interface IShellLink é o encapsulamento do objeto Shell Link, que oferece vários métodos para manipulação dos mesmos, desde alterar o ícone até adicionar teclas de atalho ao mesmo, no entando, não há nenhum método específico para ler ou salvar o arquivo em disco. Para isto, é utilizada outra interface, chamada IPersistFile.
Esta interface é necessária para prover o métodos necessários para operar arquivos no disco.
Sua interface é a seguinte:
IPersistFile = interface(IPersist) ['{0000010B-0000-0000-C000-000000000046}']
function IsDirty: HResult; stdcall;
function Load (pszFileName: POleStr; dwMode: Longint): HResult; stdcall;
function Save (pszFileName: POleStr; fRemember: BOOL): HResult; stdcall;
function SaveCompleted (pszFileName: POleStr): HResult; stdcall;
function GetCurFile (out pszFileName: POleStr): HResult; stdcall;
end;

Como a classe que implementa a interface IShelllink necessita também que se implemente a interface IPersistFile, vc pode usar o método QueryInterface da instância de IShellLink para uma instância de IPersistFile utilizando typecasting, como mostrado abaixo:
var
ISLink: IShellLink;
IPFile: IPersistFile;
begin
CoInitialize(Nil);
IObject := CreateComObject(CLSID_ShellLink);
ISLink := IObject as IShellLink;
IPFile := ISLink as IPersistFile;

// use tanto PF quanto SL
end;

Usar objetos interfaceados via COM funciona do mesmo modo que usar objetos nativos do Delphi. No exemplo a seguir, é criado um Shell Link para a aplicação calculadora:
procedure MakeCalc;
const
// NOTA: Assumindo que o local da calculadora seja:
AppName = 'c:\windows\CALC.exe';
var
SL: IShellLink;
PF: IPersistFile;
LnkName: WideString;

IObject : IUnknown;
ISLink : IShellLink;
IPFile : IPersistFile;
begin
{Objeto que implementa IShellLink necessita implementar IPersistFile}
CoInitialize(Nil);
IObject := CreateComObject(CLSID_ShellLink);
ISLink := IObject as IShellLink;
IPFile := ISLink as IPersistFile;

ISLink.SetPath(PChar(AppName)); //Adiciona o link para o path indicado
LnkName := GetFolderLocation('Desktop') + '\' + ChangeFileExt(ExtractFileName(AppName), '.lnk');

IPFile.Save(PWideChar(LnkName), True); //Salva o arquivo na pasta especificada
end;

Neste procedimento, o método de IShellLink SetPath() é utilizado para apontar o link para um documento ou executável (Calc neste caso). Então, o caminho e o nome do arquivo são criados usando o caminho retornado por GetFolderLocation('Desktop') e usado em conjunto com ChangeFileExt() para mudar a extensão de .EXE para .LNK. Este novo nome é armazenado em LnkName. Usando depois o método Save() de IPersistFile, o método salva o link em disco.

Como exemplo da criação de links, estou criei uma pequena aplicação para desligar o sistema que cria três atalhos diferentes na área de trabalho do usuário, que chamam a aplicação usando os argumentos SHUTDOWN, RESTART e LOGOFF.

Nesta aplicação ainda existem mais duas funções da interface IShellLink, usadas para adicionar um ícone ao atalho ( SetIconLocation), para adicionar os argumentos no path da aplicação (SetArguments) e para setar o diretório de trabalho que a aplicação irá usar (SetWorkingDirectory).

A aplicação pode ser encontrada aqui: http://vndmtrx.../prjShutdown.zip