ImageEn for Delphi and C++ Builder ImageEn for Delphi and C++ Builder

 

ImageEn Forum
Profile    Join    Active Topics    Forum FAQ    Search this forumSearch
Forum membership is Free!  Click Join to sign-up
Username:
Password:
Save Password
Forgot your Password?

 All Forums
 ImageEn Library for Delphi, C++ and .Net
 ImageEn and IEvolution Support Forum
 SetExternalBitmap leaves invalid IEBitmap.fOwner
 New Topic  Reply to Topic
Author Previous Topic Topic Next Topic  

timfa

USA
13 Posts

Posted - Mar 10 2025 :  17:42:14  Show Profile  Reply
Hello,
I have a TForm subclass named aForm that has a TIEBitmap instance variable named FRenderImage. In aForm I launch another Form, ImgForm, that has a TImageEnView named ImgEdit. When I create ImgForm I link FRenderImage to ImgForm using ImgForm.ImgEdit.SetExternalBitmap (FRenderImage). Optionally, I create another associated Form, aForm2 which also has an TImageEnView also named ImgEdit and I link the image to aForm2 using aForm2.ImgEdit.SetExternalBitmap (FRenderImage). This all works well. The image is updated in ImgForm and/or aForm2 as I programmatically change it in aForm. However, when I close either or both ImgForm and/or aForm2, and then subsequently change the image in FRenderImage, an exception is thrown. I tracked this issue down to the FRenderImage.fOwner still being assigned a value, e.g., the ImgEdit from ImgForm or aForm2. Calling aForm2.ImgEdit.SetExternalBitmap (Nil) or ImgForm.ImgEdit.SetExternalBitmap (Nil) doesn’t correct the problem since SetExternalBitmap doesn’t change FRenderImage.fOwner to Nil. I figured out a workaround for this issue as follows:

// Copied from imageenview.pas
type TIEBitmap_ = class(TIEBitmap);  // Access to Protected

procedure TMyForm.ImageWindowClose (Sender: TObject;
                                                var Action: TCloseAction);
begin
   if (Assigned(ImgForm)) And (Sender = ImgForm) then
   begin
      ImgForm.ImgEdit.SetExternalBitmap(nil);
      if TIEBitmap_(FRenderImage).fOwner = ImgForm.ImgEdit then
         TIEBitmap_(FRenderImage).fOwner := Nil;
      ImgForm:= nil;
      Action := caFree;
   end
   else if (Assigned(aForm2)) And (Sender = aForm2) then
   begin
      aForm2.ImgEdit.SetExternalBitmap(nil);
      if TIEBitmap_(FRenderImage).fOwner = aForm2.ImgEdit then
         TIEBitmap_(FRenderImage).fOwner := Nil;
      aForm2:= nil;
      Action := caFree;
   end;
end;
I don’t like this workaround because at some point you are going to change something and my code will break. 
You might consider setting fOwner to Nil when a Nil is passed to SetExternalBitmap as follows:
procedure TImageEnView.SetExternalBitmap(bmp: TIEBitmap);
begin
…
    else
    begin
      TIEImageLayer_( CurrentLayer as TIEImageLayer ).fFreeBitmapOnDestroy := true;
      fIEBitmap := TIEBitmap.Create;
// Next three lines are the fix…
if Not Assigned(bmp) then
   TIEBitmap_(fIEBitmap).fOwner := Nil
else
      TIEBitmap_(fIEBitmap).fOwner := Self;
      {$IFDEF IELegacyBitmapSupport}
      fBitmap := nil;
      {$ENDIF}

      // Assign new fIEBitmap to current layer
      SyncBitmapToCurrentLayer();
    end;

Without my workaround or the above change, as I recall, my code threw an invalid pointer exception in procedure TIEBitmap.UpdateOwner when fOwner is pointing to an invalid (freed) TImageEnView object.

Another thought is changing fOwner to a list of TImageEnView objects such that TIEBitmap.UpdateOwner can notify multiple TImageEnView objects of changes. You would still have to remove the TImageEnView object when bmp is Nil.

Final comment: Consider adding a note to the description of TImageEnView.SetExternalBitmap that you have to call the procedure with a Nil argument to detach your TImageEnView object from the bitmap.

I hope my description was clear enough to identify the issue I encountered.


Tim F

xequte

38875 Posts

Posted - Mar 10 2025 :  19:29:13  Show Profile  Reply
Hi Tim

Can you confirm what version of ImageEn you are using? This should already be happening in v13.7.0.

Nigel
Xequte Software
www.imageen.com
Go to Top of Page

timfa

USA
13 Posts

Posted - Mar 11 2025 :  08:04:19  Show Profile  Reply
I'm using v13.7.0. From the top of imageenview.pas: ImageEn Build 13.7.0.27.3172

Tim F
Go to Top of Page

xequte

38875 Posts

Posted - Mar 11 2025 :  22:33:54  Show Profile  Reply
Mmm, that's unexpected.

Can you confirm that after your call to:

Does this code give you any errors?

aForm2.ImgEdit.SetExternalBitmap( FRenderImage );
if TIEBitmap_(FRenderImage).fOwner = nil then
  raise Exception.Create( 'Owner not assigned' );
aForm2.ImgEdit.SetExternalBitmap(nil);
if TIEBitmap_(FRenderImage).fOwner <> nil then
  raise Exception.Create( 'Owner not reset' );


BTW, are you sure you are freeing aForm2 correctly? Your code just has aForm2 := nil.


Nigel
Xequte Software
www.imageen.com
Go to Top of Page

timfa

USA
13 Posts

Posted - Mar 12 2025 :  09:02:43  Show Profile  Reply
When you set Action to caFree, that causes the form to be freed.
From Embarcadero's Help: caFree -- The form is closed and all allocated memory for the form is freed.

My actual code is a lot more complicated then the code I originally posted:

procedure TOverlayImageSourceFrame.ImageWindowClose (Sender: TObject;
                                                var Action: TCloseAction);
begin
   if (Assigned(FImageWindow)) And (Sender = FImageWindow) then
   begin
      FImageWindow.ImgEdit.SetExternalBitmap(nil);

      if TIEBitmap_(FFrameImage).fOwner = FImageWindow.ImgEdit then
         TIEBitmap_(FFrameImage).fOwner := Nil;

      if TIEBitmap_(FOverlayImage).fOwner = FImageWindow.ImgEdit then
         TIEBitmap_(FOverlayImage).fOwner := Nil;

      if TIEBitmap_(FRenderImage).fOwner = FImageWindow.ImgEdit then
         TIEBitmap_(FRenderImage).fOwner := Nil;
      FImageWindow := nil;

      Action := caFree;
   end
   else if (Assigned(FRenderWindow)) And (Sender = FRenderWindow) then
   begin
      FRenderWindow.ImgEdit.SetExternalBitmap(nil);
      if TIEBitmap_(FRenderImage).fOwner = FRenderWindow.ImgEdit then
         TIEBitmap_(FRenderImage).fOwner := Nil;
      FRenderWindow := nil;

      Action := caFree;
   end;
end;

When I run my app I hit everyone of the IF cases depending on the window that is closed and the image that is displayed in it.

The issue is in procedure TImageEnView.SetExternalBitmap(bmp: TIEBitmap). When bmp is Nil, your code reaches this section of the procedure:

    else
    begin
      TIEImageLayer_( CurrentLayer as TIEImageLayer ).fFreeBitmapOnDestroy := true;
      fIEBitmap := TIEBitmap.Create;
      TIEBitmap_(fIEBitmap).fOwner := Self;
      {$IFDEF IELegacyBitmapSupport}
      fBitmap := nil;
      {$ENDIF}

      // Assign new fIEBitmap to current layer
      SyncBitmapToCurrentLayer();
    end;

Notice that you always set fOwner to Self. You shouldn't do that when bmp is Nil, instead fOwner should be set to Nil to unlink the TImageEnView.


Tim F
Go to Top of Page

xequte

38875 Posts

Posted - Mar 12 2025 :  19:32:33  Show Profile  Reply
Hi Tim

> When you set Action to caFree, that causes the form to be freed.

Sorry, I had assumed your ImageWindowClose event was an event from your main form, not called by your child forms.


> Notice that you always set fOwner to Self. You shouldn't do that when bmp is Nil, instead fOwner should be set to Nil to unlink the TImageEnView.

Yes, that's because for TIEBitmap_(fIEBitmap).fOwner := Self; the fIEBitmap is now created and owned by the TImageEnView, it is not the original bmp passed to SetExternalBitmap().

The code prior to that sets fOwner to nil for the original bmp: TIEBitmap_(bmp).fOwner := Self;

Note, however, there is an issue in SetExternalBitmap() if nil is passed to it multiple times. The fix in the current beta is:

procedure TImageEnView.SetExternalBitmap(bmp: TIEBitmap);
begin
  WaitForImageReady();
  if fLayersCurrent > -1 then
  begin
    // Ensure an image layer is active
    if Layers[ fLayersCurrent ].Kind <> ielkImage then
      SetLayersCurrent( 0 );  // Base layer always image

    TIEBitmap_(fIEBitmap).fOwner := nil;
    if bmp <> nil then
    begin
      if TIEImageLayer_( CurrentLayer as TIEImageLayer ).fFreeBitmapOnDestroy then
      begin
        {$IFDEF IELegacyBitmapSupport}
        if fIEBitmap.EncapsulatedFromTBitmap then
          FreeAndNil(fBitmap);
        {$ENDIF}
        FreeAndNil(fIEBitmap);
      end;

      TIEBitmap_(bmp).fOwner := Self;
      TIEImageLayer( CurrentLayer ).SetBitmapEx( bmp, False );
      TIEImageLayer_( CurrentLayer as TIEImageLayer ).fFreeBitmapOnDestroy := false;

      fIEBitmap := TIEImageLayer( CurrentLayer ).Bitmap;
      {$IFDEF IELegacyBitmapSupport}
      if fIEBitmap.EncapsulatedFromTBitmap then
        fBitmap := fIEBitmap.VclBitmap
      else
        fBitmap := nil;
      {$ENDIF}
      CallBitmapChangeEvents();
      Update();
    end
    else
    begin
      if TIEImageLayer_( CurrentLayer as TIEImageLayer ).fFreeBitmapOnDestroy then
        FreeAndNil(fIEBitmap);

      TIEImageLayer_( CurrentLayer as TIEImageLayer ).fFreeBitmapOnDestroy := true;
      fIEBitmap := TIEBitmap.Create();
      TIEBitmap_(fIEBitmap).fOwner := Self;
      {$IFDEF IELegacyBitmapSupport}
      fBitmap := nil;
      {$ENDIF}

      // Assign new fIEBitmap to current layer
      SyncBitmapToCurrentLayer();
    end;
  end;
end;


Can you create a very simple demo that reproduces the issue so I can confirm it here?

Nigel
Xequte Software
www.imageen.com
Go to Top of Page

timfa

USA
13 Posts

Posted - Mar 12 2025 :  20:35:54  Show Profile  Reply
Sorry for the confusion on my part. I wrote this issue up a week or so after I came up with my workaround. My problem is that more than one IEBitmap object is potentially referencing one or more layers of the TImageEnView object in the closing window but only one of the bitmaps is having its owner updated to Nil and/or I have more than one window referencing the same external bitmap. I guess you would have to cycle through all layers when a TImageEnView object is being freed to make sure it isn't linked to an external bitmap to adequately deal with this issue. For now I am just going to call this issue dealt with via my work around and worry about it again when an update breaks my code.

Thanks for you assistance.




Tim F
Go to Top of Page
  Previous Topic Topic Next Topic  
 New Topic  Reply to Topic
Jump To: