1

I need to call an external program from Delphi 2006 code with a long list of arguments, specifically to concatenate mutiple PDFs into one file using PDFTK. The full string to be executed has over 512 characters, but both WinExec and ShellExecute have a 512 character limit.

Are there any alternatives to these procedures that have much larger limits?

JamesW
  • 849
  • 2
  • 14
  • 27
  • Couldn't you concatenate the PDF's in iterations? ie. a+b, then the result+c, etc.? – Lasse V. Karlsen Mar 13 '12 at 16:58
  • @Lasse - Yeah I thought about that, but I don't want to have multiple calls in case it slows it down too much. I may have to fall back on that idea later though! – JamesW Mar 13 '12 at 17:10
  • @RRUZ - All implementations of WinExec I have seen seem to be set up with 'zAppName: array[0..512] of char;' ShellExecute seems to have a few different styles of call however, so I'm just looking into that a bit further now – JamesW Mar 13 '12 at 17:12
  • Based on your last comment, it seems that you found code that *calls* WinExec with a fixed-size buffer, and so you've *assumed* that WinExec itself has such a limit, and likewise for ShellExecute. So, are you asking about a problem you've *actually* experienced, or are you just guessing that the problem exists and asking how to preemptively solve it when it might be that there's nothing to solve at all? – Rob Kennedy Mar 13 '12 at 17:40
  • 2
    @JamesW, FYI [What is the command line length limit?](http://blogs.msdn.com/b/oldnewthing/archive/2003/12/10/56028.aspx) – RRUZ Mar 13 '12 at 17:57
  • So ShellExecuteEx is limited to 2K. Neato info RRUZ! – Warren P Mar 14 '12 at 21:03

3 Answers3

4

Just use a temporary BATCH file, containing the commands to be executed.

This will allow also some enhanced features, like calling several PDFTK instance in a row, add backup or copy of files, just in the same process.

Run the batch as SW_SHOWMINIMIZED to have no black console window pop up.

Arnaud Bouchez
  • 42,305
  • 3
  • 71
  • 159
  • Thanks - I'm sure that would work, but I've just found a bit of more modern ShellExecute code than the one I was looking at. It seems to work nicely. – JamesW Mar 13 '12 at 17:18
  • ... Until you hit 2049 bytes. Then it breaks. – Warren P Mar 14 '12 at 21:03
3

There are some limits on the length of the names passed to ShellExecute, but these are typically greater than 512 characters. It seems you just need to dynamically allocate the names rather than using a static char array.

If you want to go to the ultimate command line length then you can use CreateProcess which has a limit of 32,768 characters.

As another option you could consider writing the list of arguments to a temporary file. Then you would modify the external program so that it is capable of being passed the path to that file as its command line argument. You would obviously need to also modify the external program so that it could read the file and obtain the long list of files from the temporary file.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Thanks - I'm sure that would work, but I've just found a bit of more modern ShellExecute code than the one I was looking at. It seems to work nicely. – JamesW Mar 13 '12 at 17:18
  • Apparently you can only go to 8K per a single line in a .CMD file. I know I'm always hitting that one. (Anyone remember VAX VMS command lines? It was like someone had an allergic reaction to Unix short command names, and EVERYTHING_WAS_A_BIG_LONG_THING_LIKE_THIS.) – Warren P Mar 14 '12 at 21:04
3

Just found this @SwissDelphiCentre, which seems to work nicely:

procedure ShellExecute_AndWait(FileName: string; Params: string);
var
  exInfo: TShellExecuteInfo;
  Ph: DWORD;
begin
  FillChar(exInfo, SizeOf(exInfo), 0);
  with exInfo do
  begin
    cbSize := SizeOf(exInfo);
    fMask := SEE_MASK_NOCLOSEPROCESS or SEE_MASK_FLAG_DDEWAIT;
    Wnd := GetActiveWindow();
    ExInfo.lpVerb := 'open';
    ExInfo.lpParameters := PChar(Params);
    lpFile := PChar(FileName);
    nShow := SW_SHOWNORMAL;
  end;
  if ShellExecuteEx(@exInfo) then
    Ph := exInfo.HProcess
  else
  begin
    ShowMessage(SysErrorMessage(GetLastError));
    Exit;
  end;
  while WaitForSingleObject(ExInfo.hProcess, 50) <> WAIT_OBJECT_0 do
    Application.ProcessMessages;
  CloseHandle(Ph);
end;
JamesW
  • 849
  • 2
  • 14
  • 27