OOPSMP: Plug-and-Play

Plug-and-Play

The user specifies in an XML file the class objects that should be created and the class functions that should be called. OOPSMP interprets the XML file using DLLs or shared libraries and factory classes which produce instances of class objects and call class functions based on their names. The XML files included with the OOPSMP distribution are found in OOPSMP/input/xml

Follow the links below for more information:

Plug-and-play: Registration

Classes and class functions are registered for plug-and-play using macros provided by OOPSMP. These macros indicate whether or not the class is abstract, if it extends any base class, and which class functions should be registered for plug-and-play. The following lines of code illustrate what must be added to the header and source files of a class ClassName in order to successfully register the class ClassName and several of the ClassName functions:
 //header file of ClassName class
 DeclareInstanceFactory(ClassName, BaseClassNameFactory); 
 //source file of ClassName class
 BeginImplementInstanceFactory(ClassName, BaseClassNameFactory);
 RegisterFnFactory(fn1Name, fn1_arg_types);
 RegisterFnFactory(fn2Name, fn2_arg_types);
 ...
 EndImplementInstanceFactory(ClassName);
 //NOTE: if ClassName is abstract, use DeclareNoInstanceFactory, BeginImplementNoInstanceFactory, EndImplementNoInstanceFactory
 //      if ClassName does not extend any classes, BaseClassName is empty, i.e., use Factory instead of BaseClassNameFactory
If the class function fnName does not receive any arguments, then it is registered using RegisterFnFactory(fnName). Otherwise, the argument types are specified as a comma-separated list, where the macro for the i-th argument is given by the following table:
 if type = {bool, int, float, double, char}                       then use FACTORY_VAL_ARG(type, i)
 if type = {bool*, int*, float*, double*, char*, Class*, Class**} then use FACTORY_PTR_ARG(type, i)
 //NOTE: Class refers to any class that has been registered for plug-and-play
Illustration of C++ code for registering an abstract class and some of its functions:
 //header file of Example class
 class Example
 {
 public:
    Example(void);
    virtual ~Example(void);
    void fn1(void);
    void fn2(const double x, const int y);
    virtual void fn3(char *str, double *array) = 0;
    void fn4(void);
 };
 //register abstract Example class for plug-and-play functionality 
 DeclareNoInstanceFactory(Example, Factory); 
 //source file of abstract Example class  
 BeginImplementNoInstanceFactory(Example, Factory);
   RegisterFnFactory(fn1);
   RegisterFnFactory(fn2, FACTORY_VAL_ARG(const double, 0), FACTORY_VAL_ARG(const int, 1));
   RegisterFnFactory(fn3, FACTORY_PTR_ARG(char*, 0), FACTORY_PTR_ARG(double*, 1));
 EndImplementNoInstanceFactory(Example);
Illustration of C++ code for registering a non-abstract class:
 //header file of Example2 class
 class Example2 : public Example
 {
 public:
    Example2(void);
    virtual ~Example2(void);
    void fn3(char *str, double *array);
    void fn5(Example *example);     
 }; 
 //register non-abstract Example2 class for plug-and-play functionality
 DeclareInstanceFactory(Example2, ExampleFactory);
 //source file of non-abstract Example2 class
 BeginImplementInstanceFactory(Example2, ExampleFactory);
   RegisterFnFactory(fn5, FACTORY_PTR_ARG(Example*, 0));
 EndImplementInstanceFactory(Example2); 

Plug-and-play: XML Syntax

The user specifies in an XML file the class objects that should be created and the class functions that should be called. The XML syntax given in Bacus-Naur form (BNF) is as follows:
    XML_factory      ::= <factory instance="XML_className"> XML_callFns </factory> 
    XML_className    ::= XML_string 
    XML_callFns      ::= empty | XML_callFn XML_callFns       
    XML_callFn       ::= <call fn="XML_fnName"> XML_fnArgs </call>
    XML_fnName       ::= XML_string 
    XML_fnArgs       ::= empty | XML_fnArg XML_fnArgs
    XML_fnArg        ::= <arg type="bool"> XML_bool </arg> |
                         <arg type="int"> XML_int </arg> |
                         <arg type="float"> XML_float </arg> |
                         <arg type="double"> XML_double </arg> |
                         <arg type="char"> XML_char </arg> |
                         <arg type="string" XML_keep > XML_string </arg> |
                         <arg type="pointer" > XML_pointer </arg> |
                         <arg type="bool_array" XML_keep > XML_bool_array </arg> |
                         <arg type="int_array" XML_keep > XML_int_array </arg> |
                         <arg type="float_array" XML_keep  > XML_float_array </arg> |
                         <arg type="double_array" XML_keep > XML_double_array </arg> |
                         <arg type="char_array" XML_keep > XML_char_array </arg> |
                         <arg type="string_array" XML_keep > XML_string_array </arg> |
                         <arg type="pointer_array" XML_keep > XML_pointer_array </arg>
    XML_bool         ::= false | true
    XML_int          ::= standard C/C++ definition, i.e., whatever scanf uses with %d  
    XML_float        ::= standard C/C++ definition, i.e., whatever scanf uses with %f 
    XML_double       ::= standard C/C++ definition, i.e., whatever scanf uses with %lf 
    XML_char         ::= standard C/C++ definition, i.e., whatever scanf uses with %c 
    XML_string       ::= standard C/C++ definition, i.e., whatever scanf uses with %s 
    XML_pointer      ::= XML_factory
    XML_bool_array   ::= XML_bool | XML_bool XML_space XML_bool_array
    XML_int_array    ::= XML_int | XML_int XML_space XML_int_array
    XML_float_array  ::= XML_float | XML_float XML_space XML_float_array
    XML_double_array ::= XML_double | XML_double XML_space XML_double_array 
    XML_char_array   ::= XML_char | XML_char XML_char_array 
    XML_string_array ::= XML_string | XML_string XML_space XML_string_array 
    XML_pointer_array::= XML_pointer | XML_pointer XML_pointer_array 
    XML_keep         ::= empty | keep="true" | keep="false"    
    XML_space        ::= standard C/C++ definition, i.e., whatever scanf defines as white space
The correspondence between a function argument and the XML specification for that argument is given by the following table:
    if type is              then        use
    const bool    | bool    ::= <arg type="bool"> XML_bool </arg>
    const int     | int     ::= <arg type="int"> XML_int </arg>
    const float   | float   ::= <arg type="float"> XML_float </arg>
    const double  | double  ::= <arg type="double"> XML_double </arg>
    const char    | char    ::= <arg type="char"> XML_char </arg>
    const char*   | char*   ::= <arg type="string"> XML_string </arg>
    const Class*  | Class*  ::= <arg type="pointer"> XML_pointer </arg>
    
    const bool*   | bool*   ::= <arg type="bool_array"> XML_bool_array </arg>
    const int*    | int*    ::= <arg type="int_array"> XML_int_array </arg>
    const float*  | float*  ::= <arg type="float_array"> XML_float_array </arg>
    const double* | double* ::= <arg type="double_array"> XML_double_array </arg>
    const char*   | char*   ::= <arg type="char_array"> XML_char_array </arg>
    const char**  | char**  ::= <arg type="string_array"> XML_string_array </arg>
    const Class** | Class** ::= <arg type="pointer_array"> XML_pointer_array </arg>
    
    NOTE:
       Class refers to any class that has been registered for plug-and-play.
After a function call specified in the XML, the memory allocated to the i-th argument is not freed iff the argument is a class pointer or the attribute keep="true" is present in the XML specification of the argument, e.g.,
   <arg type="string" keep="true"> XML_string </arg>
   <arg type="bool_array" keep="true"> XML_bool_array </arg>
   <arg type="int_array" keep="true"> XML_int_array </arg>
   <arg type="float_array" keep="true"> XML_float_array </arg>
   <arg type="double_array" keep="true"> XML_double_array </arg>
   <arg type="char_array" keep="true"> XML_char_array </arg>
   <arg type="string_array" keep="true"> XML_string_array </arg>
   <arg type="pointer_array" keep="true"> XML_pointer_array </arg>
Example of an XML user file that specifies the class objects that should be created and the class functions that should be called (the XML file has a similar effect as the C++ code indicated alongside the XML):
  <factory instance="Example2">                  # Example2 *ex2 = new Example2();
   
    <call fn="fn1">                              #
    </call>                                      # ex2->fn1();
   
    <call fn="fn3">                              # 
      <arg type="string" keep="true">abcdef</arg># char   *s = strdup("abcdef");
      <arg type="double_array">1.0 2.0 3.0</arg> # double *a = (double *) malloc(4, sizeof(double)); a[0]=1.0; a[1]=2.0; a[2]=3.0; 
    </call>                                      # ex2->fn3(s, a);  free(a);
  
    <call fn="fn2">                              #
      <arg type="double">1.0</arg>               #
      <arg type="int">4</arg>                    #
    </call>                                      # ex2->fn2(1.0, 4);  
   
    <call fn="fn5">                              #
       <arg type="pointer">                      #
          <factory instance="Example2">          # Example2 *ex = new Example2();
             <call fn="fn2">                     #
               <arg type="double">3.0</arg>      #
               <arg type="int">5</arg>           #
             </call>                             # ex->fn2(3.0, 5);
             <call fn="fn1">                     # 
             </call>                             # ex->fn1(); 
          </factory>                             #
       </arg>                                    #
    </call>                                      # ex2->fn5(ex);
   
  </factory>

Plug-and-Play: How is it done?

The plug-and-play functionality of OOPSMP is achieved through the use of dynamically linked libraries (DLLs or shared libraries as commonly referred to in unix/linux systems) and factory classes which produce instances of class objects and call class functions based on their names. OOPSMP in a way acts as a simple interpreter of XML files, which, as said before, can be used to specify the class objects that should be created and the class functions that should be called. Let's look again at the C++ illustration code for registering the classes Example and Example2, but this time with macros expanded:
  DeclareNoInstanceFactory(Example, Factory); 
  //when expanded, the macro produces the following code:
  //  class ExampleFactory : public Factory
  //  {
  //  public:
  //    ExampleFactory(void);
  //    virtual ~ExampleFactory(void);
  //    virtual bool callFn(OOPSMPpointer_t obj, const char * const name, OOPSMPpointer_t *args);
  //  };

  BeginImplementNoInstanceFactory(Example, Factory);
    RegisterFnFactory(fn1);
    RegisterFnFactory(fn2, FACTORY_VAL_ARG(const double, 0), FACTORY_VAL_ARG(const int, 1));
    RegisterFnFactory(fn3, FACTORY_PTR_ARG(char*, 0), FACTORY_PTR_ARG(double*, 1));
  EndImplementNoInstanceFactory(Example);
  //when expanded, the macros produce the following code:
  //  OOPSMP::ExampleFactory::ExampleFactory(void) : OOPSMP::Factory() {}
  //  OOPSMP::ExampleFactory::~ExampleFactory(void) {}
  //  bool OOPSMP::ExampleFactory::callFn(OOPSMPpointer_t obj, const char * const name, OOPSMPpointer_t *args)
  //  {
  //      Example *my = static_cast<Example *>(obj);
  //      if(Factory::callFn(obj, name, args) == false)
  //      { 
  //         if(0) { return true || my == NULL; }
  //         else if(strcmp(name, "fn1") == 0) { my->fn1(); return true; }
  //         else if(strcmp(name, "fn2") == 0) { my->fn2(*((const double *) args[0]), *((const int *) args[1])); return true; }
  //         else if(strcmp(name, "fn3") == 0) { my->fn3((char *) args[0], (double *) args[1]); return true; }   
  //         return false;
  //      }
  //      return true;
  //  }

  DeclareInstanceFactory(Example2, ExampleFactory);
  //when expanded, the macro produces the following code:
  //  class Example2Factory : public ExampleFactory
  //  {
  //  public:
  //    Example2Factory(void);
  //    virtual ~Example2Factory(void);
  //    virtual OOPSMPpointer_t createInstance(void);
  //    virtual bool callFn(OOPSMPpointer_t obj, const char * const name, OOPSMPpointer_t *args);
  //  };

  BeginImplementInstanceFactory(Example2, ExampleFactory);
    RegisterFnFactory(fn5, FACTORY_PTR_ARG(Example*, 0));
  EndImplementInstanceFactory(Example2); 
  //when expanded, the macros produce the following code:
  //  OOPSMP::Example2Factory::Example2Factory(void) : OOPSMP::ExampleFactory() {}
  //  OOPSMP::Example2Factory::~Example2Factory(void) {}
  //  bool OOPSMP::Example2Factory::callFn(OOPSMPpointer_t obj, const char * const name, OOPSMPpointer_t *args)
  //  {
  //      Example2 *my = static_cast<Example2 *>(obj);
  //      if(ExampleFactory::callFn(obj, name, args) == false)
  //      { 
  //           if(0) { return true || my == NULL; }
  //         else if(strcmp(name, "fn5") == 0) { my->fn5((Example *) args[0]); return true; }
  //         return false;
  //      }
  //      return true;
  //  } 
  //  OOPSMPpointer_t OOPSMP::Example2Factory::createInstance(void) { return new Example2(); }
  //  extern "C" OOPSMP::Factory* Example2_createFactoryFn(void) { return new OOPSMP::Example2Factory(); }
When parsing the XML specification upon seeing the line <factory instance="Example2">, the loaded DLLs are searched for a "C" function Example2_createFactoryFn. A call to this function returns an object factory of the Factory class. A call to the factory->createInstance() function creates an object of the class Example2, as illustrated in the C++ code below:
    createFactoryFn_t createFactoryFn = (createFactoryFn_t) OOPSMPget_symbol_dll(dlls, "Example2_createFactoryFn");
    Factory          *factory         = Example2_createFactoryFn(); 
    OOPSMPpointer_t   object          = factory->createInstance();
Then the XML specification is parsed to see which functions are called and what the arguments to those functions should be. To continue the illustration, upon parsing the XML fragment
    &lt;call fn="fn2">
      &lt;arg type="double"&gt;1.0&lt;/arg&gt;
      &lt;arg type="int"&gt;4&lt;/arg&gt;
    &lt;/call&gt;     
OOPSMP calls the function
    factory->callFn("fn2", args)
where args is an array of two elements and args[0] and args[1] are pointers of type double * and int * and the contents of these pointers are 1.0 and 4, respectively. The call to factory->callFn("fn2", args) produces a call to
       ((Example2 *) object)->fn2(*((double *) args[0]), *((int *) args[1]));
If arg[i] is not a pointer to a class object, then the memory associated with arg[i] is freed at the end of factory->callFn call unless the attribute keep="true" is present in the XML specification of the i-th argument.