On callbacks

1,738 views
Skip to first unread message

Christian Facchini

unread,
Sep 10, 2009, 4:05:38 PM9/10/09
to ns-3-...@googlegroups.com
Dear ns-3-users,

I am a bit confused about the use of callbacks.
in particular, in the example named 'tcp-large-transfer.cc', there's a
function called 'CwndTracer':

static void
CwndTracer (uint32_t oldval, uint32_t newval)
{
NS_LOG_INFO ("Moving cwnd from " << oldval << " to " << newval);
}

Simply, I don't get where the two parameters are taken from.
Please forgive me if it is a silly question, but how can I find out
that CwndTracer takes exactly two uint32_t parameters?

Thank you in advance.
BR,
Christian

cra...@ee.washington.edu

unread,
Sep 10, 2009, 5:10:37 PM9/10/09
to ns-3-...@googlegroups.com

> I am a bit confused about the use of callbacks.
> in particular, in the example named 'tcp-large-transfer.cc', there's a
> function called 'CwndTracer':
>
> static void
> CwndTracer (uint32_t oldval, uint32_t newval)
> {
> NS_LOG_INFO ("Moving cwnd from " << oldval << " to " << newval);
> }
>
> Simply, I don't get where the two parameters are taken from.
> Please forgive me if it is a silly question, but how can I find out
> that CwndTracer takes exactly two uint32_t parameters?

It's not a silly question at all. The implementation is non-trivial.
Fortunately, you only have to figure this out once. It may be easier to
just accept that when you trace a plain-old data type, you need to define a
function that receives the old and the new value. i.e., don't ask :-)

Here are the internals. Look at the connect to begin putting the pieces
together.

Config::ConnectWithoutContext (
"/NodeList/0/$ns3::TcpL4Protocol/SocketList/0/CongestionWindow",
MakeCallback (&CwndTracer));

Then take a look at the path string, reading backward. The last part of the
path is the attribute defining the trace source. Probably the easiest thing
to do to get your bearings is just recursively grep for this Attribute name.

find . -name '*.cc' | xargs grep CongestionWindow

You will see,

