Link to home
Start Free TrialLog in
Avatar of John Fistere
John FistereFlag for United States of America

asked on

Closing an open file in order to write a new copy.

My D5 application writes a .csv file, which may or may not be open in Excel.  It could either have been opened by my program using WinExec or completely outside my application.  I think that amounts to the same thing.

When I try to rewrite the file, I get an exception if it is open.  IoResult = 32.  I would like to give the user the option of closing or renaming the file.  Of course he could switch to the open application and close the file but that is more trouble than just closing it.  The equivalent of Alt-F4 for the open file would be great.

This is part of the code I am using:

{$I-}
      CloseFile(CSVFile);
{$I+}
      IOStatus := IOResult;
      if not (RewriteFile(CSVFile,recConfig.CSVFileName)) then
        begin
          ShowMessage('Could not open output file named "'+
            recConfig.CSVFileName + '". You probably need to close the file and try again.');
          RewriteFail := True;
        end;

This is my RewriteFile function:

{------------------------------------------------------------------}
function RewriteFile(var LogicName : TextFile;Const FileName : String) : Boolean;
var
  IoResultNo: integer;
begin
   AssignFile(LogicName, FileName);
   IoResultNo := IoResult;
   if IoResultNo <> 0 then
     ShowMessage('Before rewriting file IoResult = '+IntToStr(IoResultNo)+'.');
   {$I-}
   Rewrite(LogicName);
   {$I+}
   IoResultNo := IoResult;
   if IoResultNo <> 0 then
     begin
       RewriteFile := False;
       if IoResultNo = 5 then
         ShowMessage('Logfile '+FileName+' may be Read-only.')
       else
         ShowMessage('IoResult = '+IntToStr(IoResultNo)+' rewriting file.');
     end
   else
      RewriteFile := True;
end;   { RewriteFile }

Thanks for any help you can give me.
John Fistere
Avatar of 2266180
2266180
Flag of United States of America image

well ... in windows, you cannot close/delete a file that is in use. you will have to close the application that uses the file first. for this, you can replace the winexec with something like this:

http://www.scalabium.com/faq/dct0050.htm

function WinExecAndWait32(FileName: string; Visibility: Integer): dWord;
var
  zAppName: array[0..512] of Char;
  zCurDir: array[0..255] of Char;
  WorkDir: string;
  StartupInfo: TStartupInfo;
  ProcessInfo: TProcessInformation;
begin
  StrPCopy(zAppName, FileName);
  GetDir(0, WorkDir);
  StrPCopy(zCurDir, WorkDir);
  FillChar(StartupInfo, Sizeof(StartupInfo), #0);
  StartupInfo.cb := Sizeof(StartupInfo);

  StartupInfo.dwFlags := STARTF_USESHOWWINDOW;
  StartupInfo.wShowWindow := Visibility;
  if not CreateProcess(nil,
           zAppName, { pointer to command line string }
           nil, { pointer to process security attributes }
           nil, { pointer to thread security attributes }
           false, { handle inheritance flag }
           CREATE_NEW_CONSOLE or { creation flags }
           NORMAL_PRIORITY_CLASS,
           nil, { pointer to new environment block }
           nil, { pointer to current directory name }
           StartupInfo, { pointer to STARTUPINFO }
           ProcessInfo) then
    Result := -1 { pointer to PROCESS_INF }
  else
  begin
    // save the processInfo somewhere
  end;
end;

procedure terminateProcess; // you call this in the rewrite procedure of yours or wherever you sit fit.
begin// using the save processInfo
  TerminateProcess(ProcessInfo.hProcess);
  WaitforSingleObject(ProcessInfo.hProcess, INFINITE);// it is a good idea to wait for the process to finish
  CloseHandle(ProcessInfo.hProcess);
  CloseHandle(ProcessInfo.hThread);
end;
Avatar of John Fistere

ASKER

I am starting to integrate your WinExecAndWait32 routine.

When I simply paste it in I get the diagnostic:
"Constant expression violates subrange bounds" which is caused by the Result := -1 statement.
It seems to be incompatible with the dWord Result type.  Result := 0 compiles OK.

Also, I am not clear on how to "save the process info somewhere".  I am setting this up as a generalized routine  that knows nothing about the calling application.  The Result is the pointer to the process info, isn't it?  So would I say
Result := Process Info?   Then I would save the result in the calling application.

I will go ahead with using your routine, but I need some more help.

Thanks,
John Fistere
ASKER CERTIFIED SOLUTION
Avatar of 2266180
2266180
Flag of United States of America image

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
I used another version of WinExecAndWait32 and it worked OK, not allowing the calling application to continue running.  I tried yours and it did open the file OK, but it worked just like WinExec, allowing me to run the calling application.  Then I pasted

    // save the processInfo somewhere
       WaitforSingleObject(ProcessInfo.hProcess,INFINITE);
       GetExitCodeProcess(ProcessInfo.hProcess,Res);                                                                                                                                                                                                                                                                                                      
       CloseHandle( ProcessInfo.hProcess );
       CloseHandle( ProcessInfo.hThread );
       Result := Res;                             // I added Res: dWord to the var section

int the lower section of your routine and it worked OK.  That is, it would not allow the calling application to run (get the focus.).

At this point, I have to close the Excel application called before I can get back to the calling application so I am not sure how to use your terminateProcess.

I will comment out the WaitforSingleObject line and see if that makes it possible for me to close it programmatically.

John
well ... if you look at the link I gave you in the first post, that is the exact code you posted. I just made the modifications for you as I believed it will help you ;)
I am adding the TerminateProcess routine.

