Ir al contenido


Foto

[MULTILENGUAJE] Pasar parámetros en caliente


  • Por favor identifícate para responder
2 respuestas en este tema

#1 escafandra

escafandra

    Advanced Member

  • Moderadores
  • PipPipPip
  • 3.947 mensajes
  • LocationMadrid - España

Escrito 15 febrero 2020 - 05:00

Es una pregunta que he visto repetida y que yo mismo me hecho e implementado. Se trata de pasar parámetros a una aplicación que ya se está ejecutando.
 
Ejecutar una Aplicación con sus parámetros y su manejo, es de sobra conocido. El problema aparece cuando queremos pasar otros distintos cuando la aplicación ya se está ejecutando. Quizás una forma mui apetecible para el usuario sea volver a ejecutar la aplicación con sus nuevos parámetros y que esta segunda instancia se encargue de enviarlos a la primera para autodestruirse después. Eso es lo que voy a plantear.
 
En alguna ocasión he comentado el asunto de los archivos de memoria compartida para comunicar aplicaciones. También lo podemos hacer vía sockets pero en esta ocasión voy a usar WM_COPYDATA de la que también se ha hablado. El planteamiento es simple, se envía la nueva linea de comandos y la aplicación la recibe, correcto, pero ahora hay que recibir y leer esa linea e interpretarla. Se me ha ocurrido usar justamente el mismo sistema que usa delphi con  ParamCount y ParamStr y para ello he preparado una Unit que las sobrecarga y hace transparente al desarrollador todo este asunto. Sin embargo, siempre podremos recuperar los parámetros iniciales si usamos System.ParamCount y System.ParamStr. Esta técnica nos va a abstraer de como se obtienen los parámetros y simplemente usaremos para su manejo el mismo código que si fuesen los parámetros pasados en el arranque de nuestra aplicación (sin especificar el namespace System o usando Param).
 
Para terminar la introducción tengo que añadir que no usaremos unicode para el manejo de WM_COPYDATA, de esta forma el sistema será compatible con las versiones de delphi que no lo usan. Esto quiere decir que el mensaje WM_COPYDATA va a manejar cadenas estilo C AnsiChar. Esto también será transparente para el desarrollador, que podrá después usar el tipo de cadena que le plazca.
 
Esta es la Unit con el código necesario para cambiar y leer la línea de comandos de la Aplicación:


