John Fistere
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,recCo nfig.CSVFi leName)) 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
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,recCo
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
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
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
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
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(Proces sInfo.hPro cess,INFIN ITE);
GetExitCodeProcess(Process Info.hProc ess,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
// save the processInfo somewhere
WaitforSingleObject(Proces
GetExitCodeProcess(Process
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 ;)
ASKER
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
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.
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.
ASKER
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(ProcessIn fo.hProces s); // ***This is the call that looks recursive (and requires a second parameter).
WaitforSingleObject(Proces sInfo.hPro cess, INFINITE);// it is a good idea to wait for the process to finish
CloseHandle(ProcessInfo.hP rocess);
CloseHandle(ProcessInfo.hT hread);
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
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(ProcessIn
WaitforSingleObject(Proces
CloseHandle(ProcessInfo.hP
CloseHandle(ProcessInfo.hT
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(ProcessIn fo.hProces s); // ***This is the call that looks recursive (and requires a second parameter).
WaitforSingleObject(Proces sInfo.hPro cess, INFINITE);// it is a good idea to wait for the process to finish
CloseHandle(ProcessInfo.hP rocess);
CloseHandle(ProcessInfo.hT hread);
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
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(ProcessIn
WaitforSingleObject(Proces
CloseHandle(ProcessInfo.hP
CloseHandle(ProcessInfo.hT
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
ASKER
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(ProcessIn fo.hProces s,0);
WaitforSingleObject(Proces sInfo.hPro cess, INFINITE);// it is a good idea to wait for the process to finish
CloseHandle(ProcessInfo.hP rocess);
CloseHandle(ProcessInfo.hT hread);
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
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(ProcessIn
WaitforSingleObject(Proces
CloseHandle(ProcessInfo.hP
CloseHandle(ProcessInfo.hT
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.
http://www.scalabium.com/faq/dct0050.htm
function WinExecAndWait32(FileName:
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(ProcessIn
WaitforSingleObject(Proces
CloseHandle(ProcessInfo.hP
CloseHandle(ProcessInfo.hT
end;