Home Articles Books Downloads FAQs Tips

Q: Launch another program


Answer

Call the API CreateProcess, ShellExecute or WinExec functions. WinExec is the easiest one to use, but it is also out of date, and Microsoft doesn't recommend that you use it. This FAQ demonstrates how to use ShellExecute and CreateProcess to launch the solitaire game.

Using ShellExecute

Here is an example of how to launch notepad using ShellExecute.

void __fastcall TForm1::Button1Click(TObject *Sender)
{
    ShellExecute(NULL,
                 "open",
                 "notepad.exe",
                 "",
                 "",
                 SW_SHOWDEFAULT);
}

ShellExecute can also be used to open documents in the file system, such as Word DOC files, or Excel XLS files. The second argument to ShellExecute determines what you are trying to do. Valid values in include "open", "print","explore", "find", and a few others. When you are trying to launch an executable, pass the "open" string.

The third argument is the name of the program to execute. If you were trying to open a DOC file, you would pass the file name in the third argument. The fourth argument is the command line parameters that you want to pass to the program. The next example demonstrates how you can use this argument.

void __fastcall TForm1::Button1Click(TObject *Sender)
{
    ShellExecute(NULL,
                 "open",
                 "notepad.exe",
                 "c:\\cbuilder5\\readme.txt",
                 "",
                 SW_SHOWDEFAULT);
}

Of course, since notepad is usually associated with TXT files anyway, you could also open the readme file using this syntax:

void __fastcall TForm1::Button1Click(TObject *Sender)
{
    ShellExecute(NULL,
                 "open",
                 "c:\\cbuilder5\\readme.txt",
                 "",
                 "",
                 SW_SHOWDEFAULT);
}

Using CreatProcess

The ShellExecute function is pretty straightforward, but it does not allow you to control the child process. For finer control of what's going on, use CreateProcess instead. It is a little more complex, but it also provides more flexibility.

void __fastcall TForm1::Button1Click(TObject *Sender)
{
    // solitaire is in the windows directory,
    // and has the name sol.exe.
    char szWindowDir [MAX_PATH];
    GetWindowsDirectory(szWindowDir, MAX_PATH);
    AnsiString strSolitaire = AnsiString(szWindowDir) + "\\sol.exe";

    STARTUPINFO  StartupInfo;
    ZeroMemory( &StartupInfo, sizeof(STARTUPINFO));
    StartupInfo.cb = sizeof(STARTUPINFO);

    PROCESS_INFORMATION ProcessInfo;

    if(CreateProcess(strSolitaire.c_str(),
                     NULL,
                     NULL,
                     NULL,
                     TRUE,
                     NORMAL_PRIORITY_CLASS,
                     NULL,
                     NULL,
                     &StartupInfo,
                     &ProcessInfo))
    {
        // We must close the handles returned in ProcessInfo. We can
        // close the handle at any time, might as well close it now
        CloseHandle(ProcessInfo.hProcess);
        CloseHandle(ProcessInfo.hThread);
    }
}

Note: If you want to pass command line arguments to the program, place the command line arguments in the second argument to CreateProcess. The code below shows how to launch Notepad and have it open a file. Notice that we don't use the first parameter for anything. The second parameter contains both the executable name, and the file to open. The two must be separated by a space.

void __fastcall TForm1::Button2Click(TObject *Sender)
{
    char szWindowDir [MAX_PATH];
    GetWindowsDirectory(szWindowDir, MAX_PATH);
    AnsiString strNotepad = AnsiString(szWindowDir) + "\\notepad.exe";
    strNotepad = strNotepad + " " + "c:\\cbuilder3\\deploy.txt";

    STARTUPINFO  StartupInfo;
    ZeroMemory( &StartupInfo, sizeof(STARTUPINFO));
    StartupInfo.cb = sizeof(STARTUPINFO);

    PROCESS_INFORMATION ProcessInfo;

    if(CreateProcess(NULL,
                     strNotepad.c_str(),
                     NULL,
                     NULL,
                     TRUE,
                     NORMAL_PRIORITY_CLASS,
                     NULL,
                     NULL,
                     &StartupInfo,
                     &ProcessInfo))
    {
        CloseHandle(ProcessInfo.hProcess);
        CloseHandle(ProcessInfo.hThread);
    }
}

Note: The last argument to CreateProcess is a pointer to a PROCESS_INFORMATION structure. If CreateProcess succeeds, the PROCESS_INFORMATION structure will contain a thread handle and a process handle for the new program. You must call CloseHandle to close the two handles that are returned in the PROCESS_INFORMATION structure. You can close the handles at any time, but you can't use the handles once you close them. The two previous code samples close the handles as soon as CreateProcess returns.

Note: You can use the process handle to determine if the new process has finished running yet. The code example below shows how you can wait for the Notepad program to terminate before proceeding with the program. The code calls the GetExitCodeProcess API function to determine if the process is still active.

//-------------------------------------------------
// header file
// add a bool variable to the form class
private:	// User declarations
    bool bWaiting;
     ...

//-------------------------------------------------
// source file
//
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
    // initialize the flag to false
    bWaiting = false;
}
//-------------------------------------------------
void __fastcall TForm1::FormCloseQuery(TObject *Sender, bool &CanClose)
{
    // Use OnCloseQuery to prevent the form from
    // closing while we are waiting for notepad.
    CanClose = !bWaiting;
}
//-------------------------------------------------
void __fastcall TForm1::Button3Click(TObject *Sender)
{
    // don't enter the function if we are already waiting
    // for another instance of notepad to close.
    if(bWaiting)
        return;

    char szWindowDir [MAX_PATH];
    GetWindowsDirectory(szWindowDir, MAX_PATH);
    AnsiString strNotepad = AnsiString(szWindowDir) + "\\notepad.exe";
    strNotepad = strNotepad + " " + "c:\\cbuilder3\\deploy.txt";

    STARTUPINFO  StartupInfo;
    ZeroMemory( &StartupInfo, sizeof(STARTUPINFO));
    StartupInfo.cb = sizeof(STARTUPINFO);

    PROCESS_INFORMATION ProcessInfo;

    if(CreateProcess(NULL, strNotepad.c_str(),    NULL, NULL,
                     TRUE, NORMAL_PRIORITY_CLASS, NULL, NULL,
                     &StartupInfo, &ProcessInfo))
    {
        bWaiting = true;
        Label1->Caption = "Waiting for Notepad to close";
        DWORD dwExitStatus = STILL_ACTIVE;

        // loop until Notepad closes
        do
        {
            if( !GetExitCodeProcess(ProcessInfo.hProcess, &dwExitStatus))
                break;
            Application->ProcessMessages();
        } while (dwExitStatus == STILL_ACTIVE);

        Label1->Caption = "done";
        bWaiting = false;

        // we dont' need the handles any more, so close them
        CloseHandle(ProcessInfo.hProcess);
        CloseHandle(ProcessInfo.hThread);
    }
}

Note: When CreateProcess returns, you don't know what state the new process is in. It may be fully initialized and on the screen, but it could also be invisible and without a window handle. Sometimes, it may be beneficial to wait until the new process is fully initialized and visible on the screen. You can use the API WaitForInputIdle function to pause your program until the new process is up and running.

if(CreateProcess(NULL, strNotepad.c_str(),    NULL, NULL,
                 TRUE, NORMAL_PRIORITY_CLASS, NULL, NULL,
                 &StartupInfo, &ProcessInfo))
{
    // wait for notepad to come up, time out after 2 seconds
    // WaitForInputIdle returns 0 if the wait succeeds
    WaitForInputIdle(ProcessInfo.hProcess, 2000);
    CloseHandle(ProcessInfo.hProcess);
    CloseHandle(ProcessInfo.hThread);
}

Note: You can also create a process in the suspended state by adding the CREATE_SUSPENDED value to the creation flags parameter of CreateProcess. If you add the CREATE_SUSPENDED flag, the program won't start running until until you call ResumeThread. Here is an example.

if(CreateProcess(NULL,
                 strNotepad.c_str(),
                 NULL,
                 NULL,
                 TRUE,
                 NORMAL_PRIORITY_CLASS | CREATE_SUSPENDED,
                 NULL,
                 NULL,
                 &StartupInfo,
                 &ProcessInfo))
{
    // Make the user think something intense is happening
    Sleep(3000);
    ResumeThread(ProcessInfo.hThread);

    CloseHandle(ProcessInfo.hProcess);
    CloseHandle(ProcessInfo.hThread);
}

Note: The STARTUPINFO structure allows you to control the initial location and dimension of the program's main window. You can also make the main window of the program come up hidden. Here is an example of how to control the location of the new process. Keep in mind that some programs will ignore what you pass them.

void __fastcall TForm1::Button6Click(TObject *Sender)
{
    char szWindowDir [MAX_PATH];
    GetWindowsDirectory(szWindowDir, MAX_PATH);
    AnsiString strNotepad = AnsiString(szWindowDir) + "\\notepad.exe";
    strNotepad = strNotepad + " " + "c:\\cbuilder3\\deploy.txt";

    STARTUPINFO  StartupInfo;
    ZeroMemory( &StartupInfo, sizeof(STARTUPINFO));
    StartupInfo.cb = sizeof(STARTUPINFO);
    StartupInfo.dwX = 0;
    StartupInfo.dwY = 0;
    StartupInfo.dwXSize = Screen->Width  /3;
    StartupInfo.dwYSize = Screen->Height /3;
    StartupInfo.dwFlags = STARTF_USEPOSITION | STARTF_USESIZE;

    PROCESS_INFORMATION ProcessInfo;

    if(CreateProcess(NULL, strNotepad.c_str(),    NULL, NULL,
                     TRUE, NORMAL_PRIORITY_CLASS, NULL, NULL,
                     &StartupInfo, &ProcessInfo))
    {
        // we must close the handles returned in ProcessInfo.hProcess
        // we can close the handle at any time, might as well close it now
        CloseHandle(ProcessInfo.hProcess);
        CloseHandle(ProcessInfo.hThread);
    }
}


Copyright © 1997-2000 by Harold Howe.
All rights reserved.