Combining PDFs via gsdll32.dll with Delphi 11 - delphi

I have been using this code for more than 10 years and it suddenly stopped working.
The program reads a list of pdf files and output a single pdf file containing all the files in the list. It uses gsapi.pas wrapper unit. over the years I have only had to download an updated version gsdll32.dll file from Ghostscript.to be compatible with newer versions of pdf file format. but it is not working anymore
Any ideas of what is going on?
procedure PDFMerge(input : Array of String;output : string);
var
code:integer;
instance:Pointer;
argv:PPChar;
N,I : smallint;
begin
new(instance);
N:=length(input);
setlength(argv,6+N);
code:=gsapi_new_instance(#instance,nil);
if code<>0 then
begin
raise Exception.Create('Impossible to open an instance of ghostscript. Error code: '+IntToStr(code));
end;
try
//try adding dNothing as first parameter argv[1] := pchar('dNothing');
argv[0] := pchar('AppendPdf');
argv[1] := pchar('-dNOPAUSE');
argv[2] := pchar('-dBATCH');
argv[3] := pchar('-dSAFER');
argv[4] := pchar('-sDEVICE=pdfwrite');
argv[5] := pchar('-sOutputFile='+ output);
argv[6] := pchar('-c');
argv[7] := pchar('.setpdfwrite');
argv[8] := pchar('-f');
for I := 0 to N - 1 do
argv[9+I] := pchar(input[I]);
//Call gs32.dll
code := gsapi_init_with_args(instance, length(argv), argv);
if code<0 then
raise Exception.Create('ERROR: init_args: '+IntToStr(code));
gsapi_exit(instance);
gsapi_delete_instance(instance);
finally
end;
end;
I am using Ghostscript V10.0. I found that they have a new interpreter perhaps it has to do with That? Although I did try addidng the switch "-dNEWPDF=false" but no luck. I keep getting error code -100.
Note using the equivalent command line in Ghostscript itself works fine. It seems to me they changed the way the API needs to be called. Whould that be the case?
I would appreciate any help.
I did try addidng the switch "-dNEWPDF=false" but no luck.

Related

TMemIniFile.Create hanging when called in ServiceStart

In a service running under system account the code below hangs in the TMemIniFile.Create without errors.
If we replace it with TIniFile, it works fine.
It's a Delphi Tokyo 10.2.3 Win32 app running under Windows Server 2012R2. There's no concurrent access to the INI file.
This is the first time (first client) we see this, it has been running fine on many machines.
I have no idea what to look for further. Any ideas?
It 'works' now because we switched to TIniFile, but I'd like to find the cause. From other posts I read here, TINIfile seems to be more finicky than TMemINIfile, my situation is the reverse.
There are no special characters in the INI file and it is created with an ASCII editor.
// This is set in the ServiceCreate:
FIniFileNaam := ChangeFileExt(ParamStr(0),'.INI');
// This is called from the ServiceStart:
procedure TTimetellServiceBase.LeesINI;
var lIniFile : TMemIniFile;
begin
LogMessage(FIniFileNaam, EVENTLOG_INFORMATION_TYPE, cCatInfo, cReadINI); // Logs to event log, we see this
FStartDir := ExtractFilePath(ParamStr(0));
if assigned(FLaunchThreadLijst) then FreeAndNil(FLaunchThreadLijst);
FLaunchThreadLijst := TStringList.Create;
try
if FileExists(FIniFileNaam) then
begin
// Lees waarden uit INI file
lIniFile := TMemIniFile.Create(FIniFileNaam); // This is the call that hangs. The service is unresponsive now.
try
FLaunchThreadLijst.CommaText := lIniFile.ReadString(INISECTIE_SERVICETASKS,'RunIniFiles','');
FMaxTaskDuration := lIniFile.ReadInteger(INISECTIE_SERVICETASKS,'MaxTaskDuration',FMaxTaskDuration);
finally
FreeAndNil(lIniFile);
end;
end;
finally
if (FLaunchThreadLijst.Count = 0) and FileExists(FStartDir + FExeName) then
FLaunchThreadLijst.Add(SDEFAULTTHREADNAME);
LogMessage(Format('FLaunchThreadLijst.CommaText: %s (%d items)',[FLaunchThreadLijst.CommaText,FLaunchThreadLijst.Count]), EVENTLOG_INFORMATION_TYPE, cCatInfo, cLaunchList);
end;
end;
FWIW, INI file contents:
[TASKMANAGER]
RunIniFiles=TTTasks.ini
MaxTaskDuration=2
RestartIniFiles=
KillIniFiles=

Delphi - printing XML document by InternetExplorer Ole Object

I want to print XML document in Delphi, using OleObject. My code:
var
ie : OleVariant;
begin
ie := CreateOleObject('InternetExplorer.Application');
ie.Navigate('doc.xml');
ShowWindow(frmGlowna.ie.HWND, SW_SHOWMAXIMIZED);
ie.Visible := True;
while ie.QueryStatusWB( OLECMDID_PRINT) <> OLECMDF_SUPPORTED + OLECMDF_ENABLED do
Application.ProcessMessages;
ie.ExecWB(OLECMDID_PRINT, OLECMDEXECOPT_PROMPTUSER,OleVariant(VarAsType($02, varSmallint)), vOut);
ie.Quit;
ie := Unassigned;
end;
Line:
ie.ExecWB(OLECMDID_PRINT, OLECMDEXECOPT_PROMPTUSER,OleVariant(VarAsType($02, varSmallint)), vOut);
Execute printing command and wait for finish.
Everything is OK, as long as the program does not run again or run the same program twice at the same time.
I get different errors:
Catastrophic failure
or
The object invoked has disconnected from its clients
Please help.

Copying File Fails in when open in fmOpenReadWriteMode

i am working on a little byte patching program but i encountered an error.
copying the file before modifying fails with no error, (no copied output is seen) but the file patches successfully.
Here is the Patch Code
procedure DoMyPatch();
var
i: integer;
FileName: string;
input: TFileStream;
FileByteArray, ExtractedByteArray: array of Byte;
begin
FileName := 'Cute1.res';
try
input := TFileStream.Create(FileName, fmOpenReadWrite);
except
begin
ShowMessage('Error Opening file');
Exit;
end
end;
input.Position := 0;
SetLength(FileByteArray, input.size);
input.Read(FileByteArray[0], Length(FileByteArray));
for i := 0 to Length(FileByteArray) do
begin
SetLength(ExtractedByteArray, Length(OriginalByte));
ExtractedByteArray := Copy(FileByteArray, i, Length(OriginalByte));
// function that compares my array of bytes
if CompareByteArrays(ExtractedByteArray, OriginalByte) = True then
begin
// Begin Patching
CopyFile(PChar(FileName), PChar(ChangeFileExt(FileName, '.BAK')),
true); =======>>> fails at this point, no copied output is seen.
input.Seek(i, SoFromBeginning);
input.Write(BytetoWrite[0], Length(BytetoWrite)); =====>>> patches successfully
input.Free;
ShowMessage('Patch Success');
Exit;
end;
end;
if Assigned(input) then
begin
input.Free;
end;
ShowMessage('Patch Failed');
end;
sidenote : it copies fine if i close the filestream before attempting copy.
by the way, i have tested it on Delphi 7 and XE7.
Thanks
You cannot copy the file because you locked it exclusively when you opened it for the file stream, which is why CopyFile fails.
You should close the file before attempting to call CopyFile. Which would require you to reopen the file to patch it. Or perhaps open the file with a different sharing mode.
Some other comments:
The exception handling is badly implemented. Don't catch exceptions here. Let them float up to the high level.
Lifetime management is fluffed. You can easily leak as it stands. You need to learn about try/finally.
You overrun buffers. Valid indices for a dynamic array are 0 to Length(arr)-1 inclusive. Or use low() and high().
You don't check the value returned by CopyFile. Wrap it with a call to Win32Check.
The Copy function returns a new array. So you make a spurious call to SetLength. To copy the entire array use the one parameter overload of Copy.
Showing messages in this function is probably a mistake. Better to let the caller provide user feedback.
There are loads of other oddities in the code and I've run out of energy to point them all out. I think I got the main ones.

Delphi7, Save User's Changes or other User's Information / Notes

In my program, the user completes a form and then presses Submit. Then, a textfile or a random extension file is created, in which all the user's information is written. So, whenever the user runs the application form, it will check if the file, which has all the information, exists, then it copies the information and pastes it to the form. However, it is not working for some reason (no syntax errors):
procedure TForm1.FormCreate(Sender: TObject);
var
filedest: string;
f: TextFile;
info: array[1..12] of string;
begin
filedest := ExtractFilePath(ParamStr(0)) + 'User\Identity\IdentityofMyself.txt';
if FileExists(filedest) then
begin
AssignFile(f,filedest);
Reset(f);
ReadLn(info[1], info[2], info[3], info[4], info[5], info[6], info[7],
info[8], info[9], info[10], info[11], info[12]);
Edit1.Text := info[1];
Edit2.Text := info[2];
ComboBox1.Text := info[3];
ComboBox5.Text := info[4];
ComboBox8.Text := info[4];
ComboBox6.Text := info[5];
ComboBox7.Text := info[6];
Edit3.Text := info[7];
Edit4.Text := info[8];
Edit5.Text := info[11];
Edit6.Text := info[12];
ComboBox9.Text := info[9];
ComboBox10.Text := info[10];
CloseFile(f);
end
else
begin
ShowMessage('File not found');
end;
end;
The file exists, but it shows the message File not found. I don't understand.
I took the liberty of formatting the code for you. Do you see the difference (before, after)? Also, if I were you, I would name the controls better. Instead of Edit1, Edit2, Edit3 etc. you could use eFirstName, eLastName, eEmailAddr, etc. Otherwise it will become a PITA to maintain the code, and you will be likely to confuse e.g. ComboBox7 with ComboBox4.
One concrete problem with your code is this line:
readln(info[1], info[2], info[3], info[4], info[5], info[6], info[7],
info[8], info[9], info[10], info[11], info[12]);
You forgot to specify the file f!
Also, before I formatted your code, the final end of the procedure was missing. Maybe your blocks are incorrect in your actual code, so that ShowMessage will be displayed even if the file exists? (Yet another reason to format your code properly...)
If I encountered this problem and wanted to do some quick debugging, I'd insert
ShowMessage(BoolToStr(FileExists(filedest), true));
Exit;
just after the line
filedest := ...
just to see what the returned value of FileExists(filedest) is. (Of course, you could also set a breakpoint and use the debugger.)
If you get false, you probably wonder what in the world filedest actually contains: Well, replace the 'debugging code' above with this one:
ShowMessage(filedest);
Exit;
Then use Windows Explorer (or better yet: the command prompt) to see if the file really is there or not.
I'd like to mention an another possibility to output a debug message (assuming we do not know how to operate real debugger yet):
{ ... }
filedest := ExtractFilePath(ParamStr(0)) + 'User\Identity\IdentityofMyself.txt';
AllocConsole; // create console window (uses Windows module) - required(!)
WriteLn('"' + filedest + '"'); // and output the value to verify
if FileExists(filedest) then
{ ... }

How to get version of running executable?

How can i get the version of my running application?
i have been using GetFileVersionInfo(ParamStr(0), ...):
filename := PChar(ExtractShortPathName(ParamStr(0)));
//Get the number of bytes he have to allocate for the file information structure
dwInfoLength := GetFileVersionInfoSize(lptstrFilename, {var}dwHandle);
//Get version info
GetMem(pInfoData, dwInfoLength);
GetFileVersionInfo(lptstrFilename, dwHandle, dwInfoLength, pInfoData);
//Set what information we want to extract from pInfoData
lpSubBlock := PChar(Chr(92)+Chr(0));
//Extract the desired data from pInfoData into the FileInformation structure
VerQueryValue(pInfoData, lpSubBlock, PFileInformation, LengthOfReturned);
The problem with this technique is that it requires the Windows loader to load the image before the resources can be read. i build my applications with the IMAGE_FILE_NET_RUN_FROM_SWAP image flag (in order to avoid in-page exceptions on a fiddly network).
This causes the Windows loader to load the entire image across the network again, rather than just looking at "me". Since i check, and save, my own version at startup, a 6 second application startup turns into a 10 second application startup.
How can i read the version of me, my running application?
i would assume Windows has no API to read the version of a running process, only the file that i loaded from (and if the file no longer exists, then it cannot read any version info).
But i also assume that it might be possible to read version resources out of my processes own memory (without being a member of the Administrators or Debuggers group of course).
Can i read the version of my process?
Associated Bonus Question: How can i load PE Image resources from me rather than across the network?
Found it, right here on Stackoverflow:
How to determine Delphi Application Version
i already knew how to determine an application version, but #StijnSanders suggested the "better" way, for exactly the reasons i was hitting:
I most strongly recommend not to use GetFileVersion when you want to know the version of the executable that is currently running! I have two pretty good reasons to do this:
The executable may be unaccessible (disconnected drive/share), or changed (.exe renamed to .bak and replaced by a new .exe without the running process being stopped).
The version data you're trying to read has actually already been loaded into memory, and is available to you by loading this resource, which is always better than to perform extra (relatively slow) disk operations.
Which i adapted into:
function GetModuleVersion(Instance: THandle; out iMajor, iMinor, iRelease, iBuild: Integer): Boolean;
var
fileInformation: PVSFIXEDFILEINFO;
verlen: Cardinal;
rs: TResourceStream;
m: TMemoryStream;
resource: HRSRC;
begin
//You said zero, but you mean "us"
if Instance = 0 then
Instance := HInstance;
//UPDATE: Workaround bug in Delphi if resource doesn't exist
resource := FindResource(Instance, 1, RT_VERSION);
if resource = 0 then
begin
iMajor := 0;
iMinor := 0;
iRelease := 0;
iBuild := 0;
Result := False;
Exit;
end;
m := TMemoryStream.Create;
try
rs := TResourceStream.CreateFromID(Instance, 1, RT_VERSION);
try
m.CopyFrom(rs, rs.Size);
finally
rs.Free;
end;
m.Position:=0;
if not VerQueryValue(m.Memory, '\', (*var*)Pointer(fileInformation), (*var*)verlen) then
begin
iMajor := 0;
iMinor := 0;
iRelease := 0;
iBuild := 0;
Exit;
end;
iMajor := fileInformation.dwFileVersionMS shr 16;
iMinor := fileInformation.dwFileVersionMS and $FFFF;
iRelease := fileInformation.dwFileVersionLS shr 16;
iBuild := fileInformation.dwFileVersionLS and $FFFF;
finally
m.Free;
end;
Result := True;
end;
Warning: The above code crashes sometimes due to a bug in Delphi:
rs := TResourceStream.CreateFromID(Instance, 1, RT_VERSION);
If there is no version information, Delphi tries to raise an exception:
procedure TResourceStream.Initialize(Instance: THandle; Name, ResType: PChar);
procedure Error;
begin
raise EResNotFound.CreateFmt(SResNotFound, [Name]);
end;
begin
HResInfo := FindResource(Instance, Name, ResType);
if HResInfo = 0 then Error;
...
end;
The bug, of course, is that PChar is not always a pointer to an ansi char. With non-named resources they are integer constants, cast to a PChar. In this case:
Name: PChar = PChar(1);
When Delphi tries to build the exception string, and dereferences the pointer 0x00000001 it triggers and access violation.
The fix is to manually call FindResource(Instance, 1, RT_VERSION) first:
var
...
resource: HRSRC;
begin
...
resource := FindResource(Instance, 1, RT_VERSION);
if (resource = 0)
begin
iMajor := 0;
iMinor := 0;
iRelease := 0;
iBuild := 0;
Result := False;
Exit;
end;
m := TMemoryStream.Create;
...
Note: Any code is released into the public domain. No attribution required.
You might want to try FindResource/LockResource API in order to access VERSIONINFO resource of your running module. MSDN article links an example in Examples section, and there is also a community comment with C++ sample code which does exactly this. This starts from already loaded module, not from file name (which is supposedly loaded separately with flag indicating "load resources only", and thus possibly ignoring the fact that image is already mapped into process).
Note that - per provided code snippet - you can find resource of your module and then reuse standard VerQueryValue API to continue resource parsing from there.
I suggest you to read the code JclFileUtils.pas, from JCL.
The "TJclFileVersionInfo" class has some overloaded constructors that allows work from file and from the module handle to get version information.
You can use the JCL class, or at least read it for check how it works in details.
Delphi 10.3.3 provides a reasonable method to read ProductVersion from running exe. Set this value in [Options] [ProductVersion] of project exe.
set variables as:
var
major, minor, build: Cardinal;
Version: String;
set code as:
// [GetProductVersion] uses 1st 3 values separated by decimal (.)
// as: major, minor, build. 4th optional parameter is ignored.
//
// Gui may display -- Major, Minor, Release, Build
// but reports as -- Major, Minor, Build
if GetProductVersion(Application.ExeName, major, minor, build) then
Version := 'v' + major.ToString() + '.' + minor.ToString()
+ ' Beta Build ' + build.ToString();

Resources