[Posted by Mark Atherton]
Cozi has a downloadable PC client written in .NET which includes an awesome photo collage screen saver. We've had many requests to make the screen saver available as a separate download; however the .NET 2.0 dependency makes it a large download for many people, plus we'd like to port it to other platforms in the future. So we decided to convert the screen saver to C++ (using WTL/ATL as the class library).
Before we started, it seemed like quite a big task as the layout engine in our photo collage screen saver is pretty sophisticated. However, so far it's turned out to be essentially a mechanical translation.
This was helped by a few factors:
- .NET uses GDI+ under the hood to provide nearly all the System.Drawing functionality. GDI+ is also available in the unmanaged world and (luckily) .NET had changed very little of the fundemental interfaces.
- GDI+ has a nice class-based model so you're spared all the COM tedium of QueryInterface/Release.
- ATL provides a reasonable set of collection classes that mapped quite well onto the C# collection classes.
- We just didn't bother with CPP files; there's only 1 in the whole project, and this clearly helps translate C# as otherwise you'd spend hours declaring members then defining them — this is definitely the most tedious bit about C++. Of course, there is some point at which it would get too inefficient to do that for all classes, but our project seems to compile pretty quickly so far.
- Expectations have changed. C# has dimmed the appetite for micro-optimizations, and there are many cases in the converted code where classes are being allocated and copied unnecessarily. In the past, we might have optimized these away but now we're happy to leave it and fix any problems that show up during performance testing. Similarly we use collection classes where before we'd have converted them to arrays.
Class library
As we have a certain amount of UI within the screen saver (for the configuration dialogs) we wanted a class library to help with that. The main choices were WTL/ATL and MFC; we chose WTL as it had a lower download size and we didn't need the sophistication of MFC. This then dictated using ATL for strings (CString), smart pointers (CAutoPtr) and collections. We could have used STL or another library too, but I know ATL so I preferred to stick with the library I knew.
Mechanical transformations
I ported the code over class by class and the first things I did were just mechanical changes.
- Replace Rect with Rectangle (for some reason .NET renamed this class and we use it a lot).
- Add a ; at the end of the class definition (this caught me almost every time).
- Replace "String" and "string" with "CString".
- Remove the "public" from the front of the class definition.
- Remove IDisposable if defined
- Move any code from Dispose() to the destructor.
- Change static (and enum) references from Class.Member to Class::Member.
- Add a : after public/private/protected declarations of members.
- Change Debug.Assert to ATLASSERT
- Map collection classes, usually this was just changing List<blah> to CAtlArray<blah>
- CAutoPtr<> any allocations.
- CAutoPtr<> any "usings", or just declare an object and let its destructor sort it out.
- Change properties into GetXXX and SetXXX methods.
- Pass classes either as references or as pointers.
- Remove "this." (you could change to this-> to lower the risk of unexpected consequences but it looks ugly).
- Change "const int" class members to enum {};
- Replace null with NULL (or #define null NULL).
Notes
Doing this conversion has given me some perspective on the strengths and weakness of the two languages.
- Pro for C++: Destructors are good. For code that needs deterministic cleanup (and as it happens this code needs a lot because of all the images) C++ is way better, IDispose is OK but look at the amount of code compared to C++.
- Pro for C#: Properties are good. C# properties are a vast improvement over getter and setter methods.
- Pro for C#: Field initialization is great. Having been able to initialize fields right at the point of declaration is far superior to C++ where you have to declare in one place and initialize in the constructor. This extends to consts which you can't declare within the class for some strange reason in C++.
- Pro for C#: foreach is great. I'm sure I could STL or similar to get some of the same benefits, but foreach seems so natural.
Another thing I noticed is that garbage collection is not helping as much as I'd imagined. Whilst it's great not to worry about it, so far as long as I'm religious about using CAutoPtr and related auto-cleanup classes memory leaks don't seem to be the problem they were.
Example
(Comments and error handling have been removed.)
namespace Cozi.ScreenSaver
{
using System;
using System.Drawing;
using System.Diagnostics;
using System.IO;
public class CollageImage : IDisposable
{
private const int c_resizeImagesOverMegaPixels = 9;
Bitmap m_bmp;
bool m_disposed = false;
public CollageImage( string filePath )
{
if ( File.Exists( filePath ) )
{
this.m_bmp = new Bitmap( filePath );
}
else
{
// Create an dummy bitmap if the path doesn't exist
// (i.e. net path disconnected)
this.m_bmp = null;
return;
}
int megaPixels = (this.m_bmp.Width * this.m_bmp.Height) / 1000000;
if (megaPixels > c_resizeImagesOverMegaPixels)
{
int width = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width / 2;
int height = width * this.m_bmp.Height / this.m_bmp.Width;
Image resized = new Bitmap(this.m_bmp, new Size(width, height));
this.m_bmp = (Bitmap)resized;
}
}
#region IDisposable Members
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~CollageImage()
{
Debug.WriteLine("LEAK: Failed to Dispose of CollageImage");
}
protected virtual void Dispose(bool disposing)
{
if (!this.m_disposed)
{
if (disposing)
{
if (this.m_bmp != null)
{
this.m_bmp.Dispose();
this.m_bmp = null;
}
}
this.m_disposed = true;
}
}
#endregion
public Size Size
{
get
{
if (this.m_bmp == null)
{
return new Size(1, 1);
}
return this.m_bmp.Size;
}
}
public void Draw(Graphics g, Rectangle bounds)
{
if (this.m_bmp == null)
{
return;
}
g.DrawImage(this.m_bmp, bounds);
}
}
}
Becomes:
#pragma once
#include "stdafx.h"
class CollageImage
{
CAutoPtr<bitmap> m_bmp;
enum
{
c_resizeImagesOverMegaPixels = 8
};
public: CollageImage( const CString& filePath )
{
if (::GetFileAttributes(filePath) != INVALID_FILE_ATTRIBUTES)
{
m_bmp.Attach(Bitmap::FromFile(filePath));
}
else
{
return;
}
int megaPixels = (m_bmp->GetWidth() * m_bmp->GetHeight()) / 1000000;
if (megaPixels > c_resizeImagesOverMegaPixels)
{
Image* resized = null;
int width = GetSystemMetrics(SM_CXSCREEN) / 2;
int height = width * m_bmp->GetHeight() / m_bmp->GetWidth();
Image* resized = new Bitmap(width, height);
Graphics g(resized);
g.DrawImage(m_bmp, Rect(0, 0, width, height));
m_bmp.Free();
m_bmp.Attach((Bitmap*)resized);
}
}
public: Size GetSize()
{
if (m_bmp == null)
{
return Size(1, 1);
}
return Size(m_bmp->GetWidth(), m_bmp->GetHeight());
}
public: void Draw(Graphics* g, Rect bounds)
{
if (m_bmp == null)
{
return;
}
g->DrawImage(m_bmp, bounds);
}
};





I'm curious why you chose to move away from .NET. The web experience, while good for the web, is still slow and clunky compared to your .NET WinForms app. Why not WPF, Silverlight or AIR?
Kelly
Posted by: Kelly Lasker | August 02, 2008 at 02:53 AM