Droplets C++ Tutorial: Hello World
 

Introduction
Necessary tasks
Declarations
The Application Window
    An aside on sub-windows
Adding Components
The DLL Interface
Creating the Application
Running Hello World

 

Introduction

This section of the Droplets Platform tutorials is intended for C++ programmers who are approaching the Droplets Platform for the first time. In the pages below we will run through an extremely simple "Hello World" Droplet application that demonstrates the bare essentials necessary for the construction of any Droplets application. Once we've gone over the code, we'll also detail the steps necessary to compile and run "Hello World".

The appliation below is of admittedly limited scope. It creates Droplet which, when run, displays the messages "Hello World" and "Hi Mom!" (along with a single button) in its one window as shown below:

Once we've run through the code in detail, though, you'll have learned many of the essentials of writing to the Droplets C++ API.


Let's start by taking a look at the complete code for "Hello World":


#pragma warning(disable: 4786)

#include "dropletAppApi/include/dsDllExport.h"
#include "dropletAppApi/include/dsApplicationWindow.h"
#include "dropletAppApi/include/dsLabelComponent.h"
#include "dropletAppApi/include/dsButtonComponent.h"
#include "dropletAppApi/include/dsApplicationClass.h"
#include "utilities/include/utelems.h"
#include "utilities/include/utmutex.h"
#include "dbgutil/include/du.h"
#include <sstream>
#include <objbase.h>

// derive from TDsFrame
class THwHelloWorld: public TDsFrame
{
     public
:

          THwHelloWorld(TDsProtocolWriter* writer, TDsApplicationClass* app)
          : TDsFrame(writer, app, "")

          {
               AddComponents();
               static string spec;
               static TUtMutex m;
               MakeSpecification(spec, m);
          }

          virtual void AddComponents()
          {

               TDsLabelComponent *label = new TDsLabelComponent(0,0,1,1, "Hello World!", this);
               GetMainPanel().AddComponent(label);
               TDsLabelComponent *label2 = new TDsLabelComponent(0,1,1,1, "Hi Mom!", this);
               GetMainPanel().AddComponent(label2);
               TDsButtonComponent *button = new TDsButtonComponent(0, 2, 1, 1, "button", this);
               GetMainPanel().AddComponent(button);
          }
};

// Application DLL initialization
void DsAppInitialize()
{
}

// Application DLL cleanup
void DsAppFinalize()
{
}

// The length of the largest application name (must be
// greater than or equal to the largest name)

int DsAppGetMaxAppNamesSize()
{
     return
100;
}

// The number of applications hosted by this DLL
int DsAppGetNumAppNames()
{
     return
1;
}

// The name of the nth (starting from 0) application name
// (copy into "name") which will be allocated as the size
// returned by DsAppGetMaxAppNamesSize() + 1

void DsAppGetAppName(int index, char* name)
{
     strcpy
(name, "Hello World!");
}

// create the application of type “name”.
// Since we have only one type of app in our DLL, we don’t
// have to bother checking the name here

TDsApplication* DsAppCreateApp(const char *name, TDsProtocolWriter* writer, TDsQueueTicketFactory* ticketFactory)
{

     TDsApplicationClass* app = new TDsApplicationClass(writer, ticketFactory);
     THwHelloWorld* hw = new THwHelloWorld(app->GetProtocolWriter(), app);
     app->AddWindow(hw);
     hw->Release();
     return app;
}

int DsAppDoesAppRequireImmediateAuthentication (const char* name)
{
     return false;
}

extern "C" void DsAppGetSupportedProtocols(
     const char *name,
     unsigned long * minimumProtocol,
     unsigned long * preferredProtocol)
{
     (void) name;
     *minimumProtocol = 0x00021400;
     *preferredProtocol = 0x00021400;
}


 

Necessary Tasks for any Droplets Application

The advantage of our minimalist demo is that it outlines clearly the essential ingredients in any Droplets-powered application. As "Hello World" illustrates, any Droplet must perform these five crucial tasks:

• Import the necessary resources from the Droplets API
• Create a Window subclass (TDsFrame or TDsDialog)
• Create any necessary Components within that Window subclass
• Create a DLL interface
• Create a Droplets application object and add your window to it