delphi
  1. {*******************************************************************************
  2.  
  3.   Unidad para pasar parámetros en caliente a una aplicación
  4.   © escafandra 2020
  5.  
  6.   La linea de parámetros se envía con WM_COPYDATA. La funcion SendParamsLine
  7.   sirve de apoyo. La Aplicación debe disponer de un sistema para recibir el
  8.   mensaje WM_COPYDATA si se desea puede ayudarse de GetParamsLine.
  9.  
  10.   Los nuevos parámetros sustituyen a los originales y se leen como siempre
  11.   con ParamCount y ParamStr
  12.  
  13.   Si se desean recuperar los parámetros que se pasaron originalmente a la APP
  14.   deben usarse System.ParamCount System.ParamStr
  15.  
  16. *******************************************************************************}
  17.  
  18.  
  19. unit Param;
  20.  
  21. interface
  22.  
  23. uses
  24. Windows, Messages, ShellAPI;
  25.  
  26.  
  27. procedure SetCommandLine(S: String);
  28. function ParamCount: Integer;
  29. function ParamStr(n: integer): String;
  30. function SendParamsLine(hWSrc, hWDest: HWND; CmdLine: AnsiString): LRESULT;
  31. function GetParamsLine(var Msg: TMessage): String;
  32.  
  33. implementation
  34.  
  35. type
  36. AWCHAR = Array [0..0] of PWCHAR;
  37. PAWCHAR = ^AWCHAR;
  38.  
  39. var
  40. CmdLine_: String;
  41.  
  42. {$WARN SYMBOL_PLATFORM OFF}
  43. // Establece una nueva línea de comandos para la aplicación
  44. procedure SetCommandLine(S: String);
  45. begin
  46. CmdLine_:= S;
  47. end;
  48.  
  49. // Lee la Linea de comandos de la Aplicación
  50. function GetCommandLine: String;
  51. begin
  52. Result:= CmdLine_;
  53. if CmdLine_ = '' then Result:= String(CmdLine);
  54. end;
  55.  
  56. // Devuelve el numero de parametros de la App
  57. // Sobrecarga System.ParamCount
  58. function ParamCount: Integer;
  59. begin
  60. CommandLineToArgvW(PWCHAR(WideString(GetCommandLine)), Result);
  61. Result:= Result - 1;
  62. end;
  63.  
  64. // Devuelve un parámetro dado un índice
  65. // Sobrecarga System.ParamStr
  66. // Si el indice es 0, devuelve el Path de la App: System.ParamStr(0)
  67. function ParamStr(n: integer): String;
  68. var
  69. Count: integer;
  70. Argv: PAWCHAR;
  71. begin
  72. Result:= '';
  73. Argv:= PAWCHAR(CommandLineToArgvW(PWCHAR(WideString(GetCommandLine)), Count));
  74. if n = 0 then
  75. Result:= System.ParamStr(0)
  76. else if n < Count then
  77. Result:= Argv[n];
  78. end;
  79. {$WARN SYMBOL_PLATFORM ON}
  80.  
  81. // Envía un Mensaje WM_COPYDATA con la nueva línea de parámetros
  82. function SendParamsLine(hWSrc, hWDest: HWND; CmdLine: AnsiString): LRESULT;
  83. var
  84. Data: TCOPYDATASTRUCT;
  85. begin
  86. Data.dwData:= hWSrc;
  87. Data.lpData:= PAnsiChar(CmdLine); //PCHAR(windows.GetCommandLine);
  88. Data.cbData:= lstrlenA(PAnsiChar(Data.lpData))+1;
  89. Result:= SendMessage(hWDest, WM_COPYDATA, 0, Integer(@Data));
  90. end;
  91.  
  92. // Lee un Mensaje WM_COPYDATA con la nueva línea de parámetros
  93. function GetParamsLine(var Msg: TMessage): String;
  94. begin
  95. if Msg.msg = WM_COPYDATA then
  96. begin
  97. Result:= PAnsiChar(PCopyDataStruct(Msg.lParam).lpData);
  98. Msg.Result:= 1;
  99. end;
  100. end;
  101.  
  102. end.

Como veis, uso una variable local CmdLine_ que se encarga de guardar la nueva linea de parámetros. ParamCount y ParamStr están sobrecargados por el namespace y usan la API CommandLineToArgvW para convertir la línea de comandos en una matriz o array de comandos individuales, que luego manejaremos con un índice.
 
Lo siguiente es cambiar el comportamiento de inicio nuestra aplicación, para ello tenemos que escribir código en el archivo de proyecto, cosa que ya se ha visto en el foro otras veces.

 

Un proyecto simple tipo puede ser esto:


delphi
  1. program PasaParam;
  2.  
  3. uses
  4. Forms,
  5. Unit1 in 'Unit1.pas' {Form1};
  6.  
  7. {$R *.res}
  8.  
  9. begin
  10. Application.Initialize;
  11. Application.CreateForm(TForm1, Form1);
  12. Application.Run;
  13. end.

Sobre él vamos a introducir unos cambios y quedará como esto:


delphi
  1. program PasaParam;
  2.  
  3. uses
  4. Forms, Windows, Param,
  5. Unit1 in 'Unit1.pas' {Form1};
  6.  
  7. {$R *.res}
  8.  
  9. var
  10. W : Integer;
  11. begin
  12. // Buscamos la instancia original
  13. Application.Title:= '';
  14. W:= Windows.FindWindow('TApplication', 'Mi_Aplicacion');
  15. Application.Title:= 'Mi_Aplicacion';
  16.  
  17. // Si existe le enviamos parametros, la colocamos en primer plano y nos vamos
  18. if(W<>0) then
  19. begin
  20. SendParamsLine(Application.Handle, W, windows.GetCommandLine);
  21. ShowWindow(W, SW_RESTORE);
  22. SetForegroundWindow(W);
  23. Application.Terminate;
  24. end
  25. else
  26. begin
  27. Application.Initialize;
  28. Application.CreateForm(TForm1, Form1);
  29. Application.Run;
  30. end;
  31. end.

