Topic: Executing an external application from SF application

Hi Jeff,

I would like to run an external application (e.g., notepad.exe) from a compiled SF application, but without the continued display of the cmd.exe console after the external application is loaded.  I've tried the following to run an external application from my SF program successfully.

call execute_command_line( "Dataview.exe", wait=.false. )

Is there a way to close the cmd.exe console which is displayed concurrently with my application, but without closing my application or the externally run one?  I can close the command console manually just fine, but I'd like to automatically close it from my program.

Basically, I would like to load a text editor from my SF program without the persistent and concurrent appearance of the command console.

Any suggestions for how to close the command console once an external program is loaded?

Frank

Re: Executing an external application from SF application

Frank,

You are doing what you should do by using execute_command_line, but it will launch a console every time it is called if your program doesn't already have a console window.  There's no pure-Fortran solution for this issue on Windows, but it's relatively simple to get around using a simple C file added to your project.

I've written a short C routine, LaunchExecutable, that uses the Windows API to launch an arbitrary program.  The C routine is in this file:

launch.c:

#include <windows.h>

int LaunchExecutable(char *exe, int wait)
{
SECURITY_ATTRIBUTES secattr;
PROCESS_INFORMATION pi;
STARTUPINFO si;
BOOL bSuccess;

    /* Open up some pipes */
    ZeroMemory(&secattr, sizeof(SECURITY_ATTRIBUTES));
    secattr.nLength = sizeof(SECURITY_ATTRIBUTES);
    secattr.lpSecurityDescriptor = NULL;
    secattr.bInheritHandle = TRUE;

    ZeroMemory(&si, sizeof(STARTUPINFO));
    
    si.cb = sizeof(STARTUPINFO);
    si.dwFlags = STARTF_USESHOWWINDOW;
    si.wShowWindow = SW_SHOWDEFAULT;
    
    bSuccess = CreateProcess(NULL,       /* lpApplicationName */
                             exe,        /* lpCommandLine */
                             NULL,       /* lpProcessAttributes */
                             NULL,       /* lpThreadAttributes */
                             TRUE,       /* bInheritHandles */
                             0,          /* dwCreationFlags */
                             NULL,       /* lpEnvironment */
                             NULL,       /* lpCurrentDirectory */
                             &si,        /* lpStartupInfo */
                             &pi);       /* lpProcessInformation */

    if(!bSuccess)
        return GetLastError();
        
    if(wait)
        WaitForSingleObject(pi.hProcess, INFINITE);
    
    return ERROR_SUCCESS;
}

The above routine, though, needs a Fortran wrapper to work properly.  My Fortran module, launcher, contains one routine, LaunchWindow, that implements the necessary Fortran wrapper:

launchf.f90:

module launcher
implicit none

private

public::LaunchWindow

contains

    subroutine LaunchWindow(exe, wait, status)
    use iso_c_binding
    implicit none
    
    character(*)::exe
    logical, optional, intent(in)::wait
    integer, optional, intent(out)::status
    
    integer::internal_wait
    integer::internal_ret
    
    ! For conversion to a C character array
    integer::i
    character(kind=c_char), dimension(:), allocatable::internal_exe
    
    interface
        function LaunchExecutable(exe, wait) bind(c, name="LaunchExecutable")
        use iso_c_binding
        integer(kind=c_int)::LaunchExecutable
        character(kind=c_char)::exe
        integer(kind=c_int), value::wait
        end function LaunchExecutable
    end interface
    
    internal_wait = 0
    if(present(wait)) then
        if(wait) then
            internal_wait = 1
        else
            internal_wait = 0
        end if
    end if
    
    allocate(internal_exe(len_trim(exe)+1))
    internal_exe = C_NULL_CHAR
    do i=1,len_trim(exe)
        internal_exe(i) = exe(i:i)
    end do
    
    internal_ret = LaunchExecutable(internal_exe(1), internal_wait)
    
    if(present(status)) then
        status = internal_ret
    end if
    
    deallocate(internal_exe)
    
    end subroutine LaunchWindow
    
end module launcher

