#1 2012-04-03 09:37:07

brk303
Member
Registered: 2012-02-09
Posts: 5

Application terminates

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

#2 2012-04-03 11:37:00

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,659
Website

Re: Application terminates

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

#3 2012-04-04 07:46:35

bero
Member
Registered: 2012-04-04
Posts: 6

Re: Application terminates

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 smile
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

ab wrote:

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.

Last edited by bero (2012-04-04 12:36:49)

Offline

#4 2012-04-04 11:44:18

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,659
Website

Re: Application terminates

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

#5 2012-04-05 06:10:09

bero
Member
Registered: 2012-04-04
Posts: 6

Re: Application terminates

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

#6 2012-04-05 07:25:36

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,659
Website

Re: Application terminates

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

#7 2012-04-05 20:04:20

bero
Member
Registered: 2012-04-04
Posts: 6

Re: Application terminates

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

#8 2012-04-06 05:44:06

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,659
Website

Re: Application terminates

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

#9 2012-04-06 12:01:19

bero
Member
Registered: 2012-04-04
Posts: 6

Re: Application terminates

Yep, it works fine smile
Thanks for good cooperation!

Regards
Roland Bengtsson
Team Attracs

ab wrote:

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

#10 2012-04-06 12:07:27

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,659
Website

Re: Application terminates

Thanks to you for your detailed issue report, and the time spent to clever debugging and logging content.
smile

Offline

#11 2012-04-06 12:17:17

bero
Member
Registered: 2012-04-04
Posts: 6

Re: Application terminates

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

#12 2012-04-06 12:27:59

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,659
Website

Re: Application terminates

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

#13 2012-04-06 18:22:05

bero
Member
Registered: 2012-04-04
Posts: 6

Re: Application terminates

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

ab wrote:

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

#14 2012-04-07 07:19:05

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,659
Website

Re: Application terminates

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

Board footer

Powered by FluxBB