我一直在寻找一种方法,通过Delphi app及其相应的应用程序打开保存到我电脑的文件。该文件存储在SQL数据库的Varbinary字段中,并加载到内存流中,然后通过TMemoryStream的SavetoFile方法保存。我想要完成的是在相应的应用程序中打开已保存的文件,而不知道该应用程序的可执行文件的文件路径。我使用ShellExecuteEx取得了一些成功,但某些应用程序没有返回HProcess(例如Windows Live Photo Gallery),所以我不能(或者至少不知道如何)等待应用程序关闭之前不返回手柄时继续前进。有没有办法确保我在调用ShellExecuteEx时收到句柄?如果这不是最好的方法我应该怎么做呢?
我只需知道外部应用程序的状态,因为我计划在文件关闭后删除它,我只需要编写它,因为我很确定我无法将存储在SQL表中的文件加载到内存中(通过MemoryStream,FileStream等)并直接从我的Delphi应用程序启动其相关程序。 (I've asked about that separately.)
答案 0 :(得分:4)
正如您在上一个问题中所了解的那样,尝试检测显示过程已关闭是脆弱的并且充满了问题。通常情况下,很难找到用于查看文件的进程,即使可以,也无法确定关闭文件视图是否会关闭进程。该过程可用于查看用户离开的其他文件。我认为你应该从中得到的教训是,系统不希望你做你想做的事情。
那么,解决问题的更好方法是什么?我认为你能做的最好的事情是在临时目录中创建临时文件,而不是在用户完成它们时尝试删除它们。你可以:
你明白了。关键在于,在完全普遍的情况下检测查看器何时完成文件是一个棘手的问题。所以你需要创造性地思考。在路障附近找到不同的方式。
答案 1 :(得分:1)
从我用于类似目的的单位中获取一个剪辑。多年来我在网上发现了这些功能,所以我没有信任,也没有做出任何承诺。
我个人使用WaitExec()函数在Acrobat中启动pdf(从数据库中检索)进行编辑,然后在完成后将其重新保存到我们的数据库中。
我在其他时间也使用过其他两个函数,所以我知道它们都在某种程度上工作但我认为WaitExec()在交互模式下工作得最好,而Launch()在线程或非线程中工作得更好 - 交互模式。
IsFileInUse函数可以告诉您您创建的文件是否正被任何其他进程使用,并且也可能是一个可行的选项。
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;