|
Memory Allocation / DLLs / Reference Counting
Submitted by |
(This applies to Win32 primarily)
For starters, it is generally a bad idea to free/delete memory returned from
a dll function inside another dll/exe. The opposite, new in an exe, and
delete in a dll is also true. This depends on which heap is used, but both
will almost certainly fail.
For example:
// dllTest.h
sSomething* createSomething();
// dllTest.cpp
sSomething* createSomething()
{
return new sSomething;
} |
The client might write in an exe something like:
// exeTest.cpp
sSomething* mySomething = createSomething();
// ...
delete mySomething; // <- problem |
This point is made, more clearly perhaps, in "Programming Applications for
MS Windows 4th Ed" by Jeffrey Richter (pg 678). The way to solve this
problem, is to include a method to delete any objects that are allocated
inside the dll:
// dllTest.h
...
void deleteSomething(sSomething*);
// dllTest.cpp
void deleteSomething(sSomething* obj)
{ delete obj; } |
Now consider the case where you wish to use a simple base object to
implement reference counting.
For example:
// CObject.h -- part of myDll.dll
class CObject.h
{
public:
void addRef() { m_refCount++; }
void release();
CObject() : m_refCount(1) { ... }
};
// CObject.cpp
void CObject::release()
{
if ( (--m_refCount) <= 0) delete this;
} |
A client might be tempted to create an instance of some derived object, say
CTexture, like so:
// EXEtest.cpp
CTexture* txr = new CTexture; // <- uhmm?
// ...
txr-release(); // <- crash!?! |
Here we have the same basic problem (only reversed). Because the object is
created by the exe, but will be released by the dll, it will crash.
Luckily the fix is somewhat simple. Actually, I've seen two solutions. One
is to override the 'new' operator for any reference-counted class. The
operator is then able to malloc memory from the same heap that the relase()
call will free from. This can be done with a simple macro inside each
class's .h / .cpp file. However, MFC likes to "#define new DEBUG_NEW" for
debug builds, so it is possible that you might have problems there.
The other is to do the same basic thing, but use a static 'createInstance()'
method. (Making the constructor/destructors protected to prevent accidental
new'ing.)
// Texture.h
class CTexture : public CObject
{
public:
static void createInstance(CTexture** txr);
protected:
CTexture();
~CTexture();
};
// Texture.cpp
void CTexture::createInstance(CTexture** txr)
{
*txr = new CTexture;
} |
Using a static createInstance method per class has the 'benefit' of
providing a simple class factory method for dynamic object creation:
For example:
// EXETest.cpp
CTexture* txr = NULL;
CObject::createInstance(CLSID_Texture, (CObject**)&CTexture);
// ^ maps CLSID_Texture to the static function pointer
// "CTexture::createInstance(...)", calls it, and returns the
// correct object instance.
...
txr-release(); |
This is a bit more than I intended to write for a tip, but maybe someone
will find it useful.
-doug
http://freeside.flipcode.com
|
The zip file viewer built into the Developer Toolbox made use
of the zlib library, as well as the zlibdll source additions.
|