Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Building C++ Applications with HTML5

4.88/5 (49 votes)
25 Mar 2012CPOL9 min read 233.8K  
Building C++ Applications with HTML5

Pretty pictures

Example running as a stand alone Qt/Webkit application 336018/dsa_sunburst_sm.png

Same application in FireFox running as an Apache module 336018/dsa_home_ff_sm.png

Downloads

Disk Space Analyzer Demo Application

Source Code

Introduction

HTML5 is a great way to create rich, platform-independent User Interfaces. The freedom and flexibility HTML5 offers Web Developers has eluded most native UI tools. The UI interface that can be hammered out fairly quickly and easily in HTML5 can be a longer less productive experience with other C++ GUI tools.

In this article, I'm going to attempt to help bridge the gap between HTML5 and C++ so that we can use HTML5 to create the UI portion of stand alone C++ applications. Two key elements I will use to make this happen are WebKit and Qt.

WebKit

A KHTML forked browser engine that is very solid and easy to compile and use. It is the engine behind Chrome, Safari, Android and iPhone browsers.

Qt

A platform-independent native application framework developed by Trolltech and recently acquired by Nokia. It is very reliable, complete and free to use.

Qt has what is probably the easiest to use WebKit integration, and our framework surrounding it is going to be very minimal. In fact, my hope is that after studying it, you will feel that you could move the frame work from Qt+WebKit to say, wxWidgets+Mozilla, or MFC+IEActiveX with little trouble. You'll only feel this way of course, until you try to embed Mozilla ;)

Motivation

Some reasons you might want to use HTML5 for UI development:

  • Web tools are probably the fastest, most cost effective and well understood method of creating UI's currently available. There are tons of free libraries, example code and support available.
  • Reuse website resources, designs (and designers), HTML/JavaScript code, layouts, images, etc.
  • Web based UI's are highly platform-independent. This app will also build as an Apache module or IIS CGI module. I plan to add an Android example soon.
  • An embedded web server could be used to allow remote access to a server, or provide the interface for a service that cannot directly access the users desktop.
  • Perhaps your application needs to couple tightly to a web site or online database.
  • It's not as far out there as you might think, other companies such as Netflix are using this approach to UI development.

If you were to search the web for examples and tutorials of WebKit based applications, you would find many. However, most will demonstrate techniques like executing JavaScript functions directly from C++ and making a lot of calls into the engine itself.

Having developed many applications using embedded web engines, I'm convinced that this is not the route to take. It will end up being an obstacle to scalability as more and more of internals of the engine are exposed to the higher application logic. The C++ code will become specialized around the implementation of the browser engine as will the HTML/JavaScript code. Many of the potential benefits of using the web engine will be quickly lost.

To get the full benefit, we want to be able to use C++ code and libraries we find as is. We also want to be able to use HTML/JavaScript code and libraries we find such as jQuery or Prototype as is. So, we will need a clean break between our HTML/JavaScript/C++ and the Browser Engine. This will also be the key to maintaining compatibility with platforms other than Webkit/Qt.

As it turns out, things are already designed this way, and all we have to do is make sure we don't break the rules in our integration. We will only interface our C++ with the UI using normal page requests. This will help avoid many common pitfalls, and we won't have to go digging around in the WebKit engine.

Goals

I'm going to describe a method to integrate an embedded web browser (specifically WebKit) with C++ code in an efficient and scalable way. I'm even going to take it another step and supply a basic and flexible code base to get you started. I'm going to make the assumption that you have some experience with Web Development and with C++. I'm not going into great detail on how to use C++ or HTML5 - I'm going to focus on integrating the two.

So let's lay down some objectives.

  • Create a platform-independent framework for HTML5 application development leveraging Qt and WebKit.
  • Since we want to be platform independent, ensure everything builds with GCC and Cygwin. But don't worry, there's no reason things shouldn't work with Visual Studio as well.
  • Provide an example, keeping the HTML/JavaScript and C++ seperate and pure as possible.

Taming C++

To make interfacing C++ easier, I'm borrowing a lesson from scripting languages and embedding the C++ code directly into HTML documents. This allows native expression for HTML/JavaScript code and makes it easy for C++ to present output data. As long as embedded C++ code is kept succinct, it will be easy to debug and there will be more upside than downside. We will be able to write code like the following:

C++
<html>
<?global

    int add( int a, int b )
    {
        return a + b;
    }

?>
    <body>

        5 + 7 = <?c out << add( 5, 7 ) ?>

        <ul>
        <?c
            for ( int i = 0; i < 10; i++ )
            {
        ?>
                <li><?c out << i ?></li>
        <?c
            }
        ?>

    </body>
</html>

This means that web developers and designers without C++ knowledge will be able to largely understand these documents and work with them to modify JavaScript code, apply themes and adjust CSS values, using their own prefered methods and tools even though they will not be fully functional until compiled.

I am introducing two special tags into the document: <?global ?> and <?c ?>.

<?global ?>

Whatever appears in the <?global ?> tag, will be inserted at the top of the final CPP file (the global space). This tag can contain #include statements and function definitions that will be scoped to the CPP file. <?global ?> tags can appear anywhere, they will all be extracted and inserted in the order they appear at the top of the generated CPP document.

<?c ?>

The contents of the <?c ?> tag will go into an internal function in the order they appear. The function is called each time the page is to be rendered. The prototype for this internal function looks like this in my implementation:

HTML
int _internal_run( TPropertyBag< str::t_string8 > &in, TPropertyBag< str::t_string8 > &out );

This is of course something you could change to your liking. In my implementation, the two TPropertyBag parameters provide all the data io. The in variable contains GET, POST and other request data. The out variable defines the response that is returned to the client. For the most part, it behaves like std::basic_string<>. In fact, it's actually a wrapper for this string object, but it also allows any manner of multi-dimensional response. Ultimately it ends up being a plain vanilla C++ file, and you can do anything here that you would in any other C++ file.

In order to make these documents come to life, we will have to turn the code "inside-out" so that it can be built using a standard C++ compiler. I have done this by creating a precompiler that processes the code before the C++ compiler. It turns out that the precompiler is simple to write and the extra complexity of a precompiler more than pays for itself in making the C++/Web interface neat and seamless.

Code like this:

HTML
<html>
<?global
    #include <string>
?>
    <b> <?c out << std::basic_string< char >( "Hello" ) + " World" ?> </b>
</html>

Is turned into this and compiled:

C++
#include <string>

static int _internal_run( TPropertyBag< str::t_string8 > &in,
                          TPropertyBag< str::t_string8 > &out )
{

    out << "<html>\n\n";

    out << "\n\t<b>\n\t\t";

    out << std::basic_string< char >( "Hello" ) + " World" ;

    out << "\n\t</b>\n</html>";

    return 0;
}

Later when the function is called, it will write this into the out variable:

HTML
<html>
    <b> Hello World </b>
</html>

We then pass this output to WebKit for processing. The HTML/Javascript will simply link to the pages as though they contained normal HTML and be oblivious about the embedded C++ code. If you're already a PHP developer, you should feel right at home at this point, except for eerie feelings about how the frame work will know how to map the file name to a function pointer. Funny you should ask...

Links to all the compiled functions are stored in a C++ array and referenced by the filename. We can then easily lookup the appropriate function to call based on the filename request we get from the WebKit engine. I'm even rooting the site at the host name embedded, so that we can still allow the WebKit engine to transparently access data from the Internet should we choose to allow that.

As a bonus, the precompiler also embeds our other resources such as images, JavaScript and basically any other files we want into the application in a platform-independent way. So, we end up with a single exe or dll that contains our entire User Interface!

One thing to be aware of. When there are C++ errors, the compiler will reference the precompiled file instead of the original source. Though it should still be clear to you what's going on, for this reason you will probably want to keep most the heavy lifing in standard C++ files and reserve the embedding for interfacing only.

Qt

As for the Qt work, it will be minimal. As I mentioned earlier, we will not need to delve into the depths of WebKit. I can easily outline the main parts of the integration.

Create the main window

Disable things such as scroll bars and context menus (or don't, if you prefer) to make things more 'application-like'.

C++
CMainWindow::CMainWindow()
{
    // Create web view
    m_pView = new QWebView( this );

    // Create custom network manager
    m_pNet = new CNetworkMgr( this, m_pView->page()
                                             ->networkAccessManager() );

    // Set our custom network manager
    m_pView->page()->setNetworkAccessManager( m_pNet );

    // No scrollbars
    m_pView->page()
            ->mainFrame()
              ->setScrollBarPolicy( Qt::Vertical,Qt::ScrollBarAlwaysOff );
    m_pView->page()
            ->mainFrame()
             ->setScrollBarPolicy( Qt::Horizontal,Qt::ScrollBarAlwaysOff );

    // No context menu
    m_pView->setContextMenuPolicy( Qt::PreventContextMenu );

    // Enable cross scripting
    m_pView->settings()
            ->setAttribute( QWebSettings::XSSAuditingEnabled, 0 );
    m_pView->settings()
            ->setAttribute( QWebSettings::LocalContentCanAccessRemoteUrls, 
                               1 );

    // Load the home page
    m_pView->load( QUrl( "http://embedded/index.htm" ) );

}

Overload QNetworkMgr and QNetworkReply to broker page requests

Here we will intercept page requests and redirect to our embedded resources and C++ embedded code.

C++
QNetworkReply* CNetworkMgr::createRequest( QNetworkAccessManager::Operation op, 
                                           const QNetworkRequest &req, QIODevice *device )
{
    // Anything with embedded as the host referes to our embedded resources
    if ( req.url().host() == "embedded" )
        return new CNetworkReply( this, req, op );
    
    // Disable to disallow real network access
    return QNetworkAccessManager::createRequest( op, req, device );
}

CNetworkReply::CNetworkReply( QObject *parent, 
                              const QNetworkRequest &req, 
                              const QNetworkAccessManager::Operation op )
    : QNetworkReply( parent )
{
    // Get the path to the file
    QByteArray path = req.url().path().toUtf8();

    // See if there is such a resource
    HMRES hRes = res.FindResource( 0, full.c_str() );
    str::t_string mime = res.GetMimeType( hRes );

    // If it's a binary embedded resource
    if ( isBinaryResource( hRes ) )
    {
        const void *ptr = res.Ptr( hRes );
        unsigned long sz = res.Size( hRes );
        m_content.append( QByteArray::fromRawData( (const char* )ptr, sz ) );
        
    } // end if
    
    // Page with embedded C++, so we must call a function
    else
    {
        mime = "text/html";

        // Get function pointer
        CHmResources::t_fn pFn = res.Fn( hRes );
        if ( pFn )
        {
            // in / out
            TPropertyBag< str::t_string8 > in;
            TPropertyBag< str::t_string8 > out;
            
            // Execute the page
            pFn( in, out );

            // Set the output
            m_content.append( out.data(), out.length() );

        } // end if

    } // end else
}

That's the general idea - the rest is implementation details. You can see the framework bleed is minimal, mainly the in and out TPropertyBag variables. This could have been replaced with a STDOUT capture (more like standard CGI). I decided not to do this in this implementation since I like to use STDOUT for debugging.

I've provided make files for bash/gcc that should work for you out of the box on Linux or Windows using Cygwin. For instructions on installing Cygwin on Windows see htmapp wiki.

Wrapping up

I've provided a sample app that I hope you'll agree looks pretty good, it's yet another disk analyzer. I was able to leverage open source packages like Quicksand and d3 to get it done in a lot less time.

To make the development a bit easier, you might consider building the application as a CGI or Apache module during development so that you can use standard browser tools to debug the 'live' build, though the lack of persistence may still render it a bit incomplete. In the example I provide for instance, the background thread does not run as an CGI module. Running the application with an embedded web server would solve this issue. I hope to add this feature soon.

If you want to look further, I have another library, that does have an embedded web server along with support for Visual Studio and Android, as well as many other features. If you're interested, you can take a look at that here.

Thanks for reading, I hope you found this article somewhat enlightening.

CREDITS

My most gracious thanks to the following projects:

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)