Click here to Skip to main content
15,879,326 members
Articles / Programming Languages / C++
Article

.NET style delegates for VC++ 6

Rate me:
Please Sign up or sign in to vote.
4.96/5 (29 votes)
19 Aug 2003CPOL11 min read 106.1K   556   44   23
An implementation of synchronous .NET style delegates in non - .NET VC++ 6.

Contents

Introduction

In .NET, delegates are used to implement event handling. They allow a class to register an event handler, which is then called at a later time when a certain event occurs. In non-.NET C++, this is not an easy thing to do. Non-static member functions can be quite difficult to use as callbacks. The purpose of this article is to present a method whereby both static and non-static class member functions, as well as non-member functions can be used as callback functions. Type-safety is important in this implementation, and some features are left out in order to retain this.

What are delegates?

The .NET framework SDK defines delegates as follows:

"A delegate is a class that can hold a reference to a method. Unlike other classes, a delegate class has a signature, and it can hold references only to methods that match its signature. A delegate is thus equivalent to a type-safe function pointer or a callback."

A class that exposes a delegate allows other functions and classes to register event handlers with it. The class can then call the delegate, which would iterate through its list of handlers, and call them one-by-one, passing the information passed to the delegate. The class that exposes the delegate does not need to know anything about how many handlers are registered. It leaves this up to the delegate itself.

Description

An overview of functors

This implementation makes use of functors, or function objects. These allow a non-static class member function to be called in the context of a specific object. Templates are used to allow any class-types to be used. A basic functor definition is shown below:

C++
template<class T>
class Functor
{
public:
    // Constructor takes the values and stores them
    Functor(T *pObj, int (T::*pFunc)(int))
    {
        m_pObject = pObj;
        m_pFunction = pFunc;
    }
    // Invokes the stored function
    int operator ()(int p)
    {
        return (m_pObject->*m_pFunction)(p);
    }
private:
    T *m_pObject;                // Pointer to the object
    int (T::*m_pFunction)(int);  // Pointer to the function
};

This functor object uses functions that take an int as a parameter and return an int. The operator () is the most important part of the functor. It allows the object to be used as if it were a function. It's job is to invoke the stored function pointer whenever it is called. To use this functor object, we'd use code similar to that below:

C++
class MyClass
{
public:
    int Square(int p) { return p * p; };
};

void some_function()
{
    // Create a class to call in the context of
    MyClass theClass;
    // Create and initialise the functor object
    Functor<MyClass> myFunc(&theClass, MyClass::Square);
    // Call the functor using the overloaded () operator
    int result = myFunc(5);
    // result will hold the value 25
}

Note that invoking the functor object is almost the same as invoking the function itself, thanks to the overloaded () operator. I say "almost" because the object pointer is not used - it is stored inside the functor object.

Ok, that's very nice, but why would you want to use functors over the functions themselves? Good question. Functors become much more useful when you want to call functions that look the same (have the same parameters and return value) without knowing what class or object they belong to. Take the following code for example. I've split it into sections to make it easier to follow.

First is the abstract base class representing a functor that takes an int and returns an int. It contains one function - the () operator, so that the functor can be called without knowing its type.

C++
// Abstract base class
class Functor
{
public:
    // Invoke the functor (no implementation here as it must be overridden)
    virtual int operator()(int) = 0;
};

Next is the template class that can be instantiated for any class type, assuming it has a function that takes an int and returns an int. It is derived from the abstract Functor class, so that a pointer to the specific function object can be passed wherever a pointer to the base class is expected, so that the functor can be called regardless of what class it refers to. Other than the base class and the name, this class is identical to the one presented above.

C++
// Template functor
template<class T>
class TemplateFunctor : public Functor
{
public:
    // Constructor takes the values and stores them
    TemplateFunctor(T *pObj, int (T::*pFunc)(int))
    {
        m_pObject = pObj;
        m_pFunction = pFunc;
    }
    // Invokes the stored function (overrides Functor::operator ())
    int operator ()(int p)
    {
        return (m_pObject->*m_pFunction)(p);
    }
private:
    T *m_pObject;                // Pointer to the object
    int (T::*m_pFunction)(int);  // Pointer to the function
};

