myelin: How to write callbacks in C++ and COM

Here's a quick explanation of how to improve your C-style function pointers if you're coding in C++.  The reason this page is here is to provide some quick notes for people trying to figure out how callbacks work in various different languages.  Click here for the C# / .NET version. Or click here if you want to do C#-style delegate callbacks in C++.

A problem when writing code in C++ is that function pointers [function-pointer.org] suddenly become trickier to use.  In C, all your functions are global, and to locate a function, all the code needs is a pointer to where it starts in memory.  However in C++ your functions (unless they're declared as static) are associated with objects, and you need to pass this object information along with your function pointer.

There are at least two choices:   

We'll cover the second option here.

Catching callbacks by inheriting from the callback interface

Let's say you want to be able to make a pointer to a function which looks like this:

void FooCallback( int a, int b, int c );

First make an object which has a function like that:

class IFooCallback
{
public:
	virtual void FooCallback( int a, int b, int c );
};

Now make the class containing the function that needs to be called back inherit from your new object.

class NeedsToBeCalled : public IFooCallback
{
public:
	void FooCallback( int a, int b, int c );
	{
		do_something();
	}

	void MyFunction()
	{
		// Now pass 'this' as the callback object
SetCallback( this );
} };
Hiding callbacks by catching them in private objects

Apparently this is the 'official' way to do it in Java.  Instead of implementing the callback interface with your class, make a little object inside the class that implements the interface.

class NeedsToBeCalled
{
private:
class InternalCallback : public IFooCallback
{

void FooCallback( int a, int b, int c );
{
do_something();
}
} myCallback;
public:
	void MyFunction()
	{
		// Now pass &myCallback as the callback object
		SetCallback( &myCallback );
	}
};

This is nice because it lets you call back to multiple methods of the same object; you can do the following:

class GetsCalledSeveralTimes
{
private:
	class FirstInternalCallback : public IFooCallback
	{
		void FooCallback( int a, int b, int c );
		{
			do_something();
		}
	} firstCallback;

	class SecondInternalCallback : public IFooCallback
	{
		void FooCallback( int a, int b, int c );
		{
			do_something_else();
		}
	} secondCallback;

public:
	void MyFunction()
	{
		// Now we have a choice of objects to set as the callback:
		SetCallback( &firstCallback );
		// Or:
		SetCallback( &secondCallback );
	}
};

The above example shows that you can catch the same callback (IFooCallback) in different ways within the same object.  It's a bit wordier but doesn't require extra preprocessing as delegates do, and works in both Java and C++.

Catching callbacks in COM

If you want to do it with COM, you need to make your class look like a COM class, i.e. implement IUnknown as well as the relevant callback interface. This means you need three extra methods: QueryInterface, AddRef and Release.

This usually looks something like the following example, where we implement the ISampleGrabberCB interface from the DirectShow / WDM video capture API:

private:
	//! COM reference count
	ULONG _ref_count;

public:
	//! Return a ptr to a different COM interface to this object
	HRESULT APIENTRY QueryInterface( REFIID iid, void** ppvObject )
	{
		// Match the interface and return the proper pointer
		if ( iid == IID_IUnknown) {
			*ppvObject = dynamic_cast<IUnknown*>( this );
		} else if ( iid == IID_ISampleGrabberCB ) {
			*ppvObject = dynamic_cast<ISampleGrabberCB*>( this );
		} else {
			// It didn't match an interface
			*ppvObject = NULL;
			return E_NOINTERFACE;
		}

		// Increment refcount on the pointer we're about to return
		this->AddRef();
		// Return success
		return S_OK;
	}

	//! Increment ref count
	ULONG APIENTRY AddRef()
	{
		return ( ++ _ref_count );
	}

	//! Decrement ref count
	ULONG APIENTRY Release()
	{
		return ( -- _ref_count );
	}

There's probably a simpler way to do this - I suspect you could hack QueryInterface() to look like this, seeing as the object above is pretty simple and doesn't do anything strange with COM (specifically, all its interfaces point to the same object):

HRESULT APIENTRY QueryInterface( REFIID iid, void** ppvObject )
{
	*ppvObject = this;
	this->AddRef();
	return S_OK;
}

I haven't tried though, so your mileage may vary.  It depends whether the dynamic_cast<> above actually did something or not.

    ⇒ myelin | notes | christchurch | net [ video hire ] | software [ dbwrappers | xmlrpc | pycs | pss ] | contact