1. I presume you did not intend it to be recursive, so I named the routine something else.

2. The TerminateProcess call requires a second parameter called UINT uExitCode, but I could not figure out how to include that parameter.  Got any advice?

3. From another code example I figured out that the ProcessInfo is of type TProcessInfo.  However, when I include
"CSVProcessInfo: TProcessInfo" in the var section, TProcessInfo is an "undeclared identifier".  I figure I am missing a unit reference in my uses section, but my Help info does not include a reference to TProcessInfo.  Do you think that Delphi 5 supports the type?

Thanks,
John Fistere

well ..
1) I re-looked over the code but don't see where a recursion would happen. sorry. maybe you can pinpoint it out? :)

2) that is the exitcode the application will be terminated with. see msdn :http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dllproc/base/terminateprocess.asp you should probably use 0 to indicate "normal" exit or 1 or above to signal that an error has occured and the excel application or whatever needs to close with error (can't think of a situation for this one in your case though :) )

3)you got it wrong. if you look in my code,  ProcessInfo: TProcessInformation;
but if you look at my code even better, you will see that the function header is:
function myWinExec32(FileName: string; Visibility: Integer): PProcessInformation;
thus returning a pointer. why I did this? well, as I explained in that post, you will need to know if the function succeded or not. and returning a structure (TProcessInformation) will not help, but returning a pointer will help (you will have to free the pointer yourself using dispose).
so declare CSVProcessInfo: PProcessInformation;
both PProcessInformation and TProcessInformation are delcared in windows.pas since they are used by win-api function.
Aha.  I was working with the routines you had posted on the 8th, and overlooked myWinExec32 you posted on the 9th.  That explains part of the confusion.

Re: Q 1) & 2)
In your earlier message:

procedure terminateProcess; // you call this in the rewrite procedure of yours or wherever you sit fit.
                                          //*** I made ProcessInfo a parameter of this call.
begin// using the save processInfo
  TerminateProcess(ProcessInfo.hProcess);   // ***This is the call that looks recursive (and requires a second parameter).
  WaitforSingleObject(ProcessInfo.hProcess, INFINITE);// it is a good idea to wait for the process to finish
  CloseHandle(ProcessInfo.hProcess);
  CloseHandle(ProcessInfo.hThread);
end;

Re: Q 3)  Right.  A misspelling.

I'll use your myWinExec32 and see how it goes.

I am also relooking at the functional requirement of my application.  The desire to close the Excel application from my application actually resulted from the debug process.  The Excel file would be left open when I would go back to the IDE to try something.  In actual use, if the user opens the Excel file there should be no reason to run my application until he/she renames or closes the Excel file.  Therefore I can use WinExecAndWait32 instead.

However, I am interested in making the close Excel function work, so I'll do that anyway, and then probably revert to the ...AndWait approach anyway.

Thanks,
John Fistere
oh. my bad about that recursive thing rename procedure to somethig you like. for example:

procedure myterminateProcess; // you call this in the rewrite procedure of yours or wherever you sit fit.
                                          //*** I made ProcessInfo a parameter of this call.
begin// using the save processInfo
  TerminateProcess(ProcessInfo.hProcess);   // ***This is the call that looks recursive (and requires a second parameter).
  WaitforSingleObject(ProcessInfo.hProcess, INFINITE);// it is a good idea to wait for the process to finish
  CloseHandle(ProcessInfo.hProcess);
  CloseHandle(ProcessInfo.hThread);
end;

well.. experiment is what makes us learn and understand better some processes so go ahead with it :) let me know if you need help on that or if I had other ... "slips" :D

cheers
Good news:
I now have a function:

    function ViewWithExcel(ExcelName, FName: string): PProcessInformation;

and a type declaration

    CSVInfoPointer: PProcessInformation;

The call to the close function:

    CloseAFile(CSVInfoPointer^);

which is now:

procedure CloseAFile(ProcessInfo: TProcessInformation);
begin// using the save processInfo
  TerminateProcess(ProcessInfo.hProcess,0);
  WaitforSingleObject(ProcessInfo.hProcess, INFINITE);// it is a good idea to wait for the process to finish
  CloseHandle(ProcessInfo.hProcess);
  CloseHandle(ProcessInfo.hThread);
end;

It works!   Thanks for your help.

Additional comment:

Looking in the help file:
------------------
The TerminateProcess function is used to unconditionally cause a process to exit. Use it only in extreme circumstances. The state of global data maintained by dynamic-link libraries (DLLs) may be compromised if TerminateProcess is used rather than ExitProcess.
TerminateProcess causes all threads within a process to terminate, and causes a process to exit, but DLLs attached to the process are not notified that the process is terminating.
-------------------

I wonder how bad that is.  The ExitProcess they recommend has as its only paramenter ExitCode, so I don't see how to specify the application I want closed.   Hmmm.

Anyway, thanks,
John Fistere
well .. you could remove that terminateprocess call IF you want your user to close the excel application. if not, then you will have to close it yourself .. somehow :) terminateprocess seemed like a good idea; I don't have an alternative at the moment.