Earlier this year, I ported Paint.NET to Mono. At the time, we used the port to help us identify performance bottlenecks, missing functionality and bugs in our code. It turned out to be a fantastic test case for Windows.Forms.
The port was never released because the port is botched. In general the port went fine, but I made a few mistakes early on which lead to the half-done Paint.NET port.
Broken Port of Paint.NET 1.x on Linux.
Here are some of the lessons I learned:
Removal of Code: Paint.NET 1.x used a proprietary third-party library that we did not have access to, and this third-party library used plenty of P/Invoke calls to achieve some special effects. This library for doing toolbars and special buttons was called DotNetWidgets and it seems to have morphed from a freeware library to some sort of commercial library.
During the initial phase of the port I started removing references to this library one by one and during this removal I either removed the code completely (those pieces that seemed unnecessary) or I replaced it with Windows.Forms 1.x controls that had similar functionality.
Halfway through the port, I realized it would have been better to just implement the API exposed by this third party control than to amputate left and right Paint.NET.
So I implemented a barely functioning wrapper class that exposed the same API but barely worked. This was good, as I did not have to amputate any more code. The downside is that by the time I did this, half the classes that used this library had been amputated, and I did not feel like redoing the work.
So I ended up with half the code base amputated, and half the code base semi-working (semi-working because my wrapper was not completed).
Emulation vs Refactoring: In a handful of places I decided to implement small native wrappers that are distributed with Mono and that provide some of the P/Invoke methods that Paint.NET uses. In particular the Heap* family of calls from Win32.
At the time, it made sense to emulate a handful of calls, and we also came up with a design for routing P/Invoke calls done to USER32 to be forwarded to our managed System.Windows.Forms library. The infrastructure is still in place and we could use it to simplify the porting of applications, but am still debating if this is a good idea or not.
In retrospect, I should just have taken advantage of Paint.NET's architecture. Paint.NET actually had isolated the system-dependant code in their SystemLayer library, I could have provided Unix alternatives in that library instead of trying to emulate its underlying functionality (this was another part where half the code was emulated, and half was worked around).
Based on the various Moma Reports it seems that we might be better served by having a portability library that developers could use with their application.
From the Moma reports that people have submitted, we have noticed that some people are using incorrect P/Invoke signatures. This means that their software is actually broken and is probably fragile. It only happens to work because this month stars are properly aligned, but it could break easily, or could have undesirable effects easily (and this is on Windows!).
Second, there are some APIs that can be used for many different things, like "SendMessage".
So it seems like it would be best to provide a portability library "Winforms.Portability" and make sure that the software is migrated to use it. The nice side effect of this would be that features that are not supported would be explicitly visible on the source code.
In any case, we are still considering the options and what our recommendation will be.
The Release: So I never felt like releasing the Paint.NET changes because they are broken, and people would judge Mono's Windows.Forms implementation not on its strengths and limitations but on the broken nature of this port (for example, a bunch of button actions are not even wired up).
I never went back to fix Paint.NET 1.x because the developers of Paint.NET were working towards their 2.x series, and this release would replace DotNetWidgets with the various "Strip" controls in Winforms 2. So it was not a very good time investment to work on the 1.x codebase anymore.
We still use the 1.x codebase as a gigantic test case, but we are not going to be upgrading it.
Instead, we will complete the support for the Winforms 2.x APIs that are in use and do the Paint.NET refactoring again.
The 2.x port of Paint.NET is underway, but we are still missing some classes in Windows.Forms that prevent it from building.
The SystemLayer, SetupFrontEnd, Resources, Help, PdnLib and Data components compile fine. The Effects component and the shell still needs some API calls to be implemented (TableLayout related code).
Once those APIs are completed, the real porting work will begin. That is when I will have to do the refactoring or provide a Unix-specific implementation of the SystemLayer API that Paint.NET uses.
As part of my mission to modify as little code as possible from the Paint.NET 2.x codebase, I implemented SafeHandles for Mono. The current status of our implementation is tracked here.
Implementing SafeHandle support in Mono was a lot of fun. It was the first time I touched the code in the Mono marshaler and it was interesting to figure out all the relations between SafeHandles, CriticalFinalizerObjects how P/Invoke provided the magic that allowed a type-safe object to be marshaled as an IntPtr and where to hook all the behind-the scene calls that happen inside SafeHandles.
The most important source of information was Chris Brumme's "Finalization" blog entry and Ravi Krishnaswamy's "SafeHandles: the best V2.0 feature of the .NET Framework" blog entry.
The patch to support the user visible features of SafeHandles is now on SVN and I plan on continuing some of the work outlined here. Likely, I will need some help from the runtime guys, as I do not quite understand all the issues involved.
As I was exploring the magic behind SafeHandles, I discovered that some of the features that I considered to be ratholes turned out to not be supported, and instead of trying to come up with a confusing solution, certain uses of SafeHandles are just not supported (changes on a SafeHandles passed in ref structures is not mapped back to the managed world, and instead an exception is thrown).
See the mono/mono/tests/safehandle.2.cs file for some other interesting tidbits.
Posted on 16 Dec 2006