La idea es interesante pues ShellExecuteEx se encargará de encontrar el visor por defecto de Windows, igual que ShellExecute, solo falta detectar cuando se cierra un visor (o cualquier otra aplicación que se ejecute). Para evitar el bloqueo de de nuestro programa implementaremos la solución en un Thread distinto al principal y nos comunicaremos por un mensaje de windows. Para adaptarnos al API más familiar ShellExecute, Escribiremos una función que admita los mismos parámetros y nos devolverá un ThreadId del Thread que se crea, de esta forma lo tendremos identificado en el caso de abrir más de uno. El Thread esperará a que finalice el proceso y en ese momento enviará un mensaje notificando del ThreadId que se cierra y si se ejecutó correctamente.
Vamos a poner manos a la obra:
Primero diseñamos el mensaje
delphi
const RS_FINISH = WM_USER + 1; { Message RS_FINISH WParam: ThreadId del proceso ejecutado y finalizado LParam: BOOL True si el proceso se ejecutó correctamente }
Nuestra aplicación deberá poder responder a ese mensaje para ser notificada:
delphi
procedure OnRunAndWaitShell(var Msg: TMessage); message RS_FINISH;
Y ahora vamos a diseñar la función del Thread:
delphi
function ThRunAndWaitShell(var Info: TShellExecuteInfo): BOOL; stdcall; begin Result:= ShellExecuteEx(@Info); WaitForSingleObject(Info.hProcess, INFINITE); CloseHandle(Info.hProcess); SendMessage(Info.wnd, RS_FINISH, GetCurrentThreadId, LPARAM(Result)); end;
Lo que sigue es una función que encapsula la ejecución del Thread de la función anterior y que admite los mismos parámetros que ShellExecute y que devuelve el identificador del Thread ejecutado (ThreadId)
delphi
function RunAndWaitShell(Handle: THandle; Operation, FileName, Parameters, Directory: String; nShowCmd: Integer): DWORD; function RunAndWaitShell(Handle: THandle; Operation, FileName, Parameters, Directory: String; nShowCmd: Integer): DWORD; function ThRunAndWaitShell(var Info: TShellExecuteInfo): BOOL; stdcall; begin Result:= ShellExecuteEx(@Info); WaitForSingleObject(Info.hProcess, INFINITE); SendMessage(Info.wnd, RS_FINISH, GetCurrentThreadId, LPARAM(Result)); end; const {$J+} Info: TShellExecuteInfo = (); _Operation: array[0..20] of CHAR = ''; _FileName: array[0..MAX_PATH] of CHAR = ''; _Parameters: array[0..MAX_PATH] of CHAR = ''; _Directory: array[0..MAX_PATH] of CHAR = ''; begin lstrcpyn(_Operation, PChar(Operation), 20); lstrcpyn(_FileName, PChar(FileName), MAX_PATH); lstrcpyn(_Parameters, PChar(Parameters), MAX_PATH); lstrcpyn(_Directory, PChar(Directory), MAX_PATH); with Info do begin cbSize:= SizeOf(Info); fMask:= SEE_MASK_NOCLOSEPROCESS; wnd:= Handle; lpVerb:= _Operation; lpFile:= _FileName; lpParameters:= _Parameters; lpDirectory:= _Directory; nShow:= nShowCmd; hInstApp:= 0; end; CloseHandle(CreateThread(nil, 0, @ThRunAndWaitShell, @Info, 0, Result)); {$J-} end;
Las directivas {$J+} y {$J-} son para que la constante Info pueda ser modificada por código con lo que se comporta como una variable static de C cuya vida será la de la aplicación. De esta forma no tenderemos que guardar una por cada Thread ejecutado. Recordad que los parámetros que se pasen a un thread deben tener la misma vida que el propio Thread.
Ahora vemos el código de ejemplo completo de una APP con dos botones, dos Labels. Cada botón ejecuta una aplicación que al acabar mostrará una caja de mensaje con el ThreadID y si la ejecución fue correcta (1) ó no (0).
delphi
unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, ShellAPI, StdCtrls; const RS_FINISH = WM_USER + 1; { Message RS_FINISH WParam: ThreadId del proceso ejecutado y finalizado LParam: BOOL True si el proceso se ejecutó correctamente } type TForm1 = class(TForm) Button1: TButton; Button2: TButton; Label1: TLabel; Label2: TLabel; procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); private procedure OnRunAndWaitShell(var Msg: TMessage); message RS_FINISH; public { Public declarations } end; var Form1: TForm1; implementation {$R *.dfm} function RunAndWaitShell(Handle: THandle; Operation, FileName, Parameters, Directory: String; nShowCmd: Integer): DWORD; function ThRunAndWaitShell(var Info: TShellExecuteInfo): BOOL; stdcall; begin Result:= ShellExecuteEx(@Info); WaitForSingleObject(Info.hProcess, INFINITE); SendMessage(Info.wnd, RS_FINISH, GetCurrentThreadId, LPARAM(Result)); end; const {$J+} Info: TShellExecuteInfo = (); _Operation: array[0..20] of CHAR = ''; _FileName: array[0..MAX_PATH] of CHAR = ''; _Parameters: array[0..MAX_PATH] of CHAR = ''; _Directory: array[0..MAX_PATH] of CHAR = ''; begin lstrcpyn(_Operation, PChar(Operation), 20); lstrcpyn(_FileName, PChar(FileName), MAX_PATH); lstrcpyn(_Parameters, PChar(Parameters), MAX_PATH); lstrcpyn(_Directory, PChar(Directory), MAX_PATH); with Info do begin cbSize:= SizeOf(Info); fMask:= SEE_MASK_NOCLOSEPROCESS; wnd:= Handle; lpVerb:= _Operation; lpFile:= _FileName; lpParameters:= _Parameters; lpDirectory:= _Directory; nShow:= nShowCmd; hInstApp:= 0; end; CloseHandle(CreateThread(nil, 0, @ThRunAndWaitShell, @Info, 0, Result)); {$J-} end; procedure TForm1.Button1Click(Sender: TObject); begin Label1.Caption:= IntToStr(RunAndWaitShell(Handle, 'open', 'd:\Prueba.txt', '', '', SW_SHOW)); end; procedure TForm1.Button2Click(Sender: TObject); begin Label2.Caption:= IntToStr(RunAndWaitShell(Handle, 'open', 'd:\Prueba.pdf', '', '', SW_SHOW)); end; procedure TForm1.OnRunAndWaitShell(var Msg: TMessage); begin // Fin de ejecución ShowMessage('Fin ' + IntToStr(Msg.WParam) + ' (' + IntToStr(Msg.LParam) + ')'); end; end.
Ya tenemos nuestro ShellExecute que espera a la finalización y notifica de ello.
Probado en delphi7 y Berlin
Que os sirva para algo o para experimentar.
Saludos.