./src/internet-stack/nsc-tcp-socket-impl.cc: .AddTraceSource
("CongestionWindow",
./src/internet-stack/tcp-socket-impl.cc: .AddTraceSource
("CongestionWindow",

It's the AddTraceSource call that you are interested in. If you are using
the standard ns-3 TCP, you would look in the file
internet-stack/tcp-socket-impl.cc to go further. You need to identify the
trace source. Look in this file for the .AddTraceSource you see in the grep
results. You will see,

.AddTraceSource ("CongestionWindow",
"The TCP connection's congestion window",
MakeTraceSourceAccessor (&TcpSocketImpl::m_cWnd))

Look at the MakeTraceSourceAccessor call and find that the actual traced
variable is

TcpSocketImpl::m_cWnd

Now, go and look in tcp-socket-impl.h for the declaration of this variable.
This is a

TracedValue<uint32_t>

Unless you know where this is defined you are probably stuck here. Again,
grep is your friend. Nothing interesting will show up in '*.cc' so try
'*.h'

If you do this you will find lots and lots of hits in ./core/traced-value.h
which is the next place you need to look.

You will find that the way these things work is to overload the operators
that cause the value to be changed. So you look for the = operator. If you
look there,

TracedValue &operator = (const TracedValue &o) {
TRACED_VALUE_DEBUG ("x=");
Set (o.m_v);
return *this;
}

You will find a call to Set(). There you will find:

void Set (const T &v) {
if (m_v != v)
{
m_cb (m_v, v);
m_v = v;
}
}

One thing to point out is that the trace hook will only be called if the new
value is different than the old value. This sometimes surprises people.

Anyway, the variable v is the new value and m_v is the existing (old) value,
Notice the line,

m_cb (m_v, v);

This is the callback that is hit when you make the change to the variable by
doing m_variable = something.

In the TracedValue class you can find,

private:
T m_v;
TracedCallback<T,T> m_cb;

So, in the case of TracedValue<uint32_t>, the T becomes int32_t and you have

int32_t v
int32_t m_v

So m_cb is calling (via an overloaded operator () ) a method that takes two
uint32_t, the first parameter is the existing (old) value and the second is
the new value.

So,

MakeCallback (&CwndTracer)

Needs to hook a function that takes two uint32_t, the first being the old
value and the second being the new value; which it does:

void CwndTracer (uint32_t oldval, uint32_t newval);

Non trivial, but now you know how all TracedValue work since it is
templated.

Regards,

-- Craig


C Facchini

unread,
Sep 11, 2009, 10:18:56 AM9/11/09
to ns-3-users
Dear Craig,

first of all, thank you very much for the thorough and insightful
answer!
I so appreciated it.

> [snip]
>   TcpSocketImpl::m_cWnd
>
> Now, go and look in tcp-socket-impl.h for the declaration of this variable.
I was able to get there. Anyway, before reading your explanation I
couldn't get from there on.

> [...] in ./core/traced-value.h [...]
> You will find that the way these things work is to overload the operators
> that cause the value to be changed.  So you look for the = operator. If you
> look there [...] you will find a call to Set()
It didn't occur to me that changes happen only when the variable *is
actually changed*.
And the variable is changed by means of =, ++, +=, and so on.
In fact, all those operators contain a call to the Set method.

To be honest, I still don't get the motivations behind the macro
(TRACED_VALUE_DEBUG), but that's a minor issue anyway.

> One thing to point out is that the trace hook will only be called if the new
> value is different than the old value.  This sometimes surprises people.
Is it because tracking a variable on a regular time basis is more
difficult in discrete event simulators (and doesn't add anything)?

> So, in the case of TracedValue<uint32_t>, the T becomes int32_t and you have
thanks to the conversion operator:
operator T () const { return m_v; }

>   int32_t v
>   int32_t m_v
>
> So m_cb is calling (via an overloaded operator () ) a method that takes two
> uint32_t, the first parameter is the existing (old) value and the second is
> the new value.

> Non trivial, but now you know how all TracedValue work since it is
> templated.
Definitely non trivial, at least for my programming skills.
Again thanks for taking the time to write such a clear explanation.

Regards,
Christian

Antti Mäkelä

unread,
Sep 11, 2009, 6:04:16 PM9/11/09
to ns-3-users
On Sep 11, 12:10 am, <crai...@ee.washington.edu> wrote:

> So,
>
>   MakeCallback (&CwndTracer)
>
> Needs to hook a function that takes two uint32_t, the first being the old
> value and the second being the new value;  which it does:
>
>   void CwndTracer (uint32_t oldval, uint32_t newval);

What if I connect WITH context? Context is passed as a string,
right?

I'm getting error as

got=ns3::FunctorCallbackImpl<void (*)(std::string, bool, bool), void,
std::string, bool, bool, ns3::empty, ns3::empty, ns3::empty,
ns3::empty, ns3::empty, ns3::empty>
expected=ns3::CallbackImpl<void, std::string, bool, ns3::empty,
ns3::empty, ns3::empty, ns3::empty, ns3::empty, ns3::empty,
ns3::empty>*

when trying to connect my TracedValue<bool> to a function with func
(std::string, bool, bool). If the function is just (std::string, bool)
it works (and seems to be getting only the new values). I'm setting
the TracedValue with () though, not with =.

Antti Mäkelä

unread,
Sep 11, 2009, 6:21:41 PM9/11/09
to ns-3-users
On Sep 12, 1:04 am, Antti Mäkelä <zar...@gmail.com> wrote:

>   What if I connect WITH context? Context is passed as a string,
> right?
>
> I'm getting error as

Whoops - turns out that I followed examples related to v4ping, which
use TracedCallback<Type> instead of TracedValue.

I guess TracedCallback is somewhat of a generic version for passing
multiple parameters, and TracedValue for singular ones that you can do
slight arithmetic with?

C Facchini

unread,
Sep 11, 2009, 6:39:11 PM9/11/09
to ns-3-users
Hi Antti,

> >   void CwndTracer (uint32_t oldval, uint32_t newval);
>
>   What if I connect WITH context? Context is passed as a string,
> right?
>
> I'm getting error as
>
> got=ns3::FunctorCallbackImpl<void (*)(std::string, bool, bool), void,
> std::string, bool, bool, ns3::empty, ns3::empty, ns3::empty,
> ns3::empty, ns3::empty, ns3::empty>
> expected=ns3::CallbackImpl<void, std::string, bool, ns3::empty,
> ns3::empty, ns3::empty, ns3::empty, ns3::empty, ns3::empty,
> ns3::empty>*
>
> when trying to connect my TracedValue<bool> to a function with func
> (std::string, bool, bool). If the function is just (std::string, bool)
> it works (and seems to be getting only the new values). I'm setting
> the TracedValue with () though, not with =.

The error you posted tells that you defined a function that accepts
three arguments (one string and two booleans) but he expects to find a
function taking just two arguments (one string and one boolean).
As a matter of fact, you admit that it works in the case you define a
callback that accepts the two parameters string and bool.

Out of my curiosity, what's the variable you're trying to trace?
As far as I understand, if the variable is based on the template
defined in src/core/traced-value.h you should be able to trace it
thanks to the callback, which is of the form:
TracedCallback<T,T> m_cb;
and hence it only accepts two parameters of the same type (then,
ConnectWithContext adds a string, but that's another story); still,
you seem to be using something different.

Regards,
Christian

cra...@ee.washington.edu

unread,
Sep 11, 2009, 7:11:15 PM9/11/09
to ns-3-...@googlegroups.com
[ ... ]

> I guess TracedCallback is somewhat of a generic version for passing
> multiple parameters, and TracedValue for singular ones that you can do
> slight arithmetic with?

There is a more rigorous definition. The documentation of TracedValue talks
about value semantics and so implies that a TracedValue is a trace source
that captures changes to an object with value semantics.

In general, value semantics just means that you can pass the object around,
not an address. In order to use value semantics at all you have to have an
object with an associated copy constructor and assignment operator
available. We extend the requirements to talk about the set of operators
that are pre-defined for plain-old-data (POD) types. Operator=, operator++,
operator--, operator+, operator==, etc., etc. You can see the list in
src/core/traced-value.h if you want.

I don't think there any instance in our codebase of a TracedValue tracing
anything but POD (uin32_t, int8_t, etc.), so I tend to think about
TracedValue in terms of tracing POD -- ints, chars, longs, shorts, etc. You
can extend this to user defined types, but like I said I don't think anyone
has done that yet. The signature of the trace sink is fixed since the
semantics are fixed (you get the old *value* and the new *value* of the
object with *value* semantics).

A TracedCallback is a completely different animal. There is no object with
value semantics in sight. What you are doing with a traced callback is just
executing a callback. TracedCallback is essentially just a
point-to-multipoint connection between the trace source and sinks. You can
define a callback any way you want, so the parameter list can be whatever
you want. The compiler just makes sure that things match.

TracedValue -> objects with value semantics
TracedCallback -> generic callback

-- Craig


Antti Mäkelä

unread,
Sep 12, 2009, 4:41:17 AM9/12/09
to ns-3-users
On Sep 12, 2:11 am, <crai...@ee.washington.edu> wrote:
> I don't think there any instance in our codebase of a TracedValue tracing
> anything but POD (uin32_t, int8_t, etc.), so I tend to think about
> TracedValue in terms of tracing POD -- ints, chars, longs, shorts, etc.  You

Ok, currently I'm using tracecallbacks of type "Bool" and "Time". I
guess bool could work as TracedValue as well, but how about Time?
V4ping application uses TracedCallback<Time> m_traceRtt;

Actually, I implemented my own "has the state changed" check in the
callback since I didn't truly understand the difference - all I had to
go on with were the examples. So thank you very much for this info.
Maybe this thread could be edited a bit and copypasted to the
documentation? I mean, currently http://www.nsnam.org/docs/manual.html
has "2.3.3, Tracing Subsystem" and NOTHING else.

cra...@ee.washington.edu

unread,
Sep 13, 2009, 12:27:36 AM9/13/09
to ns-3-...@googlegroups.com

> Ok, currently I'm using tracecallbacks of type "Bool" and "Time". I
> guess bool could work as TracedValue as well, but how about Time?
> V4ping application uses TracedCallback<Time> m_traceRtt;

This kind of thing is actually pretty easy to check out (okay, okay, if you
know what to type). Copy the following into, say, scratch/test.cc and build
it. I think you'll find it works fine. TracedValue<Time> will work if you
stay away from the operators that aren't defined for a Time (e.g., ++, --).

---------- Begin Included File ----------

#include <iostream>
#include "ns3/object.h"
#include "ns3/uinteger.h"
#include "ns3/nstime.h"
#include "ns3/traced-value.h"
#include "ns3/trace-source-accessor.h"

using namespace ns3;

class MyObject : public Object
{
public:
static TypeId GetTypeId (void)
{
static TypeId tid = TypeId ("MyObject")
.SetParent (Object::GetTypeId ())
.AddConstructor<MyObject> ()
.AddTraceSource ("MyTime",
"A Time value to trace.",
MakeTraceSourceAccessor (&MyObject::m_myTime))
;
return tid;
}

MyObject () {}
TracedValue<Time> m_myTime;
};

void
TimeTrace (Time oldValue, Time newValue)
{
std::cout << "Traced " << oldValue << " to " << newValue << std::endl;
}

int
main (int argc, char *argv[])
{
Ptr<MyObject> myObject = CreateObject<MyObject> ();

myObject->TraceConnectWithoutContext ("MyTime", MakeCallback
(&TimeTrace));
myObject->m_myTime = Seconds (1234.);
}

---------- End Included File ----------


C Facchini

unread,
Sep 14, 2009, 9:16:23 AM9/14/09
to ns-3-users
Dear ns-3-users,

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

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).

Is this the wrong way to do it?

cra...@ee.washington.edu

unread,
Sep 14, 2009, 6:46:53 PM9/14/09
to ns-3-...@googlegroups.com

> > 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


Tom Henderson

unread,
Sep 15, 2009, 12:00:31 AM9/15/09
to ns-3-...@googlegroups.com
FYI, Craig has extended the manual (thanks!) with this material:
http://www.nsnam.org/docs/manual.html#SEC13

C Facchini

unread,
Sep 16, 2009, 1:05:41 PM9/16/09
to ns-3-users
> 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 :-)
The greatest gift ever :)

> Hope this is all starting to make sense.
Actually, yes. The mechanisms behind all this are clearer now.

> > 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?
If I got it correctly, I should do something like:

// 1) defining the specialized callback
static void
CwndChangeAscii (uint32_t oldval, uint32_t newval, std::ofstream &os)
{
os << Simulator::Now() << ": old: " << oldval << " new: " << newval
<< std::endl;
}

int
main (int argc, char *argv[])
{
// 2) making such a bound callback
std::ofstream ascii;
ascii.open ("adhoc-transfer.tr");
Config::ConnectWithoutContext ("/NodeList/0/$ns3::TcpL4Protocol/
SocketList/0/CongestionWindow", MakeBoundCallback (&CwndChangeAscii,
ascii));
}

