Hallo,
Vanuit mijn Delphi applicatie open ik een PDF bestand via ShellExecute. Dit bestand opent netjes in mijn PDF lezer maar welke event kan ik gebruiken als ik de PDF lezer afsluit en weer terugkeer naar mijn Delphi applicatie?
Groet, Pascal
Hallo,
Vanuit mijn Delphi applicatie open ik een PDF bestand via ShellExecute. Dit bestand opent netjes in mijn PDF lezer maar welke event kan ik gebruiken als ik de PDF lezer afsluit en weer terugkeer naar mijn Delphi applicatie?
Groet, Pascal
Last edited by Pascal G++; 02-May-19 at 13:23.
Daar is geen 'event' voor want ShellExecute voert de PDF reader uit en keert gelijk terug naar je programma.
Dat doe ik ook met:
Delphi Code:
rs := ShellApi.ShellExecute(0, pchar(Action), pchar(FileName), nil, nil, SW_SHOW);
Waarom wil je zo'n event??
Misschien zijn er andere methodes om je doel te bereiken.
Anders moet je aan de gang met ShellExecuteEx(@exInfo) en Waitforsingleobject() maar dat zul je dan in een thread moeten doen want anders blokkeer je je programma.
De PDF die geopend wordt is nog niet ondertekend middels een PKI certificaat. Dit wordt gedaan in de PDF lezer. Zodra er weer wordt teruggekeerd naar de applicatie wil ik controleren of het document inmiddels wel is getekend zodat ik kan verwijderen uit de lijst met nog te tekenen documenten.
(Had je nou maar fpc met TProcess en [poWaitOnExit])
Nee. Dat is niet zo, het is createprocessw of the -ex variant.
En tsja, wat is een wrapper, en wanneer wordt het iets aparts? Vrijwel alles roept intern ergens wel OS primitives aan? De VCL is een GDI/winapi wrapper ? :-)
TProcess zit nu in de 1000-1500 regels orde grootte. Vertalen naar Delphi is ff werk, maar is wel te doen, ergens op het forum moet nog een vertaling uit 2012 of zo staan.
Last edited by marcov; 02-May-19 at 22:11.
Zoiets
https://github.com/z505/TProcess-Delphi
Ik heb ook een voorbeeld voor ShellExecuteEx(@exInfo) met Waitforsingleobject() (wat natuurlijk veel compacter is maar met minder opties) maar kan daar even niet bij.
Van die wrapper was ook geen kritiek hoor. Inderdaad is alles een wrapper en ik wist dat het om een van de standaard process apis ging in (en voor wat betreft) Windows.
Dat is een andere oude ja. Maar die ik bedoelde was nog een tikje ouder, 2005, al is er een followup in 2014. (die overigens wel het lezen waard is, vooral als je ook stderr gebruikt) https://www.nldelphi.com/showthread....=processdelphi
shellexecute heeft natuurlijk ook "shell" gebruik extra, dus dingen als documenten lanceren met alleen hun extensie. Ik zou de code daarom overigens graag zien. Aan de andere kant heeft TProcess met name de piping.Ik heb ook een voorbeeld voor ShellExecuteEx(@exInfo) met Waitforsingleobject() (wat natuurlijk veel compacter is maar met minder opties) maar kan daar even niet bij.
En piping met name. De piping is meer werk dan executie.Van die wrapper was ook geen kritiek hoor. Inderdaad is alles een wrapper en ik wist dat het om een van de standaard process apis ging in (en voor wat betreft) Windows.
Overigens heb ik redelijk zwaar ingegrepen in TProcess vorig najaar. Als je met TProcess code aan de slag gaat, neem de code van up to date trunk of fixes3_2, niet uit 3.0.4.
De veranderingen zijn met name unicode gerelateerd (duplicatie in TUtf8Process verwijderen op termijn), en is de mainloop van runcommand() nu deel van tprocess en verder geparameteriseerd (met events die je kan opgeven), wat het makkelijker maakt eigen varianten te maken. Ook weer om duplicatie te verminderen.
Eens eventjes kijken...
Dit is code uit mijn programma om bijvoorbeeld een Word-document te openen (.docx) en te wachten tot deze afgesloten wordt.
Delphi Code:
// ================================================ // ShellExecute // ================================================ FillChar(exInfo, SizeOf(exInfo), 0); with exInfo do begin cbSize := SizeOf(exInfo); lpVerb := nil; // Defaults to 'open'. fMask := SEE_MASK_NOCLOSEPROCESS or SEE_MASK_FLAG_DDEWAIT; Wnd := Application.Handle; LpFile := pChar(FTempFile); lpDirectory := nil; // Defaults to current directory. LpParameters := nil; // nShow := SW_SHOWNORMAL; nShow := SW_SHOW; end; // ================================================ // cCppException = $0EEFFACE; { used by BCB } // ================================================ if ShellExecuteEx(@exInfo) then begin Waitforsingleobject(exInfo.HProcess, infinite); CloseHandle(exInfo.HProcess); end else begin ShowMessage(SysErrorMessage(GetLastError)); end; // ================================================
Nog een paar varianten van welbekende code (uit mijn snippet library). Dit is een mix. De ene gebruikt CreateProcess en de andere weer ShellExecuteEx().
Het voordeel van de ShellExecute is inderdaad dat je dus niet eerst zelf de Associated App hoeft te zoeken. Maar ook daar heb ik een FindAssociatedApp() voor die gewoon een ShellApi.FindExecutable() doet (wat voorbeelden daarvan helemaal onderaan). (Lekker zooitje als ik ze zo allemaal bij elkaar zie )
Delphi Code:
function WinExecAndWait32(FileName, Params: string; Visibility: integer): DWORD; var { by Pat Ritchey } zAppName: array [0 .. 512] of Char; zAppDir: array [0 .. 512] of Char; StartupInfo: TStartupInfo; ProcessInfo: TProcessInformation; begin StrPCopy(zAppName, FileName + ' ' + Params); StrPCopy(zAppDir, ExtractFilePath(FileName)); 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 } zAppDir, // pointer to current directory name StartupInfo, { pointer to STARTUPINFO } ProcessInfo) then Result := DWORD(-1) { pointer to PROCESS_INF } else begin repeat Application.ProcessMessages; until WaitForSingleObject(ProcessInfo.HProcess, 0) <> WAIT_TIMEOUT; // WaitforSingleObject(ProcessInfo.hProcess, INFINITE); GetExitCodeProcess(ProcessInfo.HProcess, Result); CloseHandle(ProcessInfo.HProcess); CloseHandle(ProcessInfo.hThread); end; end;
Delphi Code:
function WinExecAndWait32V2(FileName, Params: string; Visibility: integer): DWORD; procedure WaitFor(processHandle: THandle); var Msg: TMsg; ret: DWORD; begin repeat ret := MsgWaitForMultipleObjects( 1, processHandle, false, INFINITE, QS_PAINT or QS_SENDMESSAGE ); if ret = WAIT_FAILED then Exit; if ret = (WAIT_OBJECT_0 + 1) then begin while PeekMessage(Msg, 0, WM_PAINT, WM_PAINT, PM_REMOVE) do DispatchMessage(Msg); end; until ret = WAIT_OBJECT_0; end; var zAppName: array [0 .. 512] of Char; zAppDir: array [0 .. 512] of Char; StartupInfo: TStartupInfo; ProcessInfo: TProcessInformation; begin Result := 0; // path of the exe StrPCopy(zAppName, FileName + ' ' + Params); StrPCopy(zAppDir, ExtractFilePath(FileName)); 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 NORMAL_PRIORITY_CLASS, // creation flags nil, // pointer to new environment block zAppDir, // pointer to current directory name StartupInfo, // pointer to STARTUPINFO ProcessInfo) { // pointer to PROCESS_INF } then begin Result := DWORD(-1); // 740 = ERROR_ELEVATION_REQUIRED end else begin WaitFor(ProcessInfo.HProcess); GetExitCodeProcess(ProcessInfo.HProcess, Result); CloseHandle(ProcessInfo.hThread); CloseHandle(ProcessInfo.HProcess); end; end;
Delphi Code:
function WinExecAndWait32V3(FileName, Params: string; Visibility: integer): DWORD; var // waitState: dword; exi: TShellExecuteInfo; begin FillChar(exi, SizeOf(exi), #0); exi.cbSize := SizeOf(exi); exi.lpVerb := pchar('open'); exi.lpFile := pchar(ExtractFileName(FileName)); exi.lpDirectory := pchar(ExtractFilePath(FileName)); exi.lpParameters := pchar(Params); exi.fMask := SEE_MASK_FLAG_DDEWAIT + SEE_MASK_NOCLOSEPROCESS + SEE_MASK_CONNECTNETDRV; ShellExecuteEx(@exi); { waitState := } WaitForSingleObject(exi.HProcess, INFINITE); GetExitCodeProcess(exi.HProcess, Result); end;
Delphi Code:
function ExecAndWait(const CommandLine: string): boolean; var StartupInfo: Windows.TStartupInfo; // start-up info passed to process ProcessInfo: Windows.TProcessInformation; // info about the process ProcessExitCode: Windows.DWORD; // process's exit code begin // Set default error result Result := false; // Initialise startup info structure to 0, and record length FillChar(StartupInfo, SizeOf(StartupInfo), 0); StartupInfo.cb := SizeOf(StartupInfo); // Execute application commandline if Windows.CreateProcess(nil, pchar(CommandLine), // pointer to command line string nil, // pointer to process security attributes nil, // pointer to thread security attributes false, // handle inheritance flag 0, // creation flags nil, // pointer to new environment block nil, // pointer to current directory name StartupInfo, // pointer to STARTUPINFO ProcessInfo) { // pointer to PROCESS_INF } then begin try // Now wait for application to complete if Windows.WaitForSingleObject(ProcessInfo.HProcess, INFINITE) = WAIT_OBJECT_0 then // It's completed - get its exit code if Windows.GetExitCodeProcess(ProcessInfo.HProcess, ProcessExitCode) then // Check exit code is zero => successful completion if ProcessExitCode = 0 then Result := true; finally // Tidy up Windows.CloseHandle(ProcessInfo.HProcess); Windows.CloseHandle(ProcessInfo.hThread); end; end; end;
Delphi Code:
procedure RunFileAsAdminWait( { HWND: HWND; } aFile, aParameters: string); var sei: TShellExecuteInfo; begin FillChar(sei, SizeOf(sei), 0); sei.cbSize := SizeOf(sei); sei.Wnd := GetActiveWindow(); // sei.Wnd := HWND; // sei.fMask := SEE_MASK_FLAG_NO_UI or SEE_MASK_FLAG_DDEWAIT; sei.fMask := SEE_MASK_FLAG_NO_UI or SEE_MASK_NOCLOSEPROCESS; sei.lpVerb := 'runas'; sei.lpFile := pchar(aFile); sei.lpParameters := pchar(aParameters); sei.nShow := SW_SHOWNORMAL; if not ShellExecuteEx(@sei) then RaiseLastOSError; if sei.HProcess <> 0 then begin while WaitForSingleObject(sei.HProcess, 50) = WAIT_TIMEOUT do Application.ProcessMessages; CloseHandle(sei.HProcess); end; end;
Delphi Code:
function FindAssociatedApp(const Doc: string): string; var PExecFile: array [0 .. Windows.MAX_PATH] of Char; // buffer to hold exe name begin // Win API call in ShellAPI if ShellApi.FindExecutable(pchar(Doc), nil, PExecFile) < 32 then // No associated program found Result := '' else // Return program file name Result := PExecFile; end; function ExecAssociatedApp(const FileName: string; Action: string = ''): boolean; var rs: Word; Shw: integer; begin // Shw := SW_HIDE; // Shw := SW_NORMAL; Shw := SW_SHOW; // if Action = 'print' then Shw := SW_SHOWMINIMIZED; rs := ShellApi.ShellExecute(0, pchar(Action), pchar(FileName), nil, nil, Shw); // if ShiftDown then ShowMessage(inttostr(rs)); Result := rs > 32; end; function ExecAssociatedAppWait32(const FileName: string): boolean; var ExeServer: string; begin ExeServer := FindAssociatedApp(FileName); if WinExecAndWait32(ExeServer, FileName, SW_SHOWNORMAL) > 0 then; Result := true; end;
En als laatste nog een oudje waarvan ik niet weet of die nog werkt. Maar deze werkte wel met CreatePipe en CreateProcess.
Delphi Code:
// RunDosInMemo('chkdsk.exe c:\',Memo1) ; // niet unicode !! procedure RunDosInMemo(DosApp: string; AMemo: TMemo); const ReadBuffer = 2400; var Security: TSecurityAttributes; ReadPipe, WritePipe: THandle; start: TStartupInfo; ProcessInfo: TProcessInformation; buffer: pchar; BytesRead: DWORD; Apprunning: DWORD; begin with Security do begin nlength := SizeOf(TSecurityAttributes); binherithandle := true; lpsecuritydescriptor := nil; end; if Createpipe(ReadPipe, WritePipe, @Security, 0) then begin buffer := AllocMem(ReadBuffer + 1); FillChar(start, SizeOf(start), #0); start.cb := SizeOf(start); start.hStdOutput := WritePipe; start.hStdInput := ReadPipe; start.dwFlags := STARTF_USESTDHANDLES + STARTF_USESHOWWINDOW; start.wShowWindow := SW_HIDE; if CreateProcess(nil, pchar(DosApp), @Security, @Security, true, NORMAL_PRIORITY_CLASS, nil, nil, start, ProcessInfo) then begin repeat Apprunning := WaitForSingleObject(ProcessInfo.HProcess, 100); Application.ProcessMessages; BytesRead := 0; ReadFile(ReadPipe, buffer[0], ReadBuffer, BytesRead, nil); buffer[BytesRead] := #0; OemToAnsi(buffer, buffer); AMemo.Text := AMemo.Text + string(buffer); until (Apprunning <> WAIT_TIMEOUT); end; FreeMem(buffer); CloseHandle(ProcessInfo.HProcess); CloseHandle(ProcessInfo.hThread); CloseHandle(ReadPipe); CloseHandle(WritePipe); end; end;
Ik heb het anders opgelost. Graag hoor ik of dit ook een fraaie oplossing is.
De applicatie wacht totdat het bestand is geopend en gaat dan pas verder, vervolgens wordt gewacht totdat het bestand is afgesloten. Dit voorkomt dat de gebruiker allerlei bestanden opent en deze niet eerst afwerkt.Code:if not IsFileInUse(Bestand) then begin ShellExecute(Handle, 'open', pChar(Bestand), nil,nil,SW_SHOWNORMAL); // Wait for ShellExecute to open the file While Not IsFileInUse(Bestand) do begin Sleep(1); end; While IsFileInUse(Bestand) do begin // Do Nothing end; ShowMessage('File 1 not in use.'); end;
Ik werk dus met een combinatie van ShellExecute/Waitforsingleobject en IsFileInUse().
Niet alle programma's zetten n.l. een lock op het bestand.
En in het geval van een PDF Reader zou ik me dat ook voor kunnen stellen als deze niet naar het bestand hoeft te schrijven.
In jouw geval zou jouw programma dan in de eerste while blijven hangen omdat IsFileInUse altijd false is.
Weet je dus zeker dat bijvoorbeeld bij Fox Reader e.d. de PDF ook daadwerkelijk gelocked wordt?
Dit is mijn complete Thread.Execute (omdat ik bestanden in een TThread open zodat het programma gewoon door kan gaan).
Delphi Code:
procedure TDropThread.Execute; var // Ext: string; exInfo: TShellExecuteInfo; begin if Terminated then exit; // Ext := ExtractFileExt(baseQuery.FieldByName('BESTANDSNAAM').AsString); // FTempFile := ChangeFileExt(TempFile('cw_'), Ext); FTempFile := ChangeFileExt(TempFile('cw_'), '') + '_' + ExtractFileName(baseQuery.FieldByName('BESTANDSNAAM').AsString); TBlobField(baseQuery.FieldByName('DATA')).SaveToFile(FTempFile); FTempTime := myGetFileDate(FTempFile); // ================================================ // ShellExecute // ================================================ FillChar(exInfo, SizeOf(exInfo), 0); with exInfo do begin cbSize := SizeOf(exInfo); lpVerb := nil; // Defaults to 'open'. fMask := SEE_MASK_NOCLOSEPROCESS or SEE_MASK_FLAG_DDEWAIT; Wnd := Application.Handle; LpFile := pChar(FTempFile); lpDirectory := nil; // Defaults to current directory. LpParameters := nil; // nShow := SW_SHOWNORMAL; nShow := SW_SHOW; end; // ================================================ // cCppException = $0EEFFACE; { used by BCB } // ================================================ if ShellExecuteEx(@exInfo) then begin Waitforsingleobject(exInfo.HProcess, infinite); CloseHandle(exInfo.HProcess); end else begin ShowMessage(SysErrorMessage(GetLastError)); end; // ================================================ if Terminated then exit; // toch even wachten tot file niet meer in gebruik is while FileIsInUse(FTempFile) do begin Application.ProcessMessages; Sleep(1000); end; if Terminated then exit; SaveAndClose; end;
Bedankt rvk. Ik neem deze mee. De PDF reader die ik gebruik (Foxit Reader) zet een lock op een bestand. Dat heb ik kunnen vaststellen. Door alleen de IsFileInUse te gebruiken voorkom ik dat de gebruikers iets anders kan gaan doen dan het afhandelen van het geopende bestand. Het kan namelijk wel zo zijn dat er al andere PDF bestanden zijn geopend in de PDF reader die niet getekend hoeven te worden waardoor alleen de bestanden die geopende worden vanuit het postboek de zorgen voor een while loop.
Ik ben er inmiddels achter dat mijn oplossing niet gaat werken. Zodra het bestand wordt ondertekend wordt het opnieuw opgeslagen, maar pas daarna wordt de digitale handtekening toegevoegd. Ik zal dus moeten wachten totdat de applicatie is afgesloten.
@rvk
Ik heb jouw laatste code proberen te gebruiken maar ik zie niet hoe ik deze kan gebruiken. Zou je me nog mee op weg willen helpen.
Hoe krijgen ze dat voor elkaar... eerst opslaan en dan pas de handtekening toevoegen?
Wordt het bestand onder een andere naam opgeslagen? (dan heb je sowieso natuurlijk een ander probleem)
Wat lukt er niet met mijn code?
Adobe doet dat ook. Het document wordt opgeslagen met de handtekening en vervolgens wordt het document nog een keer opgeslagen als de pincode van het certificaat wordt ingevoerd.
Ik begrijp niet wat je bedoelt met een bestand openen in een TThread.
There are currently 1 users browsing this thread. (0 members and 1 guests)
Bookmarks