Skojarzenie pliku z naszym programem bez względu na rozszerzenie
reichel
1 Wstęp
2 Rozszerzenia w systemie Windows
3 Idea
4 Implementacja interfejsu IShellExecuteHook
5 Implementacja interfejsu IShellIconOverlayIdentifier
6 Uwagi i wnioski
7 Literatura, pomocne linki
Wstęp
Na wstępie pragnę od razu wspomnieć, że tekst ten to raczej rozważania ne temat
problemu stosowania rozszerzeń niż lekarstwo na wszelkie problemy związane z nimi.
Poruszę tu problem ukrywania rozszerzeń (i ten fragment będzie miał znamiona największej użyteczności).
Pozostały fragment tyczył się zaś będzie problemu a co by było gdyby nie było rozszerzeń
.
Część ta jednak nie będzie całkowicie nieprzydatna, będzie z niej można się nauczyć obsługi takich interfejsów
jak:
IShellExecuteHook
IShellIconOverlayIdentifier
Rozszerzenia w systemie Windows
Temat jest bardzo dobrze opisany zarówno w samym MSDN [1] jak i na stronie 4programmers.net [2].
Zatem chciałbym tu tylko wspomnieć w jaki sposób można zawsze ukryć lub zawsze pokazywać rozszerzenie.
Służą do tego dwa wpisy (klucze) w rejestrze:
AlwaysShowExt
- aby rozszerzenie zawsze było pokazywane
NeverShowExt
- aby rozszerzenie nigdy nie było widoczne
są to klucze typu string (reg_sz) posiadające pustą wartość.
Umieszcza się je albo w gałęzi oznaczającej samo rozszerzenie np
HKEY_CLASSES_ROOT.moje_rozszezenie
jak w przypadku wszystkich plików (HKEY_CLASSES_ROOT*)
lub też w gałęzi opisującej typ pliku
HKEY_CLASSES_ROOT\moje_rozszezenie
jak np. skróty (HKEY_CLASSES_ROOT\lnkfile).
Idea
Chcemy, aby nasze pliki nie posiadały rozszerzenia (albo posiadały jakiekolwiek rozszerzenie),
a pomimo tego były rozpoznawane właściwie przez powłokę windows. Jedyną możliwością w tym przypadku jest rozpoznanie
pliku po jego zawartości i zakładamy, że nasz typ pliku to umożliwia.
Co zatem jest nam potrzebne? W pierwszej kolejności powinniśmy przechwycić moment uruchomienia
(kliknięcia na ikonkę)
pliku przez powłokę. Taka akcja odbywa się najczęściej poprzez wywołanie funkcji ShellExecute
(ShellExecuteEx
) z parametrem open
.
Istnieje możliwość albo przechwycenia API (co jednak nie jest najlepszą praktyką) albo zwrócenie uwagi na gotowy mechanizm pozwalający to uczynić
a opisywany poprzez interfejs rozszerzający powłokę windows: IShellExecuteHook.
Druga część związana z ikoną niestety nie jest już tak łatwa do zrealizowania (przy założeniu, że nie chcemy używać mechanizmów przechwytujących funkcje
czy też komunikaty pochodzące od okien). W systemie windows nie przewidziano żadnego mechanizmu pozwalającego na podstawienie swojej własnej ikony dla plików
nie posiadających rozszerzenia. Istotnie możemy podmienić taką ikonę globalnie, tak że wszystkie pliki bez rozszerzeń będą miały swoją własną.
Wydawało by się co prawda, że interfejs IExtractIcon jest doskonały do tego celu, ma on jednak poważny mankament - nie może być stosowany do klasy plików *
( w przeciwieństwie do zakładek IShellPropSheetExt, haków na operacje na pliku ICopyHook czy też menu podręcznego IContextMenu).
Czy oznacza to, że pozostają nam tylko haki ? Na szczęście nie, co prawda rozwiązania tego nie można określić jak doskonałego, pozwala ono
wstawić ikonę dla naszego pliku. Rozwiązanie to opiera się o dość rzadko stosowany interfejs IShellIconOverlayIdentifier
(poza implementacjami w samym windows - skrót, udostępnianie, nie spotkałem się z jego innym praktycznym zastosowaniem).
Ma on jednak pewne wady:
- niestety nie wszystkie programy biorą go pod uwagę (chociażby Total Commander),
- ikona nie zmienia koloru po zaznaczeniu (można udawać taki efekt),
- w trybie thumbnails ikona nie jest wyświetlana centralnie,
- może być tylko jedna ikona tego typu (więc jeśli stworzymy skrót do takiego pliku, stracimy ikonę),
- instalacja interfejsu wymaga restartu,
to jednak mimo tak wielu wad na razie poprzestaniemy na tym rozwiązaniu.
Implementacja interfejsu IShellExecuteHook
Interfejs ten jest banalny jeśli chodzi o oprogramowanie. Wystarczy wypełnić tutaj metodę Execute
.
Do niej podawana jest struktura TShellExecuteInfo zawierająca informacje o pliku, który został uruchomiony (w najprostszym przypadku
ikonka tego pliku została dwa razy kilknięta). teraz do akcji wkracza nasz kod. Funkcja CzyMojPlik
sprawdza czy
plik spełnia kryteria naszego pliku, czyli czy znajduje się w nim na początku napis "To moj wypasiony plik". Tu należy zwrócić uwagę na
obsługę błędów. Powinniśmy starannie łapać wszelkie wyjątki, w przeciwnym razie możemy spowodować wysypanie się powłoki (czyli w 99,9% explorer.exe).
Nie obojętne jest też dbanie o rezerwowanie pamięci oraz jej zwalnianie. W rozszerzeniach powłoki windows powinno się to robić za
pomocą interfejsu IMalloc oraz funkcji SHGetMalloc.
Poniżej kod funkcji CzyMojPlik
:
unit helper;
interface
uses Windows, SysUtils, ActiveX, ShlObj;
function CzyMojPlik(path:string):boolean;
implementation
function CzyMojPlik(path:string):boolean;
const
checkstr = 'To moj wypasiony plik';//naglowek naszego pliku
var
P:PChar;
l:longint;
F:File;
SM:IMalloc;
begin
Result := false;
if Failed(SHGetMalloc(SM)) then Exit;
try
if not FileExists(path) then exit;
AssignFile(F,path);
{$I-}
Reset(F,1);
l := Length(checkstr)+1;
if FileSize(F) < l-1 then
begin
CloseFile(F);
exit;
end;
P := SM.Alloc(l);
try
FillChar(P^,l,0);
BlockRead(F,P^,l-1);
result := StrComp(checkstr,P) = 0;
finally
SM.Free(P);
CloseFile(F);
end;
{$I+}
finally
SM := nil;//teoretycznie samo delphi zniszczy interfejs po wyjsciu z funkcji
end;
end;
end.
Jeśli teraz już mamy pewność, że pracujemy z naszym plikiem powinniśmy podmienić domyślną akcje dla tego pliku (powiedzmy, że jest to plik tekstowy i domyślnie otwiera się w notatniku).
Robimy to za pomocą podmiany wartości w strukturze TShellExecuteInfo. W miejsce parametrów lpParameters
wstawiamy
wartość z lpFile
(ona tak naprawdę zawiera parametry, oczywiście mogą być odstępstaw od tej reguły). Zerujemy też parametr lpVerb
zawierający opcje co ma się stać z plikiem: open, edit, print. W tym najprostszym przypadku nie sprawdzamy czy przypadkiem nie jest on pusty na starcie, jeśli tak by było powinniśmy
sprawdzić czy przypadkiem w lpFile
nie ma podanej aplikacji do uruchomienia (przed podaniem do lpParameters
powinno się ją usunąć).
Na koniec uruchamiamy naszą aplikację za pomocą funkcji ShellExecuteEx (zakładamy, że nasza aplikacja jest w tym samym folderze co
plik dll rozszerzenia).
Implementacja interfejsu:
unit seh_unit;
interface
uses
Windows, ActiveX, ComObj, ShlObj, ShellApi;
type
TShellExecuteHook = class(TComObject, IShellExecuteHook)
public
function Execute(var ShellExecuteInfo: TShellExecuteInfo):HResult;stdcall;
end;
const
Class_ShellExecuteHook: TGUID = '{603BD06E-7102-408E-A46E-3465EA4551E2}';
szShellExecuteHook = 'ShellExecuteHook: Moj wypasiony plik';
implementation
uses ComServ, SysUtils, Registry, helper;
function TShellExecuteHook.Execute(var ShellExecuteInfo: TShellExecuteInfo):HResult;
var
mp:array[0..MAX_PATH] of char;
begin
ShellExecuteInfo.hInstApp := 33; // Gdy blad to wartosc mniejsza rowna od 32
if CzyMojPlik(ShellExecuteInfo.lpFile) then
begin
//wykorzystamy stara strukture
ShellExecuteInfo.fMask := SEE_MASK_NOCLOSEPROCESS;
ShellExecuteInfo.lpParameters := PChar('"'+ShellExecuteInfo.lpFile+'"');
ShellExecuteInfo.lpVerb := nil;
//tu podajemy sciezke do naszej aplikacji, zakladamy ze w tym samym folderze co dll'ka
GetModuleFileName(Hinstance,mp,sizeof(mp));
ShellExecuteInfo.lpFile := PChar(ExtractFIlePath(mp)+'Project1.exe');
ShellExecuteEx(@ShellExecuteInfo);
result:= S_OK;
end
else
result:= S_FALSE;//jesli to nie nasz plik to nie robmy nic
end;
type
TShellExecuteHookFactory = class(TComObjectFactory)
public
procedure UpdateRegistry(Register: Boolean); override;
end;
procedure TShellExecuteHookFactory.UpdateRegistry(Register: Boolean);
var
ClassID: string;
begin
if Register then
begin
inherited UpdateRegistry(Register);
ClassID := GUIDToString(Class_ShellExecuteHook);
with TRegistry.Create do
try
RootKey := HKEY_LOCAL_MACHINE;
OpenKey('SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions', True);
OpenKey('Approved', True);
WriteString(ClassID, szShellExecuteHook);
CloseKey;
OpenKey('Software\Microsoft\Windows\CurrentVersion\Explorer\ShellExecuteHooks\', True);
WriteString(ClassID, szShellExecuteHook);
finally
Free;
end;
end
else
begin
with TRegistry.Create do
try
RootKey := HKEY_LOCAL_MACHINE;
OpenKey('Software\Microsoft\Windows\CurrentVersion\Explorer\ShellExecuteHooks\', True);
DeleteValue(ClassID);
CloseKey();
DeleteKey('Software\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIdentifiers\WypasPlik');
finally
Free;
end;
inherited UpdateRegistry(Register);
end;
end;
initialization
TShellExecuteHookFactory.Create(ComServer, TShellExecuteHook, Class_ShellExecuteHook, '',
szShellExecuteHook, ciMultiInstance, tmApartment);
end.
Implementacja interfejsu IShellIconOverlayIdentifier
Niestety MS Windows nie posiada ogólnej możliwości podmiany dowolnej ikony. Jednak za pomocą interfesu IShellIconOverlayIdentifier
można odrobinę oszukać system. Interfejs ten odpowiada z tworzenie ikon w stylu skrótu, pliki/foldery udostępnione, my wykorzystamy go w niestandardowy sposób.
Najczęściej ikona ta jest bardzo mała (w rogu), my jednak użyjemy normalnej ikony (najlepiej z białym tłem). Dzięki temu przesłoni ona nam oryginalną ikonę
danego rozszerzenia. Jeśli chodzi o sam interfejs jest on na tyle prosty, że jedyne o czym można wspomnieć to parametr pIPriority
w funkcji GetPriority
odpowiadający za ważność ikony, gdyż może ona być tylko jedna dla pliku (tu wada rozwiązania, musimy wybrać pomiędzy ikoną skrótu dla naszego plik a samą ikoną,
powinna się zatem jeszcze znaleźć funkcja sprawdzająca skrót. Można tą niedogodność obejść implementując interfejs IExtractIcon dla plików *.lnk).
UWAGA! Interfejs IShellIconOverlayIdentifier staje się aktywny dopiero po restarcie systemu.
//**********************************************************************
//**********************************************************************
//sioi_unit - ShellIconOverlayIdentifier
//[email protected]
//http://rudy.mif.pg.gda.pl/~reichel/
//http://reichel.pl
//2007.09.22
//**********************************************************************
//**********************************************************************
unit sioi_unit;
interface
uses
Windows, ActiveX, ComObj, ShlObj, Classes, ShellAPI;
{$R w.RES} //ikona :)
type
TShellIconOverlayIdentifier = class(TComObject,IShellIconOverlayIdentifier)
private
pMalloc: IMalloc;
protected
//IShellIconOverlayIdentifier
function IsMemberOf(pwszPath: PWideChar; dwAttrib: DWORD): HResult; stdcall;
function GetOverlayInfo(pwszIconFile: PWideChar; cchMax: Integer;
var pIndex: Integer; var pdwFlags: DWORD): HResult; stdcall;
function GetPriority(out pIPriority: Integer): HResult; stdcall;
public
procedure Initialize; override;
destructor Destroy; override;
end;
const
Class_ShellIconOverlayIdentifier: TGUID = '{88E26886-2121-4A93-9DD5-97902DFA725C}';
szShellIconOverlayIdentifier = 'ShellIconOverlayIdentifier: Moj wypasiony plik';
implementation
uses ComServ, SysUtils, Registry, Math, helper;
function TShellIconOverlayIdentifier.IsMemberOf(pwszPath: PWideChar; dwAttrib: DWORD): HResult;
var
Path:String;
begin
Result := S_FALSE;//nie jesli nie nasza ikona
Path := WideCharToString(pwszPath);
if CzyMojPlik(Path) then
begin
Result := S_OK;
end;
end;
function TShellIconOverlayIdentifier.GetOverlayInfo(pwszIconFile: PWideChar; cchMax: Integer;
var pIndex: Integer; var pdwFlags: DWORD): HResult;
var
iconfile:string;
fn:array[0..Max_PATH] of Char;
begin
GetModuleFilename(Hinstance,fn,sizeof(fn));
iconfile := fn;
StringToWideChar(iconfile,pwszIconFile,Length(iconfile)*SizeOf(WideChar) + 1);
pdwFlags := ISIOI_ICONFILE;
pIndex := 0;
result := S_OK;
end;
function TShellIconOverlayIdentifier.GetPriority(out pIPriority: Integer): HResult;
begin
pIPriority := 0;
result := S_OK;
end;
procedure TShellIconOverlayIdentifier.Initialize;
begin
inherited;
if Failed(ShGetMalloc(pMalloc)) then
pMalloc := nil;
end;
destructor TShellIconOverlayIdentifier.Destroy;
begin
inherited;
pMalloc := nil;
end;
type
TShellIconOverlayIdentifierFactory = class(TComObjectFactory)
public
procedure UpdateRegistry(Register: Boolean); override;
end;
procedure TShellIconOverlayIdentifierFactory.UpdateRegistry(Register: Boolean);
var
ClassID: string;
begin
if Register then begin
inherited UpdateRegistry(Register);
ClassID := GUIDToString(Class_ShellIconOverlayIdentifier);
with TRegistry.Create do
try
RootKey := HKEY_LOCAL_MACHINE;
OpenKey('Software\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIdentifiers\WypasPlik',True);
WriteString('',ClassID);
CloseKey;
finally
Free;
end;
if (Win32Platform = VER_PLATFORM_WIN32_NT) then
with TRegistry.Create do
try
RootKey := HKEY_LOCAL_MACHINE;
OpenKey('SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions', True);
OpenKey('Approved', True);
WriteString(ClassID, szShellIconOverlayIdentifier);
finally
Free;
end;
end
else begin
with TRegistry.Create do
try
RootKey := HKEY_LOCAL_MACHINE;
DeleteKey('Software\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIdentifiers\WypasPlik');
finally
Free;
end;
inherited UpdateRegistry(Register);
end;
end;
initialization
TShellIconOverlayIdentifierFactory.Create(ComServer, TShellIconOverlayIdentifier, Class_ShellIconOverlayIdentifier, '', szShellIconOverlayIdentifier,
ciMultiInstance, tmApartment);
end.
Uwagi i wnioski
*Oczywiście istnieje problem z plikami (ich ikonami) bez rozszerzeń, rozwiązaniem jest wspomniane białe tło.
*Należy być swiadomym zwiększania obciążenia system.
W załączniku znajduje sie kod bibliotek, przykładowa aplikacja obsługująca format pliku i zestaw plików testowych.
bez_rozszerzenia.rar