Next is a simple function that takes a pointer to a functor for a parameter and calls it. Note that it takes a parameter to the base class Functor rather than a pointer to the template class. This is needed because each variation of the template class based on the template parameter is actually a different type, and cannot be used directly if more than one type is to be supported.

C++
int OperateOnFunctor(int i, Functor *pFunc)
{
    if(pFunc)
        return (*pFunc)(i);
    else
        return 0;
}

This is a simple class that contains a function that meets the requirements of our functor - taking an int and returning an int. Note that this member function also uses a member variable, to show that the callback functions are in fact called in the context of an object, so functors referenced to different instances of the same class will produce different results.

C++
class ClassA
{
public:
    ClassA(int i) { m_Value = i; }
    int FuncA(int i)
    {
        return (m_Value - i);
    }
    int m_Value;
};

This is a simple program that creates two instances of a functor for the same class type, calls the functor on two different objects, and displays the results.

C++
int main()
{
    ClassA a(20);
    ClassA b(10);

    TemplateFunctor<ClassA> functorA(&a, ClassA::FuncA);
    TemplateFunctor<ClassA> functorB(&b, ClassA::FuncA);

    cout << "a gives the value " << OperateOnFunctor(5, &functorA) << endl;
    cout << "b gives the value " << OperateOnFunctor(5, &functorB) << endl;

    return 0;
}

This will give the following output:

a gives the value 15
b gives the value 5

In this case, both functors were calling ClassA::FuncA but for different objects. A similar but different example is calling a function from different classes. Say we implemented ClassB like this:

C++
class ClassB
{
public:
    ClassB(int i) { m_Value = i; }
    int FuncB(int i)
    {
        return (m_Value + i);  // + instead of -
    }
    int m_Value;
};

If we implement the main function like this, we'll get results that are a bit different:

C++
int main()
{
    ClassA a(20);
    ClassB b(10);

    TemplateFunctor<ClassA> functorA(&a, ClassA::FuncA);
    TemplateFunctor<ClassB> functorB(&b, ClassB::FuncB);

    cout << "a gives the value " << OperateOnFunctor(5, &functorA) << endl;
    cout << "b gives the value " << OperateOnFunctor(5, &functorB) << endl;

    return 0;
}

This will give us the following results:

a gives the value 15
b gives the value 15

In this case, functorB was calling ClassB::FuncB, hence the result (10 + 5). Note that we passed both functors to the OperateOnFunctor() function in exactly the same way. This is made possible due to the abstract Functor base class.

Parameterising functors with macros

So functors can be pretty handy things, but it's a bit of a pain to have to rewrite the classes if different numbers of parameters or a different return type are required. However, this can be made a bit simpler by using the preprocessor. Some may argue that this is macro abuse, but it works nicely, and until templates allow us to change function prototypes, it's the only way.

Say we declare a macro as follows:

C++
#define DECLARE_FUNCTOR(name, parmdecl, parmcall)                       \
    /* A function object base class for this parameter list */          \
    class name##Functor                                                 \
    {                                                                   \
    public:                                                             \
        virtual void operator () parmdecl = 0;                          \
    };                                                                  \
                                                                        \
    /* Template class derived from Functor for                          \
       make class-specific function objects */                          \
    template<class C>                                                   \
    class name##TFunctor : public name##Functor                         \
    {                                                                   \
    public:                                                             \
        /* Only constructor - stores the given data */                  \
        name##TFunctor(C* pObj, void (C::*pFunc)parmdecl)               \
        {                                                               \
            m_pObj = pObj;                                              \
            m_pFunc = pFunc;                                            \
        }                                                               \
                                                                        \
        /* Invokes the function object with the given parameters */     \
        void operator ()parmdecl    { (m_pObj->*m_pFunc)parmcall; }     \
        C *m_pObj;                  /* Object pointer */                \
        void (C::*m_pFunc)parmdecl; /* Method pointer */                \
    };

