CodeMonkey
CodeMonkey

Reputation: 135

How do I wait to delete a file until after the program I started has finished using it?

I have been looking for a way to open a file saved to my computer via a Delphi app with its appropriate application. The file is stored in a Varbinary field in a SQL database, and is loaded into a memory stream and then saved via the TMemoryStream's SavetoFile method. What I would like to accomplish is to open the saved file in its appropriate application without knowing the filepath to that application's executable. I have had some success using ShellExecuteEx, but certain applications don't return an HProcess (Windows Live Photo Gallery, for example), so I can't (or at least don't know how to) wait for the application to close before moving on when a handle isn't returned. Is there a way to ensure I receive a handle when calling ShellExecuteEx? If this is not the best way how should I go about doing this?

I only need to know the external app's status because I plan on deleting the file after it closes, and I only need to write it because I'm fairly certain I can't load the file stored in the SQL table into memory (by way of a MemoryStream, FileStream, etc.) and launch its associated program directly from my Delphi app. (I've asked about that separately.)

Upvotes: 0

Views: 2282

Answers (2)

David Heffernan
David Heffernan

Reputation: 612993

Trying to detect that the displaying process has closed is brittle and fraught with problems, as you learnt in your previous question. Often times, it's hard to find the process that is used to view the file, and even if you can, there's no certainty the closing the view of the file will close the process. The process may be used to view other files which the user leaves open. I think the lesson that you should take from that is that the system does not want you to do what you are trying to do.

So, what's the better way to solve the problem? I think the best you can do is to create the temporary files in the temporary directory and not attempt to delete them when the user has finished with them. You could:

  • Remember the files you created and when you create, say the 21st file, delete the first one you made. Then delete the 2nd when you create the 22nd and so on.
  • Or, delete all temporary files on startup. This would remove files from a previous session.
  • Or run a separate tidy up thread that, every ten minutes, say, deleted files that were created more than an hour ago.

You get the idea. The point is that it is an intractable problem to detect when the viewer has finished with the file, in full generality. So you need to think creatively. Find a different way around the road block.

Upvotes: 4

NFX
NFX

Reputation: 101

Hers a snip from a unit I use for a similar purpose. I found these functions online somewhere over the the years so I take no credit and make no promises.

I personally use the WaitExec() function to launch a pdf (retrieved from a database) in Acrobat for editing and then re-save it to our database when done.

I have used the two other functions at other times as well so I know they all work to one degree or another but I think WaitExec() worked best in an interactive mode, while Launch() worked better from a thread or non-interactive mode.

The IsFileInUse function can tell you if the file you created is in use by any other processes and may be a viable option as well.

uses SysUtils, Windows, ShellAPI, Forms, Registry, Classes, Messages, Printers,
    PSAPI, TlHelp32, SHFolder;



function IsFileInUse(fName: string): boolean;
var
    HFileRes: HFILE;
begin
    Result := False;
    if not FileExists(fName) then
        Exit;
    HFileRes := CreateFile(pchar(fName), GENERIC_READ or GENERIC_WRITE,
        0 {this is the trick!}, nil, OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL, 0);
    Result := (HFileRes = INVALID_HANDLE_VALUE);
    if not Result then
        CloseHandle(HFileRes);
end;

function Launch(sCommandLine: string; bWait: Boolean; AppHandle: HWND): Boolean;
var
    SEI: TShellExecuteInfo;
    Mask: Longint;
begin
    Mask := SEE_MASK_NOCLOSEPROCESS;
    FillChar(SEI, Sizeof(SEI), #0);
    SEI.cbsize := Sizeof(SEI);
    SEI.wnd := AppHandle;
    SEI.fmask := Mask;

    //if FExeStyleString<>'' then SEI.LPVERB:=pchar(FExeStyleString);
    SEI.LPFile := pchar(sCommandline);

    //SEI.LPParameters := pchar(FExeParameters);
    //SEI.LPDirectory  := pchar(FExepath);
    SEI.nshow := SW_SHOWNORMAL; // SW_SHOWMINIMIZED, SW_SHOWMAXIMIZED
    ShellexecuteEx(@SEI);

    if bWait then
        WaitforSingleObject(SEI.hProcess, INFINITE);
    Result := True;
end;

function WaitExec(const CmdLine:AnsiString;const DisplayMode:Integer):Integer;
  {Execute an app, wait for it to terminate then return exit code.  Returns -1
   if execution fails. DisplayMode is usually either sw_ShowNormal or sw_Hide.}
 var
   S:TStartupInfo;
   P:TProcessInformation;
   M:TMsg;
   R:DWord;
 begin
   FillChar(P,SizeOf(P),#0);
   FillChar(S,Sizeof(S),#0);
   S.cb := Sizeof(S);
   S.dwFlags := STARTF_USESHOWWINDOW;
   S.wShowWindow := DisplayMode;
   if not CreateProcess(nil,
     PChar(CmdLine),                { 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 }
     S,                             { pointer to STARTUPINFO }
     P)                             { pointer to PROCESS_INF }
   then begin
    ShowMessage('Create Process failed. Save this message for IT: ' + CmdLine);
    Result:=-1
   end
   else begin
//     WaitforSingleObject(P.hProcess,INFINITE);
//   The following replacement better satisfies DDE requirements
     repeat
       R := MsgWaitForMultipleObjects(1, // One event to wait for
       P.hProcess, // The array of events
       FALSE, // Wait for 1 event
       INFINITE, // Timeout value
       QS_ALLINPUT); // Any message wakes up
       if R>WAIT_OBJECT_0 then begin
         M.Message := 0;
         while PeekMessage(M,0,0,0,PM_REMOVE) do begin
           TranslateMessage(M);
           DispatchMessage(M);
         end
       end;

     until R=WAIT_OBJECT_0;

     // put value into Result.... non zero = success
     GetExitCodeProcess(P.hProcess,DWord(Result));

     CloseHandle(P.hProcess);
     CloseHandle(P.hThread);
     P.hProcess:=0;
     P.hThread:=0;
   end;
end;

Upvotes: 1

Related Questions