You are not logged in.
Pages: 1
Hi,
We have a problem with application terminating (just disappearing) during PDF export. It is only happens when the app is run with Citrix and it is reproducable. However we can not run it under IDE debugger, but I have added some logging to few places in SynPDF.
The problem seems to be in EnumEMFFunc, specifically in the Case statement handling of EMR_STRETCHBLT.
So I guess it's got something to do with bitmaps. Do you have any ideas what could cause the problem, or any suggestions how to investigate further, or what to log that would be of use to you ?
We're using the latest version of SynPDF.
Regards,
Daniel
Offline
Did you use the latest version from trunk?
http://synopse.info/fossil
The upcoming 1.16 revision have some enhancements to improve consistency.
The case statement implementation sounds pretty valid.
It will just ignore any other pattern than PATCOPY...
Logging the whole PEMRStretchBlt(R)^ content would make sense, anyway.
Offline
Hi, I am working with the same application as Daniel. I have found more about the exact sourceline why it silently shut down the whole client when running in Citrix Terminal session from Citrix desktop.
We use version 1.16 of SynPDF. PUREPASCAL is defined.
So I try to describe what happens as exact as possible what happens.
Line 7491 in synpdf.pas
E.DrawBitmap(xSrc,ySrc,cxSrc,cySrc, xDest,yDest,cxDest,cyDest,iUsageSrc,
pointer(cardinal(R)+offBmiSrc),pointer(cardinal(R)+offBitsSrc));
calls TPdfEnum.DrawBitmap. That procedure calls
Doc.CreateOrGetImage(B,@Box)
on line 7605.
In TPdfDocument.CreateOrGetImage there is a if statement on line 5047
if B.Palette<>0 then begin
so if this is true then it calls
DoHash(pointer(Pals),GetPaletteEntries(B.Palette,0,256,Pals)*sizeof(TPaletteEntry))
and the whole process is shut down silently.
But when it works the statement is false and DoHash is never called.
This is when application is started outside Citrix Desktop.
I have added more logs to the code and comment out the is the lines
// if B.Palette<>0 then
// begin
// SetLength(Pals,256);
// DoHash(pointer(Pals),GetPaletteEntries(B.Palette,0,256,Pals)*sizeof(TPaletteEntry));
// end;
And it works
But I'm not sure in what cases this is really needed.
Hopefully this is enough information to get it working in all cases.
Regards
Roland Bengtsson
Team Attracs
EDIT:
So I try to investigate why DoHash fails.
I found this comment.
- fixed a potential GPF issue in function HashOf() in PUREPASCAL mode (used
to reuse any existing bitmap content within the PDF document)
So there is a risk of General Protection Fault when using PUREPASCAL mode ?
I have add more logs to the code to isolate the problem.
This code in HashOf method generate general Protection Fault in the loops first element (i=0).
Result := ((Result shl 2) or (Result shr (SizeOf(Result)*8-2))) xor P[i];
My guess is that the array P is somehow broken in the previous method Hash32.
Currently I set ForceNoBitmapReuse := True as a workaround for this problem
Did you use the latest version from trunk?
http://synopse.info/fossilThe upcoming 1.16 revision have some enhancements to improve consistency.
The case statement implementation sounds pretty valid.
It will just ignore any other pattern than PATCOPY...Logging the whole PEMRStretchBlt(R)^ content would make sense, anyway.
Last edited by bero (2012-04-04 12:36:49)
Offline
Did the following modification work as expected?
See http://synopse.info/fossil/info/6dc3bbc645
The hash issue has been fixed in trunk.
In all cases, you shall better use the latest version from trunk, from http://synopse.info
Offline
Hi!
I downloaded and tested the latest trunk, still the same error.
I also added some logs to try to narrow the error for you.
This code:
function TPdfDocument.CreateOrGetImage(B: TBitmap; DrawAt: PPdfBox): PDFString;
var J: TJpegImage;
Img: TPdfImage;
Hash: TPdfImageHash;
y,w,h,row: integer;
nPals: cardinal;
Pals: array of TPaletteEntry;
const PERROW: array[TPixelFormat] of byte = (0,1,4,8,15,16,24,32,0);
procedure DoHash(bits: pointer; size: Integer);
begin // "4 algorithms to rule them all"
Hash[0] := Hash[0] xor Hash32(bits,size);
Hash[1] := Hash[1] xor HashOf(bits,size);
Hash[2] := crc32(Hash[2],bits,size);
Hash[3] := adler32(Hash[3],bits,size);
end;
begin
result := '';
if (self=nil) or (B=nil) then exit;
w := B.Width;
h := B.Height;
fillchar(Hash,sizeof(Hash),0);
if not ForceNoBitmapReuse then begin
row := PERROW[B.PixelFormat];
if row=0 then begin
B.PixelFormat := pf24bit;
row := 24;
end;
fillchar(Hash,sizeof(Hash),row);
TraceLog.Trace('Start');
if B.Palette<>0 then begin
nPals := 0;
TraceLog.Trace('GetObject');
if (GetObject(B.Palette,sizeof(nPals),@nPals)<>0) and (nPals>0) then begin
SetLength(Pals,nPals);
TraceLog.Trace('GetPalette');
if GetPaletteEntries(B.Palette,0,nPals,Pals)=nPals then
begin
TraceLog.Trace('DoHash');
DoHash(pointer(Pals),nPals*sizeof(TPaletteEntry));
end;
end;
end;
TraceLog.Trace('Scanline 1');
row := BytesPerScanline(w,row,32);
TraceLog.Trace('Scanline 2');
for y := 0 to h-1 do
begin
TraceLog.Trace('DoHash 1 ' + IntToStr(y));
DoHash(B.ScanLine[y],row);
TraceLog.Trace('DoHash 2 ' + IntToStr(y));
end;
TraceLog.Trace('GetXObject1');
result := GetXObjectImageName(Hash,w,h);
TraceLog.Trace('GetXObject2');
end;
if result='' then begin
// create new if no existing TPdfImage match
if ForceJPEGCompression=0 then
Img := TPdfImage.Create(Canvas.fDoc,B,True) else begin
J := TJpegImage.Create;
try
J.Assign(B);
Img := TPdfImage.Create(Canvas.fDoc,J,False);
finally
J.Free;
end;
end;
Img.fHash := Hash;
result := 'SynImg'+PDFString(IntToStr(FXObjectList.ItemCount));
if ForceJPEGCompression=0 then
AddXObject(result,Img) else
RegisterXObject(Img, result);
end;
// draw bitmap as XObject
if DrawAt<>nil then
with DrawAt^ do
Canvas.DrawXObject(Left,Top,Width,Height,result);
end;
Produced this output in the log
20120405 09:04:50 (6600) Start [SynPdf.TPdfDocument.CreateOrGetImage (SynPdf.pas:5054)]
20120405 09:04:50 (6600) GetObject [SynPdf.TPdfDocument.CreateOrGetImage (SynPdf.pas:5057)]
20120405 09:04:50 (6600) GetPalette [SynPdf.TPdfDocument.CreateOrGetImage (SynPdf.pas:5060)]
20120405 09:04:50 (6600) Scanline 1 [SynPdf.TPdfDocument.CreateOrGetImage (SynPdf.pas:5068)]
20120405 09:04:50 (6600) Scanline 2 [SynPdf.TPdfDocument.CreateOrGetImage (SynPdf.pas:5070)]
20120405 09:04:50 (6600) DoHash 1 0 [SynPdf.TPdfDocument.CreateOrGetImage (SynPdf.pas:5073)]
So it seems to fail for line
DoHash(B.ScanLine[y],row);
Note that this code is never executed
TraceLog.Trace('DoHash');
DoHash(pointer(Pals),nPals*sizeof(TPaletteEntry));
Regards
Roland Bengtsson
Last edited by bero (2012-04-05 06:11:53)
Offline
Can you check if you set ForceNoBitmapReuse=true, you do not have the issue?
How is supplied the bitmap?
What is the PixelFormat value? h,w,row values?
You may save the bitmap to a file, then try to guess if its format is OK, or at least try to reduce the scope of the error to just simple tests to reproduce.
The ScanLine[] use sounds quite regular here, don't you think?
I do not get why it does fail.
Offline
Well, normally I have set
fPdfDocument.ForceNoBitmapReuse := True;
as a workaround. But for this test that code is commented out.
Here is the same method again with some additional logging.
function TPdfDocument.CreateOrGetImage(B: TBitmap; DrawAt: PPdfBox): PDFString;
var J: TJpegImage;
Img: TPdfImage;
Hash: TPdfImageHash;
y,w,h,row: integer;
nPals: cardinal;
Pals: array of TPaletteEntry;
const PERROW: array[TPixelFormat] of byte = (0,1,4,8,15,16,24,32,0);
procedure DoHash(bits: pointer; size: Integer);
begin // "4 algorithms to rule them all"
Hash[0] := Hash[0] xor Hash32(bits,size);
Hash[1] := Hash[1] xor HashOf(bits,size);
Hash[2] := crc32(Hash[2],bits,size);
Hash[3] := adler32(Hash[3],bits,size);
end;
begin
result := '';
if (self=nil) or (B=nil) then exit;
w := B.Width;
h := B.Height;
TraceLog.Trace(Format('Start %d %d', [w, h]));
fillchar(Hash,sizeof(Hash),0);
if not ForceNoBitmapReuse then begin
row := PERROW[B.PixelFormat];
if row=0 then begin
B.PixelFormat := pf24bit;
row := 24;
end;
TraceLog.Trace(Format('row %d %d', [row,w]));
fillchar(Hash,sizeof(Hash),row);
if B.Palette<>0 then begin
nPals := 0;
TraceLog.Trace(Format('GetObject %d', [w]));
if (GetObject(B.Palette,sizeof(nPals),@nPals)<>0) and (nPals>0) then begin
SetLength(Pals,nPals);
TraceLog.Trace(Format('GetPalette %d', [w]));
if GetPaletteEntries(B.Palette,0,nPals,Pals)=nPals then
begin
TraceLog.Trace(Format('DoHash', []));
DoHash(pointer(Pals),nPals*sizeof(TPaletteEntry));
end;
end;
end;
TraceLog.Trace(Format('Scanline1 %d %d', [w, row]));
row := BytesPerScanline(w,row,32);
TraceLog.Trace(Format('Scanline2 %d %d', [w, row]));
for y := 0 to h-1 do
begin
TraceLog.Trace('DoHash 1 ' + IntToStr(y));
DoHash(B.ScanLine[y],row);
TraceLog.Trace('DoHash 2 ' + IntToStr(y));
end;
TraceLog.Trace(Format('GetXObject1 %d %d', [w, h]));
result := GetXObjectImageName(Hash,w,h);
TraceLog.Trace(Format('GetXObject2 %d %d', [w, h]));
end;
if result='' then begin
// create new if no existing TPdfImage match
if ForceJPEGCompression=0 then
Img := TPdfImage.Create(Canvas.fDoc,B,True) else begin
J := TJpegImage.Create;
try
J.Assign(B);
Img := TPdfImage.Create(Canvas.fDoc,J,False);
finally
J.Free;
end;
end;
Img.fHash := Hash;
result := 'SynImg'+PDFString(IntToStr(FXObjectList.ItemCount));
if ForceJPEGCompression=0 then
AddXObject(result,Img) else
RegisterXObject(Img, result);
end;
// draw bitmap as XObject
if DrawAt<>nil then
with DrawAt^ do
Canvas.DrawXObject(Left,Top,Width,Height,result);
end;
And the output in the log.
20120405 22:58:27 (7800) Start 674 155 [SynPdf.TPdfDocument.CreateOrGetImage (SynPdf.pas:5047)]
20120405 22:58:27 (7800) row 24 674 [SynPdf.TPdfDocument.CreateOrGetImage (SynPdf.pas:5055)]
20120405 22:58:27 (7800) GetObject 674 [SynPdf.TPdfDocument.CreateOrGetImage (SynPdf.pas:5059)]
20120405 22:58:27 (7800) GetPalette 674 [SynPdf.TPdfDocument.CreateOrGetImage (SynPdf.pas:5062)]
20120405 22:58:27 (7800) Scanline1 8388608 32768 [SynPdf.TPdfDocument.CreateOrGetImage (SynPdf.pas:5070)]
20120405 22:58:27 (7800) Scanline2 8388608 0 [SynPdf.TPdfDocument.CreateOrGetImage (SynPdf.pas:5072)]
20120405 22:58:27 (7800) DoHash 1 0 [SynPdf.TPdfDocument.CreateOrGetImage (SynPdf.pas:5075)]
So it seems that w is trashed somehow at the call to GetPaletteEntries.
Hope this information help you.
You said:
The ScanLine[] use sounds quite regular here, don't you think?
I don't understand what you mean here.
Regards
Roland Bengtsson
Team Attracs
Offline
If w was overriden, this was a stack corruption issue.
I think it should be fixed by now.
See http://synopse.info/fossil/info/3ca7c5c515
Offline
Yep, it works fine
Thanks for good cooperation!
Regards
Roland Bengtsson
Team Attracs
If w was overriden, this was a stack corruption issue.
I think it should be fixed by now.
See http://synopse.info/fossil/info/3ca7c5c515
Offline
Just one further note.
What is the reason to eat an exception here in synpdf.pas ?
function TPdfDocument.SaveToFile(const aFileName: TFileName): boolean;
var FS: TFileStream;
begin
try
FS := TFileStream.Create(aFileName,fmCreate);
try
SaveToStream(FS);
result := true;
finally
FS.Free;
end;
except
on E: Exception do // error on file creation (opened in reader?)
result := false;
end;
end;
I think it could be changed to this ?
function TPdfDocument.SaveToFile(const aFileName: TFileName): boolean;
var FS: TFileStream;
begin
FS := TFileStream.Create(aFileName,fmCreate);
try
SaveToStream(FS);
result := true;
finally
FS.Free;
end;
end;
Regards
Roland
Offline
By design, the function is expected to return TRUE on success, and FALSE in case of error.
In this case, any error will be an exception. So exceptions have to be catched, and return false in this case.
Offline
In my tests of Result variable is initialized with Boolean=False, Integer=0, String='', Object=nil etc.
But I have not found any official reference about this.
But if my tests is false then it means that i always must start all functions with defining Result, because an exception can happen anywhere in the code.
And that is not the case in mu code at least.
Regards
Roland
By design, the function is expected to return TRUE on success, and FALSE in case of error.
In this case, any error will be an exception. So exceptions have to be catched, and return false in this case.
Last edited by bero (2012-04-06 18:23:29)
Offline
The Delphi compiler never initialize the result variable: its content is undetermined.
There is even an explicit compiler warning when you forget to set a result variable in a function.
The official documentation of this method states the boolean result meaning:
/// save the PDF file content into a specified file
// - return FALSE on any writing error (e.g. if the file is opened in the
// Acrobar Reader)
function SaveToFile(const aFileName: TFileName): boolean;
Offline
Pages: 1