This routine takes a string with the full path to the executable.  If the executable is located within the current working directory, you probably wouldn't need the full path.  It has two optional arguments, wait which is a logical that reflects whether the routine should wait until the called process completes before returning, and status which returns any error codes that may have been returned by the call (0 means success).

As a simple example, here's a Fortran program that actually calls the routine:

hello.f90:

program helloworld
use launcher
implicit none

    integer::i
    
    CALL LaunchWindow("C:\Windows\System32\notepad.exe", status=i, wait=.FALSE.)
    
end program helloworld

The above launches Notepad and exits immediately without ever opening a console window.

Let me know if the above works for you.  I'll be happy to update any code based on any requests.

Jeff Armstrong
Approximatrix, LLC

Re: Executing an external application from SF application

Jeff,

Very interesting approach, but I'm having some difficulty in compiling your example, Helloworld.

I've placed your entire example code, C and Fortran, into SF, enabled the C Preprocessor in the Project Options menu, and then tried to compile it all together, but received pages of error code.  I'm using Windows 7, 64-bit.

Example code:
!-------------------------------------------------------------------------------
#include <windows.h>
int LaunchExecutable(char *exe, int wait)
{
.
.
}
!-------------------------------------------------------------------------------
module launcher
implicit none
private
public::LaunchWindow
contains
    subroutine LaunchWindow(exe, wait, status)
    use iso_c_binding
.
.
    end subroutine LaunchWindow   
end module launcher
!-------------------------------------------------------------------------------
program helloworld
use launcher
implicit none
    integer::i
    CALL LaunchWindow("C:\Windows\System32\notepad.exe", status=i, wait=.FALSE.)
end program helloworld
!-------------------------------------------------------------------------------

Here are some of the error messages received when I tried to compile it.  I couldn't copy all of the messages as they were pages long.  I also tried compiling with the SF options of 32-bit and 64-bit, with and without enabling the C Preprocessor, but to no avail.

==============================================================================
Generating Makefile... Okay
==============================================================================
Compiling .\Helloworld.f90
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
c:\program files (x86)\simply fortran 2\mingw-w64\x86_64-w64-mingw32\include\_mingw.h:268:2:

#error Only Win32 target is supported!
  1~~~~
Error: #error Only Win32 target is supported!
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
c:\program files (x86)\simply fortran 2\mingw-w64\x86_64-w64-mingw32\include\sdkddkver.h:182:0:

#if ((OSVER(NTDDI_VERSION) == NTDDI_WIN2K) && (_WIN32_WINNT != _WIN32_WINNT_WIN2K)) || \

Error: token "##" is not valid in preprocessor expressions
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
c:\program files (x86)\simply fortran 2\mingw-w64\x86_64-w64-mingw32\include\_mingw_stdarg.h:11:2:

#error Only Win32 target is supported!
  1~~~~
Error: #error Only Win32 target is supported!
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
c:\program files (x86)\simply fortran 2\mingw-w64\x86_64-w64-mingw32\include\winnt.h:376:2:

#error Must define a target architecture.
  1~~~~
Error: #error Must define a target architecture.
.
.
.
.

Do you know what I may be doing incorrectly?

Frank

Re: Executing an external application from SF application

Frank,

Are you putting the C code in a Fortran file?  I had meant for the C code to be in a separate file, launch.c.  You don't need to enable the C preprocessor either; that option only pertains to C preprocessor directives (#include, #ifdef, etc.) being utilized in Fortran files.  The C code must be in a C file alone in order for Simply Fortran to launch the C compiler.

Sorry for the confusion!

Jeff Armstrong
Approximatrix, LLC

Re: Executing an external application from SF application

Jeff,

I should know by now that you are always correct.  I did, in fact, place the C file in with the the fortran module when I tried to compile it.  I've since taken your advice and placed the C and F codes in separate files and it compiled just fine - no errors.

Fantastic little routine for seamlessly executing *exe files like notepad, etc.

Thank you again for sharing this in the SF forum.

Regards,

Frank

Re: Executing an external application from SF application

Frank,

Glad to hear it's working!  Let me know if there are any improvements you might need.

Jeff Armstrong
Approximatrix, LLC