Automatic Factory from Microsoft

Environment: Microsoft VC6

In short

This document describes a mechanism to create instances of classes using their classname. The list of classes that can be created using their classname happens automatically (when new classes are defined, they automatically become part of the list of registered classes). The mechanism is very useful when the class that must be created depends on external input (e.g. registry settings, script file etc).

In many applications I make use of the Factory pattern. To avoid having to write and maintain code to select what class to create I've created a mechanism that automatically registers classes into a static 'registration'-class. Because I did not find an official name for the mechanism, I called it the 'automatic factory' mechanism. The 'automatic' points to the behavior of the classes that are registered into a central list without having to write this code explicitly.

During the development of this mechanism, I took a more closer look under the hood of the DECLARE_DYNAMIC / DECLARE_DYNCREATE and DECLARE_SERIAL macros from Microsoft (actually I noticed the base-concepts of these macro's and my own mechanism were similar).

As a result of this 'closer MFC examination', I created a utility class that makes creation of classes based upon the string representation of the classtype possible (without having to write and maintain registration code).

The standard MFC mechanism to create a class dynamically uses the following syntax:

#include <MyBaseClass.h>
#include <MyClass.h>

// Get static CRuntimeClass instance belonging to the class.
CRuntimeClass* pRuntimeClass = RUNTIME_CLASS( CMyClass );
// Create instance (CObject*) and check if this is the 
// classtype (or derived from) we expect (using dynamic_cast).
CMyBaseClass* pClass  = 
   dynamic_cast<CMyBaseClass*> (pRuntimeClass->CreateObject()); 

Code 1: Creating instances the using CRuntimeClass.

In this piece of code, the class to create ('CMyClass' in the example) must be known to the routine that creates the instance at compile time, or the CRuntimeClass* of a class to create must be looked up based on some criterion (a mapping of the input to the CRuntimeClass-instance of the class to create must be created).

The automatic factory automatically creates a mapping from the name of the class to the CRuntimeClass-instance that belongs to that name.

Constructing instances using the automatic factory looks like this:

#include <MSAutofactory.h>
#include <MyBaseClass.h>

// CREATE_OBJECT returns a pointer to a CObject. To be certain 
// that the instance is derived from the 
// expected type, a dynamic_cast is used.
CMyBaseClass* pClass = dynamic_cast<CMyBaseClass*>( 
      CREATE_OBJECT(_T("CMyClass"))); // Only base class is needed.

Code 2: Creating instances using CREATE_OBJECT.

or, more shortly (the returned instance is safely casted to the base class inside the internal (template)function).
#include <MSAutofactory.h>
#include <MyBaseClass.h>

CMyBaseClass *pClass = NULL;
// CMyClass is constructed. The constructed instance is safely 
// casted to the type of the input
// parameter (which is CMyBaseClass*) in this example.
CREATE_CHECKED_OBJECT ( _T("CMyClass"), pClass );

Code 3: Creating instances using CREATE_CHECKED_OBJECT.

Construction instances this way is extremely helpful when the type that must be created depends on an 'external source' (e.g. a configuration file). When new class-types are added to the application, they are immediately looked up (and created) without the need of writing special code.

Preconditions

The preconditions are also described in the MSDN (article: "Serialization: Making a Serializable Class").

In short the preconditions are:

  1. CObject must be the base class (or part of the inheritance hierarchy).
  2. The class must have a default constructor.
  3. DECLARE_SERIAL macro.
  4. IMPLEMENT_SERIAL macro.
  5. The class may not be a template class.

A class that fulfills these preconditions is automatically registered to the current module state when the program is started. Read "Creating Instances" for a description how to create instances based upon the name of the classtype.

Example:

Header file:
class CSomeBaseClass : public CObject  // 1 <- Derived from CObject
{
  DECLARE_SERIAL(CSomeBaseClass)       // 2 <- DECLARE_SERIAL
public:
  CSomeBaseClass() {}                  // 4 <- Default constructor.
};

class CSomeDerivedClass : public CSomeBaseClass
{
  DECLARE_SERIAL(CSomeDerivedClass)
public:
  CSomeDerivedClass() {}
};
Source file:
// Precondition 4. Use the IMPLEMENT_SERIAL macro's.
IMPLEMENT_SERIAL ( CSomeBaseClass,    CObject,        1 )
IMPLEMENT_SERIAL ( CSomeDerivedClass, CSomeBaseClass, 1 )

Code 4: Defining constructable classes.

In this example code, 2 (empty) classes are defined that met the preconditions. When the program is started, the static CRuntime-instances of these 2 classes are registered to the module-state of the current application.

Creating instances.

Creating an instance of a class based upon the classtype-name is just a matter of looking up the CRuntime-class information of the class that you want to instantiate followed by a call to the CreateObject factory-function.

According to the MSDN, the 'CRuntimeClass::FromName' function can be used for this. Unfortunately, this function exists for the Windows CE-library, but not for the 'normal' Windows MFC library (belonging to Visual C++ 6.0).

For that reason, I created a (static) utility class that implements this missing functionality and also adds routines to give insight in the list of classes that can be constructed. To avoid even more 'code-typing effort', macros have been written to create instances of a given class type (look inside the MSAutoFactory.h file for a description of the various functions and their parameters).

Creating instances can be done using 2 functions:

  1. CObject* CreateObject ( LPCTSTR strClassName, bool CurrentModuleOnly = false )
    or use the macro
    CREATE_OBJECT( _classname_ )

    Create an instance of the type described in 'strClassName' and return the instance (CObject is always the base class).
  2. template <class T> bool CreateObject ( LPCTSTR strClassName, T*& pConstructedInstance, bool CurrentModuleOnly = false )
    or use the macro
    CREATE_CHECKED_OBJECT( _classname_, _outputtype_ )
    Create an instance of the classtype described in 'strClassName'. When this succeeds, a 'dynamic_cast' is used to cast the instance to the type of the parameter (the pConstructedInstance or _outputtype_).

The usage of these macro's is shown in the small example code below.


// Create a "CSomeDerivedClass" instance, check if it 
// is derived from CSomeBaseClass 
// (internally dynamic_cast is used, using the input type as 
// (template) parameter) and export it.
CSomeBaseClass *pInstance = NULL;
if ( CREATE_CHECKED_OBJECT ( _T("CSomeDerivedClass"),
                             pInstance );
{
  // Do something with the pointer you've created.
  ..
  delete ( pInstance );
}

// Alternative way (do the dynamic_cast yourself)
CObject *ptrObject = CREATE_OBJECT( _T("CSomeDerivedClass"));
CSomeBaseClass *pInstance2 = 
       dynamic_cast<CSomeBaseClass*>( ptrObject );
if ( pInstance2 )
{
  // Do something with the instance.
  .. 
  delete ( pInstance2 );
}

Code 5: Example constructing instances using the macros.

Notes.

Some notes have to made for this mechanism however:

  • Most important: The mechanism uses an internal structure of Microsoft. Although the chance of changes to this structure is small, it may happen in future releases of Visual C++.
  • Microsoft offers the following macros:
    DECLARE_DYNAMIC This macro will Add CRuntime-information to a class.
    DECLARE_DYNCREATE This macro adds and fills the 'm_pfnCreateObject' factory function.
    DECLARE_SERIAL       ; This macro adds a.o. registration of the class.
    The class only registers itself when DECLARE_SERIAL is used, when the other macros are used, the classtype is not registered.
  • The IMPLEMENT_SERIAL macro needs a 'wSchema' parameter. This parameter is used to detect if the structure of class-data that is serialized from a stream into memory has changed (refer to the serialization mechanism). For the Automatic Factory functionality it has no meaning so any number will be OK.
  • The mechanism supports statically linked DLL's. When a DLL is loaded dynamically (using LoadLibrary), the classtypes that are registered in the loaded DLL are not added to the classlist (of the application). I don't know if this behavior is the same when delayed DLL loading is used.

Downloads

Download demo project - 15 Kb


Comments

  • wheloltabotly PumeSonee Phobereurce 5736255

    Posted by TizefaTaNaday on 06/03/2013 07:53am

    FeencyBiani airjordan4retrofusion.holidaygiving.org liawsidemsFam airjordan5retrodmpragingbull.holidaygiving.org BaittEeFleeme

    Reply
  • What's your problem.

    Posted by Legacy on 08/08/2003 12:00am

    Originally posted by: C Martin

    Why don't you guys just get along. The man was just trying to share some info here. He wasn't trying to make a personal afront or try and steal an idea.

    Let's just help each other. If we don't, who will dare offer any help in the future with the fear of being lambasted for their efforts.

    Reply
  • Here is a factory implementation that I use.

    Posted by Legacy on 07/17/2002 12:00am

    Originally posted by: Navi Singh

    // this code doesn't use MFC and 
    
    // should work on all platforms
    // with all compilers.

    #include <map>
    #include <string>
    #include <iostream>

    //================================================
    // generic factory class.
    template <typename BasePtr>
    class Factory{
    public:
    struct CreatorBase;
    public:
    void Register(const std::string& class_name, CreatorBase *creator){
    registry[class_name] = creator;
    }

    void UnRegister(const CreatorBase *pointer){
    std::map<std::string, CreatorBase *>::iterator it;
    for(it=registry.begin(); it !=registry.end();++it)
    if(it->second==pointer){
    registry.erase(it);
    break;
    }
    }

    struct CreatorBase{
    CreatorBase(const char *name){
    Factory::GetFactory().Register(name,this);
    }
    virtual ~CreatorBase(){
    Factory::GetFactory().UnRegister(this);
    }
    virtual BasePtr Create()=0;
    };
    template <typename MakeT>
    struct Creator: public CreatorBase{
    Creator(const char *name): CreatorBase(name){}
    BasePtr Create(){return new MakeT;}
    };

    BasePtr Create(const std::string& class_name) {
    return (registry[class_name])->Create();
    }

    static Factory &GetFactory(){
    static Factory factory;
    return factory;
    }
    private:
    Factory(){}
    std::map<std::string, CreatorBase *> registry;
    }; // end factory

    //================================================
    // macros used for generating a unique identifier.
    #define CAT4(a,b,c,d) CAT4_DELAY(a,b,c,d)
    #define CAT4_DELAY(a,b,c,d) DO_CAT4(a,b,c,d)
    #define DO_CAT4(a,b,c,d) a##b##c##d

    // macro for registering class.
    #define REGISTER_CLASS(name, ClassT, BaseT) \
    Factory<BaseT *>::Creator<ClassT> \
    CAT4(_the_creator,ClassT,_,__LINE__)(name)

    // template function for creating objects.
    template <typename BasePtr>
    BasePtr create_object(const char *name){
    return Factory<BasePtr>::GetFactory().Create(name);
    }
    //=================================================


    // test code.
    class Stmt_Base{public: virtual int get(){return 0;};};
    class FirstClass: public Stmt_Base{public: int get(){return 1;};};
    class SecondClass: public Stmt_Base{public: int get(){return 2;};};
    class ThirdClass: public Stmt_Base{public: int get(){return 3;};};

    // compile time registeration of key, class, and base.
    REGISTER_CLASS("key1", FirstClass, Stmt_Base);
    REGISTER_CLASS("key2", SecondClass, Stmt_Base);
    REGISTER_CLASS("key3", ThirdClass, Stmt_Base);

    int main(){
    Stmt_Base *obj = create_object<Stmt_Base *>("key1");
    std::cout << "The id: " << obj->get() << std::endl;
    return 0;
    };

    Reply
  • Same as MFC?

    Posted by Legacy on 05/21/2002 12:00am

    Originally posted by: AB

    Please tell me I didn't just read an article that describes a single macro (CREATE_OBJECT) that can be coded in 1 line:
    
    dynamic_cast<CSomeBaseClass *>(RUNTIME_CLASS( CSomeBaseClass )->CreateObject());

    How is your factory mechanism different from MFC dynamic object creation?

    And why do you insist on DECLARE_SERIAL? Why not just DECLARE_DYNCREATE?

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Targeted attacks and advanced threats are customized to infiltrate your unique IT infrastructure, evade conventional defenses, and remain hidden while stealing your corporate data. To detect these criminal intrusions, analysts and security experts agree that organizations should deploy advanced threat protection as part of an expanded security monitoring strategy. For this comparative analysis of breach detection systems, product analysis reports and comparative analysis reports are used to create the security …

  • The explosion in mobile devices and applications has generated a great deal of interest in APIs. Today's businesses are under increased pressure to make it easy to build apps, supply tools to help developers work more quickly, and deploy operational analytics so they can track users, developers, application performance, and more. Apigee Edge provides comprehensive API delivery tools and both operational and business-level analytics in an integrated platform. It is available as on-premise software or through …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds