Running external applications from Eiffel

129 views
Skip to first unread message

Alexandr Naumchev

unread,
Jul 13, 2017, 4:09:47 AM7/13/17
to Eiffel Users
Hi,

Does anyone know how to run an external application from inside of an Eiffel program?

XEL

unread,
Jul 13, 2017, 6:08:07 AM7/13/17
to eiffel...@googlegroups.com
The easiest way is with a wrapper:

-- #include <stdlib.h>
-- int system(const char *command);


system (command: POINTER): INTEGER is

external
"C | %"stdlib.h%""
end

Then you pass a pointer to your null-terminated command string from Eiffel.

Finnian Reilly

unread,
Jul 13, 2017, 7:11:41 AM7/13/17
to Eiffel Users
Hi Alexandr,
the Eiffel-Loop project has a sophisticated  framework for wrapping OS commands and processing captured output and also error messages. See library OS-command.ecf. There are very many examples provided in the examples directory and also the tools directory. If you don't wish to take advantage of the full capabilities of the framework, two "quick and easy" classes are provided to give you basic command template substitution functionality.
  1. EL_OS_COMMAND
  2. EL_CAPTURED_OS_COMMAND
Library features
These are just a few of the features of the library
  • Makes it possible to create a cross platform interface to commands that are similar but fundamentally different across platforms. For example the Unix find command and the Windows dir command have a common interface in the classes EL_FIND_DIRECTORIES_COMMAND_I and EL_FIND_FILES_COMMAND_I despite the fact that they have different output encodings and different output conventions.
  • Makes it easy to capture and process both standard and error output. There is no need to add anything explicitly to the the command template to capture the output. It happens automatically during the template expansion if you inherit from EL_CAPTURED_OS_COMMAND_I. All you need to do is implement the do_with_lines procedure to process the output.
  • Any error output is automatically available in the errors attribute as a list of strings.
  • Makes use of the Evolicity template substitution library for creating command templates and substituting the value of class attributes or functions. The templates can be spread across multiple lines and contain conditional branches using constructs #if <condition> then #else #end
  • Automatic escaping of reserved characters in path subtitution variables on Unix platforms
  • Automatic quoting of path subtitution variables for the Windows platform

regards

Finnian

Bertrand Meyer

unread,
Jul 13, 2017, 7:18:25 AM7/13/17
to eiffel...@googlegroups.com, me...@inf.ethz.ch
Note that the keyword "is" disappeared from feature declarations sometime in the previous millennium. Just omit it.

-- Bertrand Meyer
LASER summer school: Software for Robotics (Registration deadline: 15 July)
Elba Island (Italy), 9-17 September 2017
http://laser-foundation.org
--
You received this message because you are subscribed to the Google Groups "Eiffel Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to eiffel-users...@googlegroups.com.
Visit this group at https://groups.google.com/group/eiffel-users.
For more options, visit https://groups.google.com/d/optout.

Alexander Kogtenkov

unread,
Jul 13, 2017, 10:12:42 AM7/13/17
to eiffel...@googlegroups.com
There is a library that is included with every EiffelStudio installation: Process. It works on all supported platforms and allows running an external application and redirecting standard input and output from/to files and user-defined agents. The only limitation is that it does not work with SCOOP.

A simplified version of the library, called BaseProcess, works also with SCOOP, but does not support redirection to agents yet.

You can easily find both when adding a new library to your project in EiffelStudio by specifying the filter "process".

An example on how to use the Process library can be found at https://www.eiffel.org/article/how_to_get_the_output_of_a_command_using_eiffelprocess

Hope this helps,
Alexander Kogtenkov

Alexandr Naumchev <anau...@gmail.com>:

Finnian Reilly

unread,
Jul 13, 2017, 11:34:03 AM7/13/17
to Eiffel Users, kwa...@mail.ru, eiffel...@googlegroups.com
I once tried to integrate the ISE process library with the Eiffel-loop OS-command library and experienced a serious latency issue. So I reverted back to using {EXECUTION_ENVIRONMENT}.system. I reported it as a bug back in 2011 with the synopsis: Running external commands using Unix implementation of PROCESS library is extremely slow

I checked the status of this report and it is still open, so I can only assume that it hasn't been fixed. In summary, a series of OS commands that should only take seconds to execute, can end up taking minutes.
-- Finnian

Alexander Kogtenkov

unread,
Jul 13, 2017, 1:36:57 PM7/13/17
to Finnian Reilly, eiffel...@googlegroups.com
Quite a bit of work was put into improvement of the simplified version of Process library, BaseProcess, that can be used like "system", but with more flexibility, compatibility and efficiency in mind compared to the Process library. The library is available starting from the previous release, 17.01.

In particular BaseProcess runs 2 times faster than "system" on a Windows machine.

So, if you do not want to use asynchronous agent-based input-output for external processes, I would recommend using this library instead of the "system" call.

Alexander Kogtenkov


Finnian Reilly <frei...@gmail.com>:

Woland's Cat

unread,
Jul 13, 2017, 1:37:03 PM7/13/17
to eiffel...@googlegroups.com

I'm sure Finnian's facility is more modern than mine, but if you want to look at some code that knows how to run a command on Linunx, Mac, Windows or cygwin, with parameters and callback and return status, there is some code here that uses the process framework. Or get the whole git repo.

I use this to call git commands, browsers and a few other things on various platforms.

- thomas
--
You received this message because you are subscribed to the Google Groups "Eiffel Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to eiffel-users...@googlegroups.com.
Visit this group at https://groups.google.com/group/eiffel-users.
For more options, visit https://groups.google.com/d/optout.


--
Thomas Beale
Principal, Ars Semantica
Consultant, ABD Team, Intermountain Healthcare
Management Board, Specifications Program Lead, openEHR Foundation
Chartered IT Professional Fellow, BCS, British Computer Society
Health IT blog | Culture blog

Karsten Heusser

unread,
Apr 7, 2020, 3:39:27 AM4/7/20
to Eiffel Users
Dear Alexander,

is there any example code
how to execute a system call
in a SCOOP program?

Thanks,
Karsten

Alexander Kogtenkov

unread,
Apr 7, 2020, 1:02:05 PM4/7/20
to eiffel...@googlegroups.com
Dear Karsten,
 
Given that this is not the first and not the second request about such an example, I’ll make it available somewhere, probably on GitHub. I guess, you are looking for a pretty advanced version that can be used not only to launch some other program, but also to capture its output. The example that I have redirects program’s output to the example window, so that one can type an arbitrary command to be executed and see the result in the window. Several commands can run in parallel, and their output is shown in this single window to demonstrate parallelism. I wanted to make the example even more sophisticated, for example, to be able to terminate selected running programs, and to handle program output better. (Unless I’m mistaken, right now standard output and error output are processed sequentially, i.e. if a program writes to one of them and then into another one and continues doing that all the time, there could be some issues. Fortunately, most programs do not behave like that, and I was not experiencing any problems so far.) This advanced functionality requires some changes in the process library, so it might be available in some future version of the example.
 
On the other hand, if the only need is to launch another program without capturing its output, standard means like {EXECUTION_ENVIRONMENT}.system/launch should do.
 
Regards,
Alexander Kogtenkov
 
Karsten Heusser <karsten...@gmx.de>:

Larry Rix

unread,
Apr 7, 2020, 2:55:50 PM4/7/20
to Eiffel Users
Here is a class that will allow you to run Windows DOS Shell commands and capture the output:

note
description: "[
Abstract notion of a {FW_PROCESS_HELPER}.
]"
design: "[
These "helper" features are designed to assist with
use of the {PROCESS_IMP} and {PROCESS_FACTORY}.
]"

class
FW_PROCESS_HELPER

feature -- Status Report

has_file_in_path (a_name: STRING): BOOLEAN
-- `has_file_in_path' as `a_name'?
local
l_result,
l_msg: STRING
do
l_msg := dos_where_not_found_message.twin
l_result := output_of_command ("where " + a_name, ".")
Result := not l_result.same_string (l_msg) xor {PLATFORM}.is_unix
end

feature -- Basic Operations

last_error: INTEGER

output_of_command (a_command_line: READABLE_STRING_32; a_directory: detachable READABLE_STRING_32): STRING_32
                -- `output_of_command' `a_command_line' launched in `a_directory' (e.g. "." = Current directory).
require
cmd_not_empty: not a_command_line.is_empty
dir_not_empty: attached a_directory as al_dir implies not al_dir.is_empty
local
l_process: BASE_PROCESS
l_buffer: SPECIAL [NATURAL_8]
l_result: STRING_32
l_args: ARRAY [STRING_32]
l_cmd: STRING_32
l_list: LIST [READABLE_STRING_32]
do
create Result.make_empty
l_list := a_command_line.split (' ')
l_cmd := l_list [1]
if l_list.count >= 2 then
create l_args.make_filled ({STRING_32} "", 1, l_list.count - 1)
across
2 |..| l_list.count as ic
loop
l_args.put (l_list [ic.item], ic.item - 1)
end
end
l_process := (create {BASE_PROCESS_FACTORY}).process_launcher (l_cmd, l_args, a_directory)
l_process.set_hidden (True)
l_process.redirect_output_to_stream
l_process.redirect_error_to_same_as_output
l_process.launch
if l_process.launched then
from
create l_buffer.make_filled (0, 512)
until
l_process.has_output_stream_closed or else l_process.has_output_stream_error
loop
l_buffer := l_buffer.aliased_resized_area_with_default (0, l_buffer.capacity)
l_process.read_output_to_special (l_buffer)
l_result := converter.console_encoding_to_utf32 (console_encoding, create {STRING_8}.make_from_c_substring ($l_buffer, 1, l_buffer.count))
l_result.prune_all ({CHARACTER_32} '%R')
Result.append (l_result)
end
l_process.wait_for_exit
end
end

launch_fail_handler (a_result: STRING)
do
last_error_result := a_result
end

last_error_result: detachable STRING

feature -- Status Report: Wait for Exit

is_not_wait_for_exit: BOOLEAN

is_wait_for_exit: BOOLEAN
do
Result := not is_not_wait_for_exit
end

set_do_not_wait_for_exit
do
is_not_wait_for_exit := True
end

set_wait_for_exit
do
is_not_wait_for_exit := False
end

feature {NONE} -- Code page conversion

converter: LOCALIZED_PRINTER
-- Converter of the input data into Unicode.
once
create Result
end

console_encoding: ENCODING
-- Current console encoding.
once
Result := (create {SYSTEM_ENCODINGS}).console_encoding
end

feature {TEST_SET_BRIDGE} -- Implementation: Constants

DOS_where_not_found_message: STRING = "INFO: Could not find files for the given pattern(s).%N"

end


Here is a series of tests that exercise the code above in Windows:

note
description: "[
Tests of {FW_PROCESS_HELPER}.
]"
testing: "type/manual"

class
FW_PROCESS_HELPER_TEST_SET

inherit
EQA_TEST_SET
rename
assert as assert_old
end

EQA_COMMONLY_USED_ASSERTIONS
undefine
default_create
end

TEST_SET_BRIDGE
undefine
default_create
end

feature -- Test routines

set_path_test
note
warning: "[
This test is a FALSE-POSITIVE!
==============================
The setting of the path to include "blah" works only for the brief moment
that the external call is in-effect. Otherwise, it does not impact this software.
The Eiffel Studio program is able to (apparently) effect the state of the path
using ISE_EIFFEL and ISE_LIBRARY as set in the Windows registry (or Linux equal).
However, how this is accomplished is unknown to this library author at this time.
Until that is discovered, this test will have its assertions commented out.
When the solution is know, effect the solution and then rework this test.
]"
local
l_mock: FW_PROCESS_HELPER
l_blah,
l_set_blah: STRING
do
if {PLATFORM}.is_windows then
create l_mock
assert_32 ("set_blah", not l_mock.output_of_command ("set_path.cmd", ".").is_empty)
l_blah := l_mock.output_of_command ("path.cmd", ".")
print ("blah: `" + l_blah + "'")
-- assert_32 ("has_blah", l_blah.has_substring (";blah"))
end
end

output_of_where_test
local
l_mock: FW_PROCESS_HELPER
do
create l_mock
if {PLATFORM}.is_windows then
assert_strings_equal ("where", l_mock.DOS_where_not_found_message, l_mock.output_of_command ("where you_wont_find_me.exe", "."))
end
end

has_file_test
local
l_mock: FW_PROCESS_HELPER
do
create l_mock
assert_32 ("not_found", not l_mock.has_file_in_path ("you_wont_find_me.exe"))
end

