Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

Dynamic Message Passing in C++


August 1996:

Dynamic Message Passing in C++

Connecting objects at run time

Pierre Arnaud

Pierre is working towards a PhD at the Swiss Federal Institute of Technology. He can be reached at Pierre.Arnaud@ di.epfl.ch or http://diwww.epfl.ch/w3lami/ team/arnaud/.


A couple of years ago, I wrote a formula editor in Objective-C. Later, when I switched back to C++, I missed the flexibility of Objective-C's message-passing capabilities, so I implemented dynamic message passing in C++. In this article, I'll present the techniques that make this possible, and illustrate dynamic message passing by way of a set of macros and classes I developed for a GUI project called "OPaC." This project provides a set of operating-system-independent classes that let you build and modify a user interface with only a few mouse clicks-even from within a running application.

When OPaC establishes a connection between two objects (a button and an active object waiting for clicks) at run time, it sends a message to an instance without knowing the name of the instance, or the number and type of its arguments at compile time. This can easily be done in Objective-C, but not in C++ (at least not without some additional code).

Calling Methods in Objective-C

As Example 1(a) illustrates, Objective-C uses a special syntax to call methods (that is, send messages). The first argument in the brackets is the receiving object, the second is the selector, and subsequent ones are parameters. The selector specifies the method to be called. The parameters must be preceded by tags, which are also part of the selector's name.

The receiving object can be of any type. If it is known at compile time, the compiler will be able to generate more efficient code and check for the validity of the message. If it is unknown, the compiler will look up the selector and find the associated method dynamically. If it is undefined for the receiving object, a run-time error occurs.

The selector need not be specified statically when compiling. It may be stored in an SEL type variable. The compiler provides a special syntax, @selector(...), to initialize these variables. They may also be initialized using sel_getUid(...), a function provided by the Objective-C library, which converts C strings to selector codes.

The base class Object implements the special method perform:, which accepts a selector and its parameters as arguments. The library also provides a __msg function with the same behavior; see Example 1(b).

Calling Methods in C++

You can also call methods of unknown objects in C++, but their superclass must be known at compile time, and the methods have to be defined as virtual for this to work; see Example 2. The compiler generates a virtual table (often called vtbl) to find the pointer to the proper method implementation at run time. Figure 1 illustrates how this is done.

It is possible to use a pointer to a member function instead of a direct method call, in which case the method to be called can be stored in a variable (like selectors in Objective-C); see Example 3(a). In fact, these pointers are just offsets in the vtbl, enabling the compiler to find the corresponding method. A pointer to a member function can only be used with the corresponding class, or one of its subclasses. It is forbidden to cast a pointer to a member function from a class A to a class B, if both classes do not inherit the method from a common superclass, as in Example 3(b). Moreover, there is no direct way in C++ to convert a string into a pointer to a member function.

Introducing Dynamic C++

When I started developing OPaC, having a hybrid Objective-C/C++ compiler that accepted both calling conventions would have been nice. I felt that it must have been possible to implement dynamic message passing in C++, so I experimented with operator overloading, developing the presentable-looking syntax object<-method arg1 arg2;. This expression is similar to the conventional way of calling methods: object->method (arg1, arg2);, but allows method to be a variable. In fact, it has to be a variable in order to work. The syntax is perfectly legal C++, even if it does look somewhat odd.

In C++, there is no operator <-, although it is possible to overload existing operators to produce new, interesting behaviors. I simply redefined the operators < and unary -, which produce the left-pointing arrow when written side-by-side.

