Jump to content


Photo

[MULTILENGUAJE] RunAndWaitShell


  • Please log in to reply
3 replies to this topic

#1 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4111 posts
  • LocationMadrid - España

Posted 21 August 2018 - 11:12 AM

Tras leer este tema Gestionar archivos temporales que se abren con terceros programas donde newtron pregunta como ejecutar el visor predeterminado de diversos archivos al tiempo y saber cuando se cierran para poder borrarlos, vi una propuesta interesante de Casimiro Notevi usando RunAndWaitShell. Las implementaciones que encontré en la red no me gustaron pues todas ellas usan ProcessMessages para evitar evitar el bloqueo de la aplicación y retomar el control al finalizar el visor, además si se ejecuta más de un archivo ya tendremos problemas de sincronismo para identificar cual acaba. El uso de ProcessMessages potencialmente dará problemas dependiendo del flujo de la aplicación y es preferible el uso de Threads.
 
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
  1. const
  2. RS_FINISH = WM_USER + 1;
  3. {
  4.   Message RS_FINISH
  5.   WParam: ThreadId del proceso ejecutado y finalizado
  6.   LParam: BOOL True si el proceso se ejecutó correctamente
  7. }

Nuestra aplicación deberá poder responder a ese mensaje para ser notificada:

delphi
  1. procedure OnRunAndWaitShell(var Msg: TMessage); message RS_FINISH;

Y ahora vamos a diseñar la función del Thread:

delphi
  1. function ThRunAndWaitShell(var Info: TShellExecuteInfo): BOOL; stdcall;
  2. begin
  3. Result:= ShellExecuteEx(@Info);
  4. WaitForSingleObject(Info.hProcess, INFINITE);
  5. CloseHandle(Info.hProcess);
  6. SendMessage(Info.wnd, RS_FINISH, GetCurrentThreadId, LPARAM(Result));
  7. 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
  1. function RunAndWaitShell(Handle: THandle; Operation, FileName, Parameters, Directory: String; nShowCmd: Integer): DWORD;
  2. function RunAndWaitShell(Handle: THandle; Operation, FileName, Parameters, Directory: String; nShowCmd: Integer): DWORD;
  3. function ThRunAndWaitShell(var Info: TShellExecuteInfo): BOOL; stdcall;
  4. begin
  5. Result:= ShellExecuteEx(@Info);
  6. WaitForSingleObject(Info.hProcess, INFINITE);
  7. SendMessage(Info.wnd, RS_FINISH, GetCurrentThreadId, LPARAM(Result));
  8. end;
  9. const
  10. {$J+}
  11. Info: TShellExecuteInfo = ();
  12. _Operation: array[0..20] of CHAR = '';
  13. _FileName: array[0..MAX_PATH] of CHAR = '';
  14. _Parameters: array[0..MAX_PATH] of CHAR = '';
  15. _Directory: array[0..MAX_PATH] of CHAR = '';
  16. begin
  17. lstrcpyn(_Operation, PChar(Operation), 20);
  18. lstrcpyn(_FileName, PChar(FileName), MAX_PATH);
  19. lstrcpyn(_Parameters, PChar(Parameters), MAX_PATH);
  20. lstrcpyn(_Directory, PChar(Directory), MAX_PATH);
  21.  
  22. with Info do
  23. begin
  24. cbSize:= SizeOf(Info);
  25. fMask:= SEE_MASK_NOCLOSEPROCESS;
  26. wnd:= Handle;
  27. lpVerb:= _Operation;
  28. lpFile:= _FileName;
  29. lpParameters:= _Parameters;
  30. lpDirectory:= _Directory;
  31. nShow:= nShowCmd;
  32. hInstApp:= 0;
  33. end;
  34. CloseHandle(CreateThread(nil, 0, @ThRunAndWaitShell, @Info, 0, Result));
  35. {$J-}
  36. 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
  1. unit Unit1;
  2.  
  3. interface
  4.  
  5. uses
  6. Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  7. Dialogs, ShellAPI, StdCtrls;
  8.  
  9. const
  10. RS_FINISH = WM_USER + 1;
  11. {
  12.   Message RS_FINISH
  13.   WParam: ThreadId del proceso ejecutado y finalizado
  14.   LParam: BOOL True si el proceso se ejecutó correctamente
  15. }
  16.  
  17. type
  18. TForm1 = class(TForm)
  19. Button1: TButton;
  20. Button2: TButton;
  21. Label1: TLabel;
  22. Label2: TLabel;
  23. procedure Button1Click(Sender: TObject);
  24. procedure Button2Click(Sender: TObject);
  25. private
  26. procedure OnRunAndWaitShell(var Msg: TMessage); message RS_FINISH;
  27. public
  28. { Public declarations }
  29. end;
  30.  
  31. var
  32. Form1: TForm1;
  33.  
  34. implementation
  35.  
  36. {$R *.dfm}
  37.  
  38. function RunAndWaitShell(Handle: THandle; Operation, FileName, Parameters, Directory: String; nShowCmd: Integer): DWORD;
  39. function ThRunAndWaitShell(var Info: TShellExecuteInfo): BOOL; stdcall;
  40. begin
  41. Result:= ShellExecuteEx(@Info);
  42. WaitForSingleObject(Info.hProcess, INFINITE);
  43. SendMessage(Info.wnd, RS_FINISH, GetCurrentThreadId, LPARAM(Result));
  44. end;
  45. const
  46. {$J+}
  47. Info: TShellExecuteInfo = ();
  48. _Operation: array[0..20] of CHAR = '';
  49. _FileName: array[0..MAX_PATH] of CHAR = '';
  50. _Parameters: array[0..MAX_PATH] of CHAR = '';
  51. _Directory: array[0..MAX_PATH] of CHAR = '';
  52. begin
  53. lstrcpyn(_Operation, PChar(Operation), 20);
  54. lstrcpyn(_FileName, PChar(FileName), MAX_PATH);
  55. lstrcpyn(_Parameters, PChar(Parameters), MAX_PATH);
  56. lstrcpyn(_Directory, PChar(Directory), MAX_PATH);
  57.  
  58. with Info do
  59. begin
  60. cbSize:= SizeOf(Info);
  61. fMask:= SEE_MASK_NOCLOSEPROCESS;
  62. wnd:= Handle;
  63. lpVerb:= _Operation;
  64. lpFile:= _FileName;
  65. lpParameters:= _Parameters;
  66. lpDirectory:= _Directory;
  67. nShow:= nShowCmd;
  68. hInstApp:= 0;
  69. end;
  70. CloseHandle(CreateThread(nil, 0, @ThRunAndWaitShell, @Info, 0, Result));
  71. {$J-}
  72. end;
  73.  
  74. procedure TForm1.Button1Click(Sender: TObject);
  75. begin
  76. Label1.Caption:= IntToStr(RunAndWaitShell(Handle, 'open', 'd:\Prueba.txt', '', '', SW_SHOW));
  77. end;
  78.  
  79. procedure TForm1.Button2Click(Sender: TObject);
  80. begin
  81. Label2.Caption:= IntToStr(RunAndWaitShell(Handle, 'open', 'd:\Prueba.pdf', '', '', SW_SHOW));
  82. end;
  83.  
  84. procedure TForm1.OnRunAndWaitShell(var Msg: TMessage);
  85. begin
  86. // Fin de ejecución
  87. ShowMessage('Fin ' + IntToStr(Msg.WParam) + ' (' + IntToStr(Msg.LParam) + ')');
  88. end;
  89.  
  90. 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.
  • 1

#2 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4111 posts
  • LocationMadrid - España

Posted 21 August 2018 - 11:17 AM

Como no podía ser de otra manera voy a publicar la versión para Builder:
 
Unit1.h

cpp
  1. #include <Forms.hpp>
  2. //---------------------------------------------------------------------------
  3.  
  4.  
  5. #define RS_FINISH WM_USER + 1
  6.  
  7. class TForm1 : public TForm
  8. {
  9. __published: // IDE-managed Components
  10. TLabel *Label1;
  11. TLabel *Label2;
  12. TButton *Button1;
  13. TButton *Button2;
  14. void __fastcall Button1Click(TObject *Sender);
  15. void __fastcall Button2Click(TObject *Sender);
  16. private:
  17. void __fastcall OnRunAndWaitShell(TMessage &Message);
  18. public: // User declarations
  19. __fastcall TForm1(TComponent* Owner);
  20.  
  21. BEGIN_MESSAGE_MAP
  22. MESSAGE_HANDLER(RS_FINISH, TMessage, OnRunAndWaitShell);
  23. END_MESSAGE_MAP(TForm);
  24. };
  25. //---------------------------------------------------------------------------
  26. extern PACKAGE TForm1 *Form1;
  27. //---------------------------------------------------------------------------
  28. #endif

Unit1.cpp

cpp
  1. //---------------------------------------------------------------------------
  2.  
  3. #include <vcl.h>
  4. #pragma hdrstop
  5.  
  6. #include "Unit1.h"
  7. //---------------------------------------------------------------------------
  8. #pragma package(smart_init)
  9. #pragma resource "*.dfm"
  10. TForm1 *Form1;
  11. //---------------------------------------------------------------------------
  12.  
  13. /*
  14. Message RS_FINISH
  15. WParam: ThreadId del proceso y finalizado
  16. LParam: BOOL True si el proceso se ejecutó correctamente
  17. */
  18.  
  19. BOOL __stdcall WINAPI ThRunAndWaitShell(LPSHELLEXECUTEINFOA Info)
  20. {
  21.   BOOL R = ShellExecuteExA(Info);
  22.   WaitForSingleObject(Info->hProcess, INFINITE);
  23.   CloseHandle(Info->hProcess);
  24. SendMessage(Info->hwnd, RS_FINISH, GetCurrentThreadId(), R);
  25.   return R;
  26. }
  27.  
  28. DWORD RunAndWaitShell(HWND hWnd, PCHAR Operation, PCHAR FileName, PCHAR Parameters, PCHAR Directory, int nShowCmd)
  29. {
  30.   static SHELLEXECUTEINFOA Info;
  31.   static CHAR fileName[MAX_PATH];
  32.   static CHAR operation[20];
  33.   static CHAR directory[MAX_PATH];
  34.   static CHAR parameters[MAX_PATH];
  35.   DWORD ThreadId;
  36.  
  37.   lstrcpynA(fileName, FileName, MAX_PATH);
  38.   lstrcpynA(directory, Directory, MAX_PATH);
  39.   lstrcpynA(operation, Operation, 20);
  40.   lstrcpynA(directory, Directory, MAX_PATH);
  41.  
  42.   Info.cbSize = sizeof(Info);
  43.   Info.fMask  = SEE_MASK_NOCLOSEPROCESS;
  44.   Info.hwnd   = hWnd;
  45.   Info.lpVerb = operation;
  46.   Info.lpFile = fileName;
  47.   Info.lpParameters = parameters;
  48.   Info.lpDirectory  = directory;
  49.   Info.nShow = nShowCmd;
  50.   Info.hInstApp = 0;
  51.  
  52.   CloseHandle(CreateThread(0, 0, (LPTHREAD_START_ROUTINE)ThRunAndWaitShell, &Info, 0, &ThreadId));
  53.   return ThreadId;
  54. }
  55.  
  56. DWORD RunAndWaitShell2(HWND hWnd, PCHAR Operation, PCHAR FileName, PCHAR Parameters, PCHAR Directory, int nShowCmd)
  57. {
  58.   static SHELLEXECUTEINFOA Info;
  59.   static CHAR fileName[MAX_PATH];
  60.   static CHAR operation[20];
  61.   static CHAR directory[MAX_PATH];
  62.   static CHAR parameters[MAX_PATH];
  63.   DWORD ThreadId;
  64.  
  65.   lstrcpynA(fileName, FileName, MAX_PATH);
  66.   lstrcpynA(directory, Directory, MAX_PATH);
  67.   lstrcpynA(operation, Operation, 20);
  68.   lstrcpynA(directory, Directory, MAX_PATH);
  69.  
  70.   Info.cbSize = sizeof(Info);
  71.   Info.fMask  = SEE_MASK_NOCLOSEPROCESS;
  72.   Info.hwnd   = hWnd;
  73.   Info.lpVerb = operation;
  74.   Info.lpFile = fileName;
  75.   Info.lpParameters = parameters;
  76.   Info.lpDirectory  = directory;
  77.   Info.nShow = nShowCmd;
  78.   Info.hInstApp = 0;
  79.  
  80.   struct{
  81.     static BOOL __stdcall WINAPI ThRunAndWaitShell(LPSHELLEXECUTEINFOA Info)
  82.     {
  83.       BOOL R = ShellExecuteExA(Info);
  84.       WaitForSingleObject(Info->hProcess, INFINITE);
  85.       SendMessage(Info->hwnd, RS_FINISH, GetCurrentThreadId(), R);
  86.       CloseHandle(Info->hProcess);
  87. return R;
  88.     }
  89.   } Th;
  90.  
  91.   CloseHandle(CreateThread(0, 0, (LPTHREAD_START_ROUTINE)Th.ThRunAndWaitShell, &Info, 0, &ThreadId));
  92.   return ThreadId;
  93. }
  94.  
  95. __fastcall TForm1::TForm1(TComponent* Owner)
  96.   : TForm(Owner)
  97. {
  98. }
  99. //---------------------------------------------------------------------------
  100.  
  101. void __fastcall TForm1::Button1Click(TObject *Sender)
  102. {
  103.   Label1->Caption = IntToStr((int)RunAndWaitShell(Handle, "open", "d:\\Prueba.docx", 0, 0, SW_SHOW));
  104. }
  105. //---------------------------------------------------------------------------
  106. void __fastcall TForm1::Button2Click(TObject *Sender)
  107. {
  108.   Label2->Caption = IntToStr((int)RunAndWaitShell(Handle, "open", "d:\\Prueba.pdf", 0, 0, SW_SHOW));
  109. }
  110. //---------------------------------------------------------------------------
  111.  
  112. void  __fastcall TForm1::OnRunAndWaitShell(TMessage &Message)
  113. {
  114.   // Fin de ejecución
  115.   ShowMessage("Fin " + IntToStr((int)Message.WParam) + " ("  + IntToStr((int)Message.LParam) + ")");
  116. }

El comportamiento y las explicaciones son idénticas a la versión delphi.
En esta ocasión he implementado dos formas equivalentes de la función para los entusiastas de C/C++

Probado en C++Builder BCB5 y Berlin
 
Saludos.
  • 0

#3 ELKurgan

ELKurgan

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 566 posts
  • LocationEspaña

Posted 23 August 2018 - 03:09 AM

Como siempre, impresionante trabajo.

 

Muchas gracias

 

Saludos


  • 0

#4 egostar

egostar

    missing my father, I love my mother.

  • Administrador
  • 14460 posts
  • LocationMéxico

Posted 24 August 2018 - 12:10 PM

Siempre un gusto ver aportaciones con la calidad que sueles mostrar. (y)

 

Mis respetos amigo.

 

Saludos


  • 0




IP.Board spam blocked by CleanTalk.