Sign in Join
Wiki copytemporary

Executing External Programs/es: what changed

Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.

Title: Executing External Programs/es

OldNewLine
1+## Introducción
2+
3+   Hay varias formas de ejecutar programas externos, pero nos centraremos en una, **TProcess**.
4+
5+Si en Delphi utilizabas siempre **ShellExecute** y/o **WinExec**, puedes empezar a usar TProcess como alternativa en FPC/Lazarus (es válido también si estás ejecutando Lazarus en Linux, porque TProcess es multiplataforma).
6+
7+   **Nota:** FPC/Lazarus puede utilizar **ShellExecute** y/o **WinExec**, pero sólo en Win32. Si tu programa está enfocado a varias plataformas, utiliza TProcess, ¡es mejor!.
8+
9+Aquí tenemos las diferentes maneras disponibles sobre como ejecutar un programa/proceso/mandato externo en RTL, FCL y LCL.
10+
11+| Metodo | Librería | Plataformas | Línea simple | Características |
12+|----|----|----|----|----|
13+| [[Executing External Programs|ExecuteProcess]] | RTL | Plataforma Cruzada | Si | Muy limitado, synchronous |
14+| [[Executing External Programs|ShellExecute]] | WinAPI | MS Windows only | Yes | Muchos. Permite iniciar programas con permisos de elevación/admin. |
15+| [[Executing External Programs|fpsystem, fpexecve]] | Unix | Unix solamente | | |
16+| [[Executing External Programs/es|TProcess]] | FCL | Plataforma cruzada | No | Completas |
17+| [[Executing External Programs|RunCommand]] | FCL | Plataforma cruzada, **Requiere FPC 2.6.2+** | Si | Cubre el uso común de TProcess |
18+| [[Executing External Programs|OpenDocument]] | LCL | Plataforma cruzada | si | Solo para abrir documentos. El documento debe abrirse con la aplicación asociada al tipo de documento. |
19+
20+## TProcess
21+
22+   Puedes utilizar TProcess para ejecutar programas externos. Algunas de sus ventajas son:
23+
24+- Independiente de la plataforma
25+- Capaz de leer desde la entrada estándar (*stdout*) y de escribir en la salida estándar (*stdin*).
26+
27+   **Nota**: TProcess no es un terminal de shell! No puedes ejecutar directamente secuencias de comandos o reorientar la salida usando operadores como "\|", "\>", "\<", "&", etc. Es posible obtener los mismos resultados con TProcess utilizando Pascal, mira algunos ejemplos más abajo..
28+
29+   **Importante**: Debes especificar la ruta completa al ejecutable. Por ejemplo "/bin/cp " en lugar de "cp". Si el programa está en la ruta estándar, puedes utilizar la función **FindDefaultExecutablePath** de la unidad **FileUtil** de la LCL.
30+
31+### Un ejemplo sencillo
32+
33+``` pascal
34+ // Este es un programa de demostración que enseña como lanzar
35+ // un programa externo.
36+ program lanzarprograma;
37+
38+ uses
39+ Classes, SysUtils, Process;
40+
41+ // Definición de "UnProceso" como una variable
42+ // de tipo "TProcess"
43+ var
44+ UnProceso: TProcess;
45+
46+ begin
47+ // Ahora creamos UnProceso.
48+ UnProceso := TProcess.Create(nil);
49+
50+ // Asignamos a UnProceso la orden que debe ejecutar.
51+ // Vamos a lanzar el compilador de FreePascal
52+ UnProceso.CommandLine := 'ppc386 -h';
53+
54+ // Definimos una opción de comportamiento de 'TProccess'
55+ // La opción poWaitOnExit hará que nuestro programa
56+ // se detenga hasta que termine el programa lanzado
57+ UnProceso.Options := UnProceso.Options + [poWaitOnExit];
58+
59+ // Lanzamos la ejecución de 'ppc386 -h'.
60+ UnProceso.Execute;
61+
62+ // Nuestro programa espera hasta que 'ppc386' finaliza.
63+ UnProceso.Free;
64+ end.
65+```
66+
67+   ¡Eso es todo! Acabas de aprender cómo se ejecuta un programa externo desde dentro de tu propio programa.
68+
69+### Un ejemplo mejorado
70+
71+   De acuerdo, pero ¿cómo leo la salida que produce un programa que he ejecutado?
72+
73+   Bien, vamos a ampliar un poco nuestro ejemplo mediante lo siguiente:
74+
75+``` pascal
76+ program lanzarprograma2;
77+
78+ uses
79+ Classes, SysUtils, Process;
80+
81+ // Definición de "UnProceso" como una variable de tipo "TProcess"
82+ // También añadimos un TStringList para almacenar los datos
83+ // leídos desde la salida del programa.
84+ var
85+ UnProceso: TProcess;
86+ UnaLista: TStringList;
87+
88+ // Aquí comienza a ejecutarse nuestro programa.
89+ begin
90+
91+ // Creamos el objeto TStringList.
92+ UnaLista := TStringList.Create;
93+
94+ // Ahora creamos UnProceso.
95+ UnProceso := TProcess.Create(nil);
96+
97+ // Asignamos a UnProceso la orden que debe ejecutar.
98+ // Vamos a lanzazar el compilador de FreePascal
99+ // (CommandLine está Obsoleto)
100+ UnProceso.CommandLine := 'ppc386 -h';
101+
102+ // Definimos una opción de comportamiento de 'TProccess'
103+ // La opción poWaitOnExit hará que nuestro programa
104+ // se detenga hasta que termine el programa lanzado
105+ // con la opción poUsePipes advertimos que vamos a utilizar tuberías de Entrada/Salida
106+ // para que se capture la salida y así poder recuperar su contenido desde nuestro programa
107+
108+ UnProceso.Options := UnProceso.Options + [poWaitOnExit, poUsePipes];
109+
110+ // Lanzamos la ejecución de 'ppc386 -h'.
111+ UnProceso.Execute;
112+
113+ // Nuestro programa se detiene hasta que 'ppc386' finaliza.
114+
115+ // Ahora leemos la salida del programa que acabamos de ejecutar
116+ // dentro de TStringList.
117+ UnaLista.LoadFromStream(UnProceso.Output);
118+
119+ // Guardamos la salida en un archivo.
120+ UnaLista.SaveToFile('salida.txt');
121+
122+ // Ahora que hemos guardado el archivo podemos liberar la memoria
123+ // TStringList y TProcess.
124+ UnaLista.Free;
125+ UnProceso.Free;
126+ end.
127+```
128+
129+### Lectura de muchos datos de salida
130+
131+   En el ejemplo anterior esperamos a que el programa termine. En ese momento leemos lo que el programa ha escrito en la salida. Pero si el programa escribe muchos datos, la tubería se llena y será necesario leerlos para vaciarla, pero el programa llamante no leerá hasta que el programa llamado haya terminado. Nos encontraremos ante un típico *bloqueo de la muerte*.
132+
133+   El siguiente ejemplo, por tanto, no utiliza poWaitOnExit, sino que va leyendo la salida mientras el programa llamado se está ejecutando. La salida se almacena en una memoria intermedia, que se utilizará más tarde para leer en un TStringList, por ejemplo.
134+
135+``` pascal
136+ program procoutlarge;
137+ { Copyright (c) 2004 by Marc Weustink
138+
139+ Este ejemplo se ha creado con la esperanza de que resulte útil,
140+ pero SIN NINGUNA GARANTÍA; ni tan siquiera la garantía implícita de
141+ COMERCIALIZACIÓN o ADECUACIÓN A UN OBJETIVO PARTICULAR.
142+ }
143+
144+ uses
145+ Classes, Process, SysUtils;
146+
147+ const
148+ READ_BYTES = 2048;
149+
150+ var
151+ S: TStringList;
152+ M: TMemoryStream;
153+ P: TProcess;
154+ n: LongInt;
155+ BytesRead: LongInt;
156+
157+ begin
158+ // No podemos utilizar poWaitOnExit porque no
159+ // conocemos el tamaño de la salida. En Linux el tamaño
160+ // de la salida de la tubería es de 2 kB. Si los datos de salida son más
161+ // necesitamos leerlos. Esto no es posible, ya que estamos
162+ // esperando. Así que aquí nos encontramos en un punto muerto.
163+ //
164+ // Se utiliza un Memorystream temporal para almacenar la salida
165+
166+ M := TMemoryStream.Create;
167+ BytesRead := 0;
168+
169+ P := TProcess.Create(nil);
170+ P.CommandLine := 'ppc386 -va bogus.pp';
171+ P.Options := [poUsePipes];
172+ WriteLn('-- ejecutando --');
173+ P.Execute;
174+ while P.Running do
175+ begin
176+ // nos aseguramos de tener espacio
177+ M.SetSize(BytesRead + READ_BYTES);
178+
179+ // intentamos leerlo
180+ n := P.Output.Read((M.Memory + BytesRead)^, READ_BYTES);
181+ if n > 0
182+ then begin
183+ Inc(BytesRead, n);
184+ Write('.')
185+ end
186+ else begin
187+ // sin datos, esperamos 100 milisegundos
188+ Sleep(100);
189+ end;
190+ end;
191+ // leemos la última parte
192+ repeat
193+ // nos aseguramos de tener espacio
194+ M.SetSize(BytesRead + READ_BYTES);
195+ // intentamos leerlo
196+ n := P.Output.Read((M.Memory + BytesRead)^, READ_BYTES);
197+ if n > 0
198+ then begin
199+ Inc(BytesRead, n);
200+ Write('.');
201+ end;
202+ until n <= 0;
203+ if BytesRead > 0 then WriteLn;
204+ M.SetSize(BytesRead);
205+ WriteLn('-- ejecutado --');
206+
207+ S := TStringList.Create;
208+ S.LoadFromStream(M);
209+ WriteLn('-- total de líneas = ', S.Count, ' --');
210+ for n := 0 to S.Count - 1 do
211+ begin
212+ WriteLn('| ', S[n]);
213+ end;
214+ WriteLn('-- fin --');
215+ S.Free;
216+ P.Free;
217+ M.Free;
218+ end.
219+```
220+
221+### Uso de entrada y salida de un TProcess
222+
223+   Véase el ejemplo processdemo en [Lazarus-CCR SVN](https://lazarus-ccr.svn.sourceforge.net/svnroot/lazarus-ccr/examples/process)
224+
225+### Notas sobre la utilización de TProcess
226+
227+   Si estas creando un programa que funciones en diversas plataformas, puedes cambiar el programa a ejecutar de acuerdo con el sistema operativo, mediante las directivas *{\$IFDEF}* y *{\$ENDIF}*.
228+
229+   Ejemplo:
230+
231+``` pascal
232+ {...}
233+ unProceso:TProcess.Create(nil)
234+ {$IFDEF WIN32}
235+ unProceso.CommandLine := 'calc.exe'; //calculadora de Windows
236+ {$ENDIF}
237+ {$IFDEF LINUX}
238+ unProceso.CommandLine := 'kcalc'; //calculadora de KDE o bien xcalc si no tienes KDE
239+ {$ENDIF}
240+ unProceso.Execute; //también puedes utilizar unProceso.Active:=True
241+ {...}
242+```
243+
244+En cuanto a TProcess tomando como referencia las propiedades del componente visual tiene las siguientes opciones (po=process options):
245+
246+- poDebugOnlyThisProcess.
247+- poDebugProcess.
248+- poDefaultErrorMode
249+- poNewConsole.
250+- poNewProcessGroup.
251+- poNoConsole.
252+- poRunSuspended.
253+- poStderrToOutPut.
254+- poUsePipes: utilizado para interconectar procesos.
255+- poWaitOnExit: esperar a que finalice el programa.
256+
257+<figure>
258+<img src="TProcess_Options.PNG" title="TProcess_Options.PNG" width="300" />
259+<figcaption>TProcess_Options.PNG</figcaption>
260+</figure>
261+
262+A falta de explicar que hace cada opción.
263+
264+[Referencia TProcess](http://lazarus-ccr.sourceforge.net/docs/fcl/process/tprocess.html).
265+
266+### Ejemplo de *conversación* con el proceso aspell
267+
268+   Dentro del código fuente [pasdoc](http://pasdoc.sourceforge.net/) puedes encontrar dos unidades que realizan corrección ortográfica *conversando* con un proceso aspell a través de tuberías:
269+
270+- [PasDoc_ProcessLineTalk.pas unit](http://cvs.sourceforge.net/viewcvs.py/*checkout*/pasdoc/pasdoc/source/component/PasDoc_ProcessLineTalk.pas) ejecuta la clase TProcessLineTalk, descendiente de TProcess, que se puede utilizar fácilmente para comunicar con cualquier proceso línea a línea.
271+
272+- [PasDoc_Aspell.pas units](http://cvs.sourceforge.net/viewcvs.py/*checkout*/pasdoc/pasdoc/source/component/PasDoc_Aspell.pas) ejecuta la clase TAspellProcess, que realiza corrección ortográfica utilizando la instancia fundamental TProcessLineTalk para ejecutar aspell y comunicarse con el proceso aspell que se está ejecutando.
273+
274+   Ambas unidades son bastante independientes del resto de fuentes de pasdoc, así que pueden servir como ejemplos reales de la utilización de TProcess para ejecutar y comunicarse a través de tuberías con otros programas.
275+
276+### Reemplazar operadores de *Shell* como "\|", "\<" y "\>"
277+
278+   A veces queremos ejecutar una orden compleja que envía (entuba) sus datos a otra orden o a un archivo. Algo como
279+
280+` ShellExecute('primeraorden.exe | segundaorden.exe');`
281+
282+o
283+
284+` ShellExecute('dir > salida.txt');`
285+
286+    Esto no funciona con TProcess. p.e.:
287+
288+` // esto no funciona`\
289+` Process.CommandLine := 'primeraorden.exe | segundaorden.exe'; `\
290+` Process.Execute;`
291+
292+#### ¿Por qué utilizar operadores especiales para redirigir la salida no funciona?
293+
294+   TProcess es justamente, eso, un proceso, no es un entorno de *shell*. No es dos procesos, únicamente es un proceso. Es posible, no obstante, redirigir la salida de la manera que queremos. Ver [[Executing External Programs/es|más adelante, la siguiente sección]].
295+
296+### Cómo redirigir la salida con TProcess
297+
298+   Podemos redirigir la salidad de un comando a otro comando utilizando una instancia de TProcess para cada commando.
299+
300+   Este es un ejemplo que explica cómo redirigir la salida de un proceso a otro. Para redirigir la salida de un proceso a un archivo o flujo ver el ejemplo [[Executing External Programs/es|Lectura de muchos datos de salida]]
301+
302+``` pascal
303+ program Proyecto1;
304+
305+ uses
306+ Classes, sysutils, process;
307+
308+ var
309+ PrimerProceso,
310+ SegundoProceso: TProcess;
311+ Buffer: array[0..127] of char;
312+ ContadorLectura: Integer;
313+ TamanyoLectura: Integer;
314+ begin
315+ PrimerProceso := TProcess.Create(nil);
316+ SegundoProceso := TProcess.Create(nil);
317+
318+ PrimerProceso.Options := [poUsePipes];
319+ SegundoProceso.Options := [poUsePipes,poStderrToOutPut];
320+
321+ PrimerProceso.CommandLine := 'pwd';
322+ SegundoProceso.CommandLine := 'grep '+ DirectorySeparator+ ' -';
323+ // esto sería lo mismo que "pwd | grep / -"
324+
325+ PrimerProceso.Execute;
326+ SegundoProceso.Execute;
327+
328+ while PrimerProceso.Running or (PrimerProceso.Output.NumBytesAvailable > 0) do
329+ begin
330+ if PrimerProceso.Output.NumBytesAvailable > 0 then
331+ begin
332+ // asegurate de que no lees más datos que los asignados
333+ // en el buffer
334+ TamanyoLectura := PrimerProceso.Output.NumBytesAvailable;
335+ if TamanyoLectura > SizeOf(Buffer) then
336+ TamanyoLectura := SizeOf(Buffer);
337+ // ahora leemos la salida en el buffer
338+ ContadorLectura := PrimerProceso.Output.Read(Buffer[0], TamanyoLectura);
339+ // y escribimos el buffer en el segundo proceso
340+ SegundoProceso.Input.Write(Buffer[0], ContadorLectura);
341+
342+ // Si SegundoProceso escribe muchos datos en la salida a continuación,
343+ // debes leer los datos aquí para evitar un punto muerto
344+ // ver el ejemplo anterior "Lectura de muchos datos de salida"
345+ end;
346+ end;
347+ // Cerrar la entrad de SegundoProceso
348+ // y finaliza el procesamiento de datos
349+ SegundoProceso.CloseInput;
350+
351+ // y esperar a que finalice
352+ // ten cuidado con que comando se ejecuta porque no puede terminar cuando
353+ // la entrada está cerrada y la siguiente línea será un bucle sin fin
354+ while SegundoProceso.Running do
355+ Sleep(1);
356+ // ¡eso es todo! el resto del programa es sólo un ejemplo
357+ // es poco "útil"
358+
359+ // vamos a reutilizar el buffer para la salida del SegundoProceso
360+ // a la salida estándar de este programa
361+ WriteLn('Comienza la salida de Grep:');
362+ TamanyoLectura := SegundoProceso.Output.NumBytesAvailable;
363+ if TamanyoLectura > SizeOf(Buffer) then
364+ TamanyoLectura := SizeOf(Buffer);
365+ if TamanyoLectura > 0 then
366+ begin
367+ ContadorLectura := SegundoProceso.Output.Read(Buffer, TamanyoLectura);
368+ WriteLn(Copy(Buffer,0, ContadorLectura));
369+ end
370+ else
371+ WriteLn('grep NO encontró lo que se buscaba. ', SegundoProceso.ExitStatus);
372+ WriteLn('Termina la salida de Grep:');
373+
374+ // Liberamos los objetos TProcess
375+ PrimerProceso.Free;
376+ SegundoProceso.Free;
377+ end.
378+```
379+
380+   Eso es todo. Ahora podemos redirigir la salida de un programa a otro.
381+
382+#### Notas
383+
384+   Este ejemplo puede parecer exagerado, ya que es posible ejecutar "complicados" comandos shell con TProcess como:
385+
386+` Process.Commandline := 'sh -c "pwd | grep / -"';`
387+
388+   Pero el ejemplo es más multiplataforma, ya que no necesita ninguna modificación para ejecutarse en Windows o Linux, etc. *sh* puede o no existir en su plataforma y, en general sólo disponible en plataformas *nix. También tenemos más flexibilidad en nuestro ejemplo ya que se puede leer y escribir desde/a la entrada, salida y salida de error estándar de cada proceso individual, lo que podría ser muy ventajoso para tu proyecto.
389+
390+### Redirigir entrada y salida bajo *root*
391+
392+   Un problema común en Unix (macOS) y Linux surge cuando se desea ejecutar algún programa en la cuenta de *root* (o, en otra cuenta de usuario). Un ejemplo sería ejecutar el comando ping''''.
393+
394+   Si puedes usar sudo para esto, se puede adaptar el ejemplo siguiente, una adaptación de uno publicado por andyman en el foro ([1](http://lazarus.freepascal.org/index.php/topic,14479.0.html)). Este ejemplo ejecuta la orden `ls` en el `/root`, pero por supuesto puede ser adaptada.
395+
396+   "Una forma mejor'' de hacerlo es usar el paquete PolicyKit, que debería estar disponible en todos los Linux recientes. [ver el hilo del foro para más detalles.](http://lazarus.freepascal.org/index.php/topic,14479.0.html)
397+
398+   Gran parte de este código es similar al ejemplo anterior, pero también se muestra cómo redirigir los flujos stdout y stderr del proceso llamado a los flujos stdout y stderr de nuestro propio código.
399+
400+``` pascal
401+ program rootls;
402+
403+ { Demonstrates using TProcess, redirecting stdout/stderr to our stdout/stderr,
404+ calling sudo on Linux/macOS, and supplying input on stdin}
405+ {$mode objfpc}{$H+}
406+
407+ uses
408+ Classes,
409+ Math, {for min}
410+ Process;
411+
412+ procedure RunsLsRoot;
413+ var
414+ Proc: TProcess;
415+ CharBuffer: array [0..511] of char;
416+ ReadCount: integer;
417+ ExitCode: integer;
418+ SudoPassword: string;
419+ begin
420+ WriteLn('Please enter the sudo password:');
421+ Readln(SudoPassword);
422+ ExitCode := -1; //Start out with failure, let's see later if it works
423+ Proc := TProcess.Create(nil); //Create a new process
424+ try
425+ Proc.Options := [poUsePipes, poStderrToOutPut]; //Use pipes to redirect program stdin,stdout,stderr
426+ Proc.CommandLine := 'sudo -S ls /root'; //Run ls /root as root using sudo
427+ // -S causes sudo to read the password from stdin.
428+ Proc.Execute; //start it. sudo will now probably ask for a password
429+
430+ // write the password to stdin of the sudo program:
431+ SudoPassword := SudoPassword + LineEnding;
432+ Proc.Input.Write(SudoPassword[1], Length(SudoPassword));
433+ SudoPassword := '%*'; //short string, hope this will scramble memory a bit; note: using PChars is more fool-proof
434+ SudoPassword := ''; // and make the program a bit safer from snooping?!?
435+
436+ // main loop to read output from stdout and stderr of sudo
437+ while Proc.Running or (Proc.Output.NumBytesAvailable > 0) or
438+ (Proc.Stderr.NumBytesAvailable > 0) do
439+ begin
440+ // read stdout and write to our stdout
441+ while Proc.Output.NumBytesAvailable > 0 do
442+ begin
443+ ReadCount := Min(512, Proc.Output.NumBytesAvailable); //Read up to buffer, not more
444+ Proc.Output.Read(CharBuffer, ReadCount);
445+ Write(StdOut, Copy(CharBuffer, 0, ReadCount));
446+ end;
447+ // read stderr and write to our stderr
448+ while Proc.Stderr.NumBytesAvailable > 0 do
449+ begin
450+ ReadCount := Min(512, Proc.Stderr.NumBytesAvailable); //Read up to buffer, not more
451+ Proc.Stderr.Read(CharBuffer, ReadCount);
452+ Write(StdErr, Copy(CharBuffer, 0, ReadCount));
453+ end;
454+ end;
455+ ExitCode := Proc.ExitStatus;
456+ finally
457+ Proc.Free;
458+ Halt(ExitCode);
459+ end;
460+ end;
461+
462+ begin
463+ RunsLsRoot;
464+ end.
465+```
466+
467+   Otras cuestiones:
468+
469+   Sería, sin duda, aconsejable ver si realmente *sudo* pide una contraseña. Esto se puede comprobar consistetemente estableciendo la variable de entorno *SUDO_PROMPT* a algo que tener en cuenta mientras la lectura de la salida estándar de ***TProcess*** para evitar el problema de que el sistema es diferente para diferentes idiomas. Configurar una variable de entorno hace que los valores predeterminados (heredados de nuestro proceso) se borren, así que tenemos que copiar el entorno de nuestro programa, si es necesario.
470+
471+### Parámetros que contienen espacios (Reemplazar comillas del interprete)
472+
473+   En el interprete de Linux, es posible escribir los argumentos entrecomillados así:
474+
475+` gdb --batch --eval-command="info symbol 0x0000DDDD" miprograma`
476+
477+   Y GDB recibirá 3 argumentos (además del primer argumento que es la ruta completa al ejecutable):
478+
479+` #--batch`\
480+` #--eval-command=info symbol 0x0000DDDD`\
481+` #la ruta completa a miprograma`
482+
483+   **TProcess** además pasa los parámetros que contengan espacios, pero utiliza un estilo diferente de entrecomillado. En lugar de sólo entrecomillar parte del parámetro, entrecomilla todo él. Tal que así:
484+
485+` TProcess.CommandLine := '/usr/bin/gdb --batch "--eval-command=info symbol 0x0000DDDD" /midirectorio/yo/miprograma';`
486+
487+   Y también recuerda pasar sólo rutas completas.
488+
489+   Véase también la discusión al respecto: <http://bugs.freepascal.org/view.php?id=14446>
490+
491+## Alternativas
492+
493+   A veces, no es necesario llamar explícitamente a un programa externo para obtener la funcionalidad que necesitas. A continuación se presentan algunos ejemplos
494+
495+### Abrir una página web en el navegador web predeterminado
496+
497+   Algo así sería más fácil:
498+
499+``` pascal
500+ uses LCLIntf;
501+ ...
502+ OpenURL(URL);
503+```
504+
505+   Sin embargo, puedes utilizar algo como esto:
506+
507+``` pascal
508+ uses Process, LCLIntf;
509+
510+ procedure OpenWebPage(URL: string);
511+ // Apparently you need to pass your URL inside ", like "www.lazarus.freepascal.org"
512+ var
513+ Browser, Params: string;
514+ begin
515+ FindDefaultBrowser(Browser, Params);
516+ with TProcess.Create(nil) do
517+ try
518+ Executable := Browser;
519+ Params:=Format(Params, [URL]);
520+ Params:=copy(Params,2,length(Params)-2); // elimina "", la nueva versión de TProcess.Parameters los hace por sí mismo
521+ Parameters.Add(Params);
522+ Options := [poNoConsole];
523+ Execute;
524+ finally
525+ Free;
526+ end;
527+ end;
528+```
529+
530+## Ver también
531+
532+- [[TXMLPropStorage/es|TXMLPropStorage]]
533+- [[Webbrowser]]
534+
535+---
536+*From the Free Pascal wiki, page "Executing External Programs/es", as it stood on 2020-08-26 after a change by Iskraelectrica, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*
537+Category: Castellano
538+Category: Español

Back to history