Still, it doesn't compile. There are some errors regarding the type
passed.
And there are also errors regarding the callback:
/usr/include/c++/4.3/bits/ios_base.h: In copy constructor
‘std::basic_ios<char, std::char_traits<char> >::basic_ios(const
std::basic_ios<char, std::char_traits<char> >&)’:
/usr/include/c++/4.3/bits/ios_base.h:782: error:
‘std::ios_base::ios_base(const std::ios_base&)’ is private
/usr/include/c++/4.3/iosfwd:52: error: within this context
/usr/include/c++/4.3/iosfwd: In copy constructor
‘std::basic_ofstream<char, std::char_traits<char> >::basic_ofstream
(const std::basic_ofstream<char, std::char_traits<char> >&)’:
/usr/include/c++/4.3/iosfwd:89: note: synthesized method
‘std::basic_ios<char, std::char_traits<char> >::basic_ios(const
std::basic_ios<char, std::char_traits<char> >&)’ first required here
/usr/include/c++/4.3/streambuf: In copy constructor
‘std::basic_filebuf<char, std::char_traits<char> >::basic_filebuf
(const std::basic_filebuf<char, std::char_traits<char> >&)’:
/usr/include/c++/4.3/streambuf:775: error:
‘std::basic_streambuf<_CharT, _Traits>::basic_streambuf(const
std::basic_streambuf<_CharT, _Traits>&) [with _CharT = char, _Traits =
std::char_traits<char>]’ is private
/usr/include/c++/4.3/iosfwd:83: error: within this context
/usr/include/c++/4.3/iosfwd: In copy constructor
‘std::basic_ofstream<char, std::char_traits<char> >::basic_ofstream
(const std::basic_ofstream<char, std::char_traits<char> >&)’:
/usr/include/c++/4.3/iosfwd:89: note: synthesized method
‘std::basic_filebuf<char, std::char_traits<char> >::basic_filebuf
(const std::basic_filebuf<char, std::char_traits<char> >&)’ first
required here
../scratch/adhoc_multihop_app.cc: In function ‘int main(int, char**)’:
../scratch/adhoc_multihop_app.cc:239: note: synthesized method
‘std::basic_ofstream<char, std::char_traits<char> >::basic_ofstream
(const std::basic_ofstream<char, std::char_traits<char> >&)’ first
required here
../scratch/adhoc_multihop_app.cc:239: error: initializing argument 2
of ‘ns3::Callback<R, T1, T2, ns3::empty, ns3::empty, ns3::empty,
ns3::empty, ns3::empty, ns3::empty, ns3::empty> ns3::MakeBoundCallback
(R (*)(TX, T1, T2), ARG) [with R = void, TX = uint32_t, ARG =
std::basic_ofstream<char, std::char_traits<char> >, T1 = uint32_t, T2
= std::ofstream&]’
debug/ns3/callback.h: In constructor ‘ns3::BoundFunctorCallbackImpl<T,
R, TX, T1, T2, T3, T4, T5, T6, T7, T8>::BoundFunctorCallbackImpl
(FUNCTOR, ARG) [with FUNCTOR = void (*)(unsigned int, unsigned int,
std::ofstream&), ARG = std::basic_ofstream<char,
std::char_traits<char> >, T = void (*)(unsigned int, unsigned int,
std::ofstream&), R = void, TX = unsigned int, T1 = unsigned int, T2 =
std::ofstream&, T3 = ns3::empty, T4 = ns3::empty, T5 = ns3::empty, T6
= ns3::empty, T7 = ns3::empty, T8 = ns3::empty]’:
debug/ns3/ptr.h:243: instantiated from ‘ns3::Ptr<T> ns3::Create(T1,
T2) [with T = ns3::BoundFunctorCallbackImpl<void (*)(unsigned int,
unsigned int, std::ofstream&), void, unsigned int, unsigned int,
std::ofstream&, ns3::empty, ns3::empty, ns3::empty, ns3::empty,
ns3::empty, ns3::empty>, T1 = void (*)(unsigned int, unsigned int,
std::ofstream&), T2 = std::basic_ofstream<char, std::char_traits<char>
>]’
debug/ns3/callback.h:925: instantiated from ‘ns3::Callback<R, T1,
T2, ns3::empty, ns3::empty, ns3::empty, ns3::empty, ns3::empty,
ns3::empty, ns3::empty> ns3::MakeBoundCallback(R (*)(TX, T1, T2), ARG)
[with R = void, TX = uint32_t, ARG = std::basic_ofstream<char,
std::char_traits<char> >, T1 = uint32_t, T2 = std::ofstream&]’
../scratch/adhoc_multihop_app.cc:239: instantiated from here
debug/ns3/callback.h:287: error: invalid conversion from ‘void*’ to
‘unsigned int’