Este proyecto trata de localizar un Handle perteneciente a otra instancia de TApplication y que tenga el nombre de nuestra aplicación. Es importante no dar a todas nuestras aplicaciones el mismo nombre y buscar para este caso un nombre inequívoco o encontrará la primera ventana que cumpla el requisito y puede no ser la que nos interesa.

Si encuentra otra instancia, enviará la línea de parámetros con SendParamsLine que usa WM_COPYDATA. Luego traerá a primer plano esa primera instancia de a APP y terminará su ejecución. Si no hemos encontrado otra instancia, nuestra App arranca normalmente y hace su tarea.
 
Queda un punto por cubrir, es la recepción de los parámetros que se manden. Para ello tenemos que husmear en los mensajes que reciba TApplication. Delphi lo tiene previsto con el método TApplication.HookMainWindow:
 


delphi
  1. procedure TForm1.FormCreate(Sender: TObject);
  2. begin
  3. Application.HookMainWindow(HookAppMessages);
  4. end;
  5.  
  6. function TForm1.HookAppMessages(var Msg: TMessage): Boolean;
  7. begin
  8. Result:= false;
  9. if Msg.msg = WM_COPYDATA then
  10. begin
  11. //...........
  12. // Código ara tratar el mensaje.....
  13. Result:= true;
  14. end;
  15. end;

Pues esto es todo lo necesario, ahora os muestro un ejemplo de una pequeña aplicación que recibe parámetros y simplemente los escribe en un Memo:
 


delphi
  1. unit Unit1;
  2.  
  3. interface
  4.  
  5. uses
  6. Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  7. StdCtrls, Param, Dialogs;
  8.  
  9. type
  10. TForm1 = class(TForm)
  11. Memo1: TMemo;
  12. procedure FormCreate(Sender: TObject);
  13. private
  14. function HookAppMessages(var Msg: TMessage): Boolean;
  15. public
  16. procedure LoadParams;
  17. end;
  18.  
  19. var
  20. Form1: TForm1;
  21.  
  22. implementation
  23.  
  24. {$R *.dfm}
  25.  
  26. procedure TForm1.FormCreate(Sender: TObject);
  27. begin
  28. Application.HookMainWindow(HookAppMessages);
  29. LoadParams;
  30. end;
  31.  
  32. function TForm1.HookAppMessages(var Msg: TMessage): Boolean;
  33. begin
  34. Result:= false;
  35. if Msg.msg = WM_COPYDATA then
  36. begin
  37. SetCommandLine(GetParamsLine(Msg));
  38. Result:= true;
  39. LoadParams;
  40. end;
  41. end;
  42.  
  43. procedure TForm1.LoadParams;
  44. var
  45. i: integer;
  46. begin
  47. Memo1.Lines.Add('Params:');
  48. for i:= 0 to ParamCount do
  49. Memo1.Lines.Add(Format('Param(%d): %s',[i, ParamStr(i)]));
  50. Memo1.Lines.Add('');
  51. end;
  52.  
  53. end.

El código está probado en delphi7 y Berlin. La comunicación entre la APP compilada con delphi7 l la compilada con Berlin es posible gracias al uso de AsnsiString el el manejo del mensaje WM_COPYDATA. como ya os comentaba.
 
Bueno, hasta aquí hemos llegado y espero que sea de utilidad. Os dejo los enlaces de descarga del código y ejemplos.
 

 

Saludos.

Archivos adjuntos


  • 1

#2 escafandra

escafandra

    Advanced Member

  • Moderadores
  • PipPipPip
  • 3.947 mensajes
  • LocationMadrid - España

Escrito 16 febrero 2020 - 08:35

Para Builder el sistema es similar:
 
Este sería el archivo cabecera Param.h


