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
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
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
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
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
Based on the various
Moma Reports it seems that we might be better served by
having a portability library that developers could use with
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
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
The 2.x port of Paint.NET is underway, but we are still
missing some classes in Windows.Forms that prevent it from
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
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