Account Options

  1. Sign in
The old Google Groups will be going away soon, but your browser is incompatible with the new version.
Google Groups Home
« Groups Home
Message from discussion On callbacks
The group you are posting to is a Usenet group. Messages posted to this group will make your email address visible to anyone on the Internet.
Your reply message has not been sent.
Your post was successful
 
From:
To:
Cc:
Followup To:
Add Cc | Add Followup-to | Edit Subject
Subject:
Validation:
For verification purposes please type the characters you see in the picture below or the numbers you hear by clicking the accessibility icon. Listen and type the numbers you hear
 
crai...@ee.washington.edu  
View profile  
 More options Sep 14 2009, 6:46 pm
From: <crai...@ee.washington.edu>
Date: Mon, 14 Sep 2009 15:46:53 -0700
Local: Mon, Sep 14 2009 6:46 pm
Subject: RE: On callbacks

> > I am a bit confused about the use of callbacks.
> I'm still a bit confused. This time about the use of
> 'MakeBoundCallback'

I'm waiting for a bunch of tests to finish so I have some time to go into
this now, so you get a bunch of words for free :-)

Allow me to go back and develop all of this callback stuff from first
principles.  Warning:  I haven't tried all of this code, and a lot of it's
just from memory so take it with a grain of salt.

Ultimately, the goal of a callback is just to have one piece of code call a
function or method in another piece of code.  Callbacks are used when you
need to call different functions or methods but don't want to change the
code that is doing the calling.  

This means you need some kind of indirection -- you treat the address of the
called function as a variable.  This variable is a pointer-to-function
variable.  The relationship between function and function pointer is really
no different that that of object and object pointer.

In C the canonical example of a function pointer is a
pointer-to-function-returning-integer (PFI).  For a PFI taking one int
parameter, this could be declared like,

  int (*pfi)(int arg) = 0;

What you get from this is a variable named simply "pfi" that is initialized
to the value 0.  If you want to initialize this pointer, you have to have a
function with a matching signature.  In this case,

  int MyFunction (int arg) {}

If you have this target, you can initialize the variable to point to your
function like,

  pfi = MyFunction;

You can then call MyFunction indirectly using the more suggestive form of
the call,

  int result = (*pfi) (1234);

This is suggestive since it looks like you are dereferencing the function
pointer just like you would dereference any pointer.  Typically, however,
people take advantage of the fact that the compiler is smart and just use a
shorter form,

  int result = pfi (1234);

Notice that the function pointer obeys value semantics, so you can pass it
around.  Typically, when you use an asynchronous interface you will pass
some entity like this to the function so it can execute it and "call back"
to let you know it completed.

In C++ you have the added complexity of pointer to member function, but it
looks only slightly different (PMI --
pointer-to-member-function-returning-int instead of PFI).

  int (MyClass::*pmi) (int arg) = 0;

This declares a variable named "pmi."  Your goal will be to call a method of
an instance of that particular class, so you have to declare that method in
a class.

  class MyClass {
  public:
    int MyMethod (int arg);
  };

You would then initialize your variable to point to that method like this,

  pmi = &MyClass::MyMethod;

This assigns the address of the method to the variable, completing the
indirection.  In order to call this method, you need to have a "this"
pointer which means there must be an object of MyClass around.

  int (MyClass::*pmi) (int arg) = 0;  // Declare a variable that is a method
pointer
  pmi = &MyClass::MyMethod;           // Point the variable at the code for
the method

  MyClass myClass;                    // Instantiate an instance of the
class
  (myClass.*pmi) (1234);              // Call the method indirectly
providing the "this" pointer

Just like in the C example, you can use this in an asynchronous call to be
"called back."  The straightforward extension you might consider is to pass
a pointer to your object and the pmi variable to the function that will call
you back.  

You might ask yourself at this time, what's the point?  You will be
permanently wiring your callback to the specific type of the called class.
Why not just accept this and pass an object pointer (do object->Method
(1234) in the calling code).  Well, we want to avoid that!  What is needed
is to decouple the calling function from the called class completely.  This
requirement led to the development of Functors.

A functor is the outgrowth of something invented in the 1960s called a
closure.  It's basically just a packaged-up function call, possibly with
some state (which is really the answer to your question, but I'll get to
that shortly).  There is really a state part and a decoupled-callback part
to the puzzle.  I'll talk about decoupling the callbacks first.

A functor has two parts, a specific part and a generic part.  This is an
inheritance relationship.  The calling code (that executes the callback)
will execute the generic overloaded operator () of a generic functor to
cause the callback to be called.  The called code will have to provide a
specialized implementation of the virtual operator () that does the
class-specific stuff that caused the close-coupling problem above.  The
called code (that made the callback) then gives the specialized code to the
calling module (that will execute the callback).  The calling module takes a
generic functor as a parameter so a cast is done in the function call.  The
calling module just needs to understand the generic functor type.

The information you'll need to make a specific functor is the "this" pointer
and the pointer-to-method.  So, you might have a function called MakeFunctor
(&MyClass::MyMethod, this) that put together a specific functor.  If this
sounds like the ns-3 MakeCallback, then you are understanding.

There is a lot of template black magic going on in ns-3 to deal with various
issues, especially variable formal parameter lists, but the essence is that
the system declares a generic part of the functor:

  template <typename T>
  class Functor
  {
  public:
      virtual void operator() (T arg) = 0;
   };

And the caller cooks up a specific part of the functor that really is just
there to implement the specific operator() method.

  template <typname T, typename ARG>
  class SpecificFunctor : public Functor
   {
      void (T::*m_cb)(ARG arg);
      T* m_p;
   public:
      SpecificFunctor(T* p, int (T::*_cb)(ARG arg))
      {
        m_p = p;
        m_cb = cb;
      }

      virtual int operator() (ARG arg)
      {
        (*m_p.*m_cb)(arg);
      }
   };

Notice that when operator() is called, it in turn calls the callback
function with the object pointer using the C++ PMI syntax with the provided
argument.

You could then declare some code that takes a generic functor,

  void LibraryFunction (Functor functor);

And build a specific functor in the client and pass this to the
LibraryFunction,

  MyClass myClass;
  SpecificFunctor<MyClass, int> functor (&myclass, MyClass::MyMethod);

When the LibraryFunction is done, it executes the callback using operator()
on the generic functor it was passed, and provides the integer argument

  void
  LibraryFunction (Functor functor)
  {
    functor(1234);
  }

Notice that the LibraryFunction is completely decoupled from the specific
implementation of the client.  The connection is made through the Functor
polymorphism.

Soooo, if you just replace the term Functor above with Callback, and instead
of declaring the SpecificFunctor directly, create it dynamically through
something called MakeCallback you have reconstructed the ns-3 callback
system.

  MakeCallback (&Function);

Will automagically do the C-style callback / functor for you, and

  MakeCallback (&MyClass::MyMethod, object-pointer);

Will automagically do the C++-style callback / functor for you.

The last part of this puzzle is the MakeBoundCallback part.  At the
beginning, I mentioned closures and the fact that they were function calls
packaged up for later.  Notice that in the Functor definitions above, there
is no way to package up any parameters for use later when the functor is
called via operator().  All of the parameters are provided by the
LibraryFunction.  

What if you want to allow the client function to provide some of the
parameters?  Andrei Alexandrescu calls the process of allowing a client to
specify one of the parameters "binding."  This is what the "Bound" refers to
in MakeBoundCallback.  One of the parameters of operator() has been bound
(fixed) by the client.

CsmaHelper::SniffEvent() is a great example of this.  If you can sort
through this one, you understand it all!  Look at src/helper/csma-helper.h
and you will find,

  static void SniffEvent (Ptr<PcapWriter> writer, Ptr<const Packet> packet);

This means that CsmaHelper::SniffEvent() is a function, not a method.  There
is no this pointer, so you will be using C-style callbacks.  If you look at
the definition of CsmaHelper::SniffEvent you will find a function that takes
two parameters.

  void
  CsmaHelper::SniffEvent (Ptr<PcapWriter> writer, Ptr<const Packet> packet)
  {
    writer->WritePacket (packet);
  }

Now, look in CsmaHelper::EnablePcap().  You will find code that does the
equivalent of

  Ptr<PcapWriter> pcap = CreateObject<PcapWriter> ();

  Config::ConnectWithoutContext (
    "/NodeList/0/DeviceList/0/$ns3::CsmaNetDevice/PromiscSniffer",
    MakeBoundCallback (&CsmaHelper::SniffEvent, pcap));

The callback function takes two parameters.  Now look in
src/devices/csma-net-device.cc for the other half of this arrangement.  The
Config::Connect is going to connect a callback to some instance of a member
Attribute that is given Callback powers.  Recalling the explanation of how
to find what is happening in trace sources, look for the Attribute
"PromiscSniffer" and you will find,

    .AddTraceSource ("PromiscSniffer",
      "Trace source simulating a promiscuous packet sniffer attached to the
device",
      MakeTraceSourceAccessor (&CsmaNetDevice::m_promiscSnifferTrace))

You know from previous discussions that you need to now look for
m_promiscSnifferTrace.  If you locate an instance of this you will see,

  m_promiscSnifferTrace (m_currentPkt);

When the callback is executed, the calling code only provides one of the
parameters.  Where does the other one required by CsmaHelper::SniffEvent
come from?  Look at MakeBoundCallback back in the CsmaHelper:

  MakeBoundCallback (&CsmaHelper::SniffEvent, pcap)

There is the other parameter.  It is fixed, or "Bound" by the helper code to
be a pointer to the PcapWriter.  The Callback (think a kind of specialized
functor) automagically provides the missing parameter for you.

This allows the client to arrange for parameters to be sent back to itself
without knowledge of the code that executes the callback.  In this case, the
CsmaNetDevice has no idea that a pointer to the pcap writer is being passed
back to the helper.  It just sends what it knows about -- the packet.

Hope this is all starting to make sense.

Regards,

-- Craig

> What I would like to achieve is to have all the ascii traces dumped
> into a unique file.
> So, by looking at how EnableAscii is implemented, I thought what I
> need was 'MakeBoundCallback'. However it is not clear to me how to
> build the callback (in particular, different files show different
> implementations, e.g. CsmaHelper::AsciiRxEvent in src/helper/csma-
> helper.cc and TxCallback in stats/wifi-example-sim.cc).

Can you see a way to get this done now?

Regards,

-- Craig


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.