The three macro parameters are defined as:

  • name - The name of the functor. The text "Functor" is appended to this name for the base class, and "TFunctor" is appended for the template class
  • parmdecl - The declaration of the parameter lists for the () operators. The list must be enclosed in parentheses
  • parmcall - The list of parameters as passed to the contained method. The example below would help to explain the relationship between these two lists

An example usage of this macro is shown below:

C++
DECLARE_FUNCTOR(Add, (int p1, int p2), (p1, p2))

This declares a functor called AddFunctor which takes two int parameters. The output of this macro would look like this (except it would actually all be on one line, and there would be no comments):

C++
/* A function object base class for this parameter list */
class AddFunctor
{
public:
    virtual void operator () (int p1, int p2) = 0;
};

/* Template class derived from AddFunctor for
   make class-specific function objects */
template<class C>
class AddTFunctor : public AddFunctor
{
public:
    /* Only constructor - stores the given data */
    AddTFunctor(C* pObj, void (C::*pFunc)(int p1, int p2))
    {
        m_pObj = pObj;
        m_pFunc = pFunc;
    }

    /* Invokes the function object with the given parameters */
    void operator ()(int p1, int p2)    { (m_pObj->*m_pFunc)(p1, p2); }
    C *m_pObj;                  /* Object pointer */
    void (C::*m_pFunc)(int p1, int p2); /* Method pointer */
};

As you can see, wherever name appeared, the text Add has been inserted, parmdecl has been replaced with (int p1, int p2) and parmcall has been replaced with (p1, p2). To illustrate the relationship between the parmdecl and parmcall parameters, look at the operator () method, firstly in the macro, and then the expanded version:

C++
void operator ()parmdecl            { (m_pObj->*m_pFunc)parmcall; }
void operator ()(int p1, int p2)    { (m_pObj->*m_pFunc)(p1, p2); }

parmdecl is the declaration of the parameter list for the function, while parmcall is the parameter list that is passed to the contained function. Unfortunately, there is no way to auto-generate this using macros. It's a bit of a kludge, but it works and allows the functions to be type-safe.

Delegate implementation

The delegates are implemented similarly to functors, but they store a list of functors that are called when they are invoked, rather than only one function pointer. This means that multiple handlers can be stored and invoked when required. The class definition (without the code) is shown below. I have left out the definition of the functors because it is shown above. The functors are actually declared in this macro also, inside the namespace declaration.

C++
#define DECLARE_DELEGATE(name, parmdecl, parmcall)                      \
namespace name##Delegate                                                \
{                                                                       \
    class Delegate                                                      \
    {                                                                   \
    public:                                                             \
        Delegate();                                                     \
        ~Delegate();                                                    \
                                                                        \
        /* Template function for adding member function callbacks */    \
        template<class C>                                         \
        void Add(C *pObj, void (C::*pFunc)parmdecl);                    \
        /* Add a non-member (or static member) callback function */     \
        void Add(void (*pFunc)parmdecl);                                \
        /* Template function for removing member function callbacks */  \
        template<class C>                                               \
        void Remove(C *pObj, void (C::*pFunc)parmdecl);                 \
        /* Removes a non-member (or static member) callback function */ \
        void Remove(void (*pFunc)parmdecl);                             \
                                                                        \
        /* Addition operators */                                        \
        void operator +=(Functor *pFunc);                               \
        void operator +=(void (*pFunc)parmdecl);                        \
        /* Subtraction operators */                                     \
        template<class C>                                               \
        void operator -=(TFunctor<C> *pFunc);                           \
        void operator -=(void (*pFunc)parmdecl);                        \
                                                                        \
        /* Calls all the callbacks in the callback list */              \
        void Invoke parmdecl;                                           \
        /* Calls all the callbacks in the callback list */              \
        void operator ()parmdecl;                                       \
                                                                        \
    private:                                                            \
        /* List of callback functions */                                \
        std::vector<Functor*> m_pFuncs;                                 \
        /* typedef'd iterator */                                        \
        typedef std::vector<Functor*>::iterator vit;                    \
    };                                                                  \
}

