Custom Domain Specific Languages with Qt Declarative / QML

Dynamic languages such as Groovy have simplified the task of creating Domain Specific Languages (DSLs).  In C++ however, creating a DSL usually requires the use of parsers and parser generators such as Antlr. Qt’s QML framework offers an alternate path to rapid DSL development by extending the QML DSL language element set to include custom elements backed by (almost traditional) C++ objects. Python bindings are available with PySide. In this article, we briefly discuss DSLs, the core QML language and methods to extend it from C++. We also present a short example of a real world custom DSL and discuss the benefits of using the DSL over the traditional  imperative approaches.

About QML

Qt Meta Language or Qt Modeling Language (QML) is a framework developed by Nokia as part of the Qt framework. Its primary use, as promoted by its developers, is user interfaces for modern applications on almost any platform (mobile through desktop). Special emphasis is placed on  touch input, fluid animations, sensors and multimedia.

The QML framework includes a declarative language for developing user interfaces A QML document describes an object hierarchy of objects, some specific to user interfaces, others not. Since QML is linked with Javascript, the language has the look and feel of JSON. In addition to core UI support, QML is highly extensible, via pure-QML reusable blocks as well as through C++ objects registered with the QML system. Here, we take a deeper look at the latter extensibility via C++, and the possibilities of developing hierarchical DSLs for virtually any application, UI or otherwise.

From QML documentation:

The QML syntax declaratively describes how to construct an in-memory object tree. In Qt, QML is mainly used to describe a visual scene graph, but it is not conceptually limited to this: the QML format is an abstract description of any object tree. All the QML element types included in Qt are implemented using the C++ extension mechanisms describe on this page. Programmers can use these APIs to add new types that interact with the existing Qt types, or to repurpose QML for their own independent use.

Finally,  a note regarding names encountered in the Qt/QML world:

  • The declarative language is known as QML,
  • The runtime is Qt Declarative (aka Qt Quick).
  • As of Qt 4.8, Qt Quick is at revision 1.1. Qt 5.0 which is in alpha at this time ships with Qt Quick 2.0.

Domain Specific Languages

We digress here briefly into the world of computer science programming paradigms. A declarative language based program can be simplistically defined as one that is not imperative, i.e. it describes what (the logic) needs to be achieved, and not how (control flow) one must go about achieving the goal of the program. A declarative language that constrained to a specific domain or area (e.g. CSS style sheets for HTML pages) is often referred to as a domain specific language or DSL.

In weighting the benefits of developing / using a domain specific language for a particular computing problem vs. a generic programming language, several tests may be employed and can be found in the literature. A brief list of desirable characteristics for a DSL are:

  • The (new) DSL lets us specify the what and not how of the particular problem.
  • The DSL is narrowly focused on the particular computational area of interest.
  • As domain experts, the DSL lets us describe the problem in a manner that is significantly more concise than the favored programming language for computational domain. It follows then that in reading the problem described in our new declarative language, other domain experts will find it easier to grasp the essence of the program over a program expressed in the the favored programming language.
  • Should doubts linger regarding the desirability of employing a DSL, especially when DSL’s purported domain broadens during its development, we may wish to fall back on the classic test – the DSL ought not be Turing complete.

The Core QML DSL

A simple QML document looks like this:

 import QtQuick 1.0

 Rectangle {
     width: 200
     height: 200
     color: "blue"

     Image {
         source: "pics/logo.png"
         anchors.centerIn: parent
     }
 }

When rendered, this QML document will generate a rectangle 200×200 pixels in size, filled with the color blue. Embedded in the rectangle, is an image, sourced from the supplied URL and positioned via its anchors.  The two objects are created dynamically via definitions imported from the first line and are stored in an object hierarchy that parallels the descriptive hierarchy.

It is easy to see that an imperative program that could generate a similar output will be longer. This is in part due to fact that Qt automatically bootstraps the plumbing necessary to eventually render the scene. However, even when we compare relevant portions of an imperative program vs a DSL especially for scenes and user interfaces, a well constructed DSL can be far more effective. Other examples of UI DSLs include Microsoft’s XML based XAML schema.

We should note that QML can contain Javascript code that can be executed at runtime (in a sandboxed environment). This begs the question whether QML will pass the test of Turing completeness. We leave the details of this for the reader to ponder over.

POQO Objects: Extending QML from C++

Adding a new element to QML via C++ is quite straightforward. To add a new QML element Person:

import People 1.0

 Person {
     name: "Bob Jones"
     shoeSize: 12
 }

We simply register the class that defines the element via a call to qmlRegisterType.

template<typename T>
 int qmlRegisterType(const char *uri, int versionMajor, int versionMinor, const char *qmlName)

qmlRegisterType<Person>("com.example.qml", 1, 0, "Person");

QML elements backed in C++ are are little more involved than plain old C++ objects (POCO). QML relies heavily on Qt’s meta object system and can only instantiate classes that derive from QObject. For visual element types, this will usually mean a subclass of QDeclarativeItem and for arbitrary objects, a direct subclass of QObject. We refer to QObject derived classes as POQO objects.

QML Elements for DirectShow

At RemoteReality, we’ve developed a Qt plugin to simplify the task of creating DirectShow graphs. It allows programs to declaratively create filter graphs, specify the filters in the graph and their interconnects.

Our software creates video pipelines that move  large (up to 100 megapixel) frame, fast (60hz) video streams from cameras to displays, in real time. Even at the the time of this article, DirectShow remains the API of choice. While filter development in DirectShow is modular because filters are COM objects, filter graph creation in an imperative sense can be tedious depending on the options and complexity of the underlying graph. We found our imperative graph creator to bloat over time and pose significant challenges with respect to extensions and testing. In contrast, our  declarative approach has significantly reduced code due to the separation of what from how, making it easier to test and extend.

About DirectShow

DirectShow is designed to address key  challenges faced by multimedia applications, including:

  • Multimedia streams contain large amounts of data, which must be processed very quickly.
  • Audio and video must be synchronized so that it starts and stops at the same time, and plays at the same rate.
  • Data can come from many sources, including local files, computer networks, television broadcasts, and video cameras.
  • Data comes in a variety of formats, such as Audio-Video Interleaved (AVI), Advanced Streaming Format (ASF), Motion Picture Experts Group (MPEG), and custom camera interfaces and formats.
  • The programmer does not know in advance what hardware devices will be present on the end-user’s system.

DirectShow’s main design goal is to simplify the task of creating digital media applications on the Windows platform, by isolating applications from the complexities of data transports, hardware differences, and synchronization.

A Simple Example

import com.remotereality.qt.dshow 1.0
import QtQuick 1.1

FilterGraph {
	// Insert a UrlSource filter into the graph and point to our AVI file
	UrlSource {
		name: "Sample Video"
		url: "http://www.example.com/sample.avi"
		id: source
		OutputPin {
			id: urlsrc_out
		}
	}

	// AVI Decompressor 
	Filter {
		clsid : "{CF49D4E0-1115-11CE-B03A-0020AF0BA770}"
		id : avi_decomp
		InputPin {
			connectTo : urlsrc_out
		}
		OutputPin {
			id : avi_out
		}
	}

	// Insert a VideoRender filter 
	VideoRenderer {
		window: videoRenderer
		InputPin {
			connectTo: avi_out
		}
	}
}

This example uses several custom QML elements including FilterGraph, Filter, InputPin, OutputPin as well as specialized filters such as VideoRenderer. The logical mapping of this graph description to a filtergraph will be quickly evident to DirectShow users. The QML elements are implemented as POQO objects.

We note that this example does not include any description of a user interface including the disposition of video subsequent to VideoRenderer. Hooks have been implemented within the filters to integrate UI elements in a Model-View separated manner.

Wrapping Up

Qt and its declarative QML components offer C++ developers the opportunity to develop custom DSLs for specific application areas – a capability widely available in languages such as Groovy, Ruby etc. To benefit from QML, the developer must implement C++ objects as POQOs (using the Qt Meta System flavor). Barring that, few restrictions exist, allowing the developer to produce DLSs that can simplify computational problems in specific areas with code that is more concise, extensible and testable.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s