cpp
  1. //---------------------------------------------------------------------------
  2.  
  3. #ifndef ParamH
  4. #define ParamH
  5. //---------------------------------------------------------------------------
  6.  
  7. namespace Param{
  8. int __fastcall ParamCount();
  9. String __fastcall ParamStr(int n);
  10. }
  11. void __fastcall SetCommandLine(String S);
  12. LRESULT __fastcall SendParamsLine(HWND hWSrc, HWND hWDest, AnsiString CmdLine);
  13. String __fastcall GetParamsLine(TMessage &Msg);
  14.  
  15. #define ParamCount Param::ParamCount
  16. #define ParamStr Param::ParamStr
  17.  
  18.  
  19. #endif

 
Y este el código Param.cpp


cpp
  1. /*******************************************************************************
  2.  
  3.   Unidad para pasar parámetros en caliente a una aplicación
  4.   © escafandra 2020
  5.  
  6.   La linea de parámetros se envía con WM_COPYDATA. La funcion SendParamsLine
  7.   sirve de apoyo. La Aplicación debe disponer de un sistema para recibir el
  8.   mensaje WM_COPYDATA si se desea puede ayudarse de GetParamsLine.
  9.  
  10.   Los nuevos parámetros sustituyen a los originales y se leen como siempre
  11.   con ParamCount y ParamStr
  12.  
  13.   Si se desean recuperar los parámetros que se pasaron originalmente a la APP
  14.   deben usarse System.ParamCount System.ParamStr
  15.  
  16. *******************************************************************************/
  17.  
  18. //---------------------------------------------------------------------------
  19. #include <vcl.h>
  20. #pragma hdrstop
  21.  
  22. #include <Windows.h>
  23. #include <ShellAPI.h>
  24. #include "Param.h"
  25.  
  26. //---------------------------------------------------------------------------
  27. #pragma package(smart_init)
  28. //#define Param::ParamCount
  29. //#define Param::ParamStr
  30.  
  31. String CmdLine_;
  32.  
  33. // Establece una nueva línea de comandos para la aplicación
  34. void __fastcall SetCommandLine(String S)
  35. {
  36. CmdLine_ = S;
  37. }
  38. // Lee la Linea de comandos de la Aplicación
  39. WideString __fastcall GetCommandLine_()
  40. {
  41. WideString Result = CmdLine_;
  42. if(CmdLine_ == "") Result = String(CmdLine);
  43. return Result;
  44. }
  45.  
  46. String __fastcall GetExeFile()
  47. {
  48. CHAR FileName[MAX_PATH + 1];
  49. GetModuleFileNameA(0, FileName, sizeof(FileName));
  50. return String(FileName);
  51. }
  52.  
  53. // Devuelve el numero de parametros de la App
  54. // Sobrecarga System.ParamCount
  55. int __fastcall ParamCount()
  56. {
  57. int Result;
  58. CommandLineToArgvW(GetCommandLine_().c_bstr(), &Result);
  59. return Result-1;
  60. }
  61.  
  62. // Devuelve un parámetro dado un índice
  63. // Sobrecarga System.ParamStr
  64. // Si el indice es 0, devuelve el Path de la App: System.ParamStr(0)
  65. String __fastcall ParamStr(int n)
  66. {
  67. int Count;
  68. WCHAR** Argv;
  69. String Result = "";
  70. Argv = CommandLineToArgvW(GetCommandLine_().c_bstr(), &Count);
  71. if(n==0)
  72. Result = GetExeFile(); //System::ParamStr(0);
  73. else if(n < Count)
  74. Result = Argv[n];
  75. return Result;
  76. }
  77.  
  78. // Envía un Mensaje WM_COPYDATA con la nueva línea de parámetros
  79. LRESULT __fastcall SendParamsLine(HWND hWSrc, HWND hWDest, AnsiString CmdLine)
  80. {
  81. COPYDATASTRUCT Data;
  82. Data.dwData = (ULONG)hWSrc;
  83. Data.lpData = CmdLine.c_str();
  84. Data.cbData = lstrlenA((PCHAR)Data.lpData)+1;
  85. return SendMessage(hWDest, WM_COPYDATA, 0, (LPARAM)&Data);
  86. }
  87.  
  88. // Lee un Mensaje WM_COPYDATA con la nueva línea de parámetros
  89. String __fastcall GetParamsLine(TMessage &Msg)
  90. {
  91. String Result = "";
  92. if(Msg.Msg == WM_COPYDATA){
  93. Result = PCHAR(PCOPYDATASTRUCT(Msg.LParam)->lpData);
  94. Msg.Result = 1;
  95. }
  96. return Result;
  97. }

 