Some key points:

  • The delegate and functor classes are put in their own namespace, so that they are one manageable unit.
  • The functors are stored in an STL vector. The vector contains pointers to the Functor base class, so it can contain instances of the template functor class for any type. Also, not shown above, is another functor that was defined to be able to call non-member functions or static-member functions. It is functionally identical, except that it doesn't store an object pointer or expect the function to be part of a class.
  • There are two methods to cause the delegate to call all the functors - either the Invoke() method or the () operator. Both methods cause exactly the same effect, in fact the () operator calls Invoke() internally to do the work.
  • There are two methods of adding and removing callbacks from the delegate. Using the Add()/Remove() methods, or the +=/-= operators. Similarly to the Invoke()/operator () pair, the two methods are functionally identical - the operators directly call the non-operator methods. Both methods have two overloads, one for class member callbacks, and one for non-class member or static member callbacks.

Also not included above macro is a non-member function that is used for creating functors to be passed to the += and -= operators. This member function is not placed in the namespace with the classes, and is called the name passed to DECLARE_DELEGATE(), appended with Handler. For example:

C++
DECLARE_DELEGATE(Add, (int p1, int p2), (p1, p2))

would make the function have the following prototype:

C++
template<class C>
AddDelegate::TFunctor<C> *AddHandler(C *pObj, 
               void (C::*pFunc)(int p1, int p2));

Using the code

The best way to show how to use the code is to give an example. The following example defines a delegate that takes an int and a float as parameters. It defines two simple classes with a compliant function in each, and also uses a static function and a non-class-member function.

C++
DECLARE_DELEGATE(Add, (int p1, float p2), (p1, p2))

class A
{
public:
    A() { value = 5; }
    virtual void Fun1(int val, float val2)
    {
        value = val*2*(int)val2;
        cout << "[A::Fun1] " << val << ", " << val2 << endl;
    }
    static void StaticFunc(int val, float val2)
    {
        cout << "[A::StaticFunc] " << val << ", " << val2 << endl;
    }
public:
    int value;
};

class B : public A
{
public:
    void Fun1(int val, float val2)
    {
        value += val*3*(int)val2;
        cout << "[B::Fun1] " << val << ", " << val2 << endl;
    }
};

void GlobalFunc(int val, float val2)
{
    cout << "[GlobalFunc] " << val << ", " << val2 << endl;
}

int main()
{
    // Create class instances
    A a;
    B b;
    // Create an instance of the delegate
    AddDelegate::Delegate del;
    // Add our handlers
    del += AddHandler(&a, A::Fun1); // or del.Add(&a, A::Fun1);
    del += AddHandler(&b, B::Fun1); // or del.Add(&b, B::Fun2);
    del += GlobalFunc;              // or del.Add(GlobalFunc);
    del += A::StaticFunc;           // or del.Add(A::StaticFunc);
    // Invoke the delegate
    del(4, 5);                      // or del.Invoke(4, 5);
    // Print the class values
    cout << "[main] a.value = " << a.value << endl;
    cout << "[main] b.value = " << b.value << endl;
    // Remove some of the handlers
    del -= AddHandler(&a, A::Fun1); // or del.Remove(&a, A::Fun1);
    del -= A::StaticFunc;           // or del.Remove(A::StaticFunc);
    // Invoke the delegate again
    del(4, 5);                      // or del.Invoke(4, 5);
    // Print the class values
    cout << "[main] a.value = " << a.value << endl;
    cout << "[main] b.value = " << b.value << endl;
    return 0;
}

This demonstrates most of the delegate operations, and will produce the following output:

[A::Fun1] 4, 5
[B::Fun1] 4, 5
[GlobalFunc] 4, 5
[A::StaticFunc] 4, 5
[main] a.value = 40
[main] a.value = 65
[B::Fun1] 4, 5
[GlobalFunc] 4, 5
[main] a.value = 40
[main] b.value = 125

The code uses the stl.h file written by Oskar Weiland to enable it to compile cleanly at warning level 4. This file is included in the zip file, and is available here. The downloadable code includes the delegate.h file, and the example program given above.

Class reference

Due to the code being customized by the DECLARE_DELEGATE() macro, I'll use <parameters> to represent that parameters that you passed.

Method:template<class C> void Delegate::Add(C *pObj, void (C::*pFunc)(<parameters>))
Description:Adds a callback function that is a non-static member function of a class. The member function must return void and take a parameter list that is the same as <parameters>.
Return value:void - nothing.
Parameters:
  • pObj - A pointer to the object to call the callback method in the context of.
  • pFunc - A pointer to the callback method to call.
Method:void Delegate::Add(void (*pFunc)(<parameters>))
Description:Adds a callback function that is either a static member function of a class or is not a class member function. The function must return void and take a parameter list that is the same as <parameters>.
Return value:void - nothing.
Parameters:pFunc - A pointer to the callback function to call.
Method:template<class C> void Delegate::Remove(C *pObj, void (C::*pFunc)parmdecl)
Description:Removes a callback function from the callback function list
Return value:void - nothing.
Parameters:
  • pObj - A pointer to the object that is being referred to.
  • pFunc - A pointer to the callback method being referred to.

These two parameters together specify the callback handler to be removed.

Method:void Delegate::Remove(void (*pFunc)parmdecl)
Description:Removes a callback function from the callback function list
Return value:void - nothing.
Parameters:pFunc - A pointer to the callback method being referred to.
Method:void Delegate::operator +=(Functor *pFunc)
Description:Adds a callback function that is a non-static member function of a class. The member function must return void and take a parameter list that is the same as <parameters>.
Return value:void - nothing.
Parameters:pFunc - A pointer to the functor to call. This should be created using the <name>Handler() function.
Method:void Delegate::operator +=(void (*pFunc)(<parameters>))
Description:Adds a callback function that is either a static member function of a class or is not a class member function. The function must return void and take a parameter list that is the same as <parameters>.
Return value:void - nothing.
Parameters:pFunc - A pointer to the callback function to call.
Method:void Delegate::operator -=(Functor *pFunc)
Description:Removes a callback function that is a non-static member function of a class.
Return value:void - nothing.
Parameters:pFunc - A pointer to the functor to remove. This should be created using the <name>Handler() function, and is deleted by the function.
Method:void Delegate::operator -=(void (*pFunc)(<parameters>))
Description:Removes a callback function that is either a static member function of a class or is not a class member function.
Return Value:void - nothing.
Parameters:pFunc - A pointer to the callback function to remove.
Method:void Delegate::Invoke(<parameters>)
Description:Calls all the callbacks in the callback list with the specified parameters.
Return Value:void - nothing.
Parameters:<parameters> - The parameters to pass to the callback functions, as specified in the parameter to DECLARE_DELEGATE().
Method:void Delegate::operator ()(<parameters>)
Description:Calls all the callbacks in the callback list with the specified parameters
Return Value:void - nothing.
Parameters:<parameters> - The parameters to pass to the callback functions, as specified in the parameter to DECLARE_DELEGATE().

To-do

  • Add a macro-parameterized class that supports return values, storing the return value for each functor so that it can be accessed later.
  • Add template classes that have a fixed number of parameters, e.g. a 1-parameter class, a 2-parameter class etc. This may or may not be done due to the large number of classes involved - separate classes have to be written for delegates that return values as opposed to those that do not.
  • Suggestions?

History

  • 19th August 2003 - Initial release

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Systems Engineer
Australia Australia
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Generalmy two cents... of euros :-) Pin
Philippe Bouteleux27-Aug-03 4:08
Philippe Bouteleux27-Aug-03 4:08 
Here is my contribution to a previous article named ".NET like Delegates in Unmanaged C++" By Jörgen Sigvardsson on CodeProject.

I submitted to him my own versions of "delegates" (which are misnamed as they look more like functors than real .NET delegates as they lack += and -= operators and multiple parameters).

Maybe you could get some interesting reflexions having a look at the code.
Keep good work on Smile | :)

// this base class is needed to be able to create a pointer to two specialized classes
// Following two sub-classes are used to create static instances that 'knows'
// how to cast our generic delegate to the right types
// between "pointer to object method" and "pointer to static class method"
template
class delegate_base
{
public:
typedef delegate_base SELF;
typedef RESULT_TYPE (SELF::* pfFuncObject)(PARAM_TYPE);
typedef RESULT_TYPE (*pfFuncStatic)(PARAM_TYPE);

typedef union {
pfFuncObject pfObject;
pfFuncStatic pfStatic;
} uFunc;

delegate_base (void) {
m_obj = NULL;
m_func.pfObject = NULL;
m_func.pfStatic = NULL;
}

// used later for comparison operators
virtual bool Equals (delegate_base &dlg, delegate_base &other) = 0;

// generic calling method
virtual RESULT_TYPE Invoke(delegate_base &dlg, PARAM_TYPE param) = 0;

// shared attributs declaration
void * m_obj;
uFunc m_func;
};

// first specific sub-classes designed for "pointer to object method"
template< typename CLASS_TYPE, typename RESULT_TYPE, typename PARAM_TYPE >
class delegate_object : public delegate_base < RESULT_TYPE, PARAM_TYPE >
{
typedef RESULT_TYPE (CLASS_TYPE::* pfDelegateFunc)(PARAM_TYPE);

public:
bool Equals (delegate_base &dlg, delegate_base &other)
{
delegate_object *other_obj = dynamic_cast *>(&other);
if (other_obj == NULL)
return false;

if ((dlg.m_obj == other_obj->m_obj) &&
(dlg.m_func.pfObject == other_obj->m_func.pfObject))
return true;

return false;
}

RESULT_TYPE Invoke(delegate_base &dlg, PARAM_TYPE param)
{
return (static_cast(dlg.m_obj)->*((pfDelegateFunc)dlg.m_func.pfObject))(param);
}
};

// second sub-class used to type a delegate as a static class method (aka plain function pointer
template< typename RESULT_TYPE, typename PARAM_TYPE >
class delegate_static : public delegate_base < RESULT_TYPE, PARAM_TYPE > {
public:

bool Equals (delegate_base &dlg, delegate_base &other)
{
delegate_static *other_static = dynamic_cast *>(&other);
if (other_static == NULL)
return false;

if (dlg.m_func.pfStatic == other_static->m_func.pfStatic)
return true;

return false;
}

RESULT_TYPE Invoke(delegate_base &dlg, PARAM_TYPE param)
{
return (dlg.m_func.pfStatic)(param);
}
};

