Icon.Save(stream) each time returns different results
See original GitHub issueDescription
We use the following code to compare two icons:
private static bool IconEquals(Icon icon1, Icon icon2) {
var stream1 = new System.IO.MemoryStream();
icon1.Save(stream1);
var stream2 = new System.IO.MemoryStream();
icon2.Save(stream2);
return System.Linq.Enumerable.SequenceEqual(stream1.ToArray(), stream2.ToArray());
}
With the net6.0 release the stream.ToArray() method each time returns different results for the same Icon. You can modify the IconEquals as follows and it will return false:
private static bool IconEquals(Icon icon1) {
var stream1 = new System.IO.MemoryStream();
icon1.Save(stream1);
var stream2 = new System.IO.MemoryStream();
icon1.Save(stream2);
return System.Linq.Enumerable.SequenceEqual(stream1.ToArray(), stream2.ToArray());
}
Reproduction Steps
Launch the following code with any Icon:
private static bool IconEquals(Icon icon1) {
var stream1 = new System.IO.MemoryStream();
icon1.Save(stream1);
var stream2 = new System.IO.MemoryStream();
icon1.Save(stream2);
return System.Linq.Enumerable.SequenceEqual(stream1.ToArray(), stream2.ToArray());
}
Expected behavior
The IconEquals method should return true.
Actual behavior
The IconEquals method returns false.
Regression?
Works on net5.0
Known Workarounds
No response
Configuration
Windows 10
Other information
No response
Issue Analytics
- State:
- Created 2 years ago
- Comments:13 (12 by maintainers)
Top Results From Across the Web
c# - Why is Image.Save(Stream, ImageFormat) throwing an ...
Just convert image to icon: Icon myIcon = Icon.FromHandle(((Bitmap)myImage).GetHicon()). and then save it using stream: myIcon.Save(myStream);.
Read more >The Difference Between stream().forEach() and ...
stream().forEach() and Collection.forEach(). In most cases, both will yield the same results, but we'll look at some subtle differences.
Read more >Save icon, is the floppy disk icon dead?
No. They will draw a steamie - just about every time. It is the archetype of its class. The same goes for icons....
Read more >Java Stream collect() Method Examples
If the stream source is unordered such as Set, then the collect() method can produce different results in each invocation.
Read more >Parallel vs Sequential Stream in Java
Using parallel streams, our code gets divide into multiple streams which can be executed parallelly on separate cores of the system and the ......
Read more >
Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free
Top Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found

OK, I did some investigating. It turns out this was caused by converting to COMWrappers. But not in the way you’d think, there isn’t a mistake in the code. Instead, as far as I can tell, this worked before completely by chance.
Background
As I stated above, the only difference between the two
<html> <body>byte[]s is byte index 9. I’m not 100% sure where the definitive documentation for the ICO file format is, but reading https://en.wikipedia.org/wiki/ICO_(file_format)#Outline, it says that byte index 9 is:and
Note 2says: " Although Microsoft’s technical documentation states that this value must be zero, the icon encoder built into .NET (System.Drawing.Icon.Save) sets this value to 255. It appears that the operating system ignores this value altogether."So that byte is “reserved” and basically isn’t used.
The problem
The issue is the data being passed into our code from Windows. Looking at the code for IPicture::SaveAsFile that we are calling into, I see it is using uninitialized stack data and writing that into the stream. Basically, it looks something like this:
And in the code, it is creating one of these
PictureDescs on the stack and not initializing theBYTE reserved;field. This means whatever happens to be on that stack in that location is what is getting written to the stream.Why did the COMWrappers change cause this?
Here I can only speculate, but my understanding is that before the COMWrappers change, the data on the stack in this exact spot is always consistent. It is still “garbage”, but it is always the “same garbage” when calling these methods back-to-back. However, if you would write these two streams out to disk between 2 different processes, even without the COMWrapper change, the values can be different in this 9th byte.
I tried the above code on .NET Framework 4.8, and it also fails with the 9th byte being different.
I was able to prove that the stack is the problem locally by changing this code to look like:
Which fixes the problem - the reserved field is always zero’d out with that change.
However, if I change that code to zero the stack out before calling
Marshal.QueryInterface:The problem is still there. So something in
Marshal.QueryInterfaceis changing the stack in such a way that the first time this is called, that one slot in the stack is getting different data than the next few times it is called..NET Framework
I took the above program and ran it on .NET Framework 4.8, and it also fails for the first call, on index 9 (if you run it without a debugger attached. For some reason attaching a debugger doesn’t make it happen.)
Potential solutions
Clearing the stack, as done above, is a hack just for investigation. I wouldn’t commit that into System.Drawing.Common. Instead, I can think of the following solutions:
reserved = 0.SaveAsFilein Windows, and explicitly set this index to0ourselves for each image in the file.For sure I’m going to write an issue for (1) to be fixed. It will just take some time, and a Windows update to get it fixed. Solution (2) would mean anyone who is comparing these streams would need to make that change. To really fix this issue, I think we should do (3).
I’ve logged Windows bug https://microsoft.visualstudio.com/OS/_workitems/edit/37327236 for (1) above.