process_helper_tests
-- New test routine
local
l_mock: FW_PROCESS_HELPER
l_result: STRING
do
if {PLATFORM}.is_windows then
create l_mock
l_result := l_mock.output_of_command ("where notepad.exe", ".")
assert_integers_equal ("notepad_length", notepad_where.count, l_result.count)
assert_strings_equal ("notepad", notepad_where, l_result)
end
end

feature {NONE} -- Implementation

notepad_where: STRING = "C:\Windows\System32\notepad.exe%NC:\Windows\notepad.exe%N"

end


Karsten Heusser

unread,
Apr 8, 2020, 12:21:25 AM4/8/20
to Eiffel Users
Dear Alexander,

Your help is highly appreciated, and I am eager to see your sophisticated example.
In the past, I used the process library to launch programs and to capture their output.
However, its concurrency capability (thread) is not compatible with SCOOP systems.
Meanwhile, I will fiddle with {EXECUTION_ENVIRONMENT}.system/launch.

Best regards,
Karsten

Karsten Heusser

unread,
Apr 8, 2020, 1:19:21 AM4/8/20
to Eiffel Users
Dear Larry,

I saw your post only after Alexander's.
Your FW_PROCESS_HELPER seems to work great!

Thanks a lot!
Karsten

Larry Rix

unread,
Apr 8, 2020, 8:33:45 AM4/8/20
to Eiffel Users
You're quite welcome!

Larry Rix

unread,
Apr 8, 2020, 8:35:26 AM4/8/20
to Eiffel Users
Ah, I see that once again I am slow to "get" important bits from time to time!

You're after concurrency and SCOOP and Thread don't play nice together. So—Alex is your guy!

Karsten Heusser

unread,
Apr 9, 2020, 4:17:53 AM4/9/20
to Eiffel Users
My app.ecf is configuring this way:

<system ...>
    <target name="app">
        ...
        <capability>
            <concurrency support="scoop"/>
            ...
        </capability>
        ...
        <library name="base_process" location="$ISE_LIBRARY\library\process\base\base_process.ecf"/>
        ...
    </target>
</system>

The compiler did not complain about concurrency incompatibilities,
and the system call's output was caught successfully
by the compiled program.

I did not yet experiment with asynchronous system calls.
I guess, Alexander's example will shine here :-)

Best,
Karsten

Larry Rix

unread,
Apr 9, 2020, 5:56:18 PM4/9/20
to Eiffel Users
Hi Karsten,

Perhaps I am wrong, but my guess is that there may be two matters involved with making this SCOOPable (the code I sent).

  1. The code (as written) does not exercise the Threading.
  2. The thread references are buried far enough in the library references that a SCOOP setting in the current top project does not give the compiler grief.
Those are just guesses on my part.

Kind regards,

Larry

Karsten Heusser

unread,
Apr 11, 2020, 2:19:01 AM4/11/20
to Eiffel Users
Hi Larry,

the results of my experiments are (as far as I can see):

1) Usage of the Base Process Library in a SCOOP system
seems to be swallowed by the compiler :-)

2) External calls cannot be spawned asynchronously,
i.e. execution of a second call starts only after the first one has finished :-(

I am curious about Alexander's examples.

Thanks again, Larry,
Karsten

Alexander Kogtenkov

unread,
Apr 13, 2020, 12:00:23 PM4/13/20
to eiffel...@googlegroups.com
Dear Karsten,
 
You can find the example at
 
 
Regards,
Alexander Kogtenkov
 
 
'Alexander Kogtenkov' via Eiffel Users <eiffel...@googlegroups.com>:

Karsten Heusser

unread,
Apr 14, 2020, 2:48:57 AM4/14/20
to Eiffel Users
Dear Alexander,

You stand by your word! Thank you very much for the efforts
to provide such clean and instructive example code.

Now and then, I will use your "Vision + Scoop Command-line Launcher" directly
as DOS-box replacement as it is much easier to use the output from the Launcher :-)

All the best,
Karsten

Karsten Heusser

unread,
Apr 14, 2020, 5:15:14 AM4/14/20
to Eiffel Users
Hi Larry,

I didn't really doubt that Alexander could make external calls execute asynchronously.
And - he did it! I very much like his example + lib.

Regards,
Karsten

Larry Rix

unread,
Apr 14, 2020, 6:16:44 AM4/14/20
to Eiffel Users
I figured Alex would get the answer you were looking for.
Reply all
Reply to author
Forward
0 new messages