Os pongo cómo quedaría el archivo de la aplicación, en este caso para Berlin:


cpp
  1. //---------------------------------------------------------------------------
  2.  
  3. #include <vcl.h>
  4. #pragma hdrstop
  5. #include <tchar.h>
  6. #include "Param.h"
  7. //---------------------------------------------------------------------------
  8. USEFORM("Unit1.cpp", Form1);
  9. //---------------------------------------------------------------------------
  10. int WINAPI _tWinMain(HINSTANCE, HINSTANCE, LPTSTR, int)
  11. {
  12. try
  13. {
  14. // Buscamos la instancia original
  15. Application->Title = "";
  16. HWND hWnd = FindWindow(L"TApplication", L"Mi_Aplicacion");
  17. Application->Title = L"Mi_Aplicacion";
  18.  
  19. // Si existe le enviamos parámetros, la colocamos en primer plano y nos vamos
  20. if(hWnd){
  21. SendParamsLine(Application->Handle, hWnd, GetCommandLine());
  22. ShowWindow(hWnd, SW_RESTORE);
  23. SetForegroundWindow(hWnd);
  24. Application->Terminate();
  25. }
  26. else{
  27. Application->Initialize();
  28. // Application->MainFormOnTaskBar = true;
  29. Application->CreateForm(__classid(TForm1), &Form1);
  30. Application->Run();
  31. }
  32. }
  33. catch (Exception &exception)
  34. {
  35. Application->ShowException(&exception);
  36. }
  37. catch (...)
  38. {
  39. try
  40. {
  41. throw Exception("");
  42. }
  43. catch (Exception &exception)
  44. {
  45. Application->ShowException(&exception);
  46. }
  47. }
  48. return 0;
  49. }
  50. //---------------------------------------------------------------------------

Importante observación es comentar la línea:


cpp
  1. // Application->MainFormOnTaskBar = true;

En caso contrario no se encontará el Handle de la aplicación. Podría entonces buscarse el del MainForm pero eso será para otra ocasión.

 

Y para terminar os pongo el mismo código de ejemplo que puse para delphi, traducido a C++


cpp
  1. //---------------------------------------------------------------------------
  2.  
  3. #include <vcl.h>
  4. #pragma hdrstop
  5.  
  6. #include "Unit1.h"
  7.  
  8. //---------------------------------------------------------------------------
  9. #pragma package(smart_init)
  10. #pragma resource "*.dfm"
  11.  
  12. #include "Param.h"
  13.  
  14. TForm1 *Form1;
  15. //---------------------------------------------------------------------------
  16. __fastcall TForm1::TForm1(TComponent* Owner)
  17. : TForm(Owner)
  18. {
  19. Application->HookMainWindow(HookAppMessages);
  20. LoadParams();
  21. }
  22. //---------------------------------------------------------------------------
  23.  
  24. bool __fastcall TForm1::HookAppMessages(TMessage &Msg)
  25. {
  26. bool Result = false;
  27. if(Msg.Msg == WM_COPYDATA){
  28. SetCommandLine(GetParamsLine(Msg));
  29. Result = true;
  30. LoadParams();
  31. }
  32. return Result;
  33. }
  34. //---------------------------------------------------------------------------
  35.  
  36. void __fastcall TForm1::LoadParams()
  37. {
  38. Memo1->Lines->Add("Params:");
  39. for(int i = 0; i<=ParamCount(); i++)
  40. Memo1->Lines->Add(Format("Param(%d): %s", ARRAYOFCONST((i, ParamStr(i)))));
  41. Memo1->Lines->Add("");
  42. }
  43. //---------------------------------------------------------------------------

El código se ha probado en Builder5 y Berlin.



Saludos.

Archivos adjuntos


  • 0

#3 ELKurgan

ELKurgan

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 554 mensajes
  • LocationEspaña

Escrito 17 febrero 2020 - 12:26

como siempre, tremendo aporte, amigo

 

Muchas gracias y un saludo

 

:ap:  :ap: 


  • 0