Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

Using Delegates in the Visual Component Framework

3.65/5 (11 votes)
17 Jun 2008BSD12 min read 1   241  
An article describing how to use delegates in the VCF.

Introduction

This article will describe the new changes to the Visual Component Framework's event handling and delegate classes. The Visual Component Framework (VCF) is a modern C++ framework, intended to be easy to use and make it easier to deal with Windows programming, as well as has a cross platform design. In addition, the framework has been recognized world wide for adding at least 10 points to your IQ.

Delegates in the VCF

The Visual Component Framework uses what might be referred to as the "delegate" pattern to implement event handling in the framework. Anyone who's done any GUI programming in .NET is already familiar with the concept of a delegate, but for those new to this, I'll explain how they work in the VCF. The VCF defines a delegate to be a special class that acts like a function pointer of sorts, and can have multiple callbacks or function pointers hooked up to it. Thus, when the delegate is invoked, it passes its parameters to each callback, each of which gets called. In other words, it's similar to a multicast delegate in .NET.

There are obviously a number of articles on CodeProject that illustrate how to create something like this in C++, some being only singlecast delegates, i.e., one delegate, one callback, others implementing multicast functionality, i.e., one delegate, multiple callbacks. The other articles are undoubtedly more sophisticated than mine; however, I had a couple of other requirements that I needed for the VCF, thus I felt the need to implement a solution myself. One of these requirements was the ability to store callbacks and be able to reference them by name. This is done so that you can get an existing callback at runtime and wire it up to a delegate. It's entirely possible to do this dynamically through the VCF's introspection features. Another unique feature is that the delegates presented here can be invoked asynchronously, with the delegate's callbacks executed in a thread pool. We'll cover this later on in the article.

Delegate basics

Using a delegate is not horribly difficult, but it does require the use of templates. Here's an example:

void MyFunction( int num )
{
    System::println( Format("The num is %d") % num ); 
}

void MyFunction2( int num )
{
    System::println( Format("MyFunction2 called, the num is %d") % 
                      num ); 
}


Delegate1<int> myDelegate;
myDelegate += MyFunction;
myDelegate += MyFunction2;

myDelegate(100);
myDelegate.invoke( 200 );

You can (obviously) use function pointers that are class members. The current limitation is that the class needs to derive directly or indirectly from VCF::Object. In the future, this will probably be removed as a constraint.

class MyClass : public Object {
public:
    void myFunc( int num ) {
        System::println( Format("MyFunction2 called - this: %p, the num is %d") 
                                 % this % num ); 
    }
};

MyClass obj;

Delegate1<int> myDelegate;
myDelegate += new 
  ClassProcedure1<int,MyClass>(obj,&MyClass::myFunc);

myDelegate(100);

One thing to note when adding callbacks: when the callback is added, the delegate will try and "validate" the callback to ensure that its function signature matches that of the delegate. The reason we do this is because the callbacks can be added dynamically at runtime, and this acts as a simple, if somewhat crude, way to make sure that things don't completely blow up in our faces when the callback gets invoked later on.

So far so good, this is fairly generic, and not horribly mind boggling. Now, let's see how we can reference a callback for reuse. To make this work, we need to derive our class directly or indirectly from ObjectWithCallbacks. So, we would end up with:

class MyClass : public ObjectWithCallbacks {
public:
    void myFunc( int num ) {
        System::println( Format("MyFunction2 called - this: %p, the num is %d") 
                                 % this 
                                  % num ); 
    }
};

Our usage looks similar:

MyClass obj;
Delegate1<int> myDelegate;
myDelegate += 
  new ClassProcedure1<int,MyClass>(obj,
                                   &MyClass::myFunc,
                                    "MyClass::myFunc");

Note the new argument passed to ClassProcedure1, a string that is the name of the callback. By convention, this is the C++ qualified function name, but it can be anything you want.

We can reference this like so:

MyClass obj;
Delegate1<int> d1;
d1 += 
  new ClassProcedure1<int,MyClass>(obj,
                                   &MyClass::myFunc,
                                    "MyClass::myFunc");


Delegate1<int> d2;
d2 += obj.getCallback( "MyClass::myFunc" );

d1(100);
d2(344);

The call to ObjectWithCallbacks::getCallback() returns a valid callback instance (or NULL if nothing exists by that name) for a given string name.

