LPPICTUREDISP resource/memory leak

I am trying to create an ActiveX control that returns a DIB wrapped in an LPPICTUREDISP, but I am having memory problems.

I have a DIB loaded into memory and I am using CreateDIBitmap() to create a DDB that I construct a CBitmap from. The CBitmap is then wrapped into a CPicture and returned out of the ActiveX control. All is fine, I get the image correctly however I get a memory or resource leak! This obviously after a few images have been returned causes a serious problem. The "rogue" memory is allocated when CreateDIBitmap is called (the image is about 2Mb) and then never released. I'm sure it is because I am not calling DeleteObject(), but if I call DeleteObject() the data in the LPPICTUREDISP becomes invalid. How can I return the image correctly, and allow the container app to clean up the memory when it is finished with the image.

I'm sure this is because I am not calling DeleteObject() on the HBITMAP returned from CreateDIBitmap(), but if I do call DeleteObject() no image is returned. Where do I go from here?

Code snippet:

LPPICTUREDISP CActiveXMember::GetPicture(long index)
{
      if (index >= 0 && index < picture_list.GetSize())
      {
            CPictureHolder PicHolder;

            CBitmap* data_bitmap = CBitmap::FromHandle(picture_list[index]->GetTrueDDB());
            PicHolder.CreateFromBitmap(data_bitmap, NULL, TRUE);
            LPPICTUREDISP disp = PicHolder.GetPictureDispatch();

            return disp;
      }

      return NULL;
}

HBITMAP CBMPThumbNail::GetTrueDDB()
{
      int width, height, size, stored_size;

      ASSERT(index >= 0);
      ASSERT(index < cache_index.GetSize());

      DWORD file_pos = cache_index[index];
      DWORD stored_file_pos = cache.GetPosition();

      cache.Seek(file_pos, CFile::begin);

      cache.Read(&size, 4);
      cache.Read(&width, 4);
      cache.Read(&height, 4);
      
      // allocate some memory and read in the bits. Then these can be
      // returned to the calling function.
      //
      LPBYTE bits = (LPBYTE)GlobalAllocPtr(GHND, size);
      cache.Read(bits, size);

      // create a valid DDB ready to attach to an LPPICTUREDISPATCH
      // OLE object.
      //
      // set the info header to be the size of the actual image, and load
      // the image bits into this temporary pointer so that we can create
      // a bitmap handle to be returned.
      //

      info->bmiHeader.biWidth = width;
      info->bmiHeader.biHeight = height;
      stored_size = info->bmiHeader.biSizeImage;
      info->bmiHeader.biSizeImage = size;

      CClientDC dc(NULL);
      CPalette pal;
      CPalette* old_pal;
      HBITMAP bitmap;

      if (num_colours && dc.GetDeviceCaps(RASTERCAPS) & RC_PALETTE)
      {
            UINT size = sizeof(LOGPALETTE) + (sizeof(PALETTEENTRY) * num_colours);
            LOGPALETTE *log_palette = (LOGPALETTE *) new BYTE[size];

            log_palette->palVersion = 0x300;
            log_palette->palNumEntries = num_colours;

            for (int i = 0; i < num_colours; i++)
            {
                  log_palette->palPalEntry[i].peRed = info->bmiColors[i].rgbRed;
                  log_palette->palPalEntry[i].peGreen = info->bmiColors[i].rgbGreen;
                  log_palette->palPalEntry[i].peBlue = info->bmiColors[i].rgbBlue;
                  log_palette->palPalEntry[i].peFlags = 0;
            }

            pal.CreatePalette(log_palette);
            delete [] log_palette;

            old_pal = dc.SelectPalette(&pal, FALSE);
            dc.RealizePalette();
      }

      bitmap = CreateDIBitmap(dc.GetSafeHdc(),
                        (LPBITMAPINFOHEADER)info,
                        (LONG)CBM_INIT,
                        bits,
                        info,
                        DIB_RGB_COLORS);

      if (pal.GetSafeHandle())
      {
            dc.SelectPalette(old_pal,FALSE);
      }

      // restore the thumbnails width, height and image size.
      //
      info->bmiHeader.biWidth = THUMBNAIL_WIDTH;
      info->bmiHeader.biHeight = THUMBNAIL_HEIGHT;
      info->bmiHeader.biSizeImage = stored_size;

      // free the allocated bits.
      //
      GlobalFreePtr(bits);

      // restore the correct file position ready for adding the next
      // record.
      //
      cache.Seek(stored_file_pos, CFile::begin);

      return bitmap;
}
sdjAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

chensuCommented:
You should call picture_list[index]->GetTrueDDB() only once. What you can do is probably

1. In the CActiveXMember constructor or OnCreate, call picture_list[index]->GetTrueDDB() and save the handles to an array (a member of CActiveXMember).

2. In the CActiveXMember::GetPicture, retrieve the handle from the array.

3. In the CActiveXMember destructor or OnDestroy, call DeleteObject on each handle in the array.
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
sdjAuthor Commented:
Am I correct in thinking that I can call DeleteObject() after I have passed the LPPICTUREDISP back to the parent APP?

For example, in GetTrueDDB() I make the returned HBITMAP a member variable and if it is not NULL I call DeleteObject() before getting the new HBITMAP?

I have tried this and it seems to work, I am assuming that after the LPPICTUREDISP has been returned the parent app will manage the memory and I can then Delete my instance of it.
0
chensuCommented:
According to the documentation,

"If bTransferOwnership is TRUE, the caller should not use the bitmap or palette object in any way after this call returns. If bTransferOwnership is FALSE, the caller is responsible for ensuring that the bitmap and palette objects remain valid for the lifetime of the picture object."

You are using TRUE, in which case it seems that you even should not call DeleteObject on the bitmap. If you are using FALSE, you should call DeleteObject only after the picture object is destroyed.
0
sdjAuthor Commented:
It just seems that this is a bit shortsighted, and there should be some way of doing it.

Lets assume you have an ActiveX control that can return an LPPICTUREDISP. In VB you can do:

image1.Picture = activex1.GetPicture()

After this there is no way to know when the VB program will have finished with the image, and you won't want to hold up any memory as the image may well be several MB. In my case there is a whole list of images so I must free up the memory, in fact I cache all the images out to a file and load them into memory on demand.

The bases of my ActiveX control is it is a list of DIBs, the list displays a thumbnail and the actual data is only loaded when returning it to the container application.
0
chensuCommented:
One place to free the images is the destructor of the ActiveX control as I said before. Another way is to provide a method to free the images. It is up to the caller (the VB program) to call the method.
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
System Programming

From novice to tech pro — start learning today.