Example 4, which means that o<-m; is interpreted as o.operator < (m.operator -()), illustrates the definitions of both overloaded operators. While it is nice to build syntactically valid C++ source code, it isn't necessarily sufficient. For o<-m to send a message, the base class Object implements a generic dispatching operator (operator <), which behaves somewhat like the Objective-C perform: selector. The only argument this operator accepts, however, is a message description that contains the name of the method to be called and the associated parameters. The operator then calls Message::Send to do the real job. The Message class implements a storage object that provides operators to store and restore simple data types, such as int, float, strings, pointers to objects, and the like. Before any data can be stored in the message, Message::Clear must be called. This clears any data that might have been previously added. Message::Send must have been called before any data can be restored; this is done automatically. The target method must be defined using Message::SetMethod, which requires a C string as its argument. This only needs to be done once and can be implemented directly by the constructor of the Message class. Consequently, when you write o<-m arg1 arg2;, the compiler goes through the steps:

    1. m.operator -();

      2. m arg1 arg2;

        3. o.operator < (m);

      The first operation calls m.Clear(), clearing any previously stored data. The second operation stores arg1 and arg2 into a linked list of typed unions, associated to instance m. The third operation calls m.Send (&o), with a pointer to Object as argument. This is the hardest part of the job.

      Calling through Trampolines

      The Message class is responsible for calling the correct method when Send is invoked, based on a pointer to the object and a C string naming the method. It relies on the Runtime run-time class, which has exactly one instance for each class derived from Object. It describes the properties of each class and maintains a list of "trampolines" to methods which may be called dynamically.

      Trampolines are simple C functions, such as Bool f (Message&m, Object* o);, that are generated automatically by a set of macros. Example 5 describes a rampoline function for a simple method. The purpose of a trampoline is to extract the parameters of the target method from the message, and to call the method associated to the object. With this mechanism, there is no need to use a pointer to a member function; moreover, no casting is involved and strong type checking can still be done by the compiler.

      Each class derived from Object provides a virtual GetRuntime method, which returns a pointer to the associated Runtime instance. This can be used to find information about any class at run time, thus finding the trampoline to every dynamically callable method. m.Send goes through the following steps:

      1. It looks for the run-time description of the object by calling the associated GetRuntime method.

      2. It then calls method GetFunction of the run-time description instance to get the pointer to the associated trampoline function.

      3. Finally, it calls this trampoline function to complete the job.

      The trampoline function restores the message parameters using operator , then calls the method of the proper class, as defined by the macros.

      Building Trampolines

      Trampoline functions are built automatically by the macros PUBLISH_METHOD (class, method);, PUBLISH_ARG (type, arg);, and PUBLISH_CALL (method (arg));.

      The first macro takes the class and method names as arguments. It generates part of the body of the trampoline function. The name of the trampoline function is built by concatenating trampo, the class name, and the method name. This ensures that each trampoline will get a unique name. The second macro must be repeated once for each parameter, specifying its type and name. It adds the code that will restore these parameters from the message description.

      The third macro specifies how the method will have to be called with the associated parameters. It adds the actual method invocation and closes the body of the trampoline.

      Registering Trampolines

      The trampolines still need to be registered by the Runtime class so they can be found whenever needed. I used an automatic registration class named AutoRuntimeSetup, which has one instance for each trampoline. It does nothing but inform Runtime about the function when its constructor is called; see Example 6(a).

      Three arguments are required by the constructor:

      Pointer to the name of the method for which the trampoline has been built.

      A reference to the run-time description.

      A pointer to the trampoline.

      An instance of AutoRuntimeSetup is added by the PUBLISH_METHOD macro. Its name is built by concatenating auto, the class name, and the method name. Example 6(b) shows how a trampoline is registered using this macro.

      One More Macro.

      One additional macro is PUBLISH_ CLASS, which implements the GetRuntime method and adds the required Runtime instance. This macro must be used once for each class, preferably in the file where constructor and destructor are implemented.

      Conclusion

      The sources I have described here (available electronically) implement the simplified versions of the macros and classes I use in my projects. The code compiles both with GNU C++ and Visual C++. It is easy to add more information to the Runtime class; for example, you could add a link to the superclass, provide details about the arguments of the published methods, add a list of the fields stored in the class or revision number, and the like. I have kept it simple to make the sources easier to understand, since some techniques may seem tricky to novices.

      Among the files available electronically are dynamic.h (a definition file that defines the macros and classes), runtime.cpp and dynamic.cpp (which implement the Runtime, Object, and Message classes), and demo.cpp (a test class that prints messages when its methods get called). When examining these files, note the way I have inserted elements into linked lists. I use the (a?b:c)=d construct, which sets either b or c to d, depending on a being True. I prefer this notation because it is more compact than the equivalent if (a)b=d; else c=d; construct.

      Figure 1: An instance and a vtbl generated by the compiler.

      Example 1: (a) Sending messages in Objective-C; (b) specifying the selector at run time.

      (a)
      
      [myButton setName: "Ok"];
      [myButton setSizeDx: 100 dy: 20];
      [[myDialog getButton: "Ok"] disable];
      
      (b)
      
      SEL action = @selector (setName:);
      [myButton perform:action with: "Ok"];
      //  ...which is the same as:
      __msg (myButton, action, "Ok");
      

      Example 2: Calling virtual methods in C++.

      class Base {
      public:
          virtual void Foo ();
      };
      void Base::Foo () { printf ("Base::Foo\n"); }
      class Other : public Base {
      public:
          virtual void Foo ();
      };
      void Other::Foo () { printf ("Other::Foo\n"); }
      void test (Base* object) { object->Foo (); }
      void main ()
      {
          Base  base;
          Other other;
          test (&base);    // prints "Base::Foo"
          test (&other);   // prints "Other::Foo" 
      }
      
      
      Example 3: (a) Using a pointer to a member function; (b) associated restrictions.
      (a)
      
      typedef void (Base:: *PtrMember)  ();
      void test (Base* object)
      {
          PtrMember ptr = Base::Foo;
          (object->*ptr) (); // calls object->Foo ()
      }
      
      
      
      (b)
      
      class A { /* ... */ };
      class B { /* ... */ };
      
      typedef void (A::*PtrAMember)();
      typedef void (B::*PtrBMember)();
      
      void test (A* a, B* b, PtrAMember pa)
      {
          PtrBMember pb = (PtrBMember)(pa);  // cast error
          (a->*pa) ();
          (b->*pa) ();                      // error, bad base class
          (b->*pb) ();
      }
      
      

      Example 4: Definitions of overloaded operators.

      Bool Object::operator < (Message&);
      Message& Message::operator - ();
      
      

      Example 5: Trampoline function for a simple method.

      Bool
      trampo_ButtonSetSize (Message& message, Object* object)
      {
          Button* instance = (Button*)(object);
          int dx; message  dx;
          int dy; message  dy;
          return (instance) ? instance->SetSize (dx, dy) : FALSE;
      }
      

      Example 6: (a) Automatic registration class; (b) registering the trampoline for SetSize.

      (a)
      class AutoRuntimeSetup
      {
      public:
          AutoRuntimeSetup (const char* name, Runtime& run, TrampoFunc func)
          { run.AddFunction (name, func); }
      };
      
      (b)
      AutoRuntimeSetup auto_ButtonSetSize ("SetSize", runtime_Button,
      trampo_ButtonSetSize);
      
      


      Related Reading


      More Insights






      Currently we allow the following HTML tags in comments:

      Single tags

      These tags can be used alone and don't need an ending tag.

      <br> Defines a single line break

      <hr> Defines a horizontal line

      Matching tags

      These require an ending tag - e.g. <i>italic text</i>

      <a> Defines an anchor

      <b> Defines bold text

      <big> Defines big text

      <blockquote> Defines a long quotation

      <caption> Defines a table caption

      <cite> Defines a citation

      <code> Defines computer code text

      <em> Defines emphasized text

      <fieldset> Defines a border around elements in a form

      <h1> This is heading 1

      <h2> This is heading 2

      <h3> This is heading 3

      <h4> This is heading 4

      <h5> This is heading 5

      <h6> This is heading 6

      <i> Defines italic text

      <p> Defines a paragraph

      <pre> Defines preformatted text

      <q> Defines a short quotation

      <samp> Defines sample computer code text

      <small> Defines small text

      <span> Defines a section in a document

      <s> Defines strikethrough text

      <strike> Defines strikethrough text

      <strong> Defines strong text

      <sub> Defines subscripted text

      <sup> Defines superscripted text

      <u> Defines underlined text

      Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

       
      Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.