In all the cases above, we are creating callbacks with no calls to delete anything. The framework handles the callbacks for you so long as you have either added the callback to a "source" class (a class that is derived from ObjectWithCallbacks), or you have added the callback to a delegate. In either case, the source class or the delegate will take responsibility for cleaning up the callbacks and deleting them.

Delegate return values

So far, the examples have shown delegates that do not have a return value. Let's look at delegates that do have a return value, for example:

double someFunc( int x, int y ) 
{
    return (double)x / (double)y;
}

Delegate2R<double,int,int> myDelegate;
myDelegate += someFunc;

This specifies a delegate that returns a double, and takes two integers as arguments. We can invoke this just like we did before:

double someFunc( int x, int y ) 
{
    return (double)x / (double)y;
}

Delegate2R<double,int,int> myDelegate;
myDelegate += someFunc;

double answer = myDelegate( 230, 345 );

It's pretty obvious what will happen. However, the question arises: how do we handle multiple callbacks? Since all delegates (in the VCF) are multicast, it's entirely possible that we could have:

double divideIt( int x, int y ) 
{
    return (double)x / (double)y;
}

double multiplyIt( int x, int y ) 
{
    return (double)x * (double)y;
}

double powerOf( int x, int y ) 
{
    return pow((double)x , (double)y );
}

Delegate2R<double,int,int> myDelegate;
myDelegate += divideIt;
myDelegate += multiplyIt;
myDelegate += powerOf;

double answer = myDelegate( 230, 345 );

Now, when we invoke the delegate, we have three functions that get called, yet we only seem to be dealing with one return value! We would expect to have return values of 0.666...7, 79350.0, and 6.253215...e+814, respectively.

No worries! We have a solution in place. Delegates that return a value (that is, any delegate class that is named DelegateRXXX, where XXX is the number of arguments it takes) have a member variable that is a vector of type T, where T is the return type. So in our example above, the delegate has a member std::vector<double> that stores the return values of the callbacks, in the order the callbacks are called. When you invoke the method in the way we did, what you will be returned is the value of the last callback called. The answer that we would see should be equal to 6.253215...e+814 (or something like that) since the powerOf() function would be the last callback called.

If we want to access all the results, we can do so like this:

double divideIt( int x, int y ) 
{
    return (double)x / (double)y;
}

double multiplyIt( int x, int y ) 
{
    return (double)x * (double)y;
}

double powerOf( int x, int y ) 
{
    return pow((double)x , (double)y );
}

Delegate2R<double,int,int> myDelegate;
myDelegate += divideIt;
myDelegate += multiplyIt;
myDelegate += powerOf;

myDelegate( 230, 345 );

for (size_t i=0;i<myDelegate.results.size();i++ ) {
    double answer = myDelegate.results[i];
}

If the return values are pointers, then the responsibility for the pointer is per whatever the documentation for the function states. In other words, the delegate does not perform any management at all over pointer types. So, if a function returns an instance to a class and states that it's the caller's responsibility to manage the instance, then this is still the case when making the call through the delegate.

Asynchronous delegates

You can invoke a delegate asynchronously by using the beginInvoke() method. This will invoke the callbacks in a separate worker thread (part of a thread pool that's managed by the framework). You can get notified (via a callback) when the callbacks have all been executed, or you can wait and block till they are all finished. The callbacks may be executed one after the other in one thread, or the individual callbacks themselves may be pushed onto separate threads in the the pool, all executing asynchronously.

To see a simple example, let's look at our previous delegate:

void myFunc( int i ) 
{

}

Delegate1<int> myDelegate;
myDelegate += myFunc;

AsyncResult* result = myDelegate.beginInvoke( 100, NULL );
result->wait();
delete result;

This will cause the myFunc() callback to be executed in another thread in the thread pool. The beginInvoke() method returns immediately, and returns a new AsyncResult instance. You can wait on the result, indefinitely, until the callbacks are all completed. At this point, the delegate invocation is finished, and you can delete the result instance. Alternately, you can wait by a specific number of milliseconds, for example:

AsyncResult* result = myDelegate.beginInvoke( 100, NULL );
while ( !result->isCompleted() ) {
    result->wait(1000);
}
delete result;

Here, we do a "busy poll" and check whether or not the result is finished. The AsyncResult::isCompleted() method will return true when all the callbacks have finished executing. In between calls to this, we wait for one second (1000 milliseconds).

Another way to call the beginInvoke is with a specific callback used when the delegate is finished executing all its callbacks.

void asyncDelegateDone( AsyncResult* asyncRes ) 
{    
    delete asyncRes;
}

CallBack* asynCB = new AsyncCallback(asyncDelegateDone);
myDelegate.beginInvoke( 100, asynCB );

When the delegate is finished, then the asyncDelegateDone() callback will be called. At this point, you can safely delete the AsyncResult instance (assuming you're not using it elsewhere). Note that the AsyncCallback is managed by you, the caller, so you'll need to delete it when you're done. Callbacks are always deleted by calling the free() method of the callback, like so:

CallBack* asynCB = new AsyncCallback(asyncDelegateDone);
myDelegate.beginInvoke( 100, asynCB );

//somewhere else in your code

asynCB->free(); //destroys the calback.

If your delegate returns a value, then you'll likely want to get the results of the callback(s). You can do this by calling endInvoke() on the delegate. This will return the value of the last callback that was executed. For example:

double divideIt( int x, int y ) 
{
    return (double)x / (double)y;
}

double multiplyIt( int x, int y ) 
{
    return (double)x * (double)y;
}

double powerOf( int x, int y ) 
{
    return pow((double)x , (double)y );
}

Delegate2R<double,int,int> myDelegate;
myDelegate += divideIt;
myDelegate += multiplyIt;
myDelegate += powerOf;

AsyncResult* result = myDelegate.beginInvoke( 301, 42, NULL );
result->wait();
double answer = myDelegate.endInvoke( result );
delete result;

You can get access to all the return values by calling endInvokeWithResults(). This will return you a vector of return values from the executed callbacks.

AsyncResult* result = myDelegate.beginInvoke( 301, 42, NULL );
result->wait();
std::vector<double> results = 
  myDelegate.endInvokeWithResults( result );


for (size_t i=0;i<results.size();i++ ) {
    double answer = results[i];
}

delete result;

If you would like to have each of the delegate callbacks execute asynchronously, then you can specify this like so:

myDelegate.setRunCallbacksAsynchronously( true );
AsyncResult* result = myDelegate.beginInvoke( 301, 42, NULL );
result->wait();

In the previous code, the three functions we added, divideIt(), multiplyIt(), and powerOf(), were being executed in the order that they were added to the delegate. If they are run asynchronously, then the order of execution may not be the order that they were added to the delegate. In addition, each function may be executed in a different thread of the thread pool.

Behind all the smoke and mirrors

So, how does any of this actually work? The basic idea uses the ThreadedFunction classes in the VCF that allow you to specify a templated class that wraps a function, and then executes that function in the context of some other thread, or returns you a Runnable instance that you can use to execute on a thread of your choosing.

When beginInvoke() is called, the delegate first creates an AsyncResult instance. It then iterates through all its callbacks. These, in turn, have a virtual function that is called, and creates the various ThreadedFunction instances. Each of these instances are stored on the AsyncResult, part of a collection of Runnables that will be run in the thread pool. When all of the callbacks have been iterated through, the AsyncResult adds these items to the thread pool for execution. At this point, the beginInvoke() returns. The thread pool will take over, and at some point, the Runnable instances will be run and the functions invoked. This will happen asynchronously, and complete when all the calls are made.

When the ThreadedFunction is created, one of the things it does is copy over its parameters into a structure that will hold them until they are ready to be used. When the actual function call is made, it uses these stored/cached values. An issue that arises is how to deal with function arguments that go out of scope due to the asynchronous nature of this. For example:

//declared globally, or at some higher scope
Delegate2R<double,int,int> myDelegate;

{
    AsyncResult* ar = myDelegate.beginInvoke( 100, 100 );
}

In this case, the function arguments are specified as an int, and will be copied over by value, so no problem arises. When we exit the scope where the call is made, the local ints that were on the stack will go away, but because a complete, by value, copy was made, there's no issue. Now, let's change things a bit.

//declared globally, or at some higher scope
Delegate2R<double,const int&,int> myDelegate;

{
    AsyncResult* ar = myDelegate.beginInvoke( 100, 100 );
}

Now, we have an issue. The variable is specified as a reference, and it will be held as one when it's "copied", because the "cache" structure declares it as a const int& variable. Since we leave scope, the 100 that was passed in goes away, and we will most likely have a corrupt value when the time comes to actually call the function. If we try this:

//declared globally, or at some higher scope
Delegate2R<double,const int&,int> myDelegate;

{
    int foo = 100;
    AsyncResult* ar = myDelegate.beginInvoke( foo,foo );
}

That's a bit better. However, the local variable "foo" still goes out of scope before the function(s) will be called. One solution might be this:

//declared globally, or at some higher scope
Delegate2R<double,const int&,int> myDelegate;

{
    int foo = 100;
    AsyncResult* ar = myDelegate.beginInvoke( foo,foo );
    ar->wait();
}

Now, beginInvoke() returns, and we wait on the AsyncResult to complete. The local variable does not go out of scope till all the function callbacks are completed. Another solution to this is to declare your variables that you're passing in at the proper scope. If the callback(s) that is executing is a class method, then perhaps the arguments you pass in can be member variables of the class.

At the moment, there is no marshaling that gets done to the function arguments. Perhaps, in the future, I may add support for that to help resolve issues like this.

Delegates and Visual Form files

While you can use delegates and wire them up to callbacks programmatically, you can also refer to them when you design forms using the VCF's Visual Form File format. For example, let's create a simple form with some UI elements on it:

MyWindow.vff

object MyWindow : VCF::Window
    top = 200
    left = 200
    height = 110pt
    width = 150pt
    caption = 'A Window'    
    container = @hlContainer
    
    object hlContainer : VCF::HorizontalLayoutContainer
        numberOfColumns = 2
        maxRowHeight = 35
        rowSpacerHeight = 10
        widths[0] = 80
        widths[1] = 80
        tweenWidths[0] = 10
    end    
    
    object lbl1 : VCF::Label
        caption = 'First Name'        
    end

    object edt1 : VCF::Label
        caption = 'Bob'
    end

    object lbl2 : VCF::Label
        caption = 'Last Name'        
    end

    object edt2 : VCF::TextControl
        text = 'Bankmann'
    end

    object lbl3 : VCF::Label
        caption = 'Age'        
    end

    object edt3 : VCF::TextControl
        readonly = true
        enabled = false
        text = '42'
    end
    
    object lbl4 : VCF::Label
        caption = 'Modify Age'        
    end

    object btn1 : VCF::CommandButton
        caption = 'Click Me'
    end
end

This displays something like this:

The VFF format is relatively straightforward (at least, I think so), and you can find out a more detailed description of it here: Visual Form File Format.

We want to execute a callback method when the user clicks on the command button labeled "Click Me". Since this is a CommandButton class, it has a delegate that is used for button clicks, called ButtonClicked. The delegate takes a single argument, a ButtonEvent pointer. Let's assume that we want to add our event handler/callback to our application class. The method looks like this:

class MyApp : public Application {
public:
    //other code removed

    void clickMe( ButtonEvent* ) {
        
    }    

};

The next step is to add a callback to our application instance. This will be referenced later in the VFF code, and so we need to make sure that the callback instance exists:

class MyApp : public Application {
public:
    //other code removed
    
    MyApp( int argc, char** argv ) : Application(argc, argv) {
        addCallback( 
          new ClassProcedure1<ButtonEvent*,MyApp>(this, 
                                                &MyApp::clickMe), 
                                                "MyApp::clickMe" );
    }


    void clickMe( ButtonEvent* ) {
        
    }    

};

Now, we have the event handler added, and we can reference it by name. Next, we need to adjust our VFF so that we make use of the callback, wiring it up to the delegate of our command button:

MyWindow.vff

object MyWindow : VCF::Window
    //other code removed for clarity
    object btn1 : VCF::CommandButton
        caption = 'Click Me'
        delegates
            ButtonClicked = [MyApp@MyApp::clickMe]
        end
    end
end

Now, we can put some code in our event handler:

void clickMe( ButtonEvent* ) {
    Dialog::showMessage( "Hello from clickMe()!" );
}

When you run the program, the code will load the form, build the UI, and when it gets to the "delegates" section, will get the callback from the application instance and add it to the button's ButtonClicked.

For more details on the specifics of wiring up the VFF and delegates, see this article: Working with Delegates in Visual Form Files.

Notes on building the examples

You'll need to have the most recent version of the VCF installed (at least 0-9-8 or better). The current examples have an exe for each demo that was built with VC6. If you'd like to build with a different version of Visual C++, then you'll need to build them manually, and make sure to build the static framework libraries, as opposed to dynamic.

If you're unsure how to build the framework proper, please see this: Building the VCF, it explains the basics of build the framework using various IDEs and tool-chains.

Conclusion

Questions about the framework are welcome, and you can post them either here, or in the forums. If you have suggestions on how to make any of this better, I'd love to hear them!

License

This article, along with any associated source code and files, is licensed under The BSD License