Link to home
Start Free TrialLog in
Avatar of SimonORorke
SimonORorke

asked on

Launching and Closing Windows Explorer

I want to launch and close Windows Explorer from a Delphi 3 program.  Launching is no problem.  But I have tried several different methods of closing Explorer once it has been launched.  None of them work, though some of them did work when I used a Delphi test application instead of Explorer.

The following are the relevant bits of a test program I wrote to demonstrate the techniques I have tried.  The program has a single form with an edit box in which the command line is to be entered, a launch button, and a different close button for each of the closing techniques I tried.  The comment at the top of the OnClick method for each close button shows the results I got for the technique executed.

Any suggestions, please?

private
ProcessInformation : TProcessInformation;

procedure TForm1.LaunchButtonClick(Sender: TObject);
var
StartupInfo : TStartupInfo;
begin
GetStartupInfo(StartupInfo);
StartupInfo.wShowWindow := SW_SHOWNORMAL;
if CreateProcess(
    Nil,   {ApplicationName}
    PChar(CommandEdit.Text),   { lpCommandLine }
    Nil,  {lpProcessAttributes}
    Nil,  {lpThreadAttribute}
    False,  {bInheritedHandles}
    NORMAL_PRIORITY_CLASS,  {dwCreationFlags}
    Nil,  {lpEnvironment}
    Nil,  {lpCurrentDirectory}
    StartupInfo, {lpStartupInfo}
    ProcessInformation  {lpProcessInformation}
    )
then begin
  ResultLabel.Caption := 'Launched ' + CommandEdit.Text;
end else begin
  ResultLabel.Caption := 'Failed to launch ' + CommandEdit.Text;
end;
end;

procedure TForm1.Close1ButtonClick(Sender: TObject);
{This fails and returns false for Explorer.
It returns true but fails to work for the test application.}
begin
if PostThreadMessage(ProcessInformation.dwThreadId,
                     WM_CLOSE, 0, 0) then begin
      ResultLabel.Caption := CommandEdit.Text + ' closed';
end else begin
      ResultLabel.Caption := 'Failed to close ' + CommandEdit.Text;
end;
end;

procedure TForm1.Close2ButtonClick(Sender: TObject);
{This returns true but fails to work for Explorer.
It works and returns true for the test application.}
var
BrowserHandle : THandle;
begin
BrowserHandle :=
  OpenProcess(PROCESS_ALL_ACCESS, False, ProcessInformation.dwProcessId);
if TerminateProcess(BrowserHandle, 0) then begin
  ResultLabel.Caption := 'Launched ' + CommandEdit.Text;
end else begin
  ResultLabel.Caption := 'Failed to launch ' + CommandEdit.Text;
end;
end;

procedure TForm1.Close3ButtonClick(Sender: TObject);
{This fails and returns false for both Explorer and the test application.}
var
BrowserHandle : THandle;
begin
BrowserHandle :=
  OpenProcess(PROCESS_ALL_ACCESS, False, ProcessInformation.dwProcessId);
if PostMessage(BrowserHandle, WM_CLOSE, 0, 0) then begin
  ResultLabel.Caption := 'Launched ' + CommandEdit.Text;
end else begin
  ResultLabel.Caption := 'Failed to launch ' + CommandEdit.Text;
end;
end;

procedure TForm1.Close4ButtonClick(Sender: TObject);
{This returns true but fails to work for Explorer.
It works and returns true for the test application.}
begin
if TerminateProcess(ProcessInformation.hProcess, 0) then begin
  ResultLabel.Caption := 'Launched ' + CommandEdit.Text;
end else begin
  ResultLabel.Caption := 'Failed to launch ' + CommandEdit.Text;
end;
end;

procedure TForm1.Close5ButtonClick(Sender: TObject);
{This fails and returns false for both Explorer and the test application.}
begin
if PostMessage(ProcessInformation.hProcess, WM_CLOSE, 0, 0) then begin
  ResultLabel.Caption := 'Launched ' + CommandEdit.Text;
end else begin
  ResultLabel.Caption := 'Failed to launch ' + CommandEdit.Text;
end;
end;
Avatar of ZifNab
ZifNab

Hi SimonORorke,

I know there are simple commands to run applications, to close it down, etc. BUT, when there exist a great freeware component for all of this, the commands would be serious simple if they want that I wouldn't just use such a component. Also for this purpose exist a component. Look for TExecFile 16bit or 32bit on Super Delphi Pages or at Torries. If you want it I can also email it to you.

Regards, Zif.
Avatar of SimonORorke

