How to prevent Embarcadero C++Builder changing the bitmap code within the DFM all the time?

129 Views Asked by At

I have to work with C++Builder. Depending on the mood, the IDE changes the hex-encoded bitmap property within the DFM file. Even if I have not changed anything within the GUI.

I'm using GIT and all the time this will result in a diff of the .dfm (which is ugly).

image

It is a VCL project with some Forms. The Bitmap value which will change randomly is from a TImageList.

Is there a way to prevent that the IDE will change the DFM all the time?

1

There are 1 best solutions below

0
Remy Lebeau On

This is a known issue.

Originally reported in XE, and closed as "Wont Do" (archived link):

QC 92769: bitmap data of TImageList in dfm changes everytime the form is viewed as text (alt-F12)

Then later, reported again in XE8, and closed as fixed in 10.3, at least for VCL:

RSP-13666: Bitmap data of TImageList changes constantly

So, to answer your question - make sure you are using C++Builder 10.3 or later, as the problem has already been corrected.

At least for VCL, anyway. There is a similar report still open for FMX:

RSP-11259: Every FMX form save changes binary image data in FMX


Some background info:

Internally, the Bitmap property is streamed through the DFM system using the TFiler::DefineBinaryProperty() method. This causes the DFM system to use an internal TMemoryStream for reading/writing the Bitmap data from/into the DFM.

TImageList does not actually hold a bitmap image, it holds a Win32 ImageList control as an HIMAGELIST handle (stored in the TImageList::Handle property). The Win32 API deals with the actual bitmap internally.

When writing to the DFM, TImageList wraps the TMemoryStream with a TStreamAdapter to get a Win32 IStream interface, and then calls the Win32 ImageList_Write/Ex() function to write the HIMAGELIST's data into the TMemoryStream (in a pre-ComCtrl32v6 format), and then the bytes in the TMemoryStream are written into the DFM. The bytes 49 4C at the beginning of the Bitmap data are an identifier for the Win32 API's IL stream format, and the bytes 42 4D are an identifier for the actual bitmap image that the HIMAGELIST holds.

In your screenshot, there is only 1 byte being changed (in other past reports, there were more bytes being changed). According to this WineHQ code (and various other 3rd party repos that I found), that changed byte belongs to the ILHEAD::cGrow field of the HIMAGELIST's stream header:

The number of images by which the image list can grow when the system needs to make room for new images. This parameter represents the number of new images that the resized image list can contain.

Which corresponds to the TImageList::AllocBy property:

Sets the number of images by which the image list grows when it needs to make room for new images.

When TImageList is writing its HIMAGELIST to the DFM, it first lets Windows write the HIMAGELIST data to the IStream however it wants. Then, sometime between XE4 and 10.3 (I don't have sources to pinpoint the exact version), new logic was added to have TImageList then go back and overwrite the cGrow value with its own AllocBy value instead. This was added to address this issue mentioned in RSP-13666:

In the final comments of the previous issue, you noted that the root cause of the issue is that the ImageList_WriteEx API seems to update an undocumented counter. This agrees with my own investigation of the issue.

In that "previous issue" (QC #92769), it says:

Wine writes an _ILHEAD struct, and the offending bytes from that struct that change are WORD cMaxImage, and WORD cGrow. These correspond to the number of images currently allocated in the list, and theoretically, how much the list will grow by once you add more than that amount. Each time the image list gets streamed, it seems to increase cGrow by 4, which makes me think that maybe Windows isn't actually using that field.

Which is exactly what you are seeing happen - the earlier cGrow value of 78 02 (632) being replaced with a new value of 7C 02 (636).

The QC ticket also says:

It seems that tweaking the stream after the fact and changing it to 0 doesn't immediately break it. But like we said that seems risky

And:

  • Unfortunately the issue that is reported is caused by native Win32 ImageList control, exactly found in the method ImageList_WriteEx(), that writes an image list to a stream. It is not a Rad Studio bug then its solution is not a simple fixing.

  • There is a reported bug (ID: 428868) to Microsoft related to this problem but in VS

And, as noted above, TImageList does use ImageList_WriteEx() when writing to a DFM stream. And Embarcadero eventually decided to take the risk and patch the stream data that ImageList_WriteEx() writes.