Important Note: One misunderstanding that comes up often when developers begin to look at the API regards the nature of Droplets' client/server paradigm. It is important to remember that we are not writing some sort of specialized client. All Droplets AppDrivers are stored on the server-side, and in all that we do below we are writing strictly to the server.

 

Declarations

The Droplets-specific declarations are the first thing to examine:


#pragma
warning(disable: 4786)

#include "dropletAppApi/include/dsDllExport.h"
#include "dropletAppApi/include/dsApplicationWindow.h"
#include "dropletAppApi/include/dsLabelComponent.h"
#include "dropletAppApi/include/dsButtonComponent.h"
#include "dropletAppApi/include/dsApplicationClass.h"
#include "utilities/include/utelems.h"
#include "utilities/include/utmutex.h"
#include "dbgutil/include/du.h"
#include <sstream>
#include <objbase.h>

The #pragma declaration simply avoids a pointless warning message when you build the demo - thus allowing more salient error messages to stand out. The other Droplets-oriented statements import the Droplets API library into your program - additional Droplets header files are available for inclusion, but this is all we need for "Hello World".

TDsApplicationClass is a concrete class (derived from the abstract TDsApplication) through which we create an application instance; it enables the Droplets Server and Client to recognize "Hello World" as a Droplet. Concrete application functionality is deferred to TDsFrame, which allows us to create a main window - plus any necessary sub-windows - and then add components to it. The only components necessary for "Hello World" are TDsLabelComponent and TDsButtonComponent, which enter text labels and buttons into the main window.

 

The Application Window

When we create our application object further on in the program, we will have to add an application window to it. Therefore, we must first create that window and populate it with any needed components. That is the work of THwHelloWorld:


class
THwHelloWorld: public TDsFrame
{
     public
:

          THwHelloWorld(TDsProtocolWriter* writer, TDsApplicationClass* app)
          : TDsFrame(writer, app, "")

          {
               AddComponents();
               static string spec;
               static TUtMutex m;
               MakeSpecification(spec, m);
          }

 

A word of explanation is in order about TDsFrame. When building a Droplets application you must create a subclass of Window which will hold your GUI components. The TDsWindow constructor is not used; you instead choose between TDsDialog and TDsFrame. The main difference is that TDsFrame allows you to create GUI windows that can be maximized and minimized independent of one another.

You'll always use the constructor for your TDsWindow subclass-derived object to create the window's components and wire up event handlers for those components. The object must also be passed two arguments, a TDsProtocolWriter - which allows the Droplets Client and Server to speak to one another - and a TDsApplication. Passing TDsApplication simply allows the window constructor to later reference its methods and operations - often necessary, though we don't do it here.

The Droplets Platform does most of the work of the TDsProtocolWriter object without programmer intervention. Once the object has been created and passed as an argument (here and when we create our TDsApplication object), the developer's responsibility to it is done.

Since application windows are typically constructed in the factory function of the DLL or in the methods of other windows, there is always easy access to a protocol writer and application object.

The constructor for a TDsFrame has the following signature:


TDsFrame (TDsProtocolWriter* writer, TDsApplication* app, const char* type)

We've discussed the protocol writer and application object directly above.

The third argument is used to differentiate and categorize the window. There are two types of window - main windows and sub-windows. A main window, as the name suggests, is the application's main window - there can be only one. When this window is closed, the application shuts down. A main window must have a blank ("") third argument:


          : TDsApplicationWindow(writer, app, "")

 

An Aside on Sub-windows

All other windows are sub-windows - and there are no sub-windows in Hello World. In Droplets with more sophisticated UIs, however, they often come in handy. Sub-window names must be unique among active windows (added to the application and not yet closed) for a given application, and their type must be unique for the class. The type name unique identifies the class, so it is usually a constant string passed directly to the TDsFrame constructor - the only restriction is that it must be unique.

One way to accomplish this is to pass in the name of the class as the type name; of course you should keep the name as short as possible for the sake of efficiency.

 

Returning to Hello World, the body of the constructor is responsible for two things:

• The creation of the components on the page (and their event handlers)
• The initialization of the page specification cache


          {

               AddComponents();
               static string spec;
               static TUtMutex m;
               MakeSpecification(spec, m);
          }

The adding of components and handling of events is relegated to a separate function called AddComponents(). It is merely called here - we'll go over the details of this function in the next section.

The Droplets Server caches page descriptions (i.e. the application window's initial state) in order to speed up initialization. This allows the cost of initializing to be incurred only the first time that each application runs. To use this mechanism - which is highly recommended for the lower cost incurred - you need to define a string and a mutex (a thread synchronization object), which are shared among all application windows of the same class (by making them static), and then pass them to MakeSpecification(). This operation creates the packet and stores the string that is passed in within that packet. Making the string static allows all objects of the class to share the specification. We include the mutex to lock the string, ensuring that this process behaves well when windows are created in different threads almost simultaneously.

 

Adding Components

Droplets components are objects whose class descends from an abstract parent class called TDsComponent. It's possible to use TDsComponent directly in Droplets, but this involves taking on a number of additional responsibilities and is not recommended.

You are more likely to use one of TDsComponent's many descendants, specialized components (like grids, buttons, checkboxes and so forth) that provide developers with a broad spectrum of functionality for their UI. Hello World uses only TDsButtonComponent and TDsLabelComponent, but there are also classes that represent binder panels, drop-down lists, radio buttons, ad banners, tables and more (check your Droplets C++ API documentation for a complete listing). Each component has attributes that determine its behavior, and events to notify the server of client actions on them.

It's now time to build the AddComponents() function referred to in the Frame constructor.


          virtual void AddComponents()
          {

               TDsLabelComponent *label = new TDsLabelComponent(0,0,1,1, "Hello World!", this);
               GetMainPanel().AddComponent(label);
               TDsLabelComponent *label2 = new TDsLabelComponent(0,1,1,1, "Hi Mom!", this);
               GetMainPanel().AddComponent(label2);
               TDsButtonComponent *button = new TDsButtonComponent(0, 2, 1, 1, "button", this);
               GetMainPanel().AddComponent(button);
          }

 

Since Hello World possesses no dynamic functionality to speak of, our task is made easy. We simply create an object for each component that we would like to add to our window and then pass it to the Window's main panel via the Panel's AddComponent() method.

Despite the simplicity, there are several important things to note here. When creating a component object, as we do here with two labels and one button - you're required to pass in a series of six arguments as follows:


(int GridX, int GridY, int GridWidth, int GridHeight, string InitializationString, TDsApplicationWindow* window)

 

The first four define the location of your components in the application window, and are similar to what you'll find in the Java JDK's GridBagLayout class - part of the java.awt package. GridX and GridY determine the location of the component within the window. In essence, the window is divided into a grid of rectangles, with (0, 0) values for GridX and GridY being the top left cell.

These variables operate relative to one another. In other words, the Droplets Server will determine the position of each component in the window based on the value of its GridX and GridY variables - relative to the other listed components. Note the value (i.e. position) of these variables as indicated in our program:


     TDsLabelComponent *label = new TDsLabelComponent(0,0,1,1, "Hello World!", this);
     TDsLabelComponent *label2 = new TDsLabelComponent(0,1,1,1, "Hi Mom!", this);
     TDsButtonComponent *button = new TDsButtonComponent(0, 2, 1, 1, "button", this);

 

In all three cases, GridX is set to 0, which means that all three components will line up horizontally. But since label's GridY is set to 0, label2's to 1 and button's to 2, the Droplets Server will set label2 directly below label, and button below label2. Keep in mind that both GridX and GridY should always contain non-negative values.

The next two variables, GridWidth and GridHeight, determine the size of the component - again, relative to the size of their neighbor components in the window. GridWidth determines the number of rows (in the window's grid) that the component will take up, while GridHeight determines the number of columns that it will consume. "1" is the value we have set for all three components. As with the previous two variables, GridWidth and GridHeight should never contain negative numbers.

The last two variables are InitializationString and Window. The latter of these simply indicates the name of the window in which the component will be palced. Writing "this" enables the AddComponent() method to place the component in whichever window has called it - in this case the Main Window. InitializationString simply inserts whatever text it contains into the component. If the variable is empty (""), the component will appear in the window without text.

 

The DLL Interface

Droplets C++ applications are implemented as DLLs with the following required interface:


TDsApplication* DsAppCreateApp(const char *name, TDsProtocolWriter* writer);
void
DsAppFinalize();
void
DsAppGetAppName();
int
DsAppGetMaxAppNamesSize();
int
DsAppGetNumAppNames();
int
DsAppInitialize();

boolean DsAppDoesAppRequireImmediateAuthentication(const char *name);
void
DsAppGetSupportedProtocols(const char *name, unsigned long *minimumProtocol, unsigned long *preferredProtocol);

The last two functions are optional, and do not have to be explicitly called in the program. We have chosen not to include them in our Hello World tutorial.

DsAppDoesAppRequireImmediateAuthentication() determines whether or not authentication is required at session start-up - by default, authentication is not required, and so our end-users will not be required to provide credentials upon starting HelloWorld.

DsAppGetSupportedProtocols() determines the minimal and optimal versions of the Droplets Client which must be enabled on the end-user's machine if the application is to run. Since printing was first supported in the fifth iteration of the Droplets Client, for example, if your Droplet is to support printing its minimum requirement will be version 1.05 of the Client. A complete listing of Droplets Client iterations - along with which features they introduced - is included elsewhere in your Droplets SDK documentation. HelloWorld does not call this function, and so is limited to the functionality of the very first version of the Droplets Client.

We'll discuss DsAppCreateApp() in the next section.

The purpose of the DLL interface is to provide a way for the Droplets Server to find out the names of the applications that are hosted by the DLL and to create applications as they are requested.

The sequence of events in the Droplets Server is as follows:

1.     Droplets Server loads the DLL
2.     DsAppInitialize is called
3.     DsAppGetMaxAppNamesSize is called
4.     A (DsAppGetMasxAppNamesSize + 1)-sized buffer is created
5.     DsAppGetNumAppNames is called to find the number of apps hosted by the DLL
6.     For each application, DsAppGetAppNames is called with the buffer created in 4.

For step 6, the Droplets Server expects the function to copy the name of the nth application into the buffer.

When a request for an application is sent to the Droplets Server, it asks the DLL that is hosting that application to create an application object by calling DsAppCreateApp. From then on, all communication with that application is done directly through the application object interface.

Finally, just before the Droplets Server unload the DLL, it calls DsAppFinalize to perform all tasks necessary for a clean application shutdown.

In Hello World, your eyes may at first be drawn to this:


void
DsAppInitialize()
{
}

void DsAppFinalize()
{
}


Why do we need these two empty functions? The truth is, even if we have nothing that we want to execute upon DLL initialization or unloading, the Droplet Server will still be expecting to find them. Placing these two empty functions in our program lets the Droplets Server know that the absence of initialization and finalization instructions isn't just a mistake - we're aware of them but do not need them.

Typically, DsAppInitialize is used to initialize any resources needed across all users - for example, database connections - and is used to create resource queues. DsAppFinalize generally reverses the effects of DsAppInitialize - for instance, closing the connection to the database.

The second portion of Hello World dedicated to the DLL interface is simply a matter of the Droplets Server's application discovery mechanism, and the consequent creation of buffers in memory to hold all application names.

The Droplets Server first needs to know the size of the largest application name, so that a buffer of appropriate size can be reserved. Obviously, the number you return here must be greater than or equal to the highest number of characters in any application name. If you're too lazy to count (as we were), 100 is usually a safe bet:


int
DsAppGetMaxAppNamesSize()
{
     return
100;
}

 

Next up is the number of application names, which allows the Droplets Server to reserve the appropriate number of buffers to contain the name of all appliations hosted by the DLL. In our case, there's only one:


int DsAppGetNumAppNames()
{
     return
1;
}

 

Finally, we call DsAppGetAppName in order to index through all application names and store them in a buffer. Since there's only one application in "Hello World"'s DLL, there's no need to worry about indexing, just a single call to strcpy():


void DsAppGetAppName(int index, char* name)
{
     strcpy
(name, "Hello World!");
}

 

On the other hand, if we had two applications stored in the same DLL - let's say, for example, that we had separate Hello World and Hi Mom! applications - we would have to return "2" in DsAppGetNumAppNames(). DsAppGetAppNames() would then use its index argument to create two buffers and we would be responsible for creating an "if-else" statement that accounted for both applications.

The final required DLL interface method is DsAppGetSupportedProtocols. Hello World is set to version 2.2 of the Droplets client/protocol (0x00021400); you should set your method to the latest protocol version.


   extern
"C" void DsAppGetSupportedProtocols(
        const char *name,
        unsigned long * minimumProtocol,
        unsigned long * preferredProtocol)
   {
        (void) name;
        *minimumProtocol = 0x00021400;
        *preferredProtocol = 0x00021400;
   }

 

There are also other optional methods that you can use in your DLL interface. For a complete list, see the Droplets C++ API documentation.

 

Creating the Application

All that is left for us to do is create the actual application type and insert our Hello World window into it. The function creates a TDsApplication object and takes three parameters:


TDsApplication* DsAppCreateApp(const char *name, TDsProtocolWriter* writer, TDsQueueTicketFactory* ticketFactory)

 

A few words about these parameters. The first assigns a name to the application in accordance with the assignment of names in DsAppGetAppName() above. Since we have only one application, the name pointer is already set to "Hello World!". If there were more than one application hosted on this DLL, the parameter name is set to the name of the application to be created.

TDsProtocolWriter was mentioned before; it is necessary in order for the Droplets Server and Client to speak to one another. The only thing that you're required to do with it in your program is to pass it as an argument here - and to the Application class and Main Window objects just below.

TDsQueueTicketFactory deserves special note. This class creates tickets that operate analogously to a train ticket - if you want to access resources on the Droplets network, you have to get your ticket first, and hold onto it until you're done with that resource. The QueueTicketFactory allows the Droplets Server to keep track of your application and its event requests - and in turn gives your application access to shared resources, including access to database handles, IMAP connections and processing by the Droplets Server itself.

The Droplets Platform uses this mechanism because its client/server paradigm sometimes necessitates HTTP tunneling - the monitoring of tickets allows the Droplets Server to determine an appropriate time to break the connection; it's also used if you want to display an hourglass on your UI while the end-user waits to receive an update.

TDsQueueTicketFactory allows the Droplets Platform to operate asynchronously, while at the same time sharing resources in a fair and rational manner among all users. Each resource that the application creates (such as a database connection) may be given a resource queue (the other alternative is a pool, which we'll discuss later in these tutorials). Applications present their QueueTickets in order to be placed on line to access a given resource, allowing the Droplets Server to monitor and manage access to these shared resources. These queues are also used to make sure that an application never blocks while waiting for a resource - see the Droplets Platform Overview documentation for more on this.

TDsApplicationClass allows DsAppCreateApp() to return an object whose class is derived from the abstract TDsApplication class. This is done quite simply in Hello World by creating a new TDsApplicationClass object (called app) and passing it to the previously created ProtocolWriter and QueueTicketFactory objects:


     TDsApplicationClass* app = new TDsApplicationClass(writer, ticketFactory);

 

Since all application functionality is contained in the window, we need to insert our Hello World window into the new ApplicationClass object before the functions returns our application. This is done by creating a new THwHelloWorld object called hw, with our ProtocolWriter and ApplicationClass objects as arguments, and then use app to access the ApplicationClass' AddWindow() method with hw as its argument:


     THwHelloWorld* hw = new THwHelloWorld(app->GetProtocolWriter(), app);
     app->AddWindow(hw);

 

Since all windows in an application are reference counted, you should release the window unless you need to maintain a reference to it after adding it to the application:


     hw->Release();

 

All that's left now is to return the application object:


     return app;

 

Running the Hello World Demo

In order to build the Hello World application, the code should be compiled into a DLL with all of the global functions expressed. Here are the steps for one way of doing this in Visual C++ 6.0:

1. With Windows Explorer, create a home directory for your project. Underneath it, create these directories: VC60 for the Visual C++ project files, Source for source code, Shared for shared libraries.

2. Copy the Shared libraries from your Droplets installation and place them in the Shared folder

3. Choose File->New from the main menu

4. Choose the Projects tab.

5. Choose Win32 Dynamic-Link Library from the choices on the left and fill in a name and location - the location should be the Vc60 directory that you created in step 1. Click OK.

6. Choose An empty DLL project. Click Finish. Click OK.

7. Copy the contents of our Hello World demo into a file and add it as a new Source file to our Visual C++ project.

8. For the typical DLL project, you will need to add the following boilerplate files to the project: export.h, export.def. You can find them in the Shared directory of your Droplets SDK installation. Copy them into your Source directory before adding them to the project.

9. You'll also need the following .lib files to get linked in. The best way to do this is to add them to your project (as opposed to specifying them in Project Settings).

We'll use the debug versions to build Hello World:

Shared\DropletAppApi\Lib\da_mg.lib
Shared\DbgUtil\Lib\du_mg.lib
Shared\Except\Lib\ex_mg.lib

For release builds, you'll need the versions of these files without the g at the end of the filename. Those are in the same directories as the ones listed above.

10. Add all of these files to the project.

11. Now you need to adjust project settings. Bring up the Settings box by hitting Alt-F7.

12. We'll do the settings for a debug build here. Slight modifications need to be made for Release build. Make sure that the Settings For: box (upper left) shows Win32 Debug.

13. Your project settings must first point to the Droplets Server executable (included as a part of your SDK). In Project | Settings, go to the Debug tab and select the General category. Enter the location of your Droplets Server (Droplet\Server\Bin\DropletServer.exe) under "Executable for Debug Session", and the location of your Droplets Server bin (Droplet\Server\Bin) under "Working Directory".

14. Under the C/C++ tab, Code Generation category, Use run-time library: option, you must select "Debug Multithreaded DLL". Same tab, Preprocessor category: type "..\Shared" into the space labeled Additional include directories.

15. Under the Link tab, General category, set Output file name: to DROPLET_SERVER_PATH\Bin\appsdebug\YourDllName_mg.dll. The "mg" is for multithreaded debug version and DROPLET_SERVER_PATH should be replaced with the path of your Droplets Server directory.

16. Under the Link tab, Input category, set Object/library modules to the following:

kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib msvcrtd.lib mfcs42d.lib mfc42d.lib mfcn42d.lib mfco42d.lib mfcd42d.lib sxlrtd.lib

... and set Ignore libraries: to the following:

libc.lib,libcmt.lib,msvcrt.lib,libcd.lib,libcmtd.lib,nafxcw.lib,nafxcwd.lib

Hit OK and File-> Save All. You're done with your project settings.

17. Compile your project.

18. You can now run the Droplets Server executable. Do so by selecting Droplets | Droplets Console from your Start menu.

The Hello World Droplet icon at the upper left corner indicates that the Droplets Server is now hosting our Hello World application.

19. You must also make sure that the Droplets Client is installed on your machine. If it hasn't been yet, go to www.droplets.com and select "Enable Droplets" in the home page. It should, though, be included as a part of your Droplets SDK.

20. Create a Droplets file to connect the standalone client to the Droplets Server. Copy the contents of this table to a file called hello.drp:


user
USER
address
127.0.0.1
port
8194
calc
Hello World!
bgrgb
192, 192, 192
calcinstance
shared
height
200
width
500
title
Hello World Application

21. Double-click on the new file in your Explorer.

As shown at the beginning of the tutorial, the application should look like this when run:

The indicators in the lower right corner show connection status. The leftmost oval is green when the connection to the Server is successful and active. The middle oval turns green whenever data is read from the Server, while the rightmost oval turns green whenever data is sent to the Server. Red ovals indicate connection errors. Hovering the mouse cursor over each oval gives you additional information about the connection.

Congratulations! You've created your first Droplet. For a look at a more functional demo, move on through the Droplets SDK tutorials.


Return to Droplet Documentation Home