ASKER

I have had a look at TExecFile.  Thanks for the suggestion.  But it does not include methods for closing previously launched applications.  So will not solve my problem.
Hi, I haven't used this fuction, but what does Terminate do then?
ASKER CERTIFIED SOLUTION
Avatar of jeurk
jeurk

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Ok, the problem is that Explorer handles two things. The first is the desktop/taskbar/shell, the second is to act like a file manager (from a user point of view). For all these jobs, only ONE instance of Explorer is loaded for performance reasons. So all Explorer windows run in the same process. CreateProcess on Explorer simply loads Explorer, Explorer looks if a previous instance exists, sends it startup parameters to the prevoius instance and exits. The Explorer window is now created by the existing instance. So your ProcessInformation structure is always invalid.

Slash/d003303
Hello Jeurk!

The advice you have given seems to have brought me close to a workable solution, but I need to know more.

    var
    h: HWND; //the explorer handle
    begin
    h := FindWindow(pchar('Progman'), nil);
    PostMessage(h, WM_QUIT, 0, 0);

The above code kills Explorer stone dead!  Nothing else made a dent in it!  :-).  But, as you warned, the difficulty arises when attempting to launch Explorer again after getting rid of it.  Bizarre things happen.  

If I follow the above code with

    WinExec('Explorer.exe', SW_SHOW);

I end up with two copies of Explorer open, even though Explorer definitely closes when not followed by WinExec.  It seems WinExec wakes up the previous copy of Explorer when opening the new one.

If, instead of WinExec, I use CreateProcess, as in my own code extract shown in the original question, the dead copy of Explorer stays dead, but I cannot open a new one.  
I get an error message that says, for example

    "Cannot Find the file /n,/e,c:\ (or one of its components.
    Make sure the path and filename are correct and that all
    required libraries are available."

" /n,/e,c:\" is just my parameters to Explorer.  The CreateProcess function works fine if Explorer has not been previously shut down.

Putting a pause between closing and opening Explorer did not help.  I tried

    Sleep(10000);

and

    while h <> 0 do begin
      h := FindWindow(pchar('Progman'), nil);
      Application.ProcessMessages;
    end;

Neither helped.  Incidentally FindWindow does not return a Bool, so the above loop is slightly different to what you suggest.

Yet I was able to launch Explorer from the desk top straight after my program closed it.  So it looks like there must be a way of doing it in my program.  

        //one side effect is that explorer launches again all the
        //apps that are registered in the run section
        //but either you know a explorer param that avoids that
        //or you read the run section and delete the keys before
        //killing explorer, and after that you restore them

Explorer command line parameters are documented in Windows 95 Resource kit.  None suggest a way round the problem.

I do not know anything about the "run section" you mentioned.  I do not know if it is relevant to problem I am getting in attempting to launch Explorer after killing it.  Can you say something more about that, please?

Simon
and hello d003303!

Your explanation of why I am getting the problem closing Explorer makes sense.  I had  noticed that Explorer starts in bacground every time Windows 95 is started.  I did wonder if that was something to do with the problem.

and hello again ZifNab!

Terminate is an emergency function for killing applications when more graceful wayus fail.  But in the case of Explorer, it does not work, no doubt for the reason d003303 explains.
Hi,
this is bizarre,
what I sent was a working solution for someone that asked how to change
the default desktop dir in windows and that needed to restart the shell.
So I made that little piece of code that worked perfectly on my
computer and the answer was accepted so I suppose that it worked too
on the guys machine.

Like d003 sayd your createprocess is not working because you are trying to launch
an explorer window when there is no shell present to handle the request.
The proof is that it is working when the shell was not killed down.

I don't understand why you get two instance of explorer cause it is not working like that
on my machine. d003 ? could you please try out the piece of code and tell how
it is working on your computer ?

The run section which am I talking about is in the registry, launch regedit and search
run, it's in /hkey_localmachine/software/microsoft/windows/currentvesion/run and runonce
it does not interact with our problem. Maybe you launch an explorer instance there ?


I am embarrassed to say that the problem with WinExec appearing to open two copies of Explorer was a simple error of program logic on my part.  Sorry for wasting your time!  

However, now that I have corrected that bug, I am getting WinExec failing with the same message as I get with CreateProcess:

          "Cannot Find the file /n,/e,c:\ (or one of its components.
          Make sure the path and filename are correct and that all
          required libraries are available."