The same happens if I pass, say, a class instance.
If I pass, say, a char or an int, the code compiles but it doesn't
trace cwnd changes (not even using wildcards in lieu of node numbers)
although packets are exchanged, according to the .pcap trace.

The complete source can be found here: http://pastebin.com/m2c6f2be6

--
Christian

cra...@ee.washington.edu

unread,
Sep 16, 2009, 2:12:06 PM9/16/09
to ns-3-...@googlegroups.com
Hi Christian,

Heh, heh. You have run into the really hard part -- the inevitable error
messages.

You are closer than you may think, though.

If it were me, I would try something new on a scale much smaller than you
have attempted. I would take the "simplest example" that I posted a few
days ago as a starting point and make a tiny change to try and turn it into
a bound callback to see what happened. If you did that, you might end up
with:

---------- Begin Included File ----------

#include <iostream>
#include "ns3/object.h"
#include "ns3/uinteger.h"

#include "ns3/traced-value.h"
#include "ns3/trace-source-accessor.h"

using namespace ns3;

class MyObject : public Object
{
public:
static TypeId GetTypeId (void)
{
static TypeId tid = TypeId ("MyObject")
.SetParent (Object::GetTypeId ())
.AddConstructor<MyObject> ()

.AddTraceSource ("MyInt",
"A Int value to trace.",
MakeTraceSourceAccessor (&MyObject::m_myInt))
;
return tid;
}

MyObject () {}
TracedValue<uint32_t> m_myInt;
};