// our public delegate interface with two constructors
// used to set the type between the two previous sub-classes
// This public delegate also inherit from the common base class
// to be able to share the definition of its attributs with the sub-classes
// (so we need to declare the virtual functions of the base class)
template
class delegate : public delegate_base < RESULT_TYPE, PARAM_TYPE >
{
public:

delegate () {
m_type = NULL;
}

bool isNull() { return m_type == NULL; }

// type a delegate as a pointer to object method
template
delegate(CLASS_TYPE& obj, RESULT_TYPE (CLASS_TYPE::* method)(PARAM_TYPE))
{
static delegate_object DLG_OBJ;

m_obj = &obj;
m_func.pfObject = (pfFuncObject)(method);
m_type = &DLG_OBJ;

}

// type a delegate as a pointer to static class method
delegate(RESULT_TYPE (* method)(PARAM_TYPE))
{
static delegate_static DLG_STC;

m_obj = NULL;
m_func.pfStatic = (pfFuncStatic)(method);
m_type = &DLG_STC;
}

// copy constructor
delegate& operator=(const delegate& other) {
m_obj = other.m_obj;
m_func = other.m_func;
m_type = other.m_type;

return *this;
}

// calling operator passes through the virtual function instanciation
RESULT_TYPE Invoke(delegate_base &dlg, PARAM_TYPE param) {
return m_type->Invoke(dlg, param);
}

RESULT_TYPE operator()(PARAM_TYPE param) {
return Invoke (*this, param);
}

// comparison operators pass through the virtual function instanciation
bool Equals (delegate_base &dlg, delegate_base &other) {
if (&dlg == &other)
return true;

if (&dlg == NULL || &other == NULL)
return false;

return m_type->Equals (dlg, other);
}

bool operator==(const delegate& other) {
return Equals (*this, const_cast &>(other));
}

bool operator!=(const delegate& other) {
return !Equals (*this, const_cast &>(other));
}

private:
delegate_base * m_type;
};
QuestionWhy not use boost? Pin
Anonymous26-Aug-03 3:28
Anonymous26-Aug-03 3:28 
AnswerOr use libsigc++ Pin
roel_26-Aug-03 3:35
roel_26-Aug-03 3:35 
AnswerRe: Why not use boost? Pin
John M. Drescher26-Aug-03 3:44
John M. Drescher26-Aug-03 3:44 
GeneralRe: Why not use boost? Pin
Ryan Binns26-Aug-03 3:52
Ryan Binns26-Aug-03 3:52 
GeneralRe: Why not use boost? Pin
Anthony_Yio25-May-04 20:29
Anthony_Yio25-May-04 20:29 
AnswerRe: Why not use boost? Pin
Ryan Binns26-Aug-03 3:50
Ryan Binns26-Aug-03 3:50 
GeneralProblems Using Pin
Phil Bolduc20-Aug-03 10:14
Phil Bolduc20-Aug-03 10:14 
GeneralRe: Problems Using Pin
Ryan Binns20-Aug-03 14:49
Ryan Binns20-Aug-03 14:49 
GeneralExcellent Article... Pin
John M. Drescher19-Aug-03 17:19
John M. Drescher19-Aug-03 17:19 
GeneralRe: Excellent Article... Pin
Ryan Binns19-Aug-03 17:43
Ryan Binns19-Aug-03 17:43 
QuestionWhat about asynch? Pin
J. Dunlap19-Aug-03 10:42
J. Dunlap19-Aug-03 10:42 
AnswerRe: What about asynch? Pin
Ryan Binns19-Aug-03 14:20
Ryan Binns19-Aug-03 14:20 
GeneralGood for you, Ryan! Pin
J. Dunlap19-Aug-03 10:39
J. Dunlap19-Aug-03 10:39 
GeneralWhere were you two days ago? :) Pin
Phillip M. Hoff19-Aug-03 10:11
Phillip M. Hoff19-Aug-03 10:11 
GeneralFinally, something *useful*! Pin
Eric Anderton19-Aug-03 5:14
Eric Anderton19-Aug-03 5:14 
GeneralRe: Finally, something *useful*! Pin
Ryan Binns19-Aug-03 14:18
Ryan Binns19-Aug-03 14:18 
GeneralNice article Pin
Ryan_Roberts19-Aug-03 4:04
Ryan_Roberts19-Aug-03 4:04 
GeneralRe: Nice article Pin
Ryan Binns19-Aug-03 4:15
Ryan Binns19-Aug-03 4:15 
GeneralRe: Nice article Pin
Neville Franks19-Aug-03 10:42
Neville Franks19-Aug-03 10:42 
GeneralCool Pin
Andrew Walker19-Aug-03 3:39
Andrew Walker19-Aug-03 3:39 
GeneralRe: Cool Pin
Ryan Binns19-Aug-03 4:01
Ryan Binns19-Aug-03 4:01 
GeneralRe: Cool Pin
Andrew Walker19-Aug-03 4:04
Andrew Walker19-Aug-03 4:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.