Saturday, May 30, 2009

Is Native Code Hard?

If you read this blog then probably your answer is, like mine, no. So what's the difference between native and managed that makes people avoid native code like the plague? What makes people not want to come back? In my opinion there are a few reasons, especially for Windows Mobile native code:
  • Lack of tools - just try to write a C# application using notepad and the command line compiler and you will see how much tools make a managed developer life easy. There is no comparable level of tool support for native code developers, and this may be linked to the next point.
  • Lack of comprehensive and cohesive libraries and frameworks - the Win32 API is way too low-level for most uses. It's very nice to be able to use it directly without paying the P/Invoke price, but this comes at a cost of having to write dozens of lines of code to achieve a very simple outcome. The OLE DB and MAPI COM interfaces are a perfect example of this - way too complex to use as they are offered. Just compare the "stock" OLE DB samples with the OLE DB Client library I published here...
  • Memory management - this is a minor issue for small applications but when the scale starts to grow it becomes a headache. When are you supposed to delete that object? Is it safe to do it now? Are there other objects referring to it?
I have recently been confronted with the memory management issue in a relatively complex application when I found myself wanting to have the memory management of a particular set of objects to be done automatically. These objects are referred to by an arbitrary number of other objects and determining when their life cycle ended was becoming more and more difficult, especially because they were being referred to by different threads.

There are two basic approaches to automatic memory management that have been used for quite a long time:
  • Reference counting - The iPhone uses a reference counting mechanism to determine when a given object can be removed from memory. The price you pay for this type of memory management is that you have to remember to increment and decrement the memory reference counts. Failing to do this will result in memory leaks. The iPhone uses an optional "autorelease pool" object that kind of protects you from this.
  • Garbage collection - The Java and .NET platforms both use garbage collection to reclaim the memory of objects that are no longer active. The price you pay for this is that the GC has a mind of its own and stops your code in order to clean up the mess. On mobile devices, if you don't manage your memory by reusing objects and by avoiding excessive allocations, the GC will kill your application performance.
One of the interesting advantages of C++ is that it implements deteministic object destruction: when your statically allocated object goes out of scope, the destructor is called. This behavior allows us to implement classes like CWaitCursor that displays the wait cursor on the constructor and hide it or replace it on the destructor. You just have to create a variable of that type and the wait cursor will be shown while the object is alive.

If we couple this with a simple reference counting mechanism, we can actually get all the benefits of a reference-counted memory management scheme without the pain that is imposed on us by the iPhone: we don't have to remember when to increment and decrement the references. How? The dead-old solution is to use reference counted smart pointers to manage our objects' life cycles.

After performing a search for reference-counted smart pointers, and realizing that my needs were quite simple, I decided to roll my own. The first thing we need is a place where to store the reference counts. Some implementations use a proxy object to do this but, for simplicity sake, I decided to put the reference count right on the object through inheritance. This has the disadvantage of having to make all the reference-counted objects inherit from one class, but it's a simple class that will only add four bytes to the object size:


class CRefCount
{
private:
LONG m_nRefCount;

public:
CRefCount(void) : m_nRefCount(0) { };

void RefIncrement()
{
InterlockedIncrement(&m_nRefCount);
}

bool RefDecrement()
{
return InterlockedDecrement(&m_nRefCount) <= 0; } };

Note that I'm using the InterlockedIncrement and InterlockedDecrement to manage the reference count because these methods can be used from different threads. Also, note that the reference count starts at zero (and not at one like on the iPhone) because I want to count how many smart pointers are referring to this object. Let's see how the smart pointer class is defined:


template <class T>
class CRefPtr
{
private:
T *p;

public:
CRefPtr() : p(NULL) { }
CRefPtr(T *pT) : p(pT)
{
if(p)
p->RefIncrement();
}

CRefPtr(const CRefPtr& ptr) : p(ptr.p)
{
if(p)
p->RefIncrement();
}

~CRefPtr() throw()
{
Release();
}

void Release()
{
T* pTemp = p;

if(pTemp)
{
p = NULL;
if(pTemp->RefDecrement())
delete pTemp;
}
}

void Attach(T* p1)
{
Release();
p = p1;
}

operator T*() const throw()
{
return p;
}

T& operator*() const
{
ATLENSURE(p!=NULL);
return *p;
}

bool operator!() const throw()
{
return (p == NULL);
}

T* operator=(const CRefPtr& rhs)
{
if(this != &rhs)
{
Release();
p = rhs.p;
if(p)
p->RefIncrement();
}
return *this;
}

T* operator=(T* pT)
{
if(p != pT)
{
Release();
p = pT;
if(p)
p->RefIncrement();
}
return *this;
}

T* operator->() const
{
ATLASSERT(p != NULL);
return p;
}
};

As you can see each smart pointer takes up the same amount of memory as a regular pointer, so you can safely pass and return it by value from your own methods and functions. This is a very bare-bones implementation and may lack some more sophisticated features, but so far has worked quite well for me. You use it like a CComPtr (I confess that I did look it up when writing this code).

3 comments:

Luis Abreu said...

Yes, it is. :)

Since most of us were born without that part of the brain that makes us understand triggers (check Joel Spolsky's blog), I'd really say that many programmers can't program in unmanaged code because it's really more difficult.

João Paulo Figueira said...

What I'm trying to prove is that writing code in C++ does not have to be harder than writing code in C#. The C# language does have a lot of features that make developing nicer, but most of what you get is actually provided by the IDE and the framework. As I said in the post, the native APIs are very low-level which makes them hard to use. In the absence of a generally accepted framework, developers tend to roll their own and thus reinvent the wheel every time.

If you want an example of a very successful native development story, just look at the Objective-C language and the Cocoa frameworks for Mac and iPhone. What you get there is a great IDE (maybe not as good as VS, but still very competent) and a very comprehensive framework, all of this supported by a native language. The explosion of applications for the iPhone seem to tell an interesting story...

RanchHand said...

I really believe that part of the "explosion of applications for the iPhone" is due to the fact that developers only have to deal with one screen size and resolution. Also, they haven't had the pleasure of reacting to new user interfaces with every major release of the software.

Contrast this with the Windows Mobile situation where it is almost a daunting task to make applications for the multitude of screen sizes; "professional" vs. "standard" and so on. In WM, developers need to spend a much higher fraction of their time catering to different screen sizes rather than adding useful capabilities.