void
IntTrace (uint32_t someBoundValue, uint32_t oldValue, uint32_t newValue)
{
std::cout << "Traced " << oldValue << " to " << newValue << " with " <<
someBoundValue << std::endl;
}

int
main (int argc, char *argv[])
{

Ptr<MyObject> myObject = CreateObject<MyObject> ();

uint32_t someBoundValue = 1234;

myObject->TraceConnectWithoutContext ("MyInt", MakeBoundCallback
(&IntTrace, someBoundValue));
myObject->m_myInt = 2468;
}

---------- End Included File ----------

You will find that this example works fine (Notice your version got the
bound value in the wrong place).

A physics professor of mine once called the following technique adiabatic
programming. Make a single tiny change to a working program. The next tiny
change would be to try and use the stream as the bound parameter.

---------- Begin Included File ----------

#include <iostream>
#include <fstream>
#include "ns3/object.h"
#include "ns3/uinteger.h"
#include "ns3/traced-value.h"
#include "ns3/trace-source-accessor.h"

using namespace ns3;

class MyObject : public Object
{
public:
static TypeId GetTypeId (void)
{
static TypeId tid = TypeId ("MyObject")
.SetParent (Object::GetTypeId ())
.AddConstructor<MyObject> ()

.AddTraceSource ("MyInt",
"A Int value to trace.",
MakeTraceSourceAccessor (&MyObject::m_myInt))
;
return tid;
}

MyObject () {}
TracedValue<uint32_t> m_myInt;
};

void
IntTrace (std::ofstream ascii, uint32_t oldValue, uint32_t newValue)
<========= change int to ofstream
{
std::cout << "Traced " << oldValue << " to " << newValue << " with " <<
ascii << std::endl;
}

int
main (int argc, char *argv[])
{

Ptr<MyObject> myObject = CreateObject<MyObject> ();

std::ofstream ascii; <===== change int to ofstream (and in the
MakeBoundCallback below)

myObject->TraceConnectWithoutContext ("MyInt", MakeBoundCallback
(&IntTrace, ascii));
myObject->m_myInt = 2468;
}

---------- End Included File ----------

But then when you try and build it -- boom, it blows up in your face.

---------- Begin Included Capture ----------

[ns-regression] ~/repos/ns-3-allinone-test/ns-3-dev > ./waf
Waf: Entering directory
`/home/craigdo/repos/ns-3-allinone-test/ns-3-dev/build'
[639/740] cxx: scratch/test-bound.cc -> build/debug/scratch/test-bound_2.o
/usr/include/c++/4.2/bits/ios_base.h: In copy constructor
â?~std::basic_ios<char, std::char_traits<char> >::basic_ios(const std:
:basic_ios<char, std::char_traits<char> >&)â?T:
/usr/include/c++/4.2/bits/ios_base.h:779: error:
â?~std::ios_base::ios_base(const std::ios_base&)â?T is private
/usr/include/c++/4.2/iosfwd:55: error: within this context
/usr/include/c++/4.2/iosfwd: In copy constructor
â?~std::basic_ofstream<char, std::char_traits<char> >::basic_ofstream(const
std
::basic_ofstream<char, std::char_traits<char> >&)â?T:
/usr/include/c++/4.2/iosfwd:92: note: synthesized method
â?~std::basic_ios<char, std::char_traits<char> >::basic_ios(const std::
basic_ios<char, std::char_traits<char> >&)â?T first required here
/usr/include/c++/4.2/streambuf: In copy constructor
â?~std::basic_filebuf<char, std::char_traits<char> >::basic_filebuf(const st
d::basic_filebuf<char, std::char_traits<char> >&)â?T:
/usr/include/c++/4.2/streambuf:794: error: â?~std::basic_streambuf<_CharT,
_Traits>::basic_streambuf(const std::basic_streambuf<
_CharT, _Traits>&) [with _CharT = char, _Traits = std::char_traits<char>]â?T
is private
/usr/include/c++/4.2/iosfwd:86: error: within this context
/usr/include/c++/4.2/iosfwd: In copy constructor
â?~std::basic_ofstream<char, std::char_traits<char> >::basic_ofstream(const
std
::basic_ofstream<char, std::char_traits<char> >&)â?T:
/usr/include/c++/4.2/iosfwd:92: note: synthesized method
â?~std::basic_filebuf<char, std::char_traits<char> >::basic_filebuf(con
st std::basic_filebuf<char, std::char_traits<char> >&)â?T first required
here
../scratch/test-bound.cc: In function â?~int main(int, char**)â?T:
../scratch/test-bound.cc:45: note: synthesized method
â?~std::basic_ofstream<char, std::char_traits<char> >::basic_ofstream(cons
t std::basic_ofstream<char, std::char_traits<char> >&)â?T first required
here
../scratch/test-bound.cc:45: error: initializing argument 2 of
â?~ns3::Callback<R, T1, T2, ns3::empty, ns3::empty, ns3::empty,


ns3::empty, ns3::empty, ns3::empty, ns3::empty> ns3::MakeBoundCallback(R

(*)(TX, T1, T2), ARG) [with R = void, TX = std::ofstre
am, ARG = std::basic_ofstream<char, std::char_traits<char> >, T1 = uint32_t,
T2 = uint32_t]â?T
Waf: Leaving directory
`/home/craigdo/repos/ns-3-allinone-test/ns-3-dev/build'
Build failed
-> task failed (err #1):
{task: cxx test-bound.cc -> test-bound_2.o}
[ns-regression] ~/repos/ns-3-allinone-test/ns-3-dev >

---------- End Included Capture ----------

But all you have done is changed the type of the bound variable!

This must be due to that variable change. It turns out that an ofstream
doesn't have a copy constructor. Think about what it would mean to copy and
ofstream. What are the semantics? Since you can't predict that correctly,
ofstream doesn't allow copies.

It turns out that an ofstream doesn't have value semantics!

You have run into a problem using the stl, not ns-3 :-)