I checked /hkey_localmachine/software/microsoft/windows/currentvesion/run and runonce.  RunOnce has nothing in it.  Run has

          SystemTray = SysTray.exe
          Tweak UI = RUNDLL32.EXE TWEAKUI.CPL,TweakMeUp

The only non-standard thing, presumably, is the Tweak UI.  So, just in case, I deleted it from the registry and rebooted.  It did not help.
jeurk,
sorry, I can't do a reliable check on your code, since I modified all my NT workstations to run seperate processes of explorer for the reason that I am doing shell extension development. When your extension GPFaults, only the explorer instance is killed then, not the whole shell.

SimonORorke,
have you tried to put the WinExec path in double quotes ?

Slash/d003303
Hello d003303 and jeurk!

On d003303's suggestion, I have now tried putting the Explorer program name in double-quotes.  This seemed a reasonable suggestion:  the technique is used by Windows itself when generating a command line for a shortcut, though only, I think, when the program's hierarchical file name contains spaces.  But it did not help.  I have also tried using the full hierarchical name of Explorer, "C:\WINDOWS\EXPLORER.EXE", both with and without double-quotes.  That did not help either.

However, I have made a new observation about the behaviour of my program which hopefully will provide a clue as to whatever refinement I need to make for a workable solution.

When my program closes Explorer with

        h := FindWindow(pchar('Progman'), nil);

the Windows 95 task bar disappears from the desktop.

When my program relaunches Explorer, the error message

        "Cannot Find the file /n,/e,c:\ (or one of its
        components).
        Make sure the path and filename are correct and that all
        required libraries are available."

is shown and the task bar reappears, including a new icon for '/n,/e,c:\' (the Explorer parameters).  The new icon is the one on the task bar that is down, which would normally mean that the corresponding program is the active one.  Once I press OK on the error message box, focus returns to the task bar, not to my program.  The strange new task bar icon has gone.  If I then switch focus back to my program and ask it to try launching Explorer again, this second time it succeeds.

I get the same result whether I use WinExec or CreateProcess.  

The reason why I have only just worked out what is happening to the task bar is that I had been usually running my test program from within Delphi.  As my Delphi source window was maximised and I do not use the 'Always on top' option of the task bar, I did not see the task bar disappearing.  Once I minimised Delphi and ran my test program separately, it all became clear!

Simon
Hello again d003303 and jeurk.

I have made some more progress. From d003303's comment that Explorer functions to run the shell and from the strange error message I was getting, I concluded that I might be able redisplay the task bar by first running explorer without parameters.  I could then run Explorer with parameters to show the Explorer window for the folder requested by the user.

This works most of the time.  But once I have done it a few times, within the same run of my program, one of a few different things go wrong.

Sometimes I get the following message.

        Windows Explorer

        My Computer or Windows Explorer has not been
        properly initialised yet.  Try again later

Sometimes I get this message:-

       Explorer

       This program has performed an illegal operation
       And will be shut down.

Sometimes my program crashes.  Sometimes Windows 95 itself crashes.

So, either running Explorer without parameters is not a reliable way of showing the task bar and initialising Explorer, or I need to find a way of waiting till the initialisation has been completed before attempting to launch another Explorer Window.  As you can see from the code extract below, I have tried to wait for the initialisation to complete, but it is clearly not necessarily effective.

// If an Explorer window has already been opened,
//close the currently open Explorer window before
// opening the new one.
if ExplorerOpen then begin
  //Get the Exlporer handle
  ExplorerHandle := FindWindow(pchar('Progman'), nil);
  //Ask Explorer to close itself.  This will make the task
  //bar as well as the Explorer window disappear.
  PostMessage(ExplorerHandle, WM_QUIT, 0, 0);
  // Wait until Explorer is definitely closed
  while ExplorerHandle <> 0 do begin
    ExplorerHandle := FindWindow(pchar('Progman'), nil);
    Application.ProcessMessages;
  end;
  // Initialise Explorer and redisplay the task bar
  WinExec(PChar('explorer.exe'), SW_SHOW);
  //Wait till the task bar is back and Explorer are
  //fully initialised before attempting to run Explorer again
  ExplorerHandle := FindWindow(pchar('Progman'), nil);
  while ExplorerHandle = 0 do begin
    ExplorerHandle := FindWindow(pchar('Progman'), nil);
    Application.ProcessMessages;
  end;
  // Wait 3 ??? seconds.  Is there a long enough time that will
  //always be reliable?
  Sleep(3000);
end;
// Run Explorer for the folder requested by the user.
WinExec(PChar(Command), SW_SHOW);
ExplorerOpen := True;

Simon