29.10.2023, Vladimír Klaus, navštíveno 650x
Všichni, kdo programují v Delphi, znají jeho moc a flexibilitu, ale také tu a tam narazíme na zádrhely, které nám způsobí vrásky na čele. Jedním z takových zapeklitých problémů je použití funkce ShellExecute pro otevření JPG souboru. Funkci používám už mnoho let a nikdy s ní nebyly žádné potíže.
Problém
Cílem bylo jednoduše otevřít JPG soubor ve výchozí aplikaci prostřednictvím funkce ShellExecute. Tento kód obvykle funguje na běžných desktopových verzích Windows. Nicméně, když jsem to zkoušel na Windows Serveru, funkce mi vracela chybový kód 31, což je SE_ERR_NOASSOC.
První krok bylo ověřit, zda soubor a cesta k němu jsou v pořádku. Ano, všechno bylo v pořádku. V dalším kroku jsem zkontroloval, jestli nemám problém s oprávněními. Jelikož jsem byl přihlášen jako Administrátor, toto také nebyl problém. Stejně tak poklepání na soubor způsobilo zcela korektní spuštění asociované aplikace a otevření daného souboru. Jelikož standardní postupy selhaly, zkoušel jsem různé způsoby, jak problém obejít. Například, pomocí explicitní cesty k mspaint.exe s parametrem cesty k souboru fungovala. Avšak, i po znovu nastavení výchozí aplikace pro otevření JPG souborů, problém přetrvával.
Řešení
Zkoušel jsem několik metod, až jsem narazil na diskuzi z roku 2003, kde se někdo potýkal s podobným problémem. Nakonec jsem problém vyřešil tím, že jsem postup rozdělil na dva kroky
- zjistit asociovanou aplikaci
- zavolat aplikaci s parametrem
var app:=ZjistiAsociovanouAplikaci(TempSoubor);
if FileExists(app) then begin
SimpleRun(app, TempSoubor);
end else begin
VKWarning('Pro příponu "%s" nemáte žádnou výchozí aplikaci!', [ExtractFileExt(TempSoubor)]);
end;
Jak vypadá zjišťování asociované aplikace? Třeba takto:
{*----------------------------------------------------------------------
Vrátí název aplikace (plnou cestu) asociované se zadaným souborem
-----------------------------------------------------------------------}
function ZjistiAsociovanouAplikaci(aFileName: string): string;
var
Aplikace: PChar;
begin
GetMem(Aplikace,MAX_PATH);
case FindExecutable(PChar(aFileName),PChar(GetCurrentDir),Aplikace) of
0..30: result:=''; //chyba; soubor neexistuje?
31: result:='' //soubor nemá žádnou asociovanou aplikaci
else result:=Aplikace;
end;
FreeMem(Aplikace);
end;
Spuštění aplikace/souboru pak takto:
{*----------------------------------------------------------------------
Jednoduché spuštění aplikace nebo zobrazení souboru pomocí
výchozí aplikace. Slouží i k navigování na web atd.
-----------------------------------------------------------------------}
procedure SimpleRun (aFileName: string; aParam: string = ''; aShowCmd: integer = SW_SHOW);
var
i: integer;
begin
//pro jistotu se přepnu do adresáře spouštěné aplikace
SetCurrentDirectory(PChar(ExtractFilePath(aFileName)));
//podle toho, jestli to volám s parametrem
if Trim(aParam)<>'' then begin
i:=ShellExecute(0,'open',PChar(aFileName),PChar(aParam),nil,aShowCmd);
end else begin
i:=ShellExecute(0,'open',PChar(aFileName),nil,nil,aShowCmd);
end;
//když se vrátí nízké číslo, jde o nějaký problém - já z toho dělám výjimku
if i<=32 then begin
raise Exception.CreateFmt('ShellExecute result = %d',[i]);
end;
end;
A voilà, funguje to! Tento problém ukazuje, že i staré problémy se občas vracejí a řešení nemusí být vždy zřejmé. Ale s trochou vytrvalosti a kreativity, i ty nejzapeklitější problémy mohou být vyřešeny. Doufám, že tento postup pomůže někomu, kdo se s něčím podobným setká.