You have to pass around the *address* of the ofstream instead of passing it
by value.

---------- Begin Included File ----------

#include <iostream>
#include <fstream>
#include "ns3/object.h"
#include "ns3/uinteger.h"
#include "ns3/traced-value.h"
#include "ns3/trace-source-accessor.h"


using namespace ns3;

class MyObject : public Object
{
public:
static TypeId GetTypeId (void)
{
static TypeId tid = TypeId ("MyObject")
.SetParent (Object::GetTypeId ())
.AddConstructor<MyObject> ()

.AddTraceSource ("MyInt",
"A Int value to trace.",
MakeTraceSourceAccessor (&MyObject::m_myInt))
;
return tid;
}

MyObject () {}
TracedValue<uint32_t> m_myInt;
};

void
IntTrace (std::ofstream *ascii, uint32_t oldValue, uint32_t newValue)
<===== pointer to ofstream!
{
std::cout << "Traced " << oldValue << " to " << newValue << " with " <<
ascii << std::endl;
}

int
main (int argc, char *argv[])
{

Ptr<MyObject> myObject = CreateObject<MyObject> ();

std::ofstream ascii;

myObject->TraceConnectWithoutContext ("MyInt", MakeBoundCallback
(&IntTrace, &ascii));
myObject->m_myInt = 2468;
}

---------- End Included File ----------

This compiles just fine.

We usually frown on passing pointers around in ns-3, but prefer to use
Objects and our reference counting system. The example above works, but you
have got to be very, very careful about the lifetime of that ofstream.

If you look at the CsmaHelper, you will see that the MakeBoundCallback takes
a Ptr<PcapWriter> as the bound parameter. This points to an Object that, in
turn, holds the ofstream object; and so you can pass the Ptr<PcapWriter>
around by value just fine. As long as someone has a reference to the
underlying writer it stays around taking care of the lifetime issue
automagically.

So I would take that approach and make my own kind of writer.

I think you are getting really, really close here.

Regards,

-- Craig


C Facchini

unread,
Sep 16, 2009, 6:48:10 PM9/16/09
to ns-3-users
Thank you so much!
It works, plus now I got where I was stuck.

> We usually frown on passing pointers around in ns-3, but prefer to use
> Objects and our reference counting system.  The example above works, but you
> have got to be very, very careful about the lifetime of that ofstream.
>
> If you look at the CsmaHelper, you will see that the MakeBoundCallback takes
> a Ptr<PcapWriter> as the bound parameter.  This points to an Object that, in
> turn, holds the ofstream object; and so you can pass the Ptr<PcapWriter>
> around by value just fine.  As long as someone has a reference to the
> underlying writer it stays around taking care of the lifetime issue
> automagically.
>
> So I would take that approach and make my own kind of writer.
I've noticed that every EnableAscii() resorts to AsciiWriter, but I
thought it was overly complicated for my programming skills (that's
why I decided to use just an ostream, which turns out to be more harm
than good as the programmer in question doesn't know how to tame wild
pointers :).

I tried writing my own AsciiWriter and have some scope issues. But
since this problem is not Callback-related, I'd better start another
topic.
Reply all
Reply to author
Forward
0 new messages