GCF LOGO VCL LOGO
GCF Component Model

GCF (Generic Component Framework) is primarily a component-framework library. Its "killer feature" is a component model that you can leverage in your applications to build "pluggable" and extensible applications on Windows, Linux and Mac OS.

This page provides an overview of the component model offered by GCF. A clear understand of the the component model will allow you to powerfully make use of it in your applications.

What is a component model?

A quick search on Google reveals this answer

An architecture and an API that allows developers to define reusable segments of code that can be combined to create a program.

GCF offers exactly that! GCF offers an architecture and an API that allows developers to define reusable segments of Qt code that can be combined to create Qt applications.

This page explains architecture of the component model offered by GCF, with links to API (classes and methods) that implement the model.

GCF Component model

GCF's component model comprises of two key concepts:

  • Concept #1: Component - defined as an entity that plugs its objects and services into the application

AND

  • Concept #2: Application - defined as an entity that locates components for loading and loads them

The concepts are captured and implemented in terms of

  • A GCF::Component class from which you can subclass and implement virtual functions, to "plug" your objects and services into the application.

AND

AND

Play the video below to get a sense of these concepts and how they come together in a GCF application.

 

 

If you are unable to view the video, the following bullet points summarize the content of the video.

 

  • Initially the object tree has one node - called "Application Node". This node contains a pointer to the application instance.
  • Components themselves are subclasses of GCF::Component. They can be loaded from shared-libraries or statically as well.
  • Upon loading of a component, a node for the same will get created in the object-tree under the "Application" node.
  • For each object that the component chooses to register, a node is created under the component's node in the object tree.
gcf-component-model1.png

 

Once all the components are loaded, the application's object tree would contain one sub-tree per component. Each of those sub-trees would contain one node for every object registered by the component with the application. Each node in the object-tree would contain a pointer to the QObject that they register.

The gFindObject method allow searching of objects in the object tree. You can search for objects by

  • their path, which is a dot (.) separated list of names from the "Application" node to the object's node.

OR

  • name of a class or interface that they implement

Since registered objects are searchable, we call them "exposed" objects.

Anatomy of the main() function of a GCF application

The code snippet below shows a typical main() function of a GCF application.

#include <GCF3/Application>
#include <GCF3/Component>
#include "BaseComponent.h"
int main(int argc, char **argv)
{
GCF::Application a(argc, argv);
// Load the first foundation component of the application
// In this code snippet, it is BaseComponent
BaseComponent *baseComponent = new BaseComponent;
baseComponent->load();
// Figure out names of components to load
QStringList componentNames = getComponentNamesFromSomewhere();
// Load those components
a.loadComponents(componentNames);
// Process command-line arguments
a.processArguments();
// Look for arguments we are interested in
QVariantMap argsMap = a.argumentsMap();
QStringList args = argsMap.keys();
Q_FOREACH(QString arg, args)
{
// if we interested in an arg,
// then we can look for its value from the map
// and process it.
}
// Run the event loop
return a.exec();
}

In the sub-sections below, each aspect of the main() function are explained in more detail.

Creating an application instance

You will need to create an instance of GCF::Application OR GCF::GuiApplication OR GCF::QmlApplication before any component is instantiated and loaded in the application.

  • The GCF::Application class is available from GCF Core Module. You should create an instance of this class only if you are creating non-gui and console-only applications.

In GCF, there is a macro called gAppService that always points to the GCF::ApplicationServices part of a GCF application instance. There is a gApp macro that always points to an instance of

The following diagram shows the class diagram of application classes in GCF.

gcf-application-classes.png

While GCF::Application, GCF::GuiApplication and GCF::QmlApplication are direct subclasses of GCF::ApplicationServices, they are all not direct subclasses of QCoreApplication.

Because of this class design, the macro gApp would return a NULL pointer if

  • you include <GCF3/Application> in a GUI or Qt Quick application

OR

  • you include <GCF3/GuiApplication> in a Qt quick or console application

OR

  • you include <GCF3/QmlApplication> in a widgets or console application.

However, the macro gAppService would never be NULL if used in a GCF application.

The foundation component

All GCF applications should ideally offer a "foundation component" after which all other componts are loaded. Though it not compulsory, we encourage you to not write "surrogate" applications that can be configured to load any GCF component.

Typically the "foundation component" would set the core personality of the application and offer core services. Auxiliary components can then extend the core application or build on top of the core services offered by the application to expand its business and technology possibility.

In a GUI application, the "foundation component" would offer a main-window with predefined panels, menus, tool-bars, dialog boxes and service-dictionaries on to which auxiliary components will merge their UI elements (widgets, actions, menus etc..) and service objects.

In a console-application, the "foundation component" would offer a set of core services, objects and service-dictionaries on to which auxiliary components will merge their service objects.

The section on registering objects explains how object merging works.

Figuring out the components to load

Once the "foundation component" is instantiated and loaded, you would want to load auxiliary components.

If component classes are statically linked into your application, then you can simply create an instance of those components and invoke the load() method on them.

If your components are available for loading from shared libraries, then you can invent a mechanism to source the list of shared libraries that you want to load. You could for example

  • read component names from an INI, XML or plain-text file and load them.

OR

  • read component names from the Windows Registry and load them. (Of-course this would work only on Microsoft Windows).

OR

  • make use of QDirIterator to iterate over all shared libraries in a predefined directory and load components from them.

OR

  • hard-code the component names to load in the main() function

OR

  • fetch names of components to load from a license file or from a web-service.

Once you are clear about the set of components to load, you can make use of GCF::ApplicationServices::loadComponent() or GCF::ApplicationServices::loadComponents() to load the components.

Processing command-line arguments

Users of your application can pass command-line arguments of the form "-key:value". Such arguments can be captured into argumentsMap() and processed in the main() function by calling either

OR

By default, GCF processes the -loadComponents: command-line argument as a comma (,) separated list of component names. It then loads those components one after the other.

For example, users of your application could launch it from terminal like this

$ ./YourGCFApplication --loadComponents:Path1/Component1,Path2/Component2,Path3/Component3

This would cause your application to also load GCF components from Path1/Component1, Path2/Component2 and Path3/Component3 libraries. [NOTE: this would work only if you have explicitly invoked processArguments() OR processArgumentsAndExec() on your application object in the main() function]

Note
If you are going to make use of GCF Investigator to record and/or run test-scripts, then you have to process arguments.

Triggering the event loop

Finally, you should call the exec() (or processArgumentsAndExec()) function on the application object to trigger the event loop and actually "start" your application.

Authoring GCF components

As stated before, a component in GCF is defined as an entity that plugs its objects into the application. Stated in another way, GCF components are basically factories of objects. They instantiate objects and register with the application. The actual process of registering objects is explained in a later section.

Creating a component in GCF is simply a matter of subclassing from GCF::Component or any of its subclasses like GCF::QmlComponent and GCF::GuiComponent. You can then instantiate it and invoke the GCF::Component::load() function on it to load the component.

Instantiating the component is simply a matter of calling the component class's constructor, if the component class is linked into the application.

If the component class is available in a dynamically loadable library, then you can either

OR

For a component class to be loadable from a dynamically loadable library, you should make use of the GCF_EXPORT_MACRO macro to export the component. This macro must be written exactly once in a library and in a source file.

How component loading works

Components can be loaded using any of the following means

During the load process several events are despatched by GCF to the component. These events can be handled by either reimplementing the QObject::event() method or any of the specialized event handler methods offered as virtual functions in GCF::Component. We recommend doing the latter.

The following video explains the flow of events during component load.

 

 

See Also
Component loading (or initialization)

How component unloading works

By default all components are unloaded when the application is about to quit. However, components can be explicitly unloaded using any of the following means.

Components can be unloaded using any of the following means

During the unload process several events are despatched by GCF to the component. These events can be handled by either reimplementing the QObject::event() method or any of the specialized event handler methods offered as virtual functions in GCF::Component. We recommend doing the latter.

The following video explains the flow of events during component unload.

 

 

See Also
Component unloading (or uninitialization)

Registering objects

The process of including a pointer to an object into the object tree is called "registering the object". Once registered, the object is available for access from other components. The objects are "exposed", so to speak.

Except for the application object, all objects registered with GCF have to be allocated on the heap.

A component can register its objects in three ways

  • You can create an instance of GCF::ObjectTreeNode under any pre-existing node in the object tree to include a pointer to your QObject.

We will now look at each method independently.

Component content file

While handling the pre-content-load event in GCF::Component::contentLoadEvent(), components can specify a content file for GCF to process and load. The content file is a XML file whose primary purpose is to provide a list of objects that need to be loaded during GCF::Component::load(). A typical content file would look like this

<content>

    <object name="a" />
    <object name="b" />
    <object name="c" />
    <object name="d" />

</content>

Basically the content-file contains content XML element as root and a series of object XML elements. Each object XML element must have a name attribute, otherwise it is ignored by GCF. Object names must be unique within the component, otherwise GCF will attempt to make it unique by adding some characters at the end of the name.

The object XML element can contain additional attributes, some of which have implicit meanings. Please read the page on Content XML File Specification to know more about attributes and their meaning.

Whenever GCF parses a object XML element, it sends a GCF::ContentObjectLoadEvent to the component. Along with this event GCF will send the name of the object in question and a key=value map of all other attributes in the object XML element.

The component can handle this event in GCF::Component::contentObjectLoadEvent() to

  • actually load the object (as a QObject instance)

AND

After the GCF::Component::contentObjectLoadEvent() returns, GCF looks for value of the parent attribute. The value of parent attribute has to be a path to an already registered object in the application. If the path corresponds to an object owned by another component, then a GCF::ContentObjectMergeEvent is sent to that component. This event can be handled in the GCF::Component::contentObjectMergeEvent() of the parent object's component to perform object-merging. Components can customize the way in which merging happens by reimplementing contentObjectMergeEvent(). By default

  • GCF::Component's implementation of the contentObjectMergeEvent() simply makes use of QObject::setParent() to perform the merging.
  • GCF::GuiComponent's implementation of contentObjectMergeEvent() takes into account the kind of parent and child that is being merged and performs merging accordingly. For example, if the child is an QAction and parent is a QMenu, it inserts the action as a menu-item in the menu. For a complete list of combinations that are handled, please read the documentation of GCF::GuiComponent::contentObjectMergeEvent()
  • GCF::QmlComponent's implementation of contentObjectMergeEvent() ignores merging between two QML objects.

The following video explains the whole process of loading and merging of objects, with the help of an example.

 

 

Using addContentObject() function

During and after the post-initialization event, components can make use of GCF::Component::addContentObject() to explicitly register objects with the application.

Explicitly creating object-tree-nodes

From any part of the application, you can create an instance of GCF::ObjectTreeNode under any node in the gAppService->objectTree(). Example:

QObject *object = createMyNewQObject();
new GCF::ObjectTreeNode(gAppService->objectTree()->rootNode(), "abcd", object);
// Now the object is registered as abcd under the root-node of the object tree.

While addContentObject() function can be used to add an object under the node of a component in the object-tree, creating object-tree-nodes by yourself allows you to include your object in any part of the object-tree.

Note
It is not possible to explicitly create nodes OR use addContentObject() to add an object and merge it with another object in the applicaiton. Object merging is possible only when you use content-xml files.

Searching for objects

The ability to discover objects and functionality from different components is very important. GCF offers two ways for doing this

Using gFindObject and gFindObjects

Once an object is registered with GCF, it is considered to be "exposed". Exposed objects can be searched by making use of the gFindObject() method. This method can be used to find objects based on their object path in gAppService->objectTree() or based on the class or interface type that they implement.

Example:

// Using gFindObject to search an object by its path
QObject *itemRegistryObject1 = gFindObject("Application.ItemRegistryComponent.ItemRegistry");
ItemRegistryClass *itemRegistry1 = qobject_cast<ItemRegistryClass*>(itemRegistryObject1);
// Using gFindObject to search for an object by its class or interface
ItemRegistryClass *itemRegistry2 = gFindObject<ItemRegistryClass>();

You can make use of the gFindObjects() method to get a list of all objects of a particular class or interface.

Connecting to nodeAdded() signal of gAppService->objectTree()

As mentioned before, every time an object is registered; a node for the same is created and included in the object-tree. The object-tree (of type GCF::ObjectTree) emits a GCF::ObjectTree::nodeAdded() signal. This signal can be connected to a slot to get notified whenever an object is included in the object tree. You could then examine the object that just got added and store it in an internal list if required.

Example:

class ItemRegistryFinder : public QObject
{
Q_OBJECT
public:
ItemRegistryFinder(QObject *parent) : QObject(parent) {
connect(gAppService->objectTree(),
this,
}
// ....
Q_SLOT void onNodeAdded(GCF::ObjectTreeNode *parent, GCF::ObjectTreeNode *child) {
Q_UNUSED(parent);
if( qobject_cast<ItemRegistryClass*>(child->object()) ) {
m_itemRegistryObjects.append(child->object());
// do something else if needed
}
}
// ...
private:
GCF::ObjectList m_itemRegistryObjects;
};

How application shut down works

When an application is about to shut down, it unloads each and every component in the application in the reverse order of their loading. At the end of this cycle the application's event loop is terminated and the main() function returns, causing the application-process to quit.

Suggested reading

Next, please read the article on Getting started with GCF. This will help you to